Skip to content

Commit 234c267

Browse files
committed
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 c5243e5)
1 parent d2d6d34 commit 234c267

File tree

3 files changed

+123
-24
lines changed

3 files changed

+123
-24
lines changed

src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@ protected CreateDocumentControllerBase(IAuthorizationService authorizationServic
1818

1919
protected async Task<IActionResult> HandleRequest(CreateDocumentRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
2020
{
21-
IEnumerable<string> cultures = requestModel.Variants
22-
.Where(v => v.Culture is not null)
23-
.Select(v => v.Culture!);
24-
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
25-
User,
26-
ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures),
27-
AuthorizationPolicies.ContentPermissionByResource);
21+
// 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.
22+
// The values are ignored in the ContentEditingService
2823

29-
if (!authorizationResult.Succeeded)
30-
{
31-
return Forbidden();
32-
}
24+
// IEnumerable<string> cultures = requestModel.Variants
25+
// .Where(v => v.Culture is not null)
26+
// .Select(v => v.Culture!);
27+
// AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
28+
// User,
29+
// ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures),
30+
// AuthorizationPolicies.ContentPermissionByResource);
31+
//
32+
// if (!authorizationResult.Succeeded)
33+
// {
34+
// return Forbidden();
35+
// }
3336

3437
return await authorizedHandler();
3538
}

src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentControllerBase.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,21 @@ protected UpdateDocumentControllerBase(IAuthorizationService authorizationServic
1717

1818
protected async Task<IActionResult> HandleRequest(Guid id, UpdateDocumentRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
1919
{
20-
IEnumerable<string> cultures = requestModel.Variants
21-
.Where(v => v.Culture is not null)
22-
.Select(v => v.Culture!);
23-
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
24-
User,
25-
ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures),
26-
AuthorizationPolicies.ContentPermissionByResource);
20+
// 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.
21+
// The values are ignored in the ContentEditingService
2722

28-
if (!authorizationResult.Succeeded)
29-
{
30-
return Forbidden();
31-
}
23+
// IEnumerable<string> cultures = requestModel.Variants
24+
// .Where(v => v.Culture is not null)
25+
// .Select(v => v.Culture!);
26+
// AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
27+
// User,
28+
// ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures),
29+
// AuthorizationPolicies.ContentPermissionByResource);
30+
//
31+
// if (!authorizationResult.Succeeded)
32+
// {
33+
// return Forbidden();
34+
// }
3235

3336
return await authorizedHandler();
3437
}

src/Umbraco.Core/Services/ContentEditingService.cs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
3+
using Umbraco.Cms.Core.DependencyInjection;
24
using Umbraco.Cms.Core.Models;
35
using Umbraco.Cms.Core.Models.ContentEditing;
6+
using Umbraco.Cms.Core.Models.Membership;
47
using Umbraco.Cms.Core.PropertyEditors;
58
using Umbraco.Cms.Core.Scoping;
69
using Umbraco.Cms.Core.Services.OperationStatus;
10+
using Umbraco.Extensions;
711

812
namespace Umbraco.Cms.Core.Services;
913

@@ -12,7 +16,11 @@ internal sealed class ContentEditingService
1216
{
1317
private readonly ITemplateService _templateService;
1418
private readonly ILogger<ContentEditingService> _logger;
19+
private readonly IUserService _userService;
20+
private readonly ILocalizationService _localizationService;
21+
private readonly ILanguageService _languageService;
1522

23+
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
1624
public ContentEditingService(
1725
IContentService contentService,
1826
IContentTypeService contentTypeService,
@@ -24,10 +32,46 @@ public ContentEditingService(
2432
IUserIdKeyResolver userIdKeyResolver,
2533
ITreeEntitySortingService treeEntitySortingService,
2634
IContentValidationService contentValidationService)
35+
: this(
36+
contentService,
37+
contentTypeService,
38+
propertyEditorCollection,
39+
dataTypeService,
40+
templateService,
41+
logger,
42+
scopeProvider,
43+
userIdKeyResolver,
44+
treeEntitySortingService,
45+
contentValidationService,
46+
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
47+
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(),
48+
StaticServiceProvider.Instance.GetRequiredService<ILanguageService>()
49+
)
50+
{
51+
52+
}
53+
54+
public ContentEditingService(
55+
IContentService contentService,
56+
IContentTypeService contentTypeService,
57+
PropertyEditorCollection propertyEditorCollection,
58+
IDataTypeService dataTypeService,
59+
ITemplateService templateService,
60+
ILogger<ContentEditingService> logger,
61+
ICoreScopeProvider scopeProvider,
62+
IUserIdKeyResolver userIdKeyResolver,
63+
ITreeEntitySortingService treeEntitySortingService,
64+
IContentValidationService contentValidationService,
65+
IUserService userService,
66+
ILocalizationService localizationService,
67+
ILanguageService languageService)
2768
: base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, contentValidationService, treeEntitySortingService)
2869
{
2970
_templateService = templateService;
3071
_logger = logger;
72+
_userService = userService;
73+
_localizationService = localizationService;
74+
_languageService = languageService;
3175
}
3276

3377
public async Task<IContent?> GetAsync(Guid key)
@@ -65,7 +109,7 @@ public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> C
65109
ContentEditingOperationStatus validationStatus = result.Status;
66110
ContentValidationResult validationResult = result.Result.ValidationResult;
67111

68-
IContent content = result.Result.Content!;
112+
IContent content = await EnsureOnlyAllowedFieldsAreUpdated(result.Result.Content!, userKey);
69113
ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, createModel.TemplateKey);
70114
if (updateTemplateStatus != ContentEditingOperationStatus.Success)
71115
{
@@ -78,6 +122,53 @@ public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> C
78122
: Attempt.FailWithStatus(saveStatus, new ContentCreateResult { Content = content });
79123
}
80124

125+
/// <summary>
126+
/// 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.
127+
/// </summary>
128+
private async Task<IContent> EnsureOnlyAllowedFieldsAreUpdated(IContent contentWithPotentialUnallowedChanges, Guid userKey)
129+
{
130+
if (contentWithPotentialUnallowedChanges.ContentType.VariesByCulture() is false)
131+
{
132+
return contentWithPotentialUnallowedChanges;
133+
}
134+
135+
IContent? existingContent = await GetAsync(contentWithPotentialUnallowedChanges.Key);
136+
137+
IUser? user = await _userService.GetAsync(userKey);
138+
139+
if (user is null)
140+
{
141+
return contentWithPotentialUnallowedChanges;
142+
}
143+
144+
var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!;
145+
146+
var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
147+
148+
foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures)
149+
{
150+
if (allowedCultures.Contains(culture))
151+
{
152+
continue;
153+
}
154+
155+
156+
// else override the updates values with the original values.
157+
foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties)
158+
{
159+
if (property.PropertyType.VariesByCulture() is false)
160+
{
161+
continue;
162+
}
163+
164+
var value = existingContent?.Properties.First(x=>x.Alias == property.Alias).GetValue(culture, null, false);
165+
property.SetValue(value, culture, null);
166+
}
167+
}
168+
169+
return contentWithPotentialUnallowedChanges;
170+
}
171+
81172
public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey)
82173
{
83174
IContent? content = ContentService.GetById(key);
@@ -102,6 +193,8 @@ public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> U
102193
ContentEditingOperationStatus validationStatus = result.Status;
103194
ContentValidationResult validationResult = result.Result.ValidationResult;
104195

196+
content = await EnsureOnlyAllowedFieldsAreUpdated(content, userKey);
197+
105198
ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, updateModel.TemplateKey);
106199
if (updateTemplateStatus != ContentEditingOperationStatus.Success)
107200
{

0 commit comments

Comments
 (0)