From 63fceebca08764360ac8f0999fd3c65a440bd220 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Nov 2020 22:17:42 +1100 Subject: [PATCH] Removes UmbracoApplicationAuthorizeAttribute and migrates usages to authz policies --- .../ModelsBuilderDashboardController.cs | 4 +- .../Authorization/AuthorizationPolicies.cs | 58 ++++++++++------ .../SectionAliasesRequirement.cs | 18 +++++ .../UmbracoSectionAuthorizeHandler.cs | 47 +++++++++++++ .../UmbracoTreeAuthorizeHandler.cs | 40 +++++------ .../Controllers/CodeFileController.cs | 4 +- .../Controllers/ContentController.cs | 9 +-- .../Controllers/DataTypeController.cs | 12 ++-- .../Controllers/LogController.cs | 6 +- .../Controllers/MediaController.cs | 4 +- .../Controllers/MemberController.cs | 4 +- .../Controllers/PackageController.cs | 4 +- .../Controllers/PackageInstallController.cs | 4 +- .../Controllers/RelationController.cs | 4 +- .../Controllers/TinyMceController.cs | 7 +- .../Controllers/UserGroupsController.cs | 4 +- .../Controllers/UsersController.cs | 4 +- .../BackOfficeServiceCollectionExtensions.cs | 58 +++++++++++++++- .../UmbracoApplicationAuthorizeAttribute.cs | 67 ------------------- .../HealthCheck/HealthCheckController.cs | 4 +- .../Profiling/WebProfilingController.cs | 4 +- .../Trees/ContentBlueprintTreeController.cs | 4 +- .../Trees/ContentTreeController.cs | 15 +---- .../Trees/MediaTreeController.cs | 11 +-- .../Trees/MemberTreeController.cs | 9 +-- 25 files changed, 233 insertions(+), 172 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Authorization/SectionAliasesRequirement.cs create mode 100644 src/Umbraco.Web.BackOffice/Authorization/UmbracoSectionAuthorizeHandler.cs delete mode 100644 src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs index 3adbc0df2c..76c33e8de4 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.Serialization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Configuration; @@ -7,6 +8,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Exceptions; using Umbraco.Core.Hosting; using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Filters; @@ -20,7 +22,7 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice /// correct CSRF security is adhered to for angular and it also ensures that this controller is not subseptipal to /// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats. /// - [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class ModelsBuilderDashboardController : UmbracoAuthorizedJsonController { private readonly ModelsBuilderSettings _config; diff --git a/src/Umbraco.Web.BackOffice/Authorization/AuthorizationPolicies.cs b/src/Umbraco.Web.BackOffice/Authorization/AuthorizationPolicies.cs index 7352a43b47..b6cad8dd06 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/AuthorizationPolicies.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/AuthorizationPolicies.cs @@ -2,31 +2,47 @@ { public static class AuthorizationPolicies { - public const string TreeAccessUsers = "TreeAccessUsers"; - public const string TreeAccessPartialViews = "TreeAccessPartialViews"; - public const string TreeAccessPartialViewMacros = "TreeAccessPartialViewMacros"; - public const string TreeAccessDataTypes = "TreeAccessDataTypes"; - public const string TreeAccessPackages = "TreeAccessPackages"; - public const string TreeAccessLogs = "TreeAccessLogs"; - public const string TreeAccessTemplates = "TreeAccessTemplates"; - public const string TreeAccessDictionary = "TreeAccessDictionary"; - public const string TreeAccessRelationTypes = "TreeAccessRelationTypes"; - public const string TreeAccessMediaTypes = "TreeAccessMediaTypes"; - public const string TreeAccessMacros = "TreeAccessMacros"; - public const string TreeAccessLanguages = "TreeAccessLanguages"; - public const string TreeAccessMemberGroups = "TreeAccessMemberGroups"; - public const string TreeAccessDocumentTypes = "TreeAccessDocumentTypes"; - public const string TreeAccessMemberTypes = "TreeAccessMemberTypes"; - public const string TreeAccessDocumentsOrDocumentTypes = "TreeAccessDocumentsAndDocumentTypes"; - public const string TreeAccessMediaOrMediaTypes = "TreeAccessMediaAndMediaTypes"; - public const string TreeAccessMembersOrMemberTypes = "TreeAccessMembersAndMemberTypes"; - public const string TreeAccessAnySchemaTypes = "TreeAccessSchemaTypes"; - public const string TreeAccessDictionaryOrTemplates = "TreeAccessDictionaryOrTemplates"; + // TODO: Rethink these names! Describe what they are doing probably + + public const string SectionAccessContentOrMedia = nameof(SectionAccessContentOrMedia); + public const string SectionAccessContent = nameof(SectionAccessContent); + public const string SectionAccessPackages = nameof(SectionAccessPackages); + public const string SectionAccessUsers = nameof(SectionAccessUsers); + public const string SectionAccessMedia = nameof(SectionAccessMedia); + public const string SectionAccessSettings = nameof(SectionAccessSettings); + public const string SectionAccessForTinyMce = nameof(SectionAccessForTinyMce); + public const string SectionAccessForMemberTree = nameof(SectionAccessForMemberTree); + public const string SectionAccessForMediaTree = nameof(SectionAccessForMediaTree); + public const string SectionAccessForContentTree = nameof(SectionAccessForContentTree); + public const string SectionAccessForDataTypeReading = nameof(SectionAccessForDataTypeReading); + public const string SectionAccessMembers = nameof(SectionAccessMembers); + + public const string TreeAccessDocuments = nameof(TreeAccessDocuments); + public const string TreeAccessUsers = nameof(TreeAccessUsers); + public const string TreeAccessPartialViews = nameof(TreeAccessPartialViews); + public const string TreeAccessPartialViewMacros = nameof(TreeAccessPartialViewMacros); + public const string TreeAccessDataTypes = nameof(TreeAccessDataTypes); + public const string TreeAccessPackages = nameof(TreeAccessPackages); + public const string TreeAccessLogs = nameof(TreeAccessLogs); + public const string TreeAccessTemplates = nameof(TreeAccessTemplates); + public const string TreeAccessDictionary = nameof(TreeAccessDictionary); + public const string TreeAccessRelationTypes = nameof(TreeAccessRelationTypes); + public const string TreeAccessMediaTypes = nameof(TreeAccessMediaTypes); + public const string TreeAccessMacros = nameof(TreeAccessMacros); + public const string TreeAccessLanguages = nameof(TreeAccessLanguages); + public const string TreeAccessMemberGroups = nameof(TreeAccessMemberGroups); + public const string TreeAccessDocumentTypes = nameof(TreeAccessDocumentTypes); + public const string TreeAccessMemberTypes = nameof(TreeAccessMemberTypes); + public const string TreeAccessDocumentsOrDocumentTypes = nameof(TreeAccessDocumentsOrDocumentTypes); + public const string TreeAccessMediaOrMediaTypes = nameof(TreeAccessMediaOrMediaTypes); + public const string TreeAccessMembersOrMemberTypes = nameof(TreeAccessMembersOrMemberTypes); + public const string TreeAccessAnySchemaTypes = nameof(TreeAccessAnySchemaTypes); + public const string TreeAccessDictionaryOrTemplates = nameof(TreeAccessDictionaryOrTemplates); /// /// Defines access based on if the user has access to any tree's exposing any types of content (documents, media, members) /// or any content types (document types, media types, member types) /// - public const string TreeAccessAnyContentOrTypes = "TreeAccessAnyContentOrTypes"; + public const string TreeAccessAnyContentOrTypes = nameof(TreeAccessAnyContentOrTypes); } } diff --git a/src/Umbraco.Web.BackOffice/Authorization/SectionAliasesRequirement.cs b/src/Umbraco.Web.BackOffice/Authorization/SectionAliasesRequirement.cs new file mode 100644 index 0000000000..9789258bf8 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Authorization/SectionAliasesRequirement.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Authorization; +using System.Collections.Generic; + +namespace Umbraco.Web.BackOffice.Authorization +{ + /// + /// Authorization requirements for + /// + public class SectionAliasesRequirement : IAuthorizationRequirement + { + /// + /// The aliases for sections that the user will need access to + /// + public IReadOnlyCollection SectionAliases { get; } + + public SectionAliasesRequirement(params string[] aliases) => SectionAliases = aliases; + } +} diff --git a/src/Umbraco.Web.BackOffice/Authorization/UmbracoSectionAuthorizeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/UmbracoSectionAuthorizeHandler.cs new file mode 100644 index 0000000000..b063c4c7d9 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Authorization/UmbracoSectionAuthorizeHandler.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Authorization; +using System.Linq; +using System.Threading.Tasks; +using Umbraco.Core.Security; + +namespace Umbraco.Web.BackOffice.Authorization +{ + + /// + /// Ensures that the current user has access to the section + /// + /// + /// The user only needs access to one of the sections specified, not all of the sections. + /// + public class UmbracoSectionAuthorizeHandler : AuthorizationHandler + { + private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + + public UmbracoSectionAuthorizeHandler(IBackOfficeSecurityAccessor backofficeSecurityAccessor) + { + _backofficeSecurityAccessor = backofficeSecurityAccessor; + } + + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SectionAliasesRequirement requirement) + { + if (IsAuthorized(requirement)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + + private bool IsAuthorized(SectionAliasesRequirement requirement) + { + var authorized = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null + && requirement.SectionAliases.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( + app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); + + return authorized; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Authorization/UmbracoTreeAuthorizeHandler.cs b/src/Umbraco.Web.BackOffice/Authorization/UmbracoTreeAuthorizeHandler.cs index b2cd18c1ce..9be8fd578f 100644 --- a/src/Umbraco.Web.BackOffice/Authorization/UmbracoTreeAuthorizeHandler.cs +++ b/src/Umbraco.Web.BackOffice/Authorization/UmbracoTreeAuthorizeHandler.cs @@ -8,8 +8,9 @@ using Umbraco.Web.Services; namespace Umbraco.Web.BackOffice.Authorization { + /// - /// Ensures that the current user has access to the application for which the specified tree(s) belongs + /// Ensures that the current user has access to the section for which the specified tree(s) belongs /// /// /// This would allow a tree to be moved between sections. @@ -17,10 +18,6 @@ namespace Umbraco.Web.BackOffice.Authorization /// public class UmbracoTreeAuthorizeHandler : AuthorizationHandler { - /// - /// Can be used by unit tests to enable/disable this filter - /// - internal static readonly bool Enable = true; private readonly ITreeService _treeService; private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; @@ -40,25 +37,6 @@ namespace Umbraco.Web.BackOffice.Authorization _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); } - private bool IsAuthorized(TreeAliasesRequirement requirement) - { - if (Enable == false) - { - return true; - } - - var apps = requirement.TreeAliases.Select(x => _treeService - .GetByAlias(x)) - .WhereNotNull() - .Select(x => x.SectionAlias) - .Distinct() - .ToArray(); - - return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null - && apps.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( - app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); - } - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TreeAliasesRequirement requirement) { if (IsAuthorized(requirement)) @@ -72,5 +50,19 @@ namespace Umbraco.Web.BackOffice.Authorization return Task.CompletedTask; } + + private bool IsAuthorized(TreeAliasesRequirement requirement) + { + var apps = requirement.TreeAliases.Select(x => _treeService + .GetByAlias(x)) + .WhereNotNull() + .Select(x => x.SectionAlias) + .Distinct() + .ToArray(); + + return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null + && apps.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( + app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); + } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 4bf76a108a..118dbf0926 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -14,6 +15,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Strings.Css; using Umbraco.Extensions; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; @@ -28,7 +30,7 @@ namespace Umbraco.Web.BackOffice.Controllers // ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/ [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] //[PrefixlessBodyModelValidator] - [UmbracoApplicationAuthorize(Constants.Applications.Settings)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class CodeFileController : BackOfficeNotificationsController { private readonly IIOHelper _ioHelper; diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index ad51ea7dfd..f91bb9c124 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -37,19 +37,16 @@ using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; using Umbraco.Web.Models.Mapping; - +using Microsoft.AspNetCore.Authorization; +using Umbraco.Web.BackOffice.Authorization; namespace Umbraco.Web.BackOffice.Controllers { /// /// The API controller used for editing content /// - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the content application. - /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)] public class ContentController : ContentControllerBase { private readonly PropertyEditorCollection _propertyEditors; diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index f8bc313bed..eb2108a5e2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -416,8 +416,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoApplicationAuthorize(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForDataTypeReading)] public IEnumerable GetAll() { return _dataTypeService @@ -432,8 +431,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoApplicationAuthorize(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForDataTypeReading)] public IDictionary> GetGroupedDataTypes() { var dataTypes = _dataTypeService @@ -464,8 +462,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoApplicationAuthorize(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForDataTypeReading)] public IDictionary> GetGroupedPropertyEditors() { var datatypes = new List(); @@ -496,8 +493,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members /// - [UmbracoApplicationAuthorize(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForDataTypeReading)] public IEnumerable GetAllPropertyEditors() { return _propertyEditorCollection diff --git a/src/Umbraco.Web.BackOffice/Controllers/LogController.cs b/src/Umbraco.Web.BackOffice/Controllers/LogController.cs index 4d816624de..a8210eb20b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LogController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LogController.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.AspNetCore.Authorization; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -10,6 +11,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Models.ContentEditing; @@ -52,7 +54,7 @@ namespace Umbraco.Web.BackOffice.Controllers _sqlContext = sqlContext ?? throw new ArgumentNullException(nameof(sqlContext)); } - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContentOrMedia)] public PagedResult GetPagedEntityLog(int id, int pageNumber = 1, int pageSize = 10, diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 0e97ed84af..5d8b9c0e97 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -40,6 +40,8 @@ using Umbraco.Web.Common.Exceptions; using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; +using Umbraco.Web.BackOffice.Authorization; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Controllers { @@ -48,7 +50,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// access to ALL of the methods on this controller will need access to the media application. /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize(Constants.Applications.Media)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)] public class MediaController : ContentControllerBase { private readonly IShortStringHelper _shortStringHelper; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 55042d458b..0b090a5e47 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Net.Mime; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -24,6 +25,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Extensions; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.ModelBinders; using Umbraco.Web.Common.Attributes; @@ -41,7 +43,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// access to ALL of the methods on this controller will need access to the member application. /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize(Constants.Applications.Members)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessMembers)] [OutgoingNoHyphenGuidFormat] public class MemberController : ContentControllerBase { diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 69d23d606b..de855ab421 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Text; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Semver; @@ -12,6 +13,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; @@ -23,7 +25,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// A controller used for managing packages in the back office /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessPackages)] public class PackageController : UmbracoAuthorizedJsonController { private readonly IHostingEnvironment _hostingEnvironment; diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs index 05631173c9..e08dfeff60 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs @@ -22,6 +22,8 @@ using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Security; +using Microsoft.AspNetCore.Authorization; +using Umbraco.Web.BackOffice.Authorization; namespace Umbraco.Web.BackOffice.Controllers { @@ -29,7 +31,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// A controller used for installing packages and managing all of the data in the packages section in the back office /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessPackages)] public class PackageInstallController : UmbracoAuthorizedJsonController { diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs index bf40e5722f..b8d5e1bc20 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; @@ -18,7 +20,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public class RelationController : UmbracoAuthorizedJsonController { private readonly UmbracoMapper _umbracoMapper; diff --git a/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs b/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs index 4bae8970bc..8f7b5c5a0e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -14,6 +15,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Media; using Umbraco.Core.Strings; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; @@ -23,10 +25,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize( - Constants.Applications.Content, - Constants.Applications.Media, - Constants.Applications.Members)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForTinyMce)] public class TinyMceController : UmbracoAuthorizedApiController { private readonly IHostingEnvironment _hostingEnvironment; diff --git a/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs b/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs index 4cba5064d1..940f2a76df 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs @@ -16,11 +16,13 @@ using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; +using Umbraco.Web.BackOffice.Authorization; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Controllers { [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize(Constants.Applications.Users)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)] [PrefixlessBodyModelValidator] public class UserGroupsController : UmbracoAuthorizedJsonController { diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index f187ec22ce..9da8e592a2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -42,11 +42,13 @@ using Task = System.Threading.Tasks.Task; using Umbraco.Net; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Security; +using Umbraco.Web.BackOffice.Authorization; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Controllers { [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize(Constants.Applications.Users)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)] [PrefixlessBodyModelValidator] [IsCurrentUserModelFilter] public class UsersController : UmbracoAuthorizedJsonController diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 7a77739342..00ab69f50e 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -109,10 +109,66 @@ namespace Umbraco.Extensions return new BackOfficeIdentityBuilder(services); } + /// + /// Add authorization handlers and policies + /// + /// private static void AddBackOfficeAuthorizationPolicies(this IServiceCollection services) { + services.AddSingleton(); + services.AddSingleton(); + services.AddAuthorization(options => { + + options.AddPolicy(AuthorizationPolicies.SectionAccessContent, policy => + policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Content))); + + options.AddPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, policy => + policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Content, Constants.Applications.Media))); + + options.AddPolicy(AuthorizationPolicies.SectionAccessUsers, policy => + policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Users))); + + options.AddPolicy(AuthorizationPolicies.SectionAccessForTinyMce, policy => + policy.Requirements.Add(new SectionAliasesRequirement( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members))); + + options.AddPolicy(AuthorizationPolicies.SectionAccessMedia, policy => + policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Media))); + + options.AddPolicy(AuthorizationPolicies.SectionAccessMembers, policy => + policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Members))); + + options.AddPolicy(AuthorizationPolicies.SectionAccessPackages, policy => + policy.Requirements.Add(new SectionAliasesRequirement(Constants.Applications.Packages))); + + options.AddPolicy(AuthorizationPolicies.SectionAccessSettings, policy => + policy.Requirements.Add(new SectionAliasesRequirement(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 + // this is not ideal but until we change permissions to be tree based (not section) there's not much else we can do here. + options.AddPolicy(AuthorizationPolicies.SectionAccessForContentTree, policy => + policy.Requirements.Add(new SectionAliasesRequirement( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users, + Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members))); + options.AddPolicy(AuthorizationPolicies.SectionAccessForMediaTree, policy => + policy.Requirements.Add(new SectionAliasesRequirement( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users, + Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members))); + options.AddPolicy(AuthorizationPolicies.SectionAccessForMemberTree, policy => + policy.Requirements.Add(new SectionAliasesRequirement( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members))); + + // Permission is granted to this policy if the user has access to any of these sections: Content, media, settings, developer, members + options.AddPolicy(AuthorizationPolicies.SectionAccessForDataTypeReading, policy => + policy.Requirements.Add(new SectionAliasesRequirement( + Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages))); + + options.AddPolicy(AuthorizationPolicies.TreeAccessDocuments, policy => + policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Content))); + options.AddPolicy(AuthorizationPolicies.TreeAccessUsers, policy => policy.Requirements.Add(new TreeAliasesRequirement(Constants.Trees.Users))); @@ -182,8 +238,6 @@ namespace Umbraco.Extensions Constants.Trees.MediaTypes, Constants.Trees.Media, Constants.Trees.MemberTypes, Constants.Trees.Members))); }); - - services.AddSingleton(); } } } diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs deleted file mode 100644 index cc1bdd4cd5..0000000000 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Umbraco.Core.Security; -using Umbraco.Web.Security; - -namespace Umbraco.Web.BackOffice.Filters -{ - public class UmbracoApplicationAuthorizeAttribute : TypeFilterAttribute - { - public UmbracoApplicationAuthorizeAttribute(params string[] appName) : base(typeof(UmbracoApplicationAuthorizeFilter)) - { - base.Arguments = new object[] - { - appName - }; - } - - private class UmbracoApplicationAuthorizeFilter : IAuthorizationFilter - { - /// - /// Can be used by unit tests to enable/disable this filter - /// - internal static bool Enable = true; - - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; - private readonly string[] _appNames; - - /// - /// Constructor to set any number of applications that the user needs access to be authorized - /// - /// - /// - /// If the user has access to any of the specified apps, they will be authorized. - /// - public UmbracoApplicationAuthorizeFilter(IBackOfficeSecurityAccessor backofficeSecurityAccessor, params string[] appName) - { - _backofficeSecurityAccessor = backofficeSecurityAccessor; - _appNames = appName; - } - - - public void OnAuthorization(AuthorizationFilterContext context) - { - if (!IsAuthorized()) - { - context.Result = new ForbidResult(); - } - } - - private bool IsAuthorized() - { - if (Enable == false) - { - return true; - } - - var authorized = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null - && _appNames.Any(app => _backofficeSecurityAccessor.BackOfficeSecurity.UserHasSectionAccess( - app, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser)); - - return authorized; - } - } - } - -} diff --git a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs index 768ede1787..44d8161519 100644 --- a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs @@ -10,6 +10,8 @@ using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Microsoft.Extensions.Logging; +using Umbraco.Web.BackOffice.Authorization; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.HealthCheck { @@ -17,7 +19,7 @@ namespace Umbraco.Web.BackOffice.HealthCheck /// The API controller used to display the health check info and execute any actions /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize(Constants.Applications.Settings)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class HealthCheckController : UmbracoAuthorizedJsonController { private readonly HealthCheckCollection _checks; diff --git a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs index 101e00d752..ae936984b0 100644 --- a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs +++ b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs @@ -4,6 +4,8 @@ using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Controllers; +using Microsoft.AspNetCore.Authorization; +using Umbraco.Web.BackOffice.Authorization; namespace Umbraco.Web.BackOffice.Profiling { @@ -11,7 +13,7 @@ namespace Umbraco.Web.BackOffice.Profiling /// The API controller used to display the state of the web profiler /// [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] - [UmbracoApplicationAuthorize(Constants.Applications.Settings)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] public class WebProfilingController : UmbracoAuthorizedJsonController { private readonly IHostingEnvironment _hosting; diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs index 5477797ccc..eba16f288f 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs @@ -1,11 +1,13 @@ using System; using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Web.Actions; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Models.Trees; @@ -21,7 +23,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// This authorizes based on access to the content section even though it exists in the settings /// - [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, SortOrder = 12, TreeGroup = Constants.Trees.Groups.Settings)] [PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [CoreTree] diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 755acdf0cb..6bc19e058c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -12,29 +12,20 @@ using Umbraco.Web.Actions; using Umbraco.Web.Models.Trees; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; -using Umbraco.Core.Configuration; using Umbraco.Core.Security; using Constants = Umbraco.Core.Constants; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.Security; using Umbraco.Web.WebApi; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; using Umbraco.Web.Trees; +using Microsoft.AspNetCore.Authorization; +using Umbraco.Web.BackOffice.Authorization; namespace Umbraco.Web.BackOffice.Trees { - //We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered - // this is not ideal but until we change permissions to be tree based (not section) there's not much else we can do here. - [UmbracoApplicationAuthorize( - Constants.Applications.Content, - Constants.Applications.Media, - Constants.Applications.Users, - Constants.Applications.Settings, - Constants.Applications.Packages, - Constants.Applications.Members)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForContentTree)] [Tree(Constants.Applications.Content, Constants.Trees.Content)] [PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [CoreTree] diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs index ab332e3843..2ae60047a3 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs @@ -20,17 +20,12 @@ using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Security; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; +using Microsoft.AspNetCore.Authorization; +using Umbraco.Web.BackOffice.Authorization; namespace Umbraco.Web.BackOffice.Trees { - //We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered - // this is not ideal but until we change permissions to be tree based (not section) there's not much else we can do here. - [UmbracoApplicationAuthorize( - Constants.Applications.Content, - Constants.Applications.Media, - Constants.Applications.Settings, - Constants.Applications.Packages, - Constants.Applications.Members)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForMediaTree)] [Tree(Constants.Applications.Media, Constants.Trees.Media)] [PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [CoreTree] diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs index 31ab66908c..e1b898c142 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs @@ -20,15 +20,12 @@ using Constants = Umbraco.Core.Constants; using Umbraco.Web.Security; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; +using Umbraco.Web.BackOffice.Authorization; +using Microsoft.AspNetCore.Authorization; namespace Umbraco.Web.BackOffice.Trees { - //We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered - // this is not ideal but until we change permissions to be tree based (not section) there's not much else we can do here. - [UmbracoApplicationAuthorize( - Constants.Applications.Content, - Constants.Applications.Media, - Constants.Applications.Members)] + [Authorize(Policy = AuthorizationPolicies.SectionAccessForMemberTree)] [Tree(Constants.Applications.Members, Constants.Trees.Members, SortOrder = 0)] [PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [CoreTree]