Allow the client to send all content, with all languages, even when the user do not have permissions to save a specific language. (#17052)
(cherry picked from commit c5243e562e)
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user