Skip to content

Commit 8ad6c36

Browse files
kjacMigaroez
andauthored
Amend user start node handling (#16094)
* Amend user start node handling * Add "has root access" to current user endpoint * Add document and media root access to user response model * Update OpenApi.json * Applied API suggestions --------- Co-authored-by: Sven Geusens <[email protected]>
1 parent f0dae52 commit 8ad6c36

File tree

11 files changed

+220
-22
lines changed

11 files changed

+220
-22
lines changed

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/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;

src/Umbraco.Cms.Api.Management/OpenApi.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34357,6 +34357,8 @@
3435734357
"fallbackPermissions",
3435834358
"hasAccessToAllLanguages",
3435934359
"hasAccessToSensitiveData",
34360+
"hasDocumentRootAccess",
34361+
"hasMediaRootAccess",
3436034362
"id",
3436134363
"isAdmin",
3436234364
"languages",
@@ -34392,6 +34394,9 @@
3439234394
"format": "uuid"
3439334395
}
3439434396
},
34397+
"hasDocumentRootAccess": {
34398+
"type": "boolean"
34399+
},
3439534400
"mediaStartNodeIds": {
3439634401
"uniqueItems": true,
3439734402
"type": "array",
@@ -34400,6 +34405,9 @@
3440034405
"format": "uuid"
3440134406
}
3440234407
},
34408+
"hasMediaRootAccess": {
34409+
"type": "boolean"
34410+
},
3440334411
"avatarUrls": {
3440434412
"type": "array",
3440534413
"items": {
@@ -43206,6 +43214,8 @@
4320643214
"required": [
4320743215
"documentStartNodeIds",
4320843216
"email",
43217+
"hasDocumentRootAccess",
43218+
"hasMediaRootAccess",
4320943219
"languageIsoCode",
4321043220
"mediaStartNodeIds",
4321143221
"name",
@@ -43242,13 +43252,19 @@
4324243252
"format": "uuid"
4324343253
}
4324443254
},
43255+
"hasDocumentRootAccess": {
43256+
"type": "boolean"
43257+
},
4324543258
"mediaStartNodeIds": {
4324643259
"uniqueItems": true,
4324743260
"type": "array",
4324843261
"items": {
4324943262
"type": "string",
4325043263
"format": "uuid"
4325143264
}
43265+
},
43266+
"hasMediaRootAccess": {
43267+
"type": "boolean"
4325243268
}
4325343269
},
4325443270
"additionalProperties": false
@@ -43626,6 +43642,8 @@
4362643642
"documentStartNodeIds",
4362743643
"email",
4362843644
"failedLoginAttempts",
43645+
"hasDocumentRootAccess",
43646+
"hasMediaRootAccess",
4362943647
"id",
4363043648
"isAdmin",
4363143649
"mediaStartNodeIds",
@@ -43670,6 +43688,9 @@
4367043688
"format": "uuid"
4367143689
}
4367243690
},
43691+
"hasDocumentRootAccess": {
43692+
"type": "boolean"
43693+
},
4367343694
"mediaStartNodeIds": {
4367443695
"uniqueItems": true,
4367543696
"type": "array",
@@ -43678,6 +43699,9 @@
4367843699
"format": "uuid"
4367943700
}
4368043701
},
43702+
"hasMediaRootAccess": {
43703+
"type": "boolean"
43704+
},
4368143705
"avatarUrls": {
4368243706
"type": "array",
4368343707
"items": {

src/Umbraco.Cms.Api.Management/Services/Entities/UserStartNodeEntitiesService.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ public IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes u
1919
// root entities for users without root access should include:
2020
// - the start nodes that are actual root entities (level == 1)
2121
// - the root level ancestors to the rest of the start nodes (required for browsing to the actual start nodes - will be marked as "no access")
22-
IEntitySlim[] userStartEntities = _entityService.GetAll(umbracoObjectType, userStartNodeIds).ToArray();
22+
IEntitySlim[] userStartEntities = userStartNodeIds.Any()
23+
? _entityService.GetAll(umbracoObjectType, userStartNodeIds).ToArray()
24+
: Array.Empty<IEntitySlim>();
2325

2426
// find the start nodes that are at root level (level == 1)
2527
IEntitySlim[] allowedTopmostEntities = userStartEntities.Where(entity => entity.Level == 1).ToArray();

src/Umbraco.Cms.Api.Management/ViewModels/User/Current/CurrentUserResponseModel.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using Umbraco.Cms.Api.Management.ViewModels.UserGroup;
21
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
3-
using Umbraco.Cms.Core.Models.Membership;
42

53
namespace Umbraco.Cms.Api.Management.ViewModels.User.Current;
64

@@ -18,8 +16,12 @@ public class CurrentUserResponseModel
1816

1917
public required ISet<Guid> DocumentStartNodeIds { get; init; } = new HashSet<Guid>();
2018

19+
public required bool HasDocumentRootAccess { get; init; }
20+
2121
public required ISet<Guid> MediaStartNodeIds { get; init; } = new HashSet<Guid>();
2222

23+
public required bool HasMediaRootAccess { get; init; }
24+
2325
public required IEnumerable<string> AvatarUrls { get; init; } = Enumerable.Empty<string>();
2426

2527
public required IEnumerable<string> Languages { get; init; } = Enumerable.Empty<string>();

src/Umbraco.Cms.Api.Management/ViewModels/User/UpdateUserRequestModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ public class UpdateUserRequestModel : UserPresentationBase
66

77
public ISet<Guid> DocumentStartNodeIds { get; set; } = new HashSet<Guid>();
88

9+
public bool HasDocumentRootAccess { get; init; }
10+
911
public ISet<Guid> MediaStartNodeIds { get; set; } = new HashSet<Guid>();
12+
13+
public bool HasMediaRootAccess { get; init; }
1014
}

src/Umbraco.Cms.Api.Management/ViewModels/User/UserResponseModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ public class UserResponseModel : UserPresentationBase
1010

1111
public ISet<Guid> DocumentStartNodeIds { get; set; } = new HashSet<Guid>();
1212

13+
public bool HasDocumentRootAccess { get; set; }
14+
1315
public ISet<Guid> MediaStartNodeIds { get; set; } = new HashSet<Guid>();
1416

17+
public bool HasMediaRootAccess { get; set; }
18+
1519
public IEnumerable<string> AvatarUrls { get; set; } = Enumerable.Empty<string>();
1620

1721
public UserState State { get; set; }

src/Umbraco.Core/Models/UserUpdateModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ public class UserUpdateModel
1414

1515
public ISet<Guid> ContentStartNodeKeys { get; set; } = new HashSet<Guid>();
1616

17+
public bool HasContentRootAccess { get; set; }
18+
1719
public ISet<Guid> MediaStartNodeKeys { get; set; } = new HashSet<Guid>();
1820

21+
public bool HasMediaRootAccess { get; set; }
22+
1923
public ISet<Guid> UserGroupKeys { get; set; } = new HashSet<Guid>();
2024
}

src/Umbraco.Core/Services/UserService.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -984,22 +984,32 @@ private async Task<UserOperationStatus> ValidateUserCreateModel(UserCreateModel
984984

985985
// We have to resolve the keys to ids to be compatible with the repository, this could be done in the factory,
986986
// but I'd rather keep the ids out of the service API as much as possible.
987-
int[]? startContentIds = GetIdsFromKeys(model.ContentStartNodeKeys, UmbracoObjectTypes.Document);
987+
List<int>? startContentIds = GetIdsFromKeys(model.ContentStartNodeKeys, UmbracoObjectTypes.Document);
988988

989-
if (startContentIds is null || startContentIds.Length != model.ContentStartNodeKeys.Count)
989+
if (startContentIds is null || startContentIds.Count != model.ContentStartNodeKeys.Count)
990990
{
991991
scope.Complete();
992992
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.ContentStartNodeNotFound, existingUser);
993993
}
994994

995-
int[]? startMediaIds = GetIdsFromKeys(model.MediaStartNodeKeys, UmbracoObjectTypes.Media);
995+
List<int>? startMediaIds = GetIdsFromKeys(model.MediaStartNodeKeys, UmbracoObjectTypes.Media);
996996

997-
if (startMediaIds is null || startMediaIds.Length != model.MediaStartNodeKeys.Count)
997+
if (startMediaIds is null || startMediaIds.Count != model.MediaStartNodeKeys.Count)
998998
{
999999
scope.Complete();
10001000
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.MediaStartNodeNotFound, existingUser);
10011001
}
10021002

1003+
if (model.HasContentRootAccess)
1004+
{
1005+
startContentIds.Add(Constants.System.Root);
1006+
}
1007+
1008+
if (model.HasMediaRootAccess)
1009+
{
1010+
startMediaIds.Add(Constants.System.Root);
1011+
}
1012+
10031013
Attempt<string?> isAuthorized = _userEditorAuthorizationHelper.IsAuthorized(
10041014
performingUser,
10051015
existingUser,
@@ -1085,15 +1095,15 @@ private IUser MapUserUpdate(
10851095
UserUpdateModel source,
10861096
ISet<IUserGroup> sourceUserGroups,
10871097
IUser target,
1088-
int[]? startContentIds,
1089-
int[]? startMediaIds)
1098+
List<int> startContentIds,
1099+
List<int> startMediaIds)
10901100
{
10911101
target.Name = source.Name;
10921102
target.Language = source.LanguageIsoCode;
10931103
target.Email = source.Email;
10941104
target.Username = source.UserName;
1095-
target.StartContentIds = startContentIds;
1096-
target.StartMediaIds = startMediaIds;
1105+
target.StartContentIds = startContentIds.ToArray();
1106+
target.StartMediaIds = startMediaIds.ToArray();
10971107

10981108
target.ClearGroups();
10991109
foreach (IUserGroup group in sourceUserGroups)
@@ -1152,13 +1162,13 @@ private UserOperationStatus ValidateUserUpdateModel(IUser existingUser, UserUpda
11521162

11531163
private static bool IsEmailValid(string email) => new EmailAddressAttribute().IsValid(email);
11541164

1155-
private int[]? GetIdsFromKeys(IEnumerable<Guid>? guids, UmbracoObjectTypes type)
1165+
private List<int>? GetIdsFromKeys(IEnumerable<Guid>? guids, UmbracoObjectTypes type)
11561166
{
1157-
int[]? keys = guids?
1167+
var keys = guids?
11581168
.Select(x => _entityService.GetId(x, type))
11591169
.Where(x => x.Success)
11601170
.Select(x => x.Result)
1161-
.ToArray();
1171+
.ToList();
11621172

11631173
return keys;
11641174
}

0 commit comments

Comments
 (0)