Skip to content

Commit

Permalink
Allow the client to send all content, with all languages, even when t…
Browse files Browse the repository at this point in the history
…he user do not have permissions to save a specific language. (#17052)
  • Loading branch information
bergmania committed Sep 18, 2024
1 parent 5c857aa commit c5243e5
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@ protected CreateDocumentControllerBase(IAuthorizationService authorizationServic

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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@ protected UpdateDocumentControllerBase(IAuthorizationService authorizationServic

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();
}
Expand Down
97 changes: 95 additions & 2 deletions src/Umbraco.Core/Services/ContentEditingService.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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,
Expand All @@ -24,10 +32,46 @@ public 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)
Expand Down Expand Up @@ -65,7 +109,7 @@ public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> C
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)
{
Expand All @@ -78,6 +122,53 @@ public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> C
: 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);
Expand All @@ -102,6 +193,8 @@ public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> U
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)
{
Expand Down

0 comments on commit c5243e5

Please sign in to comment.