migrates the custom content persmission helper and media permissions helper checks to authz policies and cleans up that code/class/namespaces

This commit is contained in:
Shannon
2020-11-24 00:37:26 +11:00
parent c34540cb06
commit 4dbfe5867b
25 changed files with 882 additions and 565 deletions

View File

@@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// The user must have access to all descendant nodes of the content item in order to continue
/// </summary>
public class ContentPermissionPublishBranchHandler : AuthorizationHandler<ContentPermissionsPublishBranchRequirement, IContent>
{
private readonly IEntityService _entityService;
private readonly ContentPermissions _contentPermissions;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public ContentPermissionPublishBranchHandler(
IEntityService entityService,
ContentPermissions contentPermissions,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_entityService = entityService;
_contentPermissions = contentPermissions;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ContentPermissionsPublishBranchRequirement requirement, IContent resource)
{
var denied = new List<IUmbracoEntity>();
var page = 0;
const int pageSize = 500;
var total = long.MaxValue;
while (page * pageSize < total)
{
var descendants = _entityService.GetPagedDescendants(resource.Id, UmbracoObjectTypes.Document, page++, pageSize, out total,
//order by shallowest to deepest, this allows us to check permissions from top to bottom so we can exit
//early if a permission higher up fails
ordering: Ordering.By("path", Direction.Ascending));
foreach (var c in descendants)
{
//if this item's path has already been denied or if the user doesn't have access to it, add to the deny list
if (denied.Any(x => c.Path.StartsWith($"{x.Path},"))
|| (_contentPermissions.CheckPermissions(c,
_backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
requirement.Permission) == ContentPermissions.ContentAccess.Denied))
{
denied.Add(c);
}
}
}
if (denied.Count == 0)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
}

View File

@@ -11,45 +11,6 @@ using Umbraco.Core.Services;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Used to authorize if the user has the correct permission access to the content for the <see cref="IContent"/> specified
/// </summary>
public class ContentPermissionResourceHandler : AuthorizationHandler<ContentPermissionResourceRequirement, IContent>
{
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IEntityService _entityService;
private readonly IUserService _userService;
public ContentPermissionResourceHandler(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IEntityService entityService,
IUserService userService)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_entityService = entityService;
_userService = userService;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ContentPermissionResourceRequirement requirement, IContent resource)
{
var permissionResult = ContentPermissionsHelper.CheckPermissions(resource,
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
_userService,
_entityService,
new[] { requirement.PermissionToCheck });
if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied)
{
context.Fail();
}
else
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
/// <summary>
/// Used to authorize if the user has the correct permission access to the content for the content id specified in a query string
@@ -59,21 +20,18 @@ namespace Umbraco.Web.BackOffice.Authorization
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IEntityService _entityService;
private readonly IUserService _userService;
private readonly IContentService _contentService;
private readonly ContentPermissions _contentPermissions;
public ContentPermissionQueryStringHandler(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IHttpContextAccessor httpContextAccessor,
IEntityService entityService,
IUserService userService,
IContentService contentService)
ContentPermissions contentPermissions)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_httpContextAccessor = httpContextAccessor;
_entityService = entityService;
_userService = userService;
_contentService = contentService;
_contentPermissions = contentPermissions;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ContentPermissionsQueryStringRequirement requirement)
@@ -118,20 +76,17 @@ namespace Umbraco.Web.BackOffice.Authorization
nodeId = requirement.NodeId.Value;
}
var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId,
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
_userService,
_contentService,
_entityService,
out var contentItem,
var permissionResult = _contentPermissions.CheckPermissions(nodeId,
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
out IContent contentItem,
new[] { requirement.PermissionToCheck });
if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound)
if (permissionResult == ContentPermissions.ContentAccess.NotFound)
{
return null;
}
if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied)
if (permissionResult == ContentPermissions.ContentAccess.Denied)
{
context.Fail();
}

View File

@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
using Umbraco.Core.Models;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Used to authorize if the user has the correct permission access to the content for the <see cref="IContent"/> specified
/// </summary>
public class ContentPermissionResourceHandler : AuthorizationHandler<ContentPermissionResourceRequirement, IContent>
{
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly ContentPermissions _contentPermissions;
public ContentPermissionResourceHandler(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
ContentPermissions contentPermissions)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_contentPermissions = contentPermissions;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ContentPermissionResourceRequirement requirement, IContent resource)
{
var permissionResult = _contentPermissions.CheckPermissions(resource,
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
requirement.PermissionsToCheck);
if (permissionResult == ContentPermissions.ContentAccess.Denied)
{
context.Fail();
}
else
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}

View File

@@ -1,7 +1,10 @@
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using Umbraco.Web.Actions;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// An authorization requirement for <see cref="ContentPermissionResourceHandler"/>
/// </summary>
@@ -13,9 +16,21 @@ namespace Umbraco.Web.BackOffice.Authorization
/// <param name="permissionToCheck"></param>
public ContentPermissionResourceRequirement(char permissionToCheck)
{
PermissionToCheck = permissionToCheck;
PermissionsToCheck = new List<char> { permissionToCheck };
}
public char PermissionToCheck { get; }
public ContentPermissionResourceRequirement(IReadOnlyList<char> permissionToCheck)
{
PermissionsToCheck = permissionToCheck;
}
public ContentPermissionResourceRequirement(int nodeId, IReadOnlyList<char> permissionToCheck)
{
NodeId = nodeId;
PermissionsToCheck = permissionToCheck;
}
public int? NodeId { get; }
public IReadOnlyList<char> PermissionsToCheck { get; }
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Authorization requirement for <see cref="ContentPermissionPublishBranchHandler"/>
/// </summary>
public class ContentPermissionsPublishBranchRequirement : IAuthorizationRequirement
{
public ContentPermissionsPublishBranchRequirement(char permission)
{
Permission = permission;
}
public char Permission { get; }
}
}

View File

@@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using System;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
namespace Umbraco.Web.BackOffice.Authorization
{
public class MediaPermissionQueryStringHandler : AuthorizationHandler<MediaPermissionsQueryStringRequirement>
{
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly MediaPermissions _mediaPermissions;
private readonly IEntityService _entityService;
public MediaPermissionQueryStringHandler(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IHttpContextAccessor httpContextAccessor,
MediaPermissions mediaPermissions)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_httpContextAccessor = httpContextAccessor;
_mediaPermissions = mediaPermissions;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MediaPermissionsQueryStringRequirement requirement)
{
StringValues routeVal;
foreach (var qs in requirement.QueryStringNames)
{
if (_httpContextAccessor.HttpContext.Request.Query.TryGetValue(qs, out routeVal))
{
break;
}
}
if (routeVal.Count == 0)
{
throw new InvalidOperationException("No argument found for the current action with by names " + string.Join(", ", requirement.QueryStringNames));
}
int nodeId;
var argument = routeVal.ToString();
// if the argument is an int, it will parse and can be assigned to nodeId
// if might be a udi, so check that next
// otherwise treat it as a guid - unlikely we ever get here
if (int.TryParse(argument, out int parsedId))
{
nodeId = parsedId;
}
else if (UdiParser.TryParse(argument, true, out var udi))
{
nodeId = _entityService.GetId(udi).Result;
}
else
{
Guid.TryParse(argument, out Guid key);
nodeId = _entityService.GetId(key, UmbracoObjectTypes.Document).Result;
}
var permissionResult = _mediaPermissions.CheckPermissions(
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
nodeId,
out var mediaItem);
if (permissionResult == MediaPermissions.MediaAccess.NotFound)
{
return null;
}
if (permissionResult == MediaPermissions.MediaAccess.Denied)
{
context.Fail();
}
else
{
context.Succeed(requirement);
}
if (mediaItem != null)
{
//store the content item in request cache so it can be resolved in the controller without re-looking it up
_httpContextAccessor.HttpContext.Items[typeof(IMedia).ToString()] = mediaItem;
}
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
using Umbraco.Core.Models;
using Umbraco.Core.Security;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Used to authorize if the user has the correct permission access to the content for the <see cref="IContent"/> specified
/// </summary>
public class MediaPermissionResourceHandler : AuthorizationHandler<MediaPermissionResourceRequirement, IMedia>
{
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly MediaPermissions _mediaPermissions;
public MediaPermissionResourceHandler(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
MediaPermissions mediaPermissions)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_mediaPermissions = mediaPermissions;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MediaPermissionResourceRequirement requirement, IMedia resource)
{
var permissionResult = MediaPermissions.MediaAccess.NotFound;
if (resource != null)
{
permissionResult = _mediaPermissions.CheckPermissions(
resource,
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser);
}
else if (requirement.NodeId.HasValue)
{
permissionResult = _mediaPermissions.CheckPermissions(
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
requirement.NodeId.Value,
out _);
}
if (permissionResult == MediaPermissions.MediaAccess.Denied)
{
context.Fail();
}
else
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// An authorization requirement for <see cref="MediaPermissionResourceHandler"/>
/// </summary>
public class MediaPermissionResourceRequirement : IAuthorizationRequirement
{
public MediaPermissionResourceRequirement()
{
}
public MediaPermissionResourceRequirement(int nodeId)
{
NodeId = nodeId;
}
public int? NodeId { get; }
}
}

View File

@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Web.BackOffice.Authorization
{
public class MediaPermissionsQueryStringRequirement : IAuthorizationRequirement
{
public MediaPermissionsQueryStringRequirement(string[] paramNames)
{
QueryStringNames = paramNames;
}
public string[] QueryStringNames { get; }
}
}