From 878e5056b59d402a54b697aea48571ce8f69216c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 29 May 2020 08:05:45 +0200 Subject: [PATCH] https://dev.azure.com/umbraco/D-Team%20Tracker/_workitems/edit/6587 - Cleanup when using typefilters --- .../Controllers/CodeFileController.cs | 2 +- .../Controllers/DataTypeController.cs | 18 ++--- .../Controllers/LanguageController.cs | 4 +- .../Controllers/LogController.cs | 2 +- .../Controllers/PackageController.cs | 2 +- .../Controllers/PackageInstallController.cs | 2 +- .../Controllers/RelationController.cs | 2 +- .../Controllers/RelationTypeController.cs | 2 +- .../Controllers/TemplateController.cs | 2 +- .../UmbracoApplicationAuthorizeAttribute.cs | 80 +++++++++++-------- .../Filters/UmbracoTreeAuthorizeAttribute.cs | 20 ++++- .../HealthCheck/HealthCheckController.cs | 2 +- .../Profiling/WebProfilingController.cs | 4 +- .../PrefixlessBodyModelValidator.cs | 45 +++++++++++ 14 files changed, 129 insertions(+), 58 deletions(-) create mode 100644 src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 05c0513bed..2d0ffc5d33 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -30,7 +30,7 @@ using Umbraco.Web.Editors; // ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/ [PluginController("UmbracoApi")] //[PrefixlessBodyModelValidator] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)] public class CodeFileController : BackOfficeNotificationsController { private readonly IIOHelper _ioHelper; diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index bdd1435b34..0d66a5e329 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// Content Types, Member Types or Media Types ... and of course to Data Types /// [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes}})] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)] public class DataTypeController : BackOfficeNotificationsController { private readonly PropertyEditorCollection _propertyEditors; @@ -408,8 +408,8 @@ 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 /// - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IEnumerable GetAll() { return _dataTypeService @@ -424,8 +424,8 @@ 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 /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoTreeAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IDictionary> GetGroupedDataTypes() { var dataTypes = _dataTypeService @@ -456,8 +456,8 @@ 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 /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoTreeAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IDictionary> GetGroupedPropertyEditors() { @@ -489,8 +489,8 @@ 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 /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, - Constants.Applications.Settings, Constants.Applications.Packages}})] + [UmbracoTreeAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, + Constants.Applications.Settings, Constants.Applications.Packages)] public IEnumerable GetAllPropertyEditors() { diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index a9f448d575..03e4ad163d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -78,7 +78,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Deletes a language with a given ID /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{ Constants.Trees.Languages} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.Languages)] [HttpDelete] [HttpPost] public IActionResult DeleteLanguage(int id) @@ -107,7 +107,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Creates or saves a language /// - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{ Constants.Trees.Languages} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.Languages)] [HttpPost] public Language SaveLanguage(Language language) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/LogController.cs b/src/Umbraco.Web.BackOffice/Controllers/LogController.cs index 76f7f35838..9bc2be8a39 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LogController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LogController.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web.BackOffice.Controllers _sqlContext = sqlContext ?? throw new ArgumentNullException(nameof(sqlContext)); } - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{ Constants.Applications.Content, Constants.Applications.Media } })] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content, Constants.Applications.Media)] public PagedResult GetPagedEntityLog(int id, int pageNumber = 1, int pageSize = 10, diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 220d67f794..23ba2e5771 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// A controller used for managing packages in the back office /// [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Packages}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)] 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 9f86d3e5cc..5330d4466f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs @@ -30,7 +30,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("UmbracoApi")] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Packages} })] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)] public class PackageInstallController : UmbracoAuthorizedJsonController { diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs index 699c06f1d9..686afe284b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationController.cs @@ -18,7 +18,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Applications.Content} })] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] public class RelationController : UmbracoAuthorizedJsonController { private readonly UmbracoMapper _umbracoMapper; diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index fe43944779..f679dd6b8e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// The API controller for editing relation types. /// [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.RelationTypes} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.RelationTypes)] public class RelationTypeController : BackOfficeNotificationsController { private readonly ILogger _logger; diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index dd51126dc5..6eae71a27e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -17,7 +17,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { [PluginController("UmbracoApi")] - [TypeFilter(typeof(UmbracoTreeAuthorizeAttribute), Arguments = new object[]{new string[]{Constants.Trees.Templates} })] + [UmbracoTreeAuthorizeAttribute(Constants.Trees.Templates)] public class TemplateController : BackOfficeNotificationsController { private readonly IFileService _fileService; diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs index 2a9f88c0be..81e61af5bf 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoApplicationAuthorizeAttribute.cs @@ -7,50 +7,62 @@ using Umbraco.Core; namespace Umbraco.Web.BackOffice.Filters { - public class UmbracoApplicationAuthorizeAttribute : Attribute, IAuthorizationFilter + public class UmbracoApplicationAuthorizeAttribute : TypeFilterAttribute { - /// - /// Can be used by unit tests to enable/disable this filter - /// - internal static bool Enable = true; - - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - 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 UmbracoApplicationAuthorizeAttribute(IUmbracoContextAccessor umbracoContextAccessor, params string[] appName) + public UmbracoApplicationAuthorizeAttribute(params string[] appName) : base(typeof(UmbracoApplicationAuthorizeFilter)) { - _umbracoContextAccessor = umbracoContextAccessor; - _appNames = appName; + base.Arguments = new object[] + { + appName + }; } - - public void OnAuthorization(AuthorizationFilterContext context) + private class UmbracoApplicationAuthorizeFilter : IAuthorizationFilter { - if (!IsAuthorized()) - { - context.Result = new ForbidResult(); - } - } + /// + /// Can be used by unit tests to enable/disable this filter + /// + internal static bool Enable = true; - private bool IsAuthorized() - { - if (Enable == false) + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + 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(IUmbracoContextAccessor umbracoContextAccessor, params string[] appName) { - return true; + _umbracoContextAccessor = umbracoContextAccessor; + _appNames = appName; } - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - var authorized = umbracoContext.Security.CurrentUser != null - && _appNames.Any(app => umbracoContext.Security.UserHasSectionAccess( - app, umbracoContext.Security.CurrentUser)); - return authorized; + public void OnAuthorization(AuthorizationFilterContext context) + { + if (!IsAuthorized()) + { + context.Result = new ForbidResult(); + } + } + + private bool IsAuthorized() + { + if (Enable == false) + { + return true; + } + + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var authorized = umbracoContext.Security.CurrentUser != null + && _appNames.Any(app => umbracoContext.Security.UserHasSectionAccess( + app, umbracoContext.Security.CurrentUser)); + + return authorized; + } } } + } diff --git a/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs index 9a8239b637..6db37d16f6 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -6,13 +6,24 @@ using Umbraco.Web.Services; namespace Umbraco.Web.BackOffice.Filters { - /// + + public class UmbracoTreeAuthorizeAttribute : TypeFilterAttribute + { + public UmbracoTreeAuthorizeAttribute(params string[] treeAliases) : base(typeof(UmbracoTreeAuthorizeFilter)) + { + base.Arguments = new object[] + { + treeAliases + }; + } + + /// /// Ensures that the current user has access to the application for which the specified tree(s) belongs /// /// /// This would allow a tree to be moved between sections /// - public sealed class UmbracoTreeAuthorizeAttribute : IAuthorizationFilter + private sealed class UmbracoTreeAuthorizeFilter : IAuthorizationFilter { /// /// Can be used by unit tests to enable/disable this filter @@ -32,7 +43,7 @@ namespace Umbraco.Web.BackOffice.Filters /// Multiple trees may be specified. /// /// - public UmbracoTreeAuthorizeAttribute(ITreeService treeService, IUmbracoContextAccessor umbracoContextAccessor, params string[] treeAliases) + public UmbracoTreeAuthorizeFilter(ITreeService treeService, IUmbracoContextAccessor umbracoContextAccessor, params string[] treeAliases) { _treeService = treeService; _umbracoContextAccessor = umbracoContextAccessor; @@ -68,4 +79,7 @@ namespace Umbraco.Web.BackOffice.Filters } } } + } + + } diff --git a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs index 00de2db0b6..131a4ac62c 100644 --- a/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthCheck/HealthCheckController.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.HealthCheck /// /// The API controller used to display the health check info and execute any actions /// - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)] 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 ba55dcb51c..b6cdf75f6f 100644 --- a/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs +++ b/src/Umbraco.Web.BackOffice/Profiling/WebProfilingController.cs @@ -10,8 +10,8 @@ namespace Umbraco.Web.BackOffice.Profiling /// /// The API controller used to display the state of the web profiler /// - [TypeFilter(typeof(UmbracoApplicationAuthorizeAttribute), Arguments = new object[]{new string []{Constants.Applications.Settings}})] - public class WebProfilingController : UmbracoAuthorizedJsonController + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)] + public class WebProfilingController : UmbracoAuthorizedJsonController { private readonly IHostingEnvironment _hosting; diff --git a/src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs b/src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs new file mode 100644 index 0000000000..d22b044e51 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Validation/PrefixlessBodyModelValidator.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Umbraco.Web.BackOffice.Validation +{ + public class PrefixlessBodyModelValidator : ObjectModelValidator + { + public PrefixlessBodyModelValidator(IModelMetadataProvider modelMetadataProvider, IList validatorProviders) : + base(modelMetadataProvider, validatorProviders) + { + + } + + public override ValidationVisitor GetValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, + ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) + { + var visitor = new PrefixlessValidationVisitor( + actionContext, + validatorProvider, + validatorCache, + metadataProvider, + validationState); + + return visitor; + } + + private class PrefixlessValidationVisitor : ValidationVisitor + { + public PrefixlessValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) + : base(actionContext, validatorProvider, validatorCache, metadataProvider, validationState) { + + } + + public override bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel) + { + return base.Validate(metadata, string.Empty, model, alwaysValidateAtTopLevel); + } + } + } + + +}