diff --git a/src/Umbraco.Core/Actions/ActionCopy.cs b/src/Umbraco.Core/Actions/ActionCopy.cs
index 477156d4ae..a568d0aa37 100644
--- a/src/Umbraco.Core/Actions/ActionCopy.cs
+++ b/src/Umbraco.Core/Actions/ActionCopy.cs
@@ -7,7 +7,9 @@ namespace Umbraco.Web.Actions
///
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";
diff --git a/src/Umbraco.Core/Actions/ActionRights.cs b/src/Umbraco.Core/Actions/ActionRights.cs
index b76f3b7800..dd021d03c0 100644
--- a/src/Umbraco.Core/Actions/ActionRights.cs
+++ b/src/Umbraco.Core/Actions/ActionRights.cs
@@ -7,7 +7,9 @@ namespace Umbraco.Web.Actions
///
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";
diff --git a/src/Umbraco.Core/Actions/ActionSort.cs b/src/Umbraco.Core/Actions/ActionSort.cs
index 8b4e01823e..9c463bc18e 100644
--- a/src/Umbraco.Core/Actions/ActionSort.cs
+++ b/src/Umbraco.Core/Actions/ActionSort.cs
@@ -9,7 +9,9 @@ namespace Umbraco.Web.Actions
///
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";
diff --git a/src/Umbraco.Core/Actions/ActionUnpublish.cs b/src/Umbraco.Core/Actions/ActionUnpublish.cs
index 8ece4c008e..f9270d926b 100644
--- a/src/Umbraco.Core/Actions/ActionUnpublish.cs
+++ b/src/Umbraco.Core/Actions/ActionUnpublish.cs
@@ -10,7 +10,9 @@ namespace Umbraco.Web.Actions
///
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";
diff --git a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersAuthorizeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs
similarity index 86%
rename from src/Umbraco.Web.BackOffice/Authorization/AdminUsersAuthorizeHandler.cs
rename to src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs
index 7113fd1b7a..05c18b4bb6 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersAuthorizeHandler.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersHandler.cs
@@ -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
///
/// if the users being edited is an admin then we must ensure that the current user is also an admin
///
- public class AdminUsersAuthorizeHandler : AuthorizationHandler
+ public class AdminUsersHandler : AuthorizationHandler
{
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;
diff --git a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersAuthorizeRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs
similarity index 52%
rename from src/Umbraco.Web.BackOffice/Authorization/AdminUsersAuthorizeRequirement.cs
rename to src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs
index a4fec9c729..3d168776e9 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/AdminUsersAuthorizeRequirement.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/AdminUsersRequirement.cs
@@ -4,11 +4,11 @@ namespace Umbraco.Web.BackOffice.Authorization
{
///
- /// Authorization requirement for the
+ /// Authorization requirement for the
///
- public class AdminUsersAuthorizeRequirement : IAuthorizationRequirement
+ public class AdminUsersRequirement : IAuthorizationRequirement
{
- public AdminUsersAuthorizeRequirement(string queryStringName = "id")
+ public AdminUsersRequirement(string queryStringName = "id")
{
QueryStringName = queryStringName;
}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeAuthorizationHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs
similarity index 79%
rename from src/Umbraco.Web.BackOffice/Authorization/BackOfficeAuthorizationHandler.cs
rename to src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs
index 1efaade3af..8c1da3b204 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeAuthorizationHandler.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeHandler.cs
@@ -9,18 +9,18 @@ namespace Umbraco.Web.BackOffice.Authorization
///
/// Ensures authorization is successful for a back office user.
///
- public class BackOfficeAuthorizationHandler : AuthorizationHandler
+ public class BackOfficeHandler : AuthorizationHandler
{
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
{
diff --git a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeAuthorizeRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs
similarity index 52%
rename from src/Umbraco.Web.BackOffice/Authorization/BackOfficeAuthorizeRequirement.cs
rename to src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs
index 684a90891f..d1b13efe2c 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/BackOfficeAuthorizeRequirement.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/BackOfficeRequirement.cs
@@ -3,11 +3,11 @@
namespace Umbraco.Web.BackOffice.Authorization
{
///
- /// Authorization requirement for the
+ /// Authorization requirement for the
///
- public class BackOfficeAuthorizeRequirement : IAuthorizationRequirement
+ public class BackOfficeRequirement : IAuthorizationRequirement
{
- public BackOfficeAuthorizeRequirement(bool requireApproval = true)
+ public BackOfficeRequirement(bool requireApproval = true)
{
RequireApproval = requireApproval;
}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionQueryStringHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionQueryStringHandler.cs
new file mode 100644
index 0000000000..b6e99d9320
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionQueryStringHandler.cs
@@ -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
+{
+ ///
+ /// Used to authorize if the user has the correct permission access to the content for the specified
+ ///
+ public class ContentPermissionResourceHandler : AuthorizationHandler
+ {
+ 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;
+ }
+ }
+
+ ///
+ /// Used to authorize if the user has the correct permission access to the content for the content id specified in a query string
+ ///
+ public class ContentPermissionQueryStringHandler : AuthorizationHandler
+ {
+ 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;
+ }
+
+ }
+}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionResourceRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionResourceRequirement.cs
new file mode 100644
index 0000000000..6781f45ab5
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionResourceRequirement.cs
@@ -0,0 +1,21 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Umbraco.Web.BackOffice.Authorization
+{
+ ///
+ /// An authorization requirement for
+ ///
+ public class ContentPermissionResourceRequirement : IAuthorizationRequirement
+ {
+ ///
+ /// Create an authorization requirement for a resource
+ ///
+ ///
+ public ContentPermissionResourceRequirement(char permissionToCheck)
+ {
+ PermissionToCheck = permissionToCheck;
+ }
+
+ public char PermissionToCheck { get; }
+ }
+}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs
new file mode 100644
index 0000000000..9d2e931495
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Authorization/ContentPermissionsQueryStringRequirement.cs
@@ -0,0 +1,38 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Umbraco.Web.BackOffice.Authorization
+{
+
+ ///
+ /// An authorization requirement for
+ ///
+ public class ContentPermissionsQueryStringRequirement : IAuthorizationRequirement
+ {
+
+ ///
+ /// Create an authorization requirement for a specific node id
+ ///
+ ///
+ ///
+ public ContentPermissionsQueryStringRequirement(int nodeId, char permissionToCheck)
+ {
+ NodeId = nodeId;
+ PermissionToCheck = permissionToCheck;
+ }
+
+ ///
+ /// Create an authorization requirement for a node id based on a query string parameter
+ ///
+ ///
+ ///
+ public ContentPermissionsQueryStringRequirement(char permissionToCheck, string[] paramNames)
+ {
+ QueryStringNames = paramNames;
+ PermissionToCheck = permissionToCheck;
+ }
+
+ public int? NodeId { get; }
+ public string[] QueryStringNames { get; }
+ public char PermissionToCheck { get; }
+ }
+}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginAuthorizeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs
similarity index 81%
rename from src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginAuthorizeHandler.cs
rename to src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs
index 510682081a..70656e202f 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginAuthorizeHandler.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginHandler.cs
@@ -8,11 +8,11 @@ namespace Umbraco.Web.BackOffice.Authorization
///
/// Ensures the resource cannot be accessed if returns true
///
- public class DenyLocalLoginAuthorizeHandler : AuthorizationHandler
+ public class DenyLocalLoginHandler : AuthorizationHandler
{
private readonly IBackOfficeExternalLoginProviders _externalLogins;
- public DenyLocalLoginAuthorizeHandler(IBackOfficeExternalLoginProviders externalLogins)
+ public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins)
{
_externalLogins = externalLogins;
}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs
index 5fe7f1c071..a4f3a7e306 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/DenyLocalLoginRequirement.cs
@@ -3,7 +3,7 @@
namespace Umbraco.Web.BackOffice.Authorization
{
///
- /// Marker requirement for the
+ /// Marker requirement for the
///
public class DenyLocalLoginRequirement : IAuthorizationRequirement
{
diff --git a/src/Umbraco.Web.BackOffice/Authorization/UmbracoSectionAuthorizeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs
similarity index 79%
rename from src/Umbraco.Web.BackOffice/Authorization/UmbracoSectionAuthorizeHandler.cs
rename to src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs
index b063c4c7d9..91ed79c685 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/UmbracoSectionAuthorizeHandler.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/SectionHandler.cs
@@ -12,16 +12,16 @@ namespace Umbraco.Web.BackOffice.Authorization
///
/// The user only needs access to one of the sections specified, not all of the sections.
///
- public class UmbracoSectionAuthorizeHandler : AuthorizationHandler
+ public class SectionHandler : AuthorizationHandler
{
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(
diff --git a/src/Umbraco.Web.BackOffice/Authorization/SectionAliasesRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs
similarity index 59%
rename from src/Umbraco.Web.BackOffice/Authorization/SectionAliasesRequirement.cs
rename to src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs
index 9789258bf8..033eccdd18 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/SectionAliasesRequirement.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/SectionRequirement.cs
@@ -4,15 +4,15 @@ using System.Collections.Generic;
namespace Umbraco.Web.BackOffice.Authorization
{
///
- /// Authorization requirements for
+ /// Authorization requirements for
///
- public class SectionAliasesRequirement : IAuthorizationRequirement
+ public class SectionRequirement : IAuthorizationRequirement
{
///
/// The aliases for sections that the user will need access to
///
public IReadOnlyCollection SectionAliases { get; }
- public SectionAliasesRequirement(params string[] aliases) => SectionAliases = aliases;
+ public SectionRequirement(params string[] aliases) => SectionAliases = aliases;
}
}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/UmbracoTreeAuthorizeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs
similarity index 86%
rename from src/Umbraco.Web.BackOffice/Authorization/UmbracoTreeAuthorizeHandler.cs
rename to src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs
index 9be8fd578f..932f467ac4 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/UmbracoTreeAuthorizeHandler.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/TreeHandler.cs
@@ -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.
///
- public class UmbracoTreeAuthorizeHandler : AuthorizationHandler
+ public class TreeHandler : AuthorizationHandler
{
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.
///
- 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))
diff --git a/src/Umbraco.Web.BackOffice/Authorization/TreeAliasesRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs
similarity index 60%
rename from src/Umbraco.Web.BackOffice/Authorization/TreeAliasesRequirement.cs
rename to src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs
index 44ecb8dfe3..7261cee51b 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/TreeAliasesRequirement.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/TreeRequirement.cs
@@ -5,15 +5,15 @@ namespace Umbraco.Web.BackOffice.Authorization
{
///
- /// Authorization requirements for
+ /// Authorization requirements for
///
- public class TreeAliasesRequirement : IAuthorizationRequirement
+ public class TreeRequirement : IAuthorizationRequirement
{
///
/// The aliases for trees that the user will need access to
///
public IReadOnlyCollection TreeAliases { get; }
- public TreeAliasesRequirement(params string[] aliases) => TreeAliases = aliases;
+ public TreeRequirement(params string[] aliases) => TreeAliases = aliases;
}
}
diff --git a/src/Umbraco.Web.BackOffice/Authorization/UserGroupAuthorizationHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs
similarity index 88%
rename from src/Umbraco.Web.BackOffice/Authorization/UserGroupAuthorizationHandler.cs
rename to src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs
index f34c58411f..5a9f21df81 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/UserGroupAuthorizationHandler.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/UserGroupHandler.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Web.BackOffice.Authorization
///
/// Authorizes that the current user has access to the user group Id in the request
///
- public class UserGroupAuthorizationHandler : AuthorizationHandler
+ public class UserGroupHandler : AuthorizationHandler
{
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;
diff --git a/src/Umbraco.Web.BackOffice/Authorization/UserGroupAuthorizeRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs
similarity index 52%
rename from src/Umbraco.Web.BackOffice/Authorization/UserGroupAuthorizeRequirement.cs
rename to src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs
index 5a411b89fd..aae5733d96 100644
--- a/src/Umbraco.Web.BackOffice/Authorization/UserGroupAuthorizeRequirement.cs
+++ b/src/Umbraco.Web.BackOffice/Authorization/UserGroupRequirement.cs
@@ -3,11 +3,11 @@
namespace Umbraco.Web.BackOffice.Authorization
{
///
- /// Authorization requirement for the
+ /// Authorization requirement for the
///
- public class UserGroupAuthorizeRequirement : IAuthorizationRequirement
+ public class UserGroupRequirement : IAuthorizationRequirement
{
- public UserGroupAuthorizeRequirement(string queryStringName = "id")
+ public UserGroupRequirement(string queryStringName = "id")
{
QueryStringName = queryStringName;
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
index a60a773b72..06f6d3b052 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
@@ -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> _allLangs;
private readonly ILogger _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();
_allLangs = new Lazy>(() => _localizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase));
@@ -158,16 +163,22 @@ namespace Umbraco.Web.BackOffice.Controllers
///
/// Permission check is done for letter 'R' which is for which the user must have access to update
///
- [EnsureUserPermissionForContent("saveModel.ContentId", 'R')]
- public ActionResult> PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel)
- {
- if (saveModel.ContentId <= 0) return NotFound();
+ public async Task>> 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
///
/// Permission check is done for letter 'R' which is for which the user must have access to view
///
- [EnsureUserPermissionForContent("contentId", 'R')]
+ [Authorize(Policy = AuthorizationPolicies.ContentPermissionAdministrationById)]
public ActionResult> GetDetailedPermissions(int contentId)
{
if (contentId <= 0) return NotFound();
@@ -336,8 +347,8 @@ namespace Umbraco.Web.BackOffice.Controllers
///
///
///
- [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
///
///
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
- [EnsureUserPermissionForContent("id")]
+ [Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
[DetermineAmbiguousActionByPassingParameters]
public ContentItemDisplay GetById(Guid id)
{
@@ -378,7 +389,7 @@ namespace Umbraco.Web.BackOffice.Controllers
///
///
[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.
///
- ///
- [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.
///
- [EnsureUserPermissionForContent("id", ActionDelete.ActionLetter)]
+ [Authorize(Policy = AuthorizationPolicies.ContentPermissionDeleteById)]
[HttpDelete]
[HttpPost]
public IActionResult DeleteById(int id)
@@ -1585,7 +1595,7 @@ namespace Umbraco.Web.BackOffice.Controllers
///
[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
///
///
///
- [EnsureUserPermissionForContent("sorted.ParentId", 'S')]
- public IActionResult PostSort(ContentSortOrder sorted)
+ public async Task 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
///
///
///
- [EnsureUserPermissionForContent("move.ParentId", 'M')]
- public IActionResult PostMove(MoveOrCopy move)
+ public async Task 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
///
///
///
- [EnsureUserPermissionForContent("copy.ParentId", 'C')]
- public IActionResult PostCopy(MoveOrCopy copy)
+ public async Task 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
///
/// The content and variants to unpublish
///
- [EnsureUserPermissionForContent("model.Id", 'Z')]
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
- public ContentItemDisplay PostUnpublish(UnpublishContent model)
+ public async Task> 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> GetNotificationOptions(int contentId)
{
var notifications = new List();
@@ -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)
{
diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs
index 81674ca11c..8e3eb96660 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs
@@ -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();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
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));
diff --git a/src/Umbraco.Web.BackOffice/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/EnsureUserPermissionForContentAttribute.cs
deleted file mode 100644
index 6e0095b4b4..0000000000
--- a/src/Umbraco.Web.BackOffice/Filters/EnsureUserPermissionForContentAttribute.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Auth filter to check if the current user has access to the content item (by id).
- ///
- ///
- /// 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.
- ///
- public sealed class EnsureUserPermissionForContentAttribute : TypeFilterAttribute
- {
-
- ///
- /// This constructor will only be able to test the start node access
- ///
- 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)
- {
-
- }
-
-
- }
- }
-}
diff --git a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs
index 9f0a6ddd1f..644ee4dd71 100644
--- a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs
+++ b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs
@@ -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);
diff --git a/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs b/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs
index d8b9ab7ff4..3f2fc0b6bb 100644
--- a/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs
+++ b/src/Umbraco.Web.Common/Authorization/FeatureAuthorizeHandler.cs
@@ -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();
- 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();
+ var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
+ return _umbracoFeatures.IsControllerEnabled(controllerType);
}
}
}