Migrates EnsureUserPermissionForContentAttribute to authz policies

This commit is contained in:
Shannon
2020-11-23 22:43:41 +11:00
parent 65a11a4e26
commit c34540cb06
24 changed files with 443 additions and 379 deletions

View File

@@ -7,7 +7,9 @@ namespace Umbraco.Web.Actions
/// </summary>
public class ActionCopy : IAction
{
public char Letter => 'O';
public const char ActionLetter = 'O';
public char Letter => ActionLetter;
public string Alias => "copy";
public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
public string Icon => "documents";

View File

@@ -7,7 +7,9 @@ namespace Umbraco.Web.Actions
/// </summary>
public class ActionRights : IAction
{
public char Letter => 'R';
public const char ActionLetter = 'R';
public char Letter => ActionLetter;
public string Alias => "rights";
public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
public string Icon => "vcard";

View File

@@ -9,7 +9,9 @@ namespace Umbraco.Web.Actions
/// </summary>
public class ActionSort : IAction
{
public char Letter => 'S';
public const char ActionLetter = 'S';
public char Letter => ActionLetter;
public string Alias => "sort";
public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
public string Icon => "navigation-vertical";

View File

@@ -10,7 +10,9 @@ namespace Umbraco.Web.Actions
/// </summary>
public class ActionUnpublish : IAction
{
public char Letter => 'Z';
public const char ActionLetter = 'Z';
public char Letter => ActionLetter;
public string Alias => "unpublish";
public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
public string Icon => "circle-dotted";

View File

@@ -1,10 +1,8 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using System.Linq;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Hosting;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Editors;
@@ -15,14 +13,14 @@ namespace Umbraco.Web.BackOffice.Authorization
/// <summary>
/// if the users being edited is an admin then we must ensure that the current user is also an admin
/// </summary>
public class AdminUsersAuthorizeHandler : AuthorizationHandler<AdminUsersAuthorizeRequirement>
public class AdminUsersHandler : AuthorizationHandler<AdminUsersRequirement>
{
private readonly IHttpContextAccessor _httpContextAcessor;
private readonly IUserService _userService;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper;
public AdminUsersAuthorizeHandler(IHttpContextAccessor httpContextAcessor,
public AdminUsersHandler(IHttpContextAccessor httpContextAcessor,
IUserService userService,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
UserEditorAuthorizationHelper userEditorAuthorizationHelper)
@@ -33,7 +31,7 @@ namespace Umbraco.Web.BackOffice.Authorization
_userEditorAuthorizationHelper = userEditorAuthorizationHelper;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminUsersAuthorizeRequirement requirement)
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminUsersRequirement requirement)
{
var isAuth = IsAuthorized(requirement);
if (!isAuth.HasValue || isAuth.Value)
@@ -48,7 +46,7 @@ namespace Umbraco.Web.BackOffice.Authorization
return Task.CompletedTask;
}
private bool? IsAuthorized(AdminUsersAuthorizeRequirement requirement)
private bool? IsAuthorized(AdminUsersRequirement requirement)
{
int[] userIds;

View File

@@ -4,11 +4,11 @@ namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Authorization requirement for the <see cref="AdminUsersAuthorizeHandler"/>
/// Authorization requirement for the <see cref="AdminUsersHandler"/>
/// </summary>
public class AdminUsersAuthorizeRequirement : IAuthorizationRequirement
public class AdminUsersRequirement : IAuthorizationRequirement
{
public AdminUsersAuthorizeRequirement(string queryStringName = "id")
public AdminUsersRequirement(string queryStringName = "id")
{
QueryStringName = queryStringName;
}

View File

@@ -9,18 +9,18 @@ namespace Umbraco.Web.BackOffice.Authorization
/// <summary>
/// Ensures authorization is successful for a back office user.
/// </summary>
public class BackOfficeAuthorizationHandler : AuthorizationHandler<BackOfficeAuthorizeRequirement>
public class BackOfficeHandler : AuthorizationHandler<BackOfficeRequirement>
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurity;
private readonly IRuntimeState _runtimeState;
public BackOfficeAuthorizationHandler(IBackOfficeSecurityAccessor backOfficeSecurity, IRuntimeState runtimeState)
public BackOfficeHandler(IBackOfficeSecurityAccessor backOfficeSecurity, IRuntimeState runtimeState)
{
_backOfficeSecurity = backOfficeSecurity;
_runtimeState = runtimeState;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BackOfficeAuthorizeRequirement requirement)
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BackOfficeRequirement requirement)
{
if (!IsAuthorized(requirement))
{
@@ -34,7 +34,7 @@ namespace Umbraco.Web.BackOffice.Authorization
return Task.CompletedTask;
}
private bool IsAuthorized(BackOfficeAuthorizeRequirement requirement)
private bool IsAuthorized(BackOfficeRequirement requirement)
{
try
{

View File

@@ -3,11 +3,11 @@
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Authorization requirement for the <see cref="BackOfficeAuthorizeRequirement"/>
/// Authorization requirement for the <see cref="BackOfficeRequirement"/>
/// </summary>
public class BackOfficeAuthorizeRequirement : IAuthorizationRequirement
public class BackOfficeRequirement : IAuthorizationRequirement
{
public BackOfficeAuthorizeRequirement(bool requireApproval = true)
public BackOfficeRequirement(bool requireApproval = true)
{
RequireApproval = requireApproval;
}

View File

@@ -0,0 +1,154 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
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
{
/// <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
/// </summary>
public class ContentPermissionQueryStringHandler : AuthorizationHandler<ContentPermissionsQueryStringRequirement>
{
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IEntityService _entityService;
private readonly IUserService _userService;
private readonly IContentService _contentService;
public ContentPermissionQueryStringHandler(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IHttpContextAccessor httpContextAccessor,
IEntityService entityService,
IUserService userService,
IContentService contentService)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_httpContextAccessor = httpContextAccessor;
_entityService = entityService;
_userService = userService;
_contentService = contentService;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ContentPermissionsQueryStringRequirement requirement)
{
int nodeId;
if (requirement.NodeId.HasValue == false)
{
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));
}
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;
}
}
else
{
nodeId = requirement.NodeId.Value;
}
var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId,
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
_userService,
_contentService,
_entityService,
out var contentItem,
new[] { requirement.PermissionToCheck });
if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound)
{
return null;
}
if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied)
{
context.Fail();
}
else
{
context.Succeed(requirement);
}
if (contentItem != 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(IContent).ToString()] = contentItem;
}
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="ContentPermissionResourceHandler"/>
/// </summary>
public class ContentPermissionResourceRequirement : IAuthorizationRequirement
{
/// <summary>
/// Create an authorization requirement for a resource
/// </summary>
/// <param name="permissionToCheck"></param>
public ContentPermissionResourceRequirement(char permissionToCheck)
{
PermissionToCheck = permissionToCheck;
}
public char PermissionToCheck { get; }
}
}

View File

@@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Authorization;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// An authorization requirement for <see cref="ContentPermissionQueryStringHandler"/>
/// </summary>
public class ContentPermissionsQueryStringRequirement : IAuthorizationRequirement
{
/// <summary>
/// Create an authorization requirement for a specific node id
/// </summary>
/// <param name="nodeId"></param>
/// <param name="permissionToCheck"></param>
public ContentPermissionsQueryStringRequirement(int nodeId, char permissionToCheck)
{
NodeId = nodeId;
PermissionToCheck = permissionToCheck;
}
/// <summary>
/// Create an authorization requirement for a node id based on a query string parameter
/// </summary>
/// <param name="paramName"></param>
/// <param name="permissionToCheck"></param>
public ContentPermissionsQueryStringRequirement(char permissionToCheck, string[] paramNames)
{
QueryStringNames = paramNames;
PermissionToCheck = permissionToCheck;
}
public int? NodeId { get; }
public string[] QueryStringNames { get; }
public char PermissionToCheck { get; }
}
}

View File

@@ -8,11 +8,11 @@ namespace Umbraco.Web.BackOffice.Authorization
/// <summary>
/// Ensures the resource cannot be accessed if <see cref="IBackOfficeExternalLoginProviders.HasDenyLocalLogin"/> returns true
/// </summary>
public class DenyLocalLoginAuthorizeHandler : AuthorizationHandler<DenyLocalLoginRequirement>
public class DenyLocalLoginHandler : AuthorizationHandler<DenyLocalLoginRequirement>
{
private readonly IBackOfficeExternalLoginProviders _externalLogins;
public DenyLocalLoginAuthorizeHandler(IBackOfficeExternalLoginProviders externalLogins)
public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins)
{
_externalLogins = externalLogins;
}

View File

@@ -3,7 +3,7 @@
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Marker requirement for the <see cref="DenyLocalLoginAuthorizeHandler"/>
/// Marker requirement for the <see cref="DenyLocalLoginHandler"/>
/// </summary>
public class DenyLocalLoginRequirement : IAuthorizationRequirement
{

View File

@@ -12,16 +12,16 @@ namespace Umbraco.Web.BackOffice.Authorization
/// <remarks>
/// The user only needs access to one of the sections specified, not all of the sections.
/// </remarks>
public class UmbracoSectionAuthorizeHandler : AuthorizationHandler<SectionAliasesRequirement>
public class SectionHandler : AuthorizationHandler<SectionRequirement>
{
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
public UmbracoSectionAuthorizeHandler(IBackOfficeSecurityAccessor backofficeSecurityAccessor)
public SectionHandler(IBackOfficeSecurityAccessor backofficeSecurityAccessor)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SectionAliasesRequirement requirement)
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SectionRequirement requirement)
{
if (IsAuthorized(requirement))
{
@@ -35,7 +35,7 @@ namespace Umbraco.Web.BackOffice.Authorization
return Task.CompletedTask;
}
private bool IsAuthorized(SectionAliasesRequirement requirement)
private bool IsAuthorized(SectionRequirement requirement)
{
var authorized = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null
&& requirement.SectionAliases.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess(

View File

@@ -4,15 +4,15 @@ using System.Collections.Generic;
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Authorization requirements for <see cref="UmbracoSectionAuthorizeHandler"/>
/// Authorization requirements for <see cref="SectionHandler"/>
/// </summary>
public class SectionAliasesRequirement : IAuthorizationRequirement
public class SectionRequirement : IAuthorizationRequirement
{
/// <summary>
/// The aliases for sections that the user will need access to
/// </summary>
public IReadOnlyCollection<string> SectionAliases { get; }
public SectionAliasesRequirement(params string[] aliases) => SectionAliases = aliases;
public SectionRequirement(params string[] aliases) => SectionAliases = aliases;
}
}

View File

@@ -16,7 +16,7 @@ namespace Umbraco.Web.BackOffice.Authorization
/// This would allow a tree to be moved between sections.
/// The user only needs access to one of the trees specified, not all of the trees.
/// </remarks>
public class UmbracoTreeAuthorizeHandler : AuthorizationHandler<TreeAliasesRequirement>
public class TreeHandler : AuthorizationHandler<TreeRequirement>
{
private readonly ITreeService _treeService;
@@ -31,13 +31,13 @@ namespace Umbraco.Web.BackOffice.Authorization
/// If the user has access to the application that the treeAlias is specified in, they will be authorized.
/// Multiple trees may be specified.
/// </param>
public UmbracoTreeAuthorizeHandler(ITreeService treeService, IBackOfficeSecurityAccessor backofficeSecurityAccessor)
public TreeHandler(ITreeService treeService, IBackOfficeSecurityAccessor backofficeSecurityAccessor)
{
_treeService = treeService ?? throw new ArgumentNullException(nameof(treeService));
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TreeAliasesRequirement requirement)
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TreeRequirement requirement)
{
if (IsAuthorized(requirement))
{
@@ -51,7 +51,7 @@ namespace Umbraco.Web.BackOffice.Authorization
return Task.CompletedTask;
}
private bool IsAuthorized(TreeAliasesRequirement requirement)
private bool IsAuthorized(TreeRequirement requirement)
{
var apps = requirement.TreeAliases.Select(x => _treeService
.GetByAlias(x))

View File

@@ -5,15 +5,15 @@ namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Authorization requirements for <see cref="UmbracoTreeAuthorizeHandler"/>
/// Authorization requirements for <see cref="TreeHandler"/>
/// </summary>
public class TreeAliasesRequirement : IAuthorizationRequirement
public class TreeRequirement : IAuthorizationRequirement
{
/// <summary>
/// The aliases for trees that the user will need access to
/// </summary>
public IReadOnlyCollection<string> TreeAliases { get; }
public TreeAliasesRequirement(params string[] aliases) => TreeAliases = aliases;
public TreeRequirement(params string[] aliases) => TreeAliases = aliases;
}
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Web.BackOffice.Authorization
/// <summary>
/// Authorizes that the current user has access to the user group Id in the request
/// </summary>
public class UserGroupAuthorizationHandler : AuthorizationHandler<UserGroupAuthorizeRequirement>
public class UserGroupHandler : AuthorizationHandler<UserGroupRequirement>
{
private readonly IHttpContextAccessor _httpContextAcessor;
private readonly IUserService _userService;
@@ -21,7 +21,7 @@ namespace Umbraco.Web.BackOffice.Authorization
private readonly IEntityService _entityService;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
public UserGroupAuthorizationHandler(IHttpContextAccessor httpContextAcessor,
public UserGroupHandler(IHttpContextAccessor httpContextAcessor,
IUserService userService,
IContentService contentService,
IMediaService mediaService,
@@ -36,7 +36,7 @@ namespace Umbraco.Web.BackOffice.Authorization
_backofficeSecurityAccessor = backofficeSecurityAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserGroupAuthorizeRequirement requirement)
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserGroupRequirement requirement)
{
var isAuth = IsAuthorized(requirement);
if (!isAuth.HasValue || isAuth.Value)
@@ -51,7 +51,7 @@ namespace Umbraco.Web.BackOffice.Authorization
return Task.CompletedTask;
}
private bool? IsAuthorized(UserGroupAuthorizeRequirement requirement)
private bool? IsAuthorized(UserGroupRequirement requirement)
{
var currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;

View File

@@ -3,11 +3,11 @@
namespace Umbraco.Web.BackOffice.Authorization
{
/// <summary>
/// Authorization requirement for the <see cref="UserGroupAuthorizationHandler"/>
/// Authorization requirement for the <see cref="UserGroupHandler"/>
/// </summary>
public class UserGroupAuthorizeRequirement : IAuthorizationRequirement
public class UserGroupRequirement : IAuthorizationRequirement
{
public UserGroupAuthorizeRequirement(string queryStringName = "id")
public UserGroupRequirement(string queryStringName = "id")
{
QueryStringName = queryStringName;
}

View File

@@ -39,6 +39,8 @@ using Umbraco.Web.Common.Filters;
using Umbraco.Web.Models.Mapping;
using Microsoft.AspNetCore.Authorization;
using Umbraco.Web.Common.Authorization;
using Umbraco.Web.BackOffice.Authorization;
using System.Threading.Tasks;
namespace Umbraco.Web.BackOffice.Controllers
{
@@ -68,6 +70,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly ActionCollection _actionCollection;
private readonly IMemberGroupService _memberGroupService;
private readonly ISqlContext _sqlContext;
private readonly IAuthorizationService _authorizationService;
private readonly Lazy<IDictionary<string, ILanguage>> _allLangs;
private readonly ILogger<ContentController> _logger;
@@ -97,7 +100,8 @@ namespace Umbraco.Web.BackOffice.Controllers
ActionCollection actionCollection,
IMemberGroupService memberGroupService,
ISqlContext sqlContext,
IJsonSerializer serializer)
IJsonSerializer serializer,
IAuthorizationService authorizationService)
: base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, serializer)
{
_propertyEditors = propertyEditors;
@@ -119,6 +123,7 @@ namespace Umbraco.Web.BackOffice.Controllers
_actionCollection = actionCollection;
_memberGroupService = memberGroupService;
_sqlContext = sqlContext;
_authorizationService = authorizationService;
_logger = loggerFactory.CreateLogger<ContentController>();
_allLangs = new Lazy<IDictionary<string, ILanguage>>(() => _localizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase));
@@ -158,16 +163,22 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <remarks>
/// Permission check is done for letter 'R' which is for <see cref="ActionRights"/> which the user must have access to update
/// </remarks>
[EnsureUserPermissionForContent("saveModel.ContentId", 'R')]
public ActionResult<IEnumerable<AssignedUserGroupPermissions>> PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel)
{
if (saveModel.ContentId <= 0) return NotFound();
public async Task<ActionResult<IEnumerable<AssignedUserGroupPermissions>>> PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel)
{ if (saveModel.ContentId <= 0) return NotFound();
// TODO: Should non-admins be allowed to set granular permissions?
var content = _contentService.GetById(saveModel.ContentId);
if (content == null) return NotFound();
// Authorize...
var requirement = new ContentPermissionResourceRequirement(ActionRights.ActionLetter);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, content, requirement);
if (!authorizationResult.Succeeded)
{
return Forbid();
}
//current permissions explicitly assigned to this content item
var contentPermissions = _contentService.GetPermissions(content)
.ToDictionary(x => x.UserGroupId, x => x);
@@ -220,7 +231,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <remarks>
/// Permission check is done for letter 'R' which is for <see cref="ActionRights"/> which the user must have access to view
/// </remarks>
[EnsureUserPermissionForContent("contentId", 'R')]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionAdministrationById)]
public ActionResult<IEnumerable<AssignedUserGroupPermissions>> GetDetailedPermissions(int contentId)
{
if (contentId <= 0) return NotFound();
@@ -336,8 +347,8 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <param name="id"></param>
/// <param name="culture"></param>
/// <returns></returns>
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
[EnsureUserPermissionForContent("id")]
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
[DetermineAmbiguousActionByPassingParameters]
public ContentItemDisplay GetById(int id)
{
@@ -357,7 +368,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <param name="id"></param>
/// <returns></returns>
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
[EnsureUserPermissionForContent("id")]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
[DetermineAmbiguousActionByPassingParameters]
public ContentItemDisplay GetById(Guid id)
{
@@ -378,7 +389,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <param name="id"></param>
/// <returns></returns>
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
[EnsureUserPermissionForContent("id")]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
[DetermineAmbiguousActionByPassingParameters]
public ContentItemDisplay GetById(Udi id)
{
@@ -1491,8 +1502,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// The EnsureUserPermissionForContent attribute will deny access to this method if the current user
/// does not have Publish access to this node.
/// </remarks>
///
[EnsureUserPermissionForContent("id", 'U')]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionPublishById)]
public IActionResult PostPublishById(int id)
{
var foundContent = GetObjectFromRequest(() => _contentService.GetById(id));
@@ -1539,7 +1549,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// The CanAccessContentAuthorize attribute will deny access to this method if the current user
/// does not have Delete access to this node.
/// </remarks>
[EnsureUserPermissionForContent("id", ActionDelete.ActionLetter)]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionDeleteById)]
[HttpDelete]
[HttpPost]
public IActionResult DeleteById(int id)
@@ -1585,7 +1595,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// </remarks>
[HttpDelete]
[HttpPost]
[EnsureUserPermissionForContent(Constants.System.RecycleBinContent, ActionDelete.ActionLetter)]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionEmptyRecycleBin)]
public IActionResult EmptyRecycleBin()
{
_contentService.EmptyRecycleBin(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(Constants.Security.SuperUserId));
@@ -1598,8 +1608,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// </summary>
/// <param name="sorted"></param>
/// <returns></returns>
[EnsureUserPermissionForContent("sorted.ParentId", 'S')]
public IActionResult PostSort(ContentSortOrder sorted)
public async Task<IActionResult> PostSort(ContentSortOrder sorted)
{
if (sorted == null)
{
@@ -1612,12 +1621,18 @@ namespace Umbraco.Web.BackOffice.Controllers
return Ok();
}
// Authorize...
var requirement = new ContentPermissionResourceRequirement(ActionSort.ActionLetter);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, _contentService.GetById(sorted.ParentId), requirement);
if (!authorizationResult.Succeeded)
{
return Forbid();
}
try
{
var contentService = _contentService;
// Save content with new sort order and update content xml in db accordingly
var sortResult = contentService.Sort(sorted.IdSortOrder, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id);
var sortResult = _contentService.Sort(sorted.IdSortOrder, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id);
if (!sortResult.Success)
{
_logger.LogWarning("Content sorting failed, this was probably caused by an event being cancelled");
@@ -1639,9 +1654,16 @@ namespace Umbraco.Web.BackOffice.Controllers
/// </summary>
/// <param name="move"></param>
/// <returns></returns>
[EnsureUserPermissionForContent("move.ParentId", 'M')]
public IActionResult PostMove(MoveOrCopy move)
public async Task<IActionResult> PostMove(MoveOrCopy move)
{
// Authorize...
var requirement = new ContentPermissionResourceRequirement(ActionMove.ActionLetter);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, _contentService.GetById(move.ParentId), requirement);
if (!authorizationResult.Succeeded)
{
return Forbid();
}
var toMove = ValidateMoveOrCopy(move);
_contentService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
@@ -1654,9 +1676,16 @@ namespace Umbraco.Web.BackOffice.Controllers
/// </summary>
/// <param name="copy"></param>
/// <returns></returns>
[EnsureUserPermissionForContent("copy.ParentId", 'C')]
public IActionResult PostCopy(MoveOrCopy copy)
public async Task<IActionResult> PostCopy(MoveOrCopy copy)
{
// Authorize...
var requirement = new ContentPermissionResourceRequirement(ActionCopy.ActionLetter);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, _contentService.GetById(copy.ParentId), requirement);
if (!authorizationResult.Succeeded)
{
return Forbid();
}
var toCopy = ValidateMoveOrCopy(copy);
var c = _contentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
@@ -1669,14 +1698,23 @@ namespace Umbraco.Web.BackOffice.Controllers
/// </summary>
/// <param name="model">The content and variants to unpublish</param>
/// <returns></returns>
[EnsureUserPermissionForContent("model.Id", 'Z')]
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
public ContentItemDisplay PostUnpublish(UnpublishContent model)
public async Task<ActionResult<ContentItemDisplay>> PostUnpublish(UnpublishContent model)
{
var foundContent = GetObjectFromRequest(() => _contentService.GetById(model.Id));
var foundContent = _contentService.GetById(model.Id);
if (foundContent == null)
{
HandleContentNotFound(model.Id);
}
// Authorize...
var requirement = new ContentPermissionResourceRequirement(ActionUnpublish.ActionLetter);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, foundContent, requirement);
if (!authorizationResult.Succeeded)
{
return Forbid();
}
var languageCount = _allLangs.Value.Count();
if (model.Cultures.Length == 0 || model.Cultures.Length == languageCount)
@@ -2267,7 +2305,7 @@ namespace Umbraco.Web.BackOffice.Controllers
return display;
}
[EnsureUserPermissionForContent("contentId", ActionBrowse.ActionLetter)]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
public ActionResult<IEnumerable<NotifySetting>> GetNotificationOptions(int contentId)
{
var notifications = new List<NotifySetting>();
@@ -2359,7 +2397,7 @@ namespace Umbraco.Web.BackOffice.Controllers
: content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture);
}
[EnsureUserPermissionForContent("contentId", ActionRollback.ActionLetter)]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionRollbackById)]
[HttpPost]
public IActionResult PostRollbackContent(int contentId, int versionId, string culture = "*")
{
@@ -2391,7 +2429,7 @@ namespace Umbraco.Web.BackOffice.Controllers
throw HttpResponseException.CreateValidationErrorResponse(notificationModel);
}
[EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionProtectById)]
[HttpGet]
public IActionResult GetPublicAccess(int contentId)
{
@@ -2440,7 +2478,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
// set up public access using role based access
[EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionProtectById)]
[HttpPost]
public IActionResult PostPublicAccess(int contentId, [FromQuery(Name = "groups[]")]string[] groups, [FromQuery(Name = "usernames[]")]string[] usernames, int loginPageId, int errorPageId)
{
@@ -2507,7 +2545,7 @@ namespace Umbraco.Web.BackOffice.Controllers
: Problem();
}
[EnsureUserPermissionForContent("contentId", ActionProtect.ActionLetter)]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionProtectById)]
[HttpPost]
public IActionResult RemovePublicAccess(int contentId)
{

View File

@@ -10,6 +10,7 @@ using Umbraco.Core.Security;
using Umbraco.Core.Serialization;
using Umbraco.Infrastructure.BackOffice;
using Umbraco.Net;
using Umbraco.Web.Actions;
using Umbraco.Web.BackOffice.Authorization;
using Umbraco.Web.BackOffice.Filters;
using Umbraco.Web.BackOffice.Security;
@@ -120,39 +121,86 @@ namespace Umbraco.Extensions
// NOTE: Even though we are registering these handlers globally they will only actually execute their logic for
// any auth defining a matching requirement and scheme.
services.AddSingleton<IAuthorizationHandler, BackOfficeAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, UmbracoTreeAuthorizeHandler>();
services.AddSingleton<IAuthorizationHandler, UmbracoSectionAuthorizeHandler>();
services.AddSingleton<IAuthorizationHandler, AdminUsersAuthorizeHandler>();
services.AddSingleton<IAuthorizationHandler, UserGroupAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DenyLocalLoginAuthorizeHandler>();
services.AddSingleton<IAuthorizationHandler, BackOfficeHandler>();
services.AddSingleton<IAuthorizationHandler, TreeHandler>();
services.AddSingleton<IAuthorizationHandler, SectionHandler>();
services.AddSingleton<IAuthorizationHandler, AdminUsersHandler>();
services.AddSingleton<IAuthorizationHandler, UserGroupHandler>();
services.AddSingleton<IAuthorizationHandler, ContentPermissionQueryStringHandler>();
services.AddSingleton<IAuthorizationHandler, ContentPermissionResourceHandler>();
services.AddSingleton<IAuthorizationHandler, DenyLocalLoginHandler>();
services.AddAuthorization(options =>
{
// these are the query strings we will check for content ids when permission checking
var contentPermissionQueryStrings = new[] { "id", "contentId" };
options.AddPolicy(AuthorizationPolicies.ContentPermissionEmptyRecycleBin, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(Constants.System.RecycleBinContent, ActionDelete.ActionLetter));
});
options.AddPolicy(AuthorizationPolicies.ContentPermissionAdministrationById, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(ActionRights.ActionLetter, contentPermissionQueryStrings));
});
options.AddPolicy(AuthorizationPolicies.ContentPermissionProtectById, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(ActionProtect.ActionLetter, contentPermissionQueryStrings));
});
options.AddPolicy(AuthorizationPolicies.ContentPermissionRollbackById, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(ActionRollback.ActionLetter, contentPermissionQueryStrings));
});
options.AddPolicy(AuthorizationPolicies.ContentPermissionBrowseById, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(ActionPublish.ActionLetter, contentPermissionQueryStrings));
});
options.AddPolicy(AuthorizationPolicies.ContentPermissionBrowseById, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(ActionBrowse.ActionLetter, contentPermissionQueryStrings));
});
options.AddPolicy(AuthorizationPolicies.ContentPermissionDeleteById, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(ActionDelete.ActionLetter, contentPermissionQueryStrings));
});
options.AddPolicy(AuthorizationPolicies.BackOfficeAccess, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new BackOfficeAuthorizeRequirement());
policy.Requirements.Add(new BackOfficeRequirement());
});
options.AddPolicy(AuthorizationPolicies.BackOfficeAccessWithoutApproval, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new BackOfficeAuthorizeRequirement(false));
policy.Requirements.Add(new BackOfficeRequirement(false));
});
options.AddPolicy(AuthorizationPolicies.AdminUserEditsRequireAdmin, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new AdminUsersAuthorizeRequirement());
policy.Requirements.Add(new AdminUsersAuthorizeRequirement("userIds"));
policy.Requirements.Add(new AdminUsersRequirement());
policy.Requirements.Add(new AdminUsersRequirement("userIds"));
});
options.AddPolicy(AuthorizationPolicies.UserBelongsToUserGroupInRequest, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new UserGroupAuthorizeRequirement());
policy.Requirements.Add(new UserGroupAuthorizeRequirement("userGroupIds"));
policy.Requirements.Add(new UserGroupRequirement());
policy.Requirements.Add(new UserGroupRequirement("userGroupIds"));
});
options.AddPolicy(AuthorizationPolicies.DenyLocalLoginIfConfigured, policy =>
@@ -164,50 +212,50 @@ namespace Umbraco.Extensions
options.AddPolicy(AuthorizationPolicies.SectionAccessContent, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Content));
policy.Requirements.Add(new SectionRequirement(Constants.Applications.Content));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Content, Constants.Applications.Media));
policy.Requirements.Add(new SectionRequirement(Constants.Applications.Content, Constants.Applications.Media));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessUsers, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Users));
policy.Requirements.Add(new SectionRequirement(Constants.Applications.Users));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessForTinyMce, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(
policy.Requirements.Add(new SectionRequirement(
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessMedia, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Media));
policy.Requirements.Add(new SectionRequirement(Constants.Applications.Media));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessMembers, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Members));
policy.Requirements.Add(new SectionRequirement(Constants.Applications.Members));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessPackages, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Packages));
policy.Requirements.Add(new SectionRequirement(Constants.Applications.Packages));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessSettings, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Settings));
policy.Requirements.Add(new SectionRequirement(Constants.Applications.Settings));
});
//We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered
@@ -215,21 +263,21 @@ namespace Umbraco.Extensions
options.AddPolicy(AuthorizationPolicies.SectionAccessForContentTree, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(
policy.Requirements.Add(new SectionRequirement(
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessForMediaTree, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(
policy.Requirements.Add(new SectionRequirement(
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members));
});
options.AddPolicy(AuthorizationPolicies.SectionAccessForMemberTree, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(
policy.Requirements.Add(new SectionRequirement(
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members));
});
@@ -237,7 +285,7 @@ namespace Umbraco.Extensions
options.AddPolicy(AuthorizationPolicies.SectionAccessForDataTypeReading, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new SectionAliasesRequirement(
policy.Requirements.Add(new SectionRequirement(
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members,
Constants.Applications.Settings, Constants.Applications.Packages));
});
@@ -245,139 +293,139 @@ namespace Umbraco.Extensions
options.AddPolicy(AuthorizationPolicies.TreeAccessDocuments, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Content));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Content));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessUsers, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Users));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Users));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessPartialViews, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.PartialViews));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.PartialViews));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessPartialViewMacros, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.PartialViewMacros));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.PartialViewMacros));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessPackages, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Packages));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Packages));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessLogs, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.LogViewer));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.LogViewer));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessDataTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.DataTypes));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.DataTypes));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessTemplates, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Templates));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Templates));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessMemberTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.MemberTypes));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.MemberTypes));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessRelationTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.RelationTypes));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.RelationTypes));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.DocumentTypes));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.DocumentTypes));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessMemberGroups, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.MemberGroups));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.MemberGroups));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessMediaTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.MediaTypes));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.MediaTypes));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessMacros, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Macros));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Macros));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessLanguages, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Languages));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Languages));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Dictionary));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Dictionary));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessDictionary, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Dictionary, Constants.Trees.Dictionary));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Dictionary, Constants.Trees.Dictionary));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessDictionaryOrTemplates, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Dictionary, Constants.Trees.Templates));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.Dictionary, Constants.Trees.Templates));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.DocumentTypes, Constants.Trees.Content));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.DocumentTypes, Constants.Trees.Content));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.MediaTypes, Constants.Trees.Media));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.MediaTypes, Constants.Trees.Media));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessMembersOrMemberTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.MemberTypes, Constants.Trees.Members));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.MemberTypes, Constants.Trees.Members));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessAnySchemaTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes));
policy.Requirements.Add(new TreeRequirement(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes));
});
options.AddPolicy(AuthorizationPolicies.TreeAccessAnyContentOrTypes, policy =>
{
policy.AuthenticationSchemes.Add(Constants.Security.BackOfficeAuthenticationType);
policy.Requirements.Add(new TreeAliasesRequirement(
policy.Requirements.Add(new TreeRequirement(
Constants.Trees.DocumentTypes, Constants.Trees.Content,
Constants.Trees.MediaTypes, Constants.Trees.Media,
Constants.Trees.MemberTypes, Constants.Trees.Members));

View File

@@ -1,252 +0,0 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Actions;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Filters
{
/// <summary>
/// Auth filter to check if the current user has access to the content item (by id).
/// </summary>
/// <remarks>
/// This first checks if the user can access this based on their start node, and then checks node permissions
/// By default the permission that is checked is browse but this can be specified in the ctor.
/// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params.
/// </remarks>
public sealed class EnsureUserPermissionForContentAttribute : TypeFilterAttribute
{
/// <summary>
/// This constructor will only be able to test the start node access
/// </summary>
public EnsureUserPermissionForContentAttribute(int nodeId)
: base(typeof(EnsureUserPermissionForContentFilter))
{
Arguments = new object[]
{
nodeId
};
}
public EnsureUserPermissionForContentAttribute(int nodeId, char permissionToCheck)
: base(typeof(EnsureUserPermissionForContentFilter))
{
Arguments = new object[]
{
nodeId, permissionToCheck
};
}
public EnsureUserPermissionForContentAttribute(string paramName)
: base(typeof(EnsureUserPermissionForContentFilter))
{
if (paramName == null) throw new ArgumentNullException(nameof(paramName));
if (string.IsNullOrEmpty(paramName))
throw new ArgumentException("Value can't be empty.", nameof(paramName));
Arguments = new object[]
{
paramName, ActionBrowse.ActionLetter
};
}
public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck)
: base(typeof(EnsureUserPermissionForContentFilter))
{
if (paramName == null) throw new ArgumentNullException(nameof(paramName));
if (string.IsNullOrEmpty(paramName))
throw new ArgumentException("Value can't be empty.", nameof(paramName));
Arguments = new object[]
{
paramName, permissionToCheck
};
}
private sealed class EnsureUserPermissionForContentFilter : IActionFilter
{
private readonly int? _nodeId;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IEntityService _entityService;
private readonly IUserService _userService;
private readonly IContentService _contentService;
private readonly string _paramName;
private readonly char? _permissionToCheck;
public EnsureUserPermissionForContentFilter(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IEntityService entityService,
IUserService userService,
IContentService contentService,
string paramName)
:this(backofficeSecurityAccessor, entityService, userService, contentService, null, paramName, ActionBrowse.ActionLetter)
{
}
public EnsureUserPermissionForContentFilter(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IEntityService entityService,
IUserService userService,
IContentService contentService,
int nodeId,
char permissionToCheck)
:this(backofficeSecurityAccessor, entityService, userService, contentService, nodeId, null, permissionToCheck)
{
}
public EnsureUserPermissionForContentFilter(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IEntityService entityService,
IUserService userService,
IContentService contentService,
int nodeId)
:this(backofficeSecurityAccessor, entityService, userService, contentService, nodeId, null, null)
{
}
public EnsureUserPermissionForContentFilter(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IEntityService entityService,
IUserService userService,
IContentService contentService,
string paramName, char permissionToCheck)
:this(backofficeSecurityAccessor, entityService, userService, contentService, null, paramName, permissionToCheck)
{
}
private EnsureUserPermissionForContentFilter(
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IEntityService entityService,
IUserService userService,
IContentService contentService,
int? nodeId, string paramName, char? permissionToCheck)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
_paramName = paramName;
if (permissionToCheck.HasValue)
{
_permissionToCheck = permissionToCheck.Value;
}
if (nodeId.HasValue)
{
_nodeId = nodeId.Value;
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser == null)
{
//not logged in
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
int nodeId;
if (_nodeId.HasValue == false)
{
var parts = _paramName.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
if (context.ActionArguments[parts[0]] == null)
{
throw new InvalidOperationException("No argument found for the current action with the name: " +
_paramName);
}
if (parts.Length == 1)
{
var argument = context.ActionArguments[parts[0]].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;
}
}
else
{
//now we need to see if we can get the property of whatever object it is
var pType = context.ActionArguments[parts[0]].GetType();
var prop = pType.GetProperty(parts[1]);
if (prop == null)
{
throw new InvalidOperationException(
"No argument found for the current action with the name: " + _paramName);
}
nodeId = (int) prop.GetValue(context.ActionArguments[parts[0]]);
}
}
else
{
nodeId = _nodeId.Value;
}
var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId,
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser,
_userService,
_contentService,
_entityService,
out var contentItem,
_permissionToCheck.HasValue ? new[] { _permissionToCheck.Value } : null);
if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound)
{
context.Result = new NotFoundResult();
return;
}
if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied)
{
context.Result = new ForbidResult();
return;
}
if (contentItem != null)
{
//store the content item in request cache so it can be resolved in the controller without re-looking it up
context.HttpContext.Items[typeof(IContent).ToString()] = contentItem;
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
}
}

View File

@@ -13,6 +13,16 @@
public const string AdminUserEditsRequireAdmin = nameof(AdminUserEditsRequireAdmin);
public const string DenyLocalLoginIfConfigured = nameof(DenyLocalLoginIfConfigured);
// Content permission access
public const string ContentPermissionEmptyRecycleBin = nameof(ContentPermissionEmptyRecycleBin);
public const string ContentPermissionAdministrationById = nameof(ContentPermissionAdministrationById);
public const string ContentPermissionPublishById = nameof(ContentPermissionPublishById);
public const string ContentPermissionRollbackById = nameof(ContentPermissionRollbackById);
public const string ContentPermissionProtectById = nameof(ContentPermissionProtectById);
public const string ContentPermissionBrowseById = nameof(ContentPermissionBrowseById);
public const string ContentPermissionDeleteById = nameof(ContentPermissionDeleteById);
// Single section access
public const string SectionAccessContent = nameof(SectionAccessContent);

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using System;
using System.Threading.Tasks;
using Umbraco.Web.Features;
@@ -34,14 +35,14 @@ namespace Umbraco.Web.BackOffice.Authorization
private bool? IsAllowed(AuthorizationHandlerContext context)
{
if (context.Resource is Endpoint endpoint)
if (!(context.Resource is Endpoint endpoint))
{
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
return _umbracoFeatures.IsControllerEnabled(controllerType);
throw new InvalidOperationException("This authorization handler can only be applied to controllers routed with endpoint routing");
}
return null;
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
return _umbracoFeatures.IsControllerEnabled(controllerType);
}
}
}