diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs index 669c2cdc93..1c1dad37c9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentControllerBase.cs @@ -18,21 +18,18 @@ public abstract class CreateDocumentControllerBase : DocumentControllerBase protected async Task HandleRequest(CreateDocumentRequestModel requestModel, Func> authorizedHandler) { - // 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 + // We intentionally don't pass in cultures here. + // This is to support the client sending values for all cultures even if the user doesn't have access to the language. + // Values for unauthorized languages are later ignored in the ContentEditingService. + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + User, + ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id), + AuthorizationPolicies.ContentPermissionByResource); - // IEnumerable 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(); - // } + if (authorizationResult.Succeeded is false) + { + return Forbidden(); + } return await authorizedHandler(); } diff --git a/tests/Umbraco.Tests.Common/TestHelpers/DocumentUpdateHelper.cs b/tests/Umbraco.Tests.Common/TestHelpers/DocumentUpdateHelper.cs index d3a8546fd8..25873a8dca 100644 --- a/tests/Umbraco.Tests.Common/TestHelpers/DocumentUpdateHelper.cs +++ b/tests/Umbraco.Tests.Common/TestHelpers/DocumentUpdateHelper.cs @@ -28,4 +28,32 @@ public static class DocumentUpdateHelper return updateRequestModel; } + + public static CreateDocumentRequestModel CreateDocumentRequestModel(ContentCreateModel createModel) + { + var createDocumentRequestModel = new CreateDocumentRequestModel + { + Template = ReferenceByIdModel.ReferenceOrNull(createModel.TemplateKey), + DocumentType = new ReferenceByIdModel(createModel.ContentTypeKey), + Parent = ReferenceByIdModel.ReferenceOrNull(createModel.ParentKey), + }; + + createDocumentRequestModel.Variants = + [ + new DocumentVariantRequestModel + { + Segment = null, + Culture = null, + Name = createModel.InvariantName!, + } + ]; + createDocumentRequestModel.Values = createModel.InvariantProperties.Select(x => new DocumentValueModel + { + Alias = x.Alias, + Value = x.Value, + }); + + + return createDocumentRequestModel; + } } diff --git a/tests/Umbraco.Tests.Integration/ManagementApi/Policies/CreateDocumentTests.cs b/tests/Umbraco.Tests.Integration/ManagementApi/Policies/CreateDocumentTests.cs new file mode 100644 index 0000000000..bf59619f65 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/ManagementApi/Policies/CreateDocumentTests.cs @@ -0,0 +1,130 @@ +using System.Linq.Expressions; +using System.Net; +using System.Text; +using NUnit.Framework; +using Umbraco.Cms.Api.Management.Controllers.Document; +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Actions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.ContentTypeEditing; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.TestHelpers; + +namespace Umbraco.Cms.Tests.Integration.ManagementApi.Policies; + +[TestFixture] +public class CreateDocumentTests : ManagementApiTest +{ + private IUserGroupService UserGroupService => GetRequiredService(); + + private IShortStringHelper ShortStringHelper => GetRequiredService(); + + private ITemplateService TemplateService => GetRequiredService(); + + private IContentTypeEditingService ContentTypeEditingService => GetRequiredService(); + + private IJsonSerializer JsonSerializer => GetRequiredService(); + + private IContentService ContentService => GetRequiredService(); + + protected override Expression> MethodSelector => + x => x.Create(CancellationToken.None, null!); + + [Test] + public async Task ReadonlyUserCannotCreateDocument() + { + var userGroup = await CreateReadonlyUserGroupAsync(); + + await AuthenticateClientAsync(Client, async userService => + { + var email = "test@test.com"; + var testUserCreateModel = new UserCreateModel + { + Email = email, + Name = "Test Mc.Gee", + UserName = email, + UserGroupKeys = new HashSet { userGroup.Key }, + }; + + var userCreationResult = + await userService.CreateAsync(Constants.Security.SuperUserKey, testUserCreateModel, true); + + Assert.IsTrue(userCreationResult.Success); + + return (userCreationResult.Result.CreatedUser, "1234567890"); + }); + + var (contentType, template) = await CreateDocumentTypeAsync(); + var contentCreateModel = ContentEditingBuilder.CreateSimpleContent(contentType.Key); + + var requestModel = DocumentUpdateHelper.CreateDocumentRequestModel(contentCreateModel); + + var response = await GetManagementApiResponseAsync(requestModel); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task EditorCanCreateDocument() + { + await AuthenticateClientAsync(Client, "editor@editor.com", "1234567890", false); + + var (contentType, template) = await CreateDocumentTypeAsync(); + var requestModel = DocumentUpdateHelper.CreateDocumentRequestModel(ContentEditingBuilder.CreateSimpleContent(contentType.Key)); + + var response = await GetManagementApiResponseAsync(requestModel); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Created)); + + var locationHeader = response.Headers.GetValues("Location").First(); + var key = Guid.Parse(locationHeader.Split('/')[^1]); + var createdContent = ContentService.GetById(key); + Assert.NotNull(createdContent); + } + + private async Task GetManagementApiResponseAsync(CreateDocumentRequestModel requestModel) + { + var url = GetManagementApiUrl(x => x.Create(CancellationToken.None, requestModel)); + var requestBody = new StringContent(JsonSerializer.Serialize(requestModel), Encoding.UTF8, "application/json"); + var response = await Client.PostAsync(url, requestBody); + return response; + } + + private async Task CreateReadonlyUserGroupAsync() + { + var userGroup = new UserGroup(ShortStringHelper) + { + Name = "Test", + Alias = "test", + Permissions = new HashSet { ActionBrowse.ActionLetter }, + HasAccessToAllLanguages = true, + StartContentId = -1, + StartMediaId = -1 + }; + userGroup.AddAllowedSection("content"); + userGroup.AddAllowedSection("media"); + + var groupCreationResult = await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey); + Assert.IsTrue(groupCreationResult.Success); + return groupCreationResult.Result; + } + + private async Task<(IContentType contentType, ITemplate template)> CreateDocumentTypeAsync() + { + var userKey = Constants.Security.SuperUserKey; + var template = TemplateBuilder.CreateTextPageTemplate(); + var templateAttempt = await TemplateService.CreateAsync(template, userKey); + Assert.IsTrue(templateAttempt.Success); + + var contentTypeCreateModel = ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key); + var contentTypeAttempt = await ContentTypeEditingService.CreateAsync(contentTypeCreateModel, userKey); + Assert.IsTrue(contentTypeAttempt.Success); + + return (contentTypeAttempt.Result!, templateAttempt.Result!); + } +}