Allow the client to send all content, with all languages, even when the user do not have permissions to save a specific language. (#17052)

This commit is contained in:
Bjarke Berg
2024-09-18 13:10:15 +02:00
committed by GitHub
parent 5c857aa97d
commit c5243e562e
3 changed files with 123 additions and 24 deletions

View File

@@ -18,18 +18,21 @@ public abstract class CreateDocumentControllerBase : DocumentControllerBase
protected async Task<IActionResult> HandleRequest(CreateDocumentRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
{
IEnumerable<string> cultures = requestModel.Variants
.Where(v => v.Culture is not null)
.Select(v => v.Culture!);
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures),
AuthorizationPolicies.ContentPermissionByResource);
// TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages.
// The values are ignored in the ContentEditingService
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
// IEnumerable<string> cultures = requestModel.Variants
// .Where(v => v.Culture is not null)
// .Select(v => v.Culture!);
// AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
// User,
// ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures),
// AuthorizationPolicies.ContentPermissionByResource);
//
// if (!authorizationResult.Succeeded)
// {
// return Forbidden();
// }
return await authorizedHandler();
}

View File

@@ -17,18 +17,21 @@ public abstract class UpdateDocumentControllerBase : DocumentControllerBase
protected async Task<IActionResult> HandleRequest(Guid id, UpdateDocumentRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
{
IEnumerable<string> cultures = requestModel.Variants
.Where(v => v.Culture is not null)
.Select(v => v.Culture!);
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures),
AuthorizationPolicies.ContentPermissionByResource);
// TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages.
// The values are ignored in the ContentEditingService
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
// IEnumerable<string> cultures = requestModel.Variants
// .Where(v => v.Culture is not null)
// .Select(v => v.Culture!);
// AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
// User,
// ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures),
// AuthorizationPolicies.ContentPermissionByResource);
//
// if (!authorizationResult.Succeeded)
// {
// return Forbidden();
// }
return await authorizedHandler();
}

View File

@@ -1,9 +1,13 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services;
@@ -12,7 +16,11 @@ internal sealed class ContentEditingService
{
private readonly ITemplateService _templateService;
private readonly ILogger<ContentEditingService> _logger;
private readonly IUserService _userService;
private readonly ILocalizationService _localizationService;
private readonly ILanguageService _languageService;
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
public ContentEditingService(
IContentService contentService,
IContentTypeService contentTypeService,
@@ -24,10 +32,46 @@ internal sealed class ContentEditingService
IUserIdKeyResolver userIdKeyResolver,
ITreeEntitySortingService treeEntitySortingService,
IContentValidationService contentValidationService)
: this(
contentService,
contentTypeService,
propertyEditorCollection,
dataTypeService,
templateService,
logger,
scopeProvider,
userIdKeyResolver,
treeEntitySortingService,
contentValidationService,
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(),
StaticServiceProvider.Instance.GetRequiredService<ILanguageService>()
)
{
}
public ContentEditingService(
IContentService contentService,
IContentTypeService contentTypeService,
PropertyEditorCollection propertyEditorCollection,
IDataTypeService dataTypeService,
ITemplateService templateService,
ILogger<ContentEditingService> logger,
ICoreScopeProvider scopeProvider,
IUserIdKeyResolver userIdKeyResolver,
ITreeEntitySortingService treeEntitySortingService,
IContentValidationService contentValidationService,
IUserService userService,
ILocalizationService localizationService,
ILanguageService languageService)
: base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, contentValidationService, treeEntitySortingService)
{
_templateService = templateService;
_logger = logger;
_userService = userService;
_localizationService = localizationService;
_languageService = languageService;
}
public async Task<IContent?> GetAsync(Guid key)
@@ -65,7 +109,7 @@ internal sealed class ContentEditingService
ContentEditingOperationStatus validationStatus = result.Status;
ContentValidationResult validationResult = result.Result.ValidationResult;
IContent content = result.Result.Content!;
IContent content = await EnsureOnlyAllowedFieldsAreUpdated(result.Result.Content!, userKey);
ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, createModel.TemplateKey);
if (updateTemplateStatus != ContentEditingOperationStatus.Success)
{
@@ -78,6 +122,53 @@ internal sealed class ContentEditingService
: Attempt.FailWithStatus(saveStatus, new ContentCreateResult { Content = content });
}
/// <summary>
/// A temporary method that ensures the data is sent in is overridden by the original data, in cases where the user do not have permissions to change the data.
/// </summary>
private async Task<IContent> EnsureOnlyAllowedFieldsAreUpdated(IContent contentWithPotentialUnallowedChanges, Guid userKey)
{
if (contentWithPotentialUnallowedChanges.ContentType.VariesByCulture() is false)
{
return contentWithPotentialUnallowedChanges;
}
IContent? existingContent = await GetAsync(contentWithPotentialUnallowedChanges.Key);
IUser? user = await _userService.GetAsync(userKey);
if (user is null)
{
return contentWithPotentialUnallowedChanges;
}
var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!;
var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures)
{
if (allowedCultures.Contains(culture))
{
continue;
}
// else override the updates values with the original values.
foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties)
{
if (property.PropertyType.VariesByCulture() is false)
{
continue;
}
var value = existingContent?.Properties.First(x=>x.Alias == property.Alias).GetValue(culture, null, false);
property.SetValue(value, culture, null);
}
}
return contentWithPotentialUnallowedChanges;
}
public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey)
{
IContent? content = ContentService.GetById(key);
@@ -102,6 +193,8 @@ internal sealed class ContentEditingService
ContentEditingOperationStatus validationStatus = result.Status;
ContentValidationResult validationResult = result.Result.ValidationResult;
content = await EnsureOnlyAllowedFieldsAreUpdated(content, userKey);
ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, updateModel.TemplateKey);
if (updateTemplateStatus != ContentEditingOperationStatus.Success)
{