Skip to content

Commit ba06e85

Browse files
committed
Merge branch 'refs/heads/v14/dev' into release/14.0
2 parents caebf37 + 6dbb7e2 commit ba06e85

29 files changed

+418
-69
lines changed

src/Umbraco.Cms.Api.Management/Controllers/UserGroup/ByKeyUserGroupController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using Microsoft.AspNetCore.Http;
44
using Microsoft.AspNetCore.Mvc;
55
using Umbraco.Cms.Api.Management.Factories;
6-
using Umbraco.Cms.Api.Management.Security.Authorization.UserGroup;
76
using Umbraco.Cms.Api.Management.ViewModels.UserGroup;
87
using Umbraco.Cms.Core.Models.Membership;
98
using Umbraco.Cms.Core.Security.Authorization;

src/Umbraco.Cms.Api.Management/Controllers/UserGroup/UserGroupControllerBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,15 @@ protected IActionResult UserGroupOperationStatusResult(UserGroupOperationStatus
2929
.WithTitle("Duplicate alias")
3030
.WithDetail("A user group already exists with the attempted alias.")
3131
.Build()),
32+
UserGroupOperationStatus.CanNotUpdateAliasIsSystemUserGroup => BadRequest(problemDetailsBuilder
33+
.WithTitle("System user group")
34+
.WithDetail("Changing the alias is not allowed on a system user group.")
35+
.Build()),
3236
UserGroupOperationStatus.MissingUser => Unauthorized(problemDetailsBuilder
3337
.WithTitle("Missing user")
3438
.WithDetail("A performing user was not found when attempting the operation.")
3539
.Build()),
36-
UserGroupOperationStatus.IsSystemUserGroup => BadRequest(problemDetailsBuilder
40+
UserGroupOperationStatus.CanNotDeleteIsSystemUserGroup => BadRequest(problemDetailsBuilder
3741
.WithTitle("System user group")
3842
.WithDetail("The operation is not allowed on a system user group.")
3943
.Build()),

src/Umbraco.Cms.Api.Management/Controllers/Webhook/CreateWebhookController.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
using Microsoft.AspNetCore.Authorization;
33
using Microsoft.AspNetCore.Http;
44
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.Factories;
56
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
67
using Umbraco.Cms.Core;
7-
using Umbraco.Cms.Core.Mapping;
88
using Umbraco.Cms.Core.Models;
99
using Umbraco.Cms.Core.Services;
1010
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -17,13 +17,13 @@ namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
1717
public class CreateWebhookController : WebhookControllerBase
1818
{
1919
private readonly IWebhookService _webhookService;
20-
private readonly IUmbracoMapper _umbracoMapper;
20+
private readonly IWebhookPresentationFactory _webhookPresentationFactory;
2121

2222
public CreateWebhookController(
23-
IWebhookService webhookService, IUmbracoMapper umbracoMapper)
23+
IWebhookService webhookService, IWebhookPresentationFactory webhookPresentationFactory)
2424
{
2525
_webhookService = webhookService;
26-
_umbracoMapper = umbracoMapper;
26+
_webhookPresentationFactory = webhookPresentationFactory;
2727
}
2828

2929
[HttpPost]
@@ -35,7 +35,7 @@ public async Task<IActionResult> Create(
3535
CancellationToken cancellationToken,
3636
CreateWebhookRequestModel createWebhookRequestModel)
3737
{
38-
IWebhook created = _umbracoMapper.Map<IWebhook>(createWebhookRequestModel)!;
38+
IWebhook created = _webhookPresentationFactory.CreateWebhook(createWebhookRequestModel);
3939

4040
Attempt<IWebhook, WebhookOperationStatus> result = await _webhookService.CreateAsync(created);
4141

src/Umbraco.Cms.Api.Management/Controllers/Webhook/DeleteWebhookController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public DeleteWebhookController(IWebhookService webhookService, IBackOfficeSecuri
2424
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
2525
}
2626

27-
[HttpDelete($"{{{nameof(id)}}}")]
27+
[HttpDelete("{id:guid}")]
2828
[MapToApiVersion("1.0")]
2929
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
3030
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]

src/Umbraco.Cms.Api.Management/Controllers/Webhook/UpdateWebhookController.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
using Microsoft.AspNetCore.Authorization;
33
using Microsoft.AspNetCore.Http;
44
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.Factories;
56
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
67
using Umbraco.Cms.Core;
7-
using Umbraco.Cms.Core.Mapping;
88
using Umbraco.Cms.Core.Models;
9-
using Umbraco.Cms.Core.Security;
109
using Umbraco.Cms.Core.Services;
1110
using Umbraco.Cms.Core.Services.OperationStatus;
12-
using Umbraco.Cms.Core.Webhooks;
1311
using Umbraco.Cms.Web.Common.Authorization;
1412

1513
namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
@@ -19,14 +17,14 @@ namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
1917
public class UpdateWebhookController : WebhookControllerBase
2018
{
2119
private readonly IWebhookService _webhookService;
22-
private readonly IUmbracoMapper _umbracoMapper;
20+
private readonly IWebhookPresentationFactory _webhookPresentationFactory;
21+
2322

2423
public UpdateWebhookController(
25-
IWebhookService webhookService,
26-
IUmbracoMapper umbracoMapper)
24+
IWebhookService webhookService, IWebhookPresentationFactory webhookPresentationFactory)
2725
{
2826
_webhookService = webhookService;
29-
_umbracoMapper = umbracoMapper;
27+
_webhookPresentationFactory = webhookPresentationFactory;
3028
}
3129

3230
[HttpPut("{id:guid}")]
@@ -45,7 +43,7 @@ public async Task<IActionResult> Update(
4543
return WebhookNotFound();
4644
}
4745

48-
IWebhook updated = _umbracoMapper.Map(updateWebhookRequestModel, current);
46+
IWebhook updated = _webhookPresentationFactory.CreateWebhook(updateWebhookRequestModel, id);
4947

5048
Attempt<IWebhook, WebhookOperationStatus> result = await _webhookService.UpdateAsync(updated);
5149

src/Umbraco.Cms.Api.Management/Factories/IWebhookPresentationFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ public interface IWebhookPresentationFactory
1010

1111
IWebhook CreateWebhook(CreateWebhookRequestModel webhookRequestModel);
1212

13-
IWebhook CreateWebhook(UpdateWebhookRequestModel webhookRequestModel);
13+
IWebhook CreateWebhook(UpdateWebhookRequestModel webhookRequestModel, Guid existingWebhookKey);
1414
}

src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ public async Task<UserGroupResponseModel> CreateAsync(IUserGroup userGroup)
5151

5252
return new UserGroupResponseModel
5353
{
54-
Name = userGroup.Name ?? string.Empty,
5554
Id = userGroup.Key,
55+
Name = userGroup.Name ?? string.Empty,
56+
Alias = userGroup.Alias,
5657
DocumentStartNode = ReferenceByIdModel.ReferenceOrNull(contentStartNodeKey),
5758
DocumentRootAccess = contentRootAccess,
5859
MediaStartNode = ReferenceByIdModel.ReferenceOrNull(mediaStartNodeKey),
@@ -63,7 +64,8 @@ public async Task<UserGroupResponseModel> CreateAsync(IUserGroup userGroup)
6364
FallbackPermissions = userGroup.Permissions,
6465
Permissions = await _permissionPresentationFactory.CreateAsync(userGroup.GranularPermissions),
6566
Sections = userGroup.AllowedSections.Select(SectionMapper.GetName),
66-
IsSystemGroup = userGroup.IsSystemUserGroup()
67+
IsDeletable = !userGroup.IsSystemUserGroup(),
68+
AliasCanBeChanged = !userGroup.IsSystemUserGroup(),
6769
};
6870
}
6971

@@ -83,8 +85,9 @@ public async Task<UserGroupResponseModel> CreateAsync(IReadOnlyUserGroup userGro
8385

8486
return new UserGroupResponseModel
8587
{
86-
Name = userGroup.Name ?? string.Empty,
8788
Id = userGroup.Key,
89+
Name = userGroup.Name ?? string.Empty,
90+
Alias = userGroup.Alias,
8891
DocumentStartNode = ReferenceByIdModel.ReferenceOrNull(contentStartNodeKey),
8992
MediaStartNode = ReferenceByIdModel.ReferenceOrNull(mediaStartNodeKey),
9093
Icon = userGroup.Icon,
@@ -93,6 +96,8 @@ public async Task<UserGroupResponseModel> CreateAsync(IReadOnlyUserGroup userGro
9396
FallbackPermissions = userGroup.Permissions,
9497
Permissions = await _permissionPresentationFactory.CreateAsync(userGroup.GranularPermissions),
9598
Sections = userGroup.AllowedSections.Select(SectionMapper.GetName),
99+
IsDeletable = !userGroup.IsSystemUserGroup(),
100+
AliasCanBeChanged = !userGroup.IsSystemUserGroup(),
96101
};
97102
}
98103

@@ -107,6 +112,7 @@ public async Task<IEnumerable<UserGroupResponseModel>> CreateMultipleAsync(IEnum
107112

108113
return userGroupViewModels;
109114
}
115+
110116
/// <inheritdoc />
111117
public async Task<IEnumerable<UserGroupResponseModel>> CreateMultipleAsync(IEnumerable<IReadOnlyUserGroup> userGroups)
112118
{
@@ -122,18 +128,21 @@ public async Task<IEnumerable<UserGroupResponseModel>> CreateMultipleAsync(IEnum
122128
/// <inheritdoc />
123129
public async Task<Attempt<IUserGroup, UserGroupOperationStatus>> CreateAsync(CreateUserGroupRequestModel requestModel)
124130
{
125-
var cleanedName = requestModel.Name.CleanForXss('[', ']', '(', ')', ':');
126-
127131
var group = new UserGroup(_shortStringHelper)
128132
{
129-
Name = cleanedName,
130-
Alias = cleanedName,
133+
Name = CleanUserGroupNameOrAliasForXss(requestModel.Name),
134+
Alias = CleanUserGroupNameOrAliasForXss(requestModel.Alias),
131135
Icon = requestModel.Icon,
132136
HasAccessToAllLanguages = requestModel.HasAccessToAllLanguages,
133137
Permissions = requestModel.FallbackPermissions,
134-
GranularPermissions = await _permissionPresentationFactory.CreatePermissionSetsAsync(requestModel.Permissions)
138+
GranularPermissions = await _permissionPresentationFactory.CreatePermissionSetsAsync(requestModel.Permissions),
135139
};
136140

141+
if (requestModel.Id.HasValue)
142+
{
143+
group.Key = requestModel.Id.Value;
144+
}
145+
137146
Attempt<UserGroupOperationStatus> assignmentAttempt = AssignStartNodesToUserGroup(requestModel, group);
138147
if (assignmentAttempt.Success is false)
139148
{
@@ -186,7 +195,8 @@ public async Task<Attempt<IUserGroup, UserGroupOperationStatus>> UpdateAsync(IUs
186195
current.AddAllowedSection(SectionMapper.GetAlias(sectionName));
187196
}
188197

189-
current.Name = request.Name.CleanForXss('[', ']', '(', ')', ':');
198+
current.Name = CleanUserGroupNameOrAliasForXss(request.Name);
199+
current.Alias = CleanUserGroupNameOrAliasForXss(request.Alias);
190200
current.Icon = request.Icon;
191201
current.HasAccessToAllLanguages = request.HasAccessToAllLanguages;
192202

@@ -196,6 +206,9 @@ public async Task<Attempt<IUserGroup, UserGroupOperationStatus>> UpdateAsync(IUs
196206
return Attempt.SucceedWithStatus(UserGroupOperationStatus.Success, current);
197207
}
198208

209+
private static string CleanUserGroupNameOrAliasForXss(string input)
210+
=> input.CleanForXss('[', ']', '(', ')', ':');
211+
199212
private async Task<Attempt<IEnumerable<string>, UserGroupOperationStatus>> MapLanguageIdsToIsoCodeAsync(IEnumerable<int> ids)
200213
{
201214
IEnumerable<ILanguage> languages = await _languageService.GetAllAsync();

src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Umbraco.Cms.Api.Management.Security;
44
using Umbraco.Cms.Api.Management.ViewModels.User;
55
using Umbraco.Cms.Api.Management.ViewModels.User.Current;
6+
using Umbraco.Cms.Core;
67
using Umbraco.Cms.Api.Management.ViewModels.User.Item;
78
using Umbraco.Cms.Core.Cache;
89
using Umbraco.Cms.Core.Configuration.Models;
@@ -70,7 +71,9 @@ public UserResponseModel CreateResponseModel(IUser user)
7071
State = user.UserState,
7172
UserGroupIds = new HashSet<Guid>(user.Groups.Select(x => x.Key)),
7273
DocumentStartNodeIds = GetKeysFromIds(user.StartContentIds, UmbracoObjectTypes.Document),
74+
HasDocumentRootAccess = HasRootAccess(user.StartContentIds),
7375
MediaStartNodeIds = GetKeysFromIds(user.StartMediaIds, UmbracoObjectTypes.Media),
76+
HasMediaRootAccess = HasRootAccess(user.StartMediaIds),
7477
FailedLoginAttempts = user.FailedPasswordAttempts,
7578
LastLoginDate = user.LastLoginDate,
7679
LastLockoutDate = user.LastLockoutDate,
@@ -159,7 +162,9 @@ public async Task<UserUpdateModel> CreateUpdateModelAsync(Guid existingUserKey,
159162
UserName = updateModel.UserName,
160163
LanguageIsoCode = updateModel.LanguageIsoCode,
161164
ContentStartNodeKeys = updateModel.DocumentStartNodeIds,
165+
HasContentRootAccess = updateModel.HasDocumentRootAccess,
162166
MediaStartNodeKeys = updateModel.MediaStartNodeIds,
167+
HasMediaRootAccess = updateModel.HasMediaRootAccess
163168
};
164169

165170
model.UserGroupKeys = updateModel.UserGroupIds;
@@ -172,8 +177,10 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
172177
var presentationUser = CreateResponseModel(user);
173178
var presentationGroups = await _userGroupPresentationFactory.CreateMultipleAsync(user.Groups);
174179
var languages = presentationGroups.SelectMany(x => x.Languages).Distinct().ToArray();
175-
var mediaStartNodeKeys = GetKeysFromIds(user.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media);
176-
var documentStartNodeKeys = GetKeysFromIds(user.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document);
180+
var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches);
181+
var mediaStartNodeKeys = GetKeysFromIds(mediaStartNodeIds, UmbracoObjectTypes.Media);
182+
var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches);
183+
var documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document);
177184

178185
var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet();
179186
var fallbackPermissions = presentationGroups.SelectMany(x => x.FallbackPermissions).ToHashSet();
@@ -192,7 +199,9 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
192199
AvatarUrls = presentationUser.AvatarUrls,
193200
LanguageIsoCode = presentationUser.LanguageIsoCode,
194201
MediaStartNodeIds = mediaStartNodeKeys,
202+
HasMediaRootAccess = HasRootAccess(mediaStartNodeIds),
195203
DocumentStartNodeIds = documentStartNodeKeys,
204+
HasDocumentRootAccess = HasRootAccess(contentStartNodeIds),
196205
Permissions = permissions,
197206
FallbackPermissions = fallbackPermissions,
198207
HasAccessToAllLanguages = hasAccessToAllLanguages,
@@ -214,5 +223,6 @@ private ISet<Guid> GetKeysFromIds(IEnumerable<int>? ids, UmbracoObjectTypes type
214223
: new HashSet<Guid>(keys);
215224
}
216225

217-
226+
private bool HasRootAccess(IEnumerable<int>? startNodeIds)
227+
=> startNodeIds?.Contains(Constants.System.Root) is true;
218228
}

src/Umbraco.Cms.Api.Management/Factories/WebhookPresentationFactory.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@ public WebhookResponseModel CreateResponseModel(IWebhook webhook)
2828

2929
public IWebhook CreateWebhook(CreateWebhookRequestModel webhookRequestModel)
3030
{
31-
var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers);
31+
var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers)
32+
{
33+
Key = webhookRequestModel.Id ?? Guid.NewGuid(),
34+
};
3235
return target;
3336
}
3437

35-
public IWebhook CreateWebhook(UpdateWebhookRequestModel webhookRequestModel)
38+
public IWebhook CreateWebhook(UpdateWebhookRequestModel webhookRequestModel, Guid existingWebhookkey)
3639
{
37-
var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers);
40+
var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers)
41+
{
42+
Key = existingWebhookkey,
43+
};
3844
return target;
3945
}
4046

src/Umbraco.Cms.Api.Management/Filters/RequireTreeRootAccessAttribute.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ public override void OnActionExecuting(ActionExecutingContext context)
1616
IUser? user = backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
1717

1818
var startNodeIds = user != null ? GetUserStartNodeIds(user, context) : Array.Empty<int>();
19-
20-
// TODO: remove this once we have backoffice auth in place
21-
startNodeIds = new[] { Constants.System.Root };
22-
2319
if (startNodeIds.Contains(Constants.System.Root))
2420
{
2521
return;

0 commit comments

Comments
 (0)