Skip to content

Commit

Permalink
exposes required permissions to users next to the header with a shiel… (
Browse files Browse the repository at this point in the history
#2011)

* exposes required permissions to users next to the header with a shield icon

* fix test project build
  • Loading branch information
Masterjun3 authored Oct 18, 2024
1 parent 0329372 commit a65ac08
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 19 deletions.
38 changes: 25 additions & 13 deletions TASVideos.Core/Extensions/WikiHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ namespace TASVideos.Extensions;

public static class WikiHelper
{
public static bool UserCanEditWikiPage(string? pageName, string? userName, IReadOnlyCollection<PermissionTo> userPermissions)
public static bool UserCanEditWikiPage(string? pageName, string? userName, IReadOnlyCollection<PermissionTo> userPermissions, out HashSet<PermissionTo> relevantPermissions)
{
relevantPermissions = [];

if (string.IsNullOrWhiteSpace(pageName) || string.IsNullOrWhiteSpace(userName))
{
return false;
Expand All @@ -17,51 +19,61 @@ public static bool UserCanEditWikiPage(string? pageName, string? userName, IRead
// Anyone who can edit anything (including the user's own homepage) should be allowed to edit his
if (pageName == "SandBox")
{
return userPermissions.Contains(PermissionTo.EditGameResources)
|| userPermissions.Contains(PermissionTo.EditHomePage)
|| userPermissions.Contains(PermissionTo.EditWikiPages)
|| userPermissions.Contains(PermissionTo.EditSystemPages)
|| userPermissions.Contains(PermissionTo.EditSubmissions)
|| userPermissions.Contains(PermissionTo.EditPublicationMetaData);
relevantPermissions = [
PermissionTo.EditGameResources,
PermissionTo.EditHomePage,
PermissionTo.EditWikiPages,
PermissionTo.EditSystemPages,
PermissionTo.EditSubmissions,
PermissionTo.EditPublicationMetaData
];
return relevantPermissions.Any(userPermissions.Contains);
}

if (IsPublicationPage(pageName, out _))
{
return userPermissions.Contains(PermissionTo.EditPublicationMetaData);
relevantPermissions = [PermissionTo.EditPublicationMetaData];
return relevantPermissions.Any(userPermissions.Contains);
}

if (IsSubmissionPage(pageName, out _))
{
return userPermissions.Contains(PermissionTo.EditSubmissions);
relevantPermissions = [PermissionTo.EditSubmissions];
return relevantPermissions.Any(userPermissions.Contains);
}

if (pageName.StartsWith("GameResources/"))
{
return userPermissions.Contains(PermissionTo.EditGameResources);
relevantPermissions = [PermissionTo.EditGameResources];
return relevantPermissions.Any(userPermissions.Contains);
}

if (pageName.StartsWith("System/"))
{
return userPermissions.Contains(PermissionTo.EditSystemPages);
relevantPermissions = [PermissionTo.EditSystemPages];
return relevantPermissions.Any(userPermissions.Contains);
}

if (pageName.StartsWith(LinkConstants.HomePages))
{
relevantPermissions = [PermissionTo.EditHomePage];

// A home page is defined as Homepages/[UserName]
// If a user can exploit this fact to create an exploit
// then we should first reconsider rules about allowed patterns of usernames and what defines a valid wiki page
// before deciding to nuke this feature
var homepage = pageName[LinkConstants.HomePages.Length..].Split('/')[0];
if (string.Equals(homepage, userName, StringComparison.OrdinalIgnoreCase)
&& userPermissions.Contains(PermissionTo.EditHomePage))
&& relevantPermissions.Any(userPermissions.Contains))
{
return true;
}

// Notice we fall back to EditWikiPages if it is not the user's homepage, regular editors should be able to edit homepages
}

return userPermissions.Contains(PermissionTo.EditWikiPages);
relevantPermissions = [PermissionTo.EditWikiPages];
return relevantPermissions.Any(userPermissions.Contains);
}

public static bool IsValidWikiPageName(string pageName)
Expand Down
2 changes: 1 addition & 1 deletion TASVideos/Extensions/ClaimsPrincipalExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace TASVideos;
public static class ClaimsPrincipalExtensions
{
public static bool CanEditWiki(this ClaimsPrincipal user, string pageName)
=> WikiHelper.UserCanEditWikiPage(pageName, user.Name(), user.Permissions());
=> WikiHelper.UserCanEditWikiPage(pageName, user.Name(), user.Permissions(), out _);
}
17 changes: 16 additions & 1 deletion TASVideos/Extensions/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
namespace TASVideos.Extensions;
using TASVideos.Pages;

namespace TASVideos.Extensions;

public static class HttpContextExtensions
{
public static string CurrentPathToReturnUrl(this HttpContext? context)
{
return context is null ? "" : $"{context.Request.Path}{context.Request.QueryString}";
}

public static void SetRequiredPermissionsView(this HttpContext? context, RequirePermissionsView requiredPermissions)
{
if (context is not null)
{
context.Items["RequiredPermissions"] = requiredPermissions;
}
}

public static RequirePermissionsView? GetRequiredPermissionsView(this HttpContext? context)
{
return context?.Items["RequiredPermissions"] as RequirePermissionsView;
}
}
14 changes: 13 additions & 1 deletion TASVideos/Pages/RequireBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.Mvc.Filters;

namespace TASVideos.Pages;
Expand Down Expand Up @@ -40,4 +41,15 @@ protected static async Task<IReadOnlyCollection<PermissionTo>> GetUserPermission

return context.HttpContext.User.Permissions();
}

protected static void SetRequiredPermissionsView(PageHandlerExecutingContext context, HashSet<PermissionTo> requiredPermissions, bool matchAny)
{
context.HttpContext.SetRequiredPermissionsView(new RequirePermissionsView { Permissions = requiredPermissions, MatchAny = matchAny });
}
}

public class RequirePermissionsView
{
public HashSet<PermissionTo> Permissions { get; set; } = [];
public bool MatchAny { get; set; }
}
4 changes: 3 additions & 1 deletion TASVideos/Pages/RequireEdit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext contex

var userPerms = await GetUserPermissions(context);
var canEdit = WikiHelper
.UserCanEditWikiPage(pageToEdit, user.Name(), userPerms);
.UserCanEditWikiPage(pageToEdit, user.Name(), userPerms, out HashSet<PermissionTo> relevantPermissions);

if (canEdit)
{
SetRequiredPermissionsView(context, relevantPermissions, matchAny: true);

await next.Invoke();
}
else
Expand Down
1 change: 1 addition & 0 deletions TASVideos/Pages/RequirePermission.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext contex
if ((MatchAny && RequiredPermissions.Any(r => userPerms.Contains(r)))
|| RequiredPermissions.IsSubsetOf(userPerms))
{
SetRequiredPermissionsView(context, RequiredPermissions, MatchAny);
await next.Invoke();
}
else
Expand Down
27 changes: 27 additions & 0 deletions TASVideos/Pages/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
}
}

var permissionsRequired = Context.GetRequiredPermissionsView() as RequirePermissionsView;
bool requiresPermissions = permissionsRequired is not null && permissionsRequired.Permissions.Count > 0;

<div class="container mb-2">
<h1 class="page-title card card-body d-block">
<a condition="prev.HasValue" href="@string.Format(fmtStr!, prev)"><i class="fa fa-arrow-left"></i></a>
Expand All @@ -112,8 +115,32 @@
@heading
}
<a condition="next.HasValue" class="float-end" href="@string.Format(fmtStr!, next)"><i class="fa fa-arrow-right"></i></a>
<a condition="requiresPermissions" class="float-end text-body-secondary" data-id="permission-modal-button" href="#"><i class="fa-solid fa-shield-halved"></i></a>
</h1>
</div>

<div condition="requiresPermissions" id="permission-modal" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fa-solid fa-shield-halved"></i> This page requires permissions</h5>
<button type='button' class='btn-close' data-bs-dismiss='modal'></button>
</div>
<div class="modal-body">
<p>You can visit this page because you have @(permissionsRequired!.Permissions.Count == 1 ? "" : permissionsRequired.MatchAny ? "one of " : "all of ")the following required permissions:</p>
<ul>
@foreach (var permission in permissionsRequired.Permissions)
{
<li>@permission.ToString().SplitCamelCase()</li>
}
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-silver" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
}

<div class="container">
Expand Down
2 changes: 1 addition & 1 deletion TASVideos/Pages/Wiki/PageHistory.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
ViewData.SetTitle("Page History For " + Model.PageName);
ViewData.UseDiff();
bool hasDiff = Model.FromRevision.HasValue && Model.ToRevision.HasValue;
var canEdit = WikiHelper.UserCanEditWikiPage(Model.Path, User.Name(), User.Permissions());
var canEdit = WikiHelper.UserCanEditWikiPage(Model.Path, User.Name(), User.Permissions(), out _);
}

@functions {
Expand Down
8 changes: 8 additions & 0 deletions TASVideos/wwwroot/js/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,11 @@ if (location.hash) {
}
}
}

Array.from(document.querySelectorAll('[data-id="permission-modal-button"]')).forEach(popupBtn => {
popupBtn.addEventListener('click', () => {
let modal = new bootstrap.Modal('#permission-modal')
modal.show();
return false;
})
});
3 changes: 2 additions & 1 deletion tests/TASVideos.Test/Extensions/WikiHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public void UserCanEditWikiPage(
var actual = WikiHelper.UserCanEditWikiPage(
pageName,
userName,
userPermissions.ToList());
userPermissions.ToList(),
out _);

Assert.AreEqual(expected, actual);
}
Expand Down

0 comments on commit a65ac08

Please sign in to comment.