1
1
using Microsoft . Extensions . DependencyInjection ;
2
2
using Microsoft . Extensions . Options ;
3
+ using Umbraco . Cms . Api . Management . Mapping . Permissions ;
3
4
using Umbraco . Cms . Api . Management . Routing ;
4
5
using Umbraco . Cms . Api . Management . Security ;
5
6
using Umbraco . Cms . Api . Management . ViewModels ;
22
23
23
24
namespace Umbraco . Cms . Api . Management . Factories ;
24
25
26
+ /// <summary>
27
+ /// Factory for creating user presentation models, implementing <see cref="IUserPresentationFactory"/>.
28
+ /// </summary>
25
29
public class UserPresentationFactory : IUserPresentationFactory
26
30
{
27
31
private readonly IEntityService _entityService ;
@@ -34,9 +38,11 @@ public class UserPresentationFactory : IUserPresentationFactory
34
38
private readonly IPasswordConfigurationPresentationFactory _passwordConfigurationPresentationFactory ;
35
39
private readonly IBackOfficeExternalLoginProviders _externalLoginProviders ;
36
40
private readonly SecuritySettings _securitySettings ;
37
- private readonly IUserService _userService ;
38
- private readonly IContentService _contentService ;
41
+ private readonly Dictionary < Type , IPermissionPresentationMapper > _permissionPresentationMappersByType ;
39
42
43
+ /// <summary>
44
+ /// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
45
+ /// </summary>
40
46
[ Obsolete ( "Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17." ) ]
41
47
public UserPresentationFactory (
42
48
IEntityService entityService ,
@@ -50,7 +56,7 @@ public UserPresentationFactory(
50
56
IOptionsSnapshot < SecuritySettings > securitySettings ,
51
57
IBackOfficeExternalLoginProviders externalLoginProviders )
52
58
: this (
53
- entityService ,
59
+ entityService ,
54
60
appCaches ,
55
61
mediaFileManager ,
56
62
imageUrlGenerator ,
@@ -61,10 +67,15 @@ public UserPresentationFactory(
61
67
securitySettings ,
62
68
externalLoginProviders ,
63
69
StaticServiceProvider . Instance . GetRequiredService < IUserService > ( ) ,
64
- StaticServiceProvider . Instance . GetRequiredService < IContentService > ( ) )
70
+ StaticServiceProvider . Instance . GetRequiredService < IContentService > ( ) ,
71
+ StaticServiceProvider . Instance . GetRequiredService < IEnumerable < IPermissionPresentationMapper > > ( ) )
65
72
{
66
73
}
67
74
75
+ /// <summary>
76
+ /// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
77
+ /// </summary>
78
+ [ Obsolete ( "Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17." ) ]
68
79
public UserPresentationFactory (
69
80
IEntityService entityService ,
70
81
AppCaches appCaches ,
@@ -78,6 +89,44 @@ public UserPresentationFactory(
78
89
IBackOfficeExternalLoginProviders externalLoginProviders ,
79
90
IUserService userService ,
80
91
IContentService contentService )
92
+ : this (
93
+ entityService ,
94
+ appCaches ,
95
+ mediaFileManager ,
96
+ imageUrlGenerator ,
97
+ userGroupPresentationFactory ,
98
+ absoluteUrlBuilder ,
99
+ emailSender ,
100
+ passwordConfigurationPresentationFactory ,
101
+ securitySettings ,
102
+ externalLoginProviders ,
103
+ userService ,
104
+ contentService ,
105
+ StaticServiceProvider . Instance . GetRequiredService < IEnumerable < IPermissionPresentationMapper > > ( ) )
106
+ {
107
+ }
108
+
109
+ // TODO (V17): Remove the unused userService and contentService parameters from this constructor.
110
+
111
+ /// <summary>
112
+ /// Initializes a new instance of the <see cref="UserPresentationFactory"/> class.
113
+ /// </summary>
114
+ public UserPresentationFactory (
115
+ IEntityService entityService ,
116
+ AppCaches appCaches ,
117
+ MediaFileManager mediaFileManager ,
118
+ IImageUrlGenerator imageUrlGenerator ,
119
+ IUserGroupPresentationFactory userGroupPresentationFactory ,
120
+ IAbsoluteUrlBuilder absoluteUrlBuilder ,
121
+ IEmailSender emailSender ,
122
+ IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory ,
123
+ IOptionsSnapshot < SecuritySettings > securitySettings ,
124
+ IBackOfficeExternalLoginProviders externalLoginProviders ,
125
+ #pragma warning disable IDE0060 // Remove unused parameter - need to keep these until the next major to avoid breaking changes and/or ambiguous constructor errors
126
+ IUserService userService ,
127
+ IContentService contentService ,
128
+ #pragma warning restore IDE0060 // Remove unused parameter
129
+ IEnumerable < IPermissionPresentationMapper > permissionPresentationMappers )
81
130
{
82
131
_entityService = entityService ;
83
132
_appCaches = appCaches ;
@@ -89,10 +138,10 @@ public UserPresentationFactory(
89
138
_externalLoginProviders = externalLoginProviders ;
90
139
_securitySettings = securitySettings . Value ;
91
140
_absoluteUrlBuilder = absoluteUrlBuilder ;
92
- _userService = userService ;
93
- _contentService = contentService ;
141
+ _permissionPresentationMappersByType = permissionPresentationMappers . ToDictionary ( x => x . PresentationModelToHandle ) ;
94
142
}
95
143
144
+ /// <inheritdoc/>
96
145
public UserResponseModel CreateResponseModel ( IUser user )
97
146
{
98
147
var responseModel = new UserResponseModel
@@ -123,16 +172,18 @@ public UserResponseModel CreateResponseModel(IUser user)
123
172
return responseModel ;
124
173
}
125
174
175
+ /// <inheritdoc/>
126
176
public UserItemResponseModel CreateItemResponseModel ( IUser user ) =>
127
177
new ( )
128
178
{
129
179
Id = user . Key ,
130
180
Name = user . Name ?? user . Username ,
131
181
AvatarUrls = user . GetUserAvatarUrls ( _appCaches . RuntimeCache , _mediaFileManager , _imageUrlGenerator )
132
182
. Select ( url => _absoluteUrlBuilder . ToAbsoluteUrl ( url ) . ToString ( ) ) ,
133
- Kind = user . Kind
183
+ Kind = user . Kind ,
134
184
} ;
135
185
186
+ /// <inheritdoc/>
136
187
public Task < UserCreateModel > CreateCreationModelAsync ( CreateUserRequestModel requestModel )
137
188
{
138
189
var createModel = new UserCreateModel
@@ -142,12 +193,13 @@ public Task<UserCreateModel> CreateCreationModelAsync(CreateUserRequestModel req
142
193
Name = requestModel . Name ,
143
194
UserName = requestModel . UserName ,
144
195
UserGroupKeys = requestModel . UserGroupIds . Select ( x => x . Id ) . ToHashSet ( ) ,
145
- Kind = requestModel . Kind
196
+ Kind = requestModel . Kind ,
146
197
} ;
147
198
148
199
return Task . FromResult ( createModel ) ;
149
200
}
150
201
202
+ /// <inheritdoc/>
151
203
public Task < UserInviteModel > CreateInviteModelAsync ( InviteUserRequestModel requestModel )
152
204
{
153
205
var inviteModel = new UserInviteModel
@@ -162,6 +214,7 @@ public Task<UserInviteModel> CreateInviteModelAsync(InviteUserRequestModel reque
162
214
return Task . FromResult ( inviteModel ) ;
163
215
}
164
216
217
+ /// <inheritdoc/>
165
218
public Task < UserResendInviteModel > CreateResendInviteModelAsync ( ResendInviteUserRequestModel requestModel )
166
219
{
167
220
var inviteModel = new UserResendInviteModel
@@ -173,6 +226,7 @@ public Task<UserResendInviteModel> CreateResendInviteModelAsync(ResendInviteUser
173
226
return Task . FromResult ( inviteModel ) ;
174
227
}
175
228
229
+ /// <inheritdoc/>
176
230
public Task < CurrentUserConfigurationResponseModel > CreateCurrentUserConfigurationModelAsync ( )
177
231
{
178
232
var model = new CurrentUserConfigurationResponseModel
@@ -188,6 +242,7 @@ public Task<CurrentUserConfigurationResponseModel> CreateCurrentUserConfiguratio
188
242
return Task . FromResult ( model ) ;
189
243
}
190
244
245
+ /// <inheritdoc/>
191
246
public Task < UserConfigurationResponseModel > CreateUserConfigurationModelAsync ( ) =>
192
247
Task . FromResult ( new UserConfigurationResponseModel
193
248
{
@@ -201,6 +256,7 @@ public Task<UserConfigurationResponseModel> CreateUserConfigurationModelAsync()
201
256
AllowTwoFactor = _externalLoginProviders . HasDenyLocalLogin ( ) is false ,
202
257
} ) ;
203
258
259
+ /// <inheritdoc/>
204
260
public Task < UserUpdateModel > CreateUpdateModelAsync ( Guid existingUserKey , UpdateUserRequestModel updateModel )
205
261
{
206
262
var model = new UserUpdateModel
@@ -214,24 +270,24 @@ public Task<UserUpdateModel> CreateUpdateModelAsync(Guid existingUserKey, Update
214
270
HasContentRootAccess = updateModel . HasDocumentRootAccess ,
215
271
MediaStartNodeKeys = updateModel . MediaStartNodeIds . Select ( x => x . Id ) . ToHashSet ( ) ,
216
272
HasMediaRootAccess = updateModel . HasMediaRootAccess ,
273
+ UserGroupKeys = updateModel . UserGroupIds . Select ( x => x . Id ) . ToHashSet ( )
217
274
} ;
218
275
219
- model . UserGroupKeys = updateModel . UserGroupIds . Select ( x => x . Id ) . ToHashSet ( ) ;
220
-
221
276
return Task . FromResult ( model ) ;
222
277
}
223
278
279
+ /// <inheritdoc/>
224
280
public async Task < CurrentUserResponseModel > CreateCurrentUserResponseModelAsync ( IUser user )
225
281
{
226
- var presentationUser = CreateResponseModel ( user ) ;
227
- var presentationGroups = await _userGroupPresentationFactory . CreateMultipleAsync ( user . Groups ) ;
282
+ UserResponseModel presentationUser = CreateResponseModel ( user ) ;
283
+ IEnumerable < UserGroupResponseModel > presentationGroups = await _userGroupPresentationFactory . CreateMultipleAsync ( user . Groups ) ;
228
284
var languages = presentationGroups . SelectMany ( x => x . Languages ) . Distinct ( ) . ToArray ( ) ;
229
285
var mediaStartNodeIds = user . CalculateMediaStartNodeIds ( _entityService , _appCaches ) ;
230
- var mediaStartNodeKeys = GetKeysFromIds ( mediaStartNodeIds , UmbracoObjectTypes . Media ) ;
286
+ ISet < ReferenceByIdModel > mediaStartNodeKeys = GetKeysFromIds ( mediaStartNodeIds , UmbracoObjectTypes . Media ) ;
231
287
var contentStartNodeIds = user . CalculateContentStartNodeIds ( _entityService , _appCaches ) ;
232
- var documentStartNodeKeys = GetKeysFromIds ( contentStartNodeIds , UmbracoObjectTypes . Document ) ;
288
+ ISet < ReferenceByIdModel > documentStartNodeKeys = GetKeysFromIds ( contentStartNodeIds , UmbracoObjectTypes . Document ) ;
233
289
234
- var permissions = GetAggregatedGranularPermissions ( user , presentationGroups ) ;
290
+ HashSet < IPermissionPresentationModel > permissions = GetAggregatedGranularPermissions ( user , presentationGroups ) ;
235
291
var fallbackPermissions = presentationGroups . SelectMany ( x => x . FallbackPermissions ) . ToHashSet ( ) ;
236
292
237
293
var hasAccessToAllLanguages = presentationGroups . Any ( x => x . HasAccessToAllLanguages ) ;
@@ -263,70 +319,56 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
263
319
264
320
private HashSet < IPermissionPresentationModel > GetAggregatedGranularPermissions ( IUser user , IEnumerable < UserGroupResponseModel > presentationGroups )
265
321
{
266
- var aggregatedPermissions = new HashSet < IPermissionPresentationModel > ( ) ;
267
-
268
322
var permissions = presentationGroups . SelectMany ( x => x . Permissions ) . ToHashSet ( ) ;
269
-
270
- AggregateAndAddDocumentPermissions ( user , aggregatedPermissions , permissions ) ;
271
-
272
- AggregateAndAddDocumentPropertyValuePermissions ( aggregatedPermissions , permissions ) ;
273
-
274
- return aggregatedPermissions ;
323
+ return GetAggregatedGranularPermissions ( user , permissions ) ;
275
324
}
276
325
277
- private void AggregateAndAddDocumentPermissions ( IUser user , HashSet < IPermissionPresentationModel > aggregatedPermissions , HashSet < IPermissionPresentationModel > permissions )
326
+ private HashSet < IPermissionPresentationModel > GetAggregatedGranularPermissions ( IUser user , HashSet < IPermissionPresentationModel > permissions )
278
327
{
279
- // The raw permission data consists of several permissions for each document. We want to aggregate this server-side so
280
- // we return one set of aggregate permissions per document that the client will use.
281
-
282
- // Get the unique document keys that have granular permissions.
283
- IEnumerable < Guid > documentKeysWithGranularPermissions = permissions
284
- . Where ( x => x is DocumentPermissionPresentationModel )
285
- . Cast < DocumentPermissionPresentationModel > ( )
286
- . Select ( x => x . Document . Id )
287
- . Distinct ( ) ;
328
+ // The raw permission data consists of several permissions for each entity (e.g. document), as permissions are assigned to user groups
329
+ // and a user may be part of multiple groups. We want to aggregate this server-side so we return one set of aggregate permissions per
330
+ // entity that the client will use.
331
+ // We need to handle here not just permissions known to core (e.g. document and document property value permissions), but also custom
332
+ // permissions defined by packages or implemetors.
333
+ IEnumerable < ( Type , IEnumerable < IPermissionPresentationModel > ) > permissionModelsByType = permissions
334
+ . GroupBy ( x => x . GetType ( ) )
335
+ . Select ( x => ( x . Key , x . Select ( y => y ) ) ) ;
288
336
289
- foreach ( Guid documentKey in documentKeysWithGranularPermissions )
337
+ var aggregatedPermissions = new HashSet < IPermissionPresentationModel > ( ) ;
338
+ foreach ( ( Type Type , IEnumerable < IPermissionPresentationModel > Models ) permissionModelByType in permissionModelsByType )
290
339
{
291
- // Retrieve the path of the document.
292
- var path = _contentService . GetById ( documentKey ) ? . Path ;
293
- if ( string . IsNullOrEmpty ( path ) )
340
+ if ( _permissionPresentationMappersByType . TryGetValue ( permissionModelByType . Type , out IPermissionPresentationMapper ? mapper ) )
294
341
{
295
- continue ;
296
- }
297
342
298
- // With the path we can call the same logic as used server-side for authorizing access to resources.
299
- EntityPermissionSet permissionsForPath = _userService . GetPermissionsForPath ( user , path ) ;
300
- aggregatedPermissions . Add ( new DocumentPermissionPresentationModel
343
+ IEnumerable < IPermissionPresentationModel > aggregatedModels = mapper . AggregatePresentationModels ( user , permissionModelByType . Models ) ;
344
+ foreach ( IPermissionPresentationModel aggregatedModel in aggregatedModels )
345
+ {
346
+ aggregatedPermissions . Add ( aggregatedModel ) ;
347
+ }
348
+ }
349
+ else
301
350
{
302
- Document = new ReferenceByIdModel ( documentKey ) ,
303
- Verbs = permissionsForPath . GetAllPermissions ( )
304
- } ) ;
351
+ IEnumerable < ( string Context , ISet < string > Verbs ) > groupedModels = permissionModelByType . Models
352
+ . Where ( x => x is UnknownTypePermissionPresentationModel )
353
+ . Cast < UnknownTypePermissionPresentationModel > ( )
354
+ . GroupBy ( x => x . Context )
355
+ . Select ( x => ( x . Key , ( ISet < string > ) x . SelectMany ( y => y . Verbs ) . Distinct ( ) . ToHashSet ( ) ) ) ;
356
+
357
+ foreach ( ( string context , ISet < string > verbs ) in groupedModels )
358
+ {
359
+ aggregatedPermissions . Add ( new UnknownTypePermissionPresentationModel
360
+ {
361
+ Context = context ,
362
+ Verbs = verbs
363
+ } ) ;
364
+ }
365
+ }
305
366
}
306
- }
307
367
308
- private static void AggregateAndAddDocumentPropertyValuePermissions ( HashSet < IPermissionPresentationModel > aggregatedPermissions , HashSet < IPermissionPresentationModel > permissions )
309
- {
310
- // We also have permissions for document type/property type combinations.
311
- // These don't have an ancestor relationship that we need to take into account, but should be aggregated
312
- // and included in the set.
313
- IEnumerable < ( ( Guid DocumentTypeId , Guid PropertyTypeId ) Key , ISet < string > Verbs ) > documentTypePropertyTypeKeysWithGranularPermissions = permissions
314
- . Where ( x => x is DocumentPropertyValuePermissionPresentationModel )
315
- . Cast < DocumentPropertyValuePermissionPresentationModel > ( )
316
- . GroupBy ( x => ( x . DocumentType . Id , x . PropertyType . Id ) )
317
- . Select ( x => ( x . Key , ( ISet < string > ) x . SelectMany ( y => y . Verbs ) . Distinct ( ) . ToHashSet ( ) ) ) ;
318
-
319
- foreach ( ( ( Guid DocumentTypeId , Guid PropertyTypeId ) Key , ISet < string > Verbs ) documentTypePropertyTypeKey in documentTypePropertyTypeKeysWithGranularPermissions )
320
- {
321
- aggregatedPermissions . Add ( new DocumentPropertyValuePermissionPresentationModel
322
- {
323
- DocumentType = new ReferenceByIdModel ( documentTypePropertyTypeKey . Key . DocumentTypeId ) ,
324
- PropertyType = new ReferenceByIdModel ( documentTypePropertyTypeKey . Key . PropertyTypeId ) ,
325
- Verbs = documentTypePropertyTypeKey . Verbs
326
- } ) ;
327
- }
368
+ return aggregatedPermissions ;
328
369
}
329
370
371
+ /// <inheritdoc/>
330
372
public Task < CalculatedUserStartNodesResponseModel > CreateCalculatedUserStartNodesResponseModelAsync ( IUser user )
331
373
{
332
374
var mediaStartNodeIds = user . CalculateMediaStartNodeIds ( _entityService , _appCaches ) ;
@@ -357,6 +399,6 @@ private ISet<ReferenceByIdModel> GetKeysFromIds(IEnumerable<int>? ids, UmbracoOb
357
399
: new HashSet < ReferenceByIdModel > ( models ) ;
358
400
}
359
401
360
- private bool HasRootAccess ( IEnumerable < int > ? startNodeIds )
402
+ private static bool HasRootAccess ( IEnumerable < int > ? startNodeIds )
361
403
=> startNodeIds ? . Contains ( Constants . System . Root ) is true ;
362
404
}
0 commit comments