diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 2a77b2194ce6..c1cc61c4b285 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -826,6 +826,7 @@ public PagedResult> GetChildren( /// The content id to copy /// The name of the blueprint /// + [Authorize(Policy = AuthorizationPolicies.ContentPermissionCreateBlueprintFromId)] [HttpPost] public ActionResult CreateBlueprintFromContent( [FromQuery] int contentId, @@ -881,8 +882,9 @@ private bool EnsureUniqueName(string? name, IContent? content, string modelName) /// /// Saves content /// + [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] [FileUploadCleanupFilter] - [ContentSaveValidation] + [ContentSaveValidation(skipUserAccessValidation:true)] // skip user access validation because we "only" require Settings access to create new blueprints from scratch public async Task?>?> PostSaveBlueprint( [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { @@ -2077,6 +2079,7 @@ public IActionResult PostPublishByIdAndCulture(PublishContent model) return Ok(); } + [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] [HttpDelete] [HttpPost] public IActionResult DeleteBlueprint(int id) diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 4e5ed094ac97..3e54bb705a14 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -43,7 +43,6 @@ public LanguageController(ILocalizationService localizationService, IUmbracoMapp /// /// [HttpGet] - [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] public IEnumerable? GetAllLanguages() { IEnumerable allLanguages = _localizationService.GetAllLanguages(); diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs index 97722d830576..7ec5ff4730fb 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeAuth.cs @@ -179,6 +179,13 @@ private static void CreatePolicies(AuthorizationOptions options, string backOffi policy.Requirements.Add(new ContentPermissionsQueryStringRequirement(ActionDelete.ActionLetter)); }); + options.AddPolicy(AuthorizationPolicies.ContentPermissionCreateBlueprintFromId, policy => + { + policy.AuthenticationSchemes.Add(backOfficeAuthenticationScheme); + policy.Requirements.Add( + new ContentPermissionsQueryStringRequirement(ActionCreateBlueprintFromContent.ActionLetter, "contentId")); + }); + options.AddPolicy(AuthorizationPolicies.BackOfficeAccess, policy => { policy.AuthenticationSchemes.Add(backOfficeAuthenticationScheme); diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs index f2e61b694a82..33017ada8835 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs @@ -20,9 +20,12 @@ namespace Umbraco.Cms.Web.BackOffice.Filters; /// internal sealed class ContentSaveValidationAttribute : TypeFilterAttribute { - public ContentSaveValidationAttribute() : base(typeof(ContentSaveValidationFilter)) => + public ContentSaveValidationAttribute(bool skipUserAccessValidation = false) + : base(typeof(ContentSaveValidationFilter)) + { Order = -3000; // More important than ModelStateInvalidFilter.FilterOrder - + Arguments = new object[] { skipUserAccessValidation }; + } private sealed class ContentSaveValidationFilter : IAsyncActionFilter { @@ -32,6 +35,7 @@ private sealed class ContentSaveValidationFilter : IAsyncActionFilter private readonly ILocalizationService _localizationService; private readonly ILoggerFactory _loggerFactory; private readonly IPropertyValidationService _propertyValidationService; + private readonly bool _skipUserAccessValidation; public ContentSaveValidationFilter( @@ -40,7 +44,8 @@ public ContentSaveValidationFilter( IPropertyValidationService propertyValidationService, IAuthorizationService authorizationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - ILocalizationService localizationService) + ILocalizationService localizationService, + bool skipUserAccessValidation) { _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); @@ -49,6 +54,7 @@ public ContentSaveValidationFilter( _authorizationService = authorizationService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _localizationService = localizationService; + _skipUserAccessValidation = skipUserAccessValidation; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) @@ -88,7 +94,7 @@ private async Task OnActionExecutingAsync(ActionExecutingContext context) return; } - if (!await ValidateUserAccessAsync(model, context)) + if (_skipUserAccessValidation is false && await ValidateUserAccessAsync(model, context) is false) { return; } diff --git a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs index 50e399d4f0d1..a2e7b77d9a91 100644 --- a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs +++ b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs @@ -22,6 +22,7 @@ public static class AuthorizationPolicies public const string ContentPermissionProtectById = nameof(ContentPermissionProtectById); public const string ContentPermissionBrowseById = nameof(ContentPermissionBrowseById); public const string ContentPermissionDeleteById = nameof(ContentPermissionDeleteById); + public const string ContentPermissionCreateBlueprintFromId = nameof(ContentPermissionCreateBlueprintFromId); public const string MediaPermissionByResource = nameof(MediaPermissionByResource); public const string MediaPermissionPathById = nameof(MediaPermissionPathById);