Skip to content

Commit 6668f3a

Browse files
V15: Add authorization to create (#18169)
* Add failing tests * Re-add auth
1 parent c1f74f4 commit 6668f3a

File tree

3 files changed

+169
-14
lines changed

3 files changed

+169
-14
lines changed

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

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

1919
protected async Task<IActionResult> HandleRequest(CreateDocumentRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
2020
{
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
21+
// We intentionally don't pass in cultures here.
22+
// This is to support the client sending values for all cultures even if the user doesn't have access to the language.
23+
// Values for unauthorized languages are later ignored in the ContentEditingService.
24+
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
25+
User,
26+
ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id),
27+
AuthorizationPolicies.ContentPermissionByResource);
2328

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-
// }
29+
if (authorizationResult.Succeeded is false)
30+
{
31+
return Forbidden();
32+
}
3633

3734
return await authorizedHandler();
3835
}

tests/Umbraco.Tests.Common/TestHelpers/DocumentUpdateHelper.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,32 @@ public static UpdateDocumentRequestModel CreateInvariantDocumentUpdateRequestMod
2828

2929
return updateRequestModel;
3030
}
31+
32+
public static CreateDocumentRequestModel CreateDocumentRequestModel(ContentCreateModel createModel)
33+
{
34+
var createDocumentRequestModel = new CreateDocumentRequestModel
35+
{
36+
Template = ReferenceByIdModel.ReferenceOrNull(createModel.TemplateKey),
37+
DocumentType = new ReferenceByIdModel(createModel.ContentTypeKey),
38+
Parent = ReferenceByIdModel.ReferenceOrNull(createModel.ParentKey),
39+
};
40+
41+
createDocumentRequestModel.Variants =
42+
[
43+
new DocumentVariantRequestModel
44+
{
45+
Segment = null,
46+
Culture = null,
47+
Name = createModel.InvariantName!,
48+
}
49+
];
50+
createDocumentRequestModel.Values = createModel.InvariantProperties.Select(x => new DocumentValueModel
51+
{
52+
Alias = x.Alias,
53+
Value = x.Value,
54+
});
55+
56+
57+
return createDocumentRequestModel;
58+
}
3159
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System.Linq.Expressions;
2+
using System.Net;
3+
using System.Text;
4+
using NUnit.Framework;
5+
using Umbraco.Cms.Api.Management.Controllers.Document;
6+
using Umbraco.Cms.Api.Management.ViewModels.Document;
7+
using Umbraco.Cms.Core;
8+
using Umbraco.Cms.Core.Actions;
9+
using Umbraco.Cms.Core.Models;
10+
using Umbraco.Cms.Core.Models.Membership;
11+
using Umbraco.Cms.Core.Serialization;
12+
using Umbraco.Cms.Core.Services;
13+
using Umbraco.Cms.Core.Services.ContentTypeEditing;
14+
using Umbraco.Cms.Core.Strings;
15+
using Umbraco.Cms.Tests.Common.Builders;
16+
using Umbraco.Cms.Tests.Common.TestHelpers;
17+
18+
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Policies;
19+
20+
[TestFixture]
21+
public class CreateDocumentTests : ManagementApiTest<CreateDocumentController>
22+
{
23+
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
24+
25+
private IShortStringHelper ShortStringHelper => GetRequiredService<IShortStringHelper>();
26+
27+
private ITemplateService TemplateService => GetRequiredService<ITemplateService>();
28+
29+
private IContentTypeEditingService ContentTypeEditingService => GetRequiredService<IContentTypeEditingService>();
30+
31+
private IJsonSerializer JsonSerializer => GetRequiredService<IJsonSerializer>();
32+
33+
private IContentService ContentService => GetRequiredService<IContentService>();
34+
35+
protected override Expression<Func<CreateDocumentController, object>> MethodSelector =>
36+
x => x.Create(CancellationToken.None, null!);
37+
38+
[Test]
39+
public async Task ReadonlyUserCannotCreateDocument()
40+
{
41+
var userGroup = await CreateReadonlyUserGroupAsync();
42+
43+
await AuthenticateClientAsync(Client, async userService =>
44+
{
45+
var email = "[email protected]";
46+
var testUserCreateModel = new UserCreateModel
47+
{
48+
Email = email,
49+
Name = "Test Mc.Gee",
50+
UserName = email,
51+
UserGroupKeys = new HashSet<Guid> { userGroup.Key },
52+
};
53+
54+
var userCreationResult =
55+
await userService.CreateAsync(Constants.Security.SuperUserKey, testUserCreateModel, true);
56+
57+
Assert.IsTrue(userCreationResult.Success);
58+
59+
return (userCreationResult.Result.CreatedUser, "1234567890");
60+
});
61+
62+
var (contentType, template) = await CreateDocumentTypeAsync();
63+
var contentCreateModel = ContentEditingBuilder.CreateSimpleContent(contentType.Key);
64+
65+
var requestModel = DocumentUpdateHelper.CreateDocumentRequestModel(contentCreateModel);
66+
67+
var response = await GetManagementApiResponseAsync(requestModel);
68+
69+
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden));
70+
}
71+
72+
[Test]
73+
public async Task EditorCanCreateDocument()
74+
{
75+
await AuthenticateClientAsync(Client, "[email protected]", "1234567890", false);
76+
77+
var (contentType, template) = await CreateDocumentTypeAsync();
78+
var requestModel = DocumentUpdateHelper.CreateDocumentRequestModel(ContentEditingBuilder.CreateSimpleContent(contentType.Key));
79+
80+
var response = await GetManagementApiResponseAsync(requestModel);
81+
82+
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Created));
83+
84+
var locationHeader = response.Headers.GetValues("Location").First();
85+
var key = Guid.Parse(locationHeader.Split('/')[^1]);
86+
var createdContent = ContentService.GetById(key);
87+
Assert.NotNull(createdContent);
88+
}
89+
90+
private async Task<HttpResponseMessage> GetManagementApiResponseAsync(CreateDocumentRequestModel requestModel)
91+
{
92+
var url = GetManagementApiUrl<CreateDocumentController>(x => x.Create(CancellationToken.None, requestModel));
93+
var requestBody = new StringContent(JsonSerializer.Serialize(requestModel), Encoding.UTF8, "application/json");
94+
var response = await Client.PostAsync(url, requestBody);
95+
return response;
96+
}
97+
98+
private async Task<IUserGroup> CreateReadonlyUserGroupAsync()
99+
{
100+
var userGroup = new UserGroup(ShortStringHelper)
101+
{
102+
Name = "Test",
103+
Alias = "test",
104+
Permissions = new HashSet<string> { ActionBrowse.ActionLetter },
105+
HasAccessToAllLanguages = true,
106+
StartContentId = -1,
107+
StartMediaId = -1
108+
};
109+
userGroup.AddAllowedSection("content");
110+
userGroup.AddAllowedSection("media");
111+
112+
var groupCreationResult = await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
113+
Assert.IsTrue(groupCreationResult.Success);
114+
return groupCreationResult.Result;
115+
}
116+
117+
private async Task<(IContentType contentType, ITemplate template)> CreateDocumentTypeAsync()
118+
{
119+
var userKey = Constants.Security.SuperUserKey;
120+
var template = TemplateBuilder.CreateTextPageTemplate();
121+
var templateAttempt = await TemplateService.CreateAsync(template, userKey);
122+
Assert.IsTrue(templateAttempt.Success);
123+
124+
var contentTypeCreateModel = ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key);
125+
var contentTypeAttempt = await ContentTypeEditingService.CreateAsync(contentTypeCreateModel, userKey);
126+
Assert.IsTrue(contentTypeAttempt.Success);
127+
128+
return (contentTypeAttempt.Result!, templateAttempt.Result!);
129+
}
130+
}

0 commit comments

Comments
 (0)