Skip to content

Commit 1f52d01

Browse files
authored
Do not rely on claims to figure out user access + prepare for claims removal (#16552)
* Do not rely on claims to figure out user access + prepare for claims removal * Fix typos :) * Ensure we fire all relevant notifications when creating and updating user groups * Leave it to the cache refreshers to flush user cache data (start nodes)
1 parent 01909ca commit 1f52d01

File tree

9 files changed

+112
-209
lines changed

9 files changed

+112
-209
lines changed

src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,8 @@ public static IUmbracoBuilder AddBackOfficeAuthentication(this IUmbracoBuilder b
2727

2828
public static IUmbracoBuilder AddTokenRevocation(this IUmbracoBuilder builder)
2929
{
30-
builder.AddNotificationAsyncHandler<UserSavingNotification, RevokeUserAuthenticationTokensNotificationHandler>();
3130
builder.AddNotificationAsyncHandler<UserSavedNotification, RevokeUserAuthenticationTokensNotificationHandler>();
3231
builder.AddNotificationAsyncHandler<UserDeletedNotification, RevokeUserAuthenticationTokensNotificationHandler>();
33-
builder.AddNotificationAsyncHandler<UserGroupDeletingNotification, RevokeUserAuthenticationTokensNotificationHandler>();
34-
builder.AddNotificationAsyncHandler<UserGroupDeletedNotification, RevokeUserAuthenticationTokensNotificationHandler>();
3532
builder.AddNotificationAsyncHandler<UserLoginSuccessNotification, RevokeUserAuthenticationTokensNotificationHandler>();
3633

3734
return builder;

src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,20 @@ internal static IUmbracoBuilder AddAuthorizationPolicies(this IUmbracoBuilder bu
2828
builder.Services.AddSingleton<IAuthorizationHandler, MediaPermissionHandler>();
2929
builder.Services.AddSingleton<IAuthorizationHandler, UserGroupPermissionHandler>();
3030
builder.Services.AddSingleton<IAuthorizationHandler, UserPermissionHandler>();
31+
builder.Services.AddSingleton<IAuthorizationHandler, AllowedApplicationHandler>();
3132

3233
builder.Services.AddAuthorization(CreatePolicies);
3334
return builder;
3435
}
3536

3637
private static void CreatePolicies(AuthorizationOptions options)
3738
{
38-
void AddPolicy(string policyName, string claimType, params string[] allowedClaimValues)
39-
{
40-
options.AddPolicy(policyName, policy =>
39+
void AddAllowedApplicationsPolicy(string policyName, params string[] allowedClaimValues)
40+
=> options.AddPolicy(policyName, policy =>
4141
{
4242
policy.AuthenticationSchemes.Add(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
43-
policy.RequireClaim(claimType, allowedClaimValues);
43+
policy.Requirements.Add(new AllowedApplicationRequirement(allowedClaimValues));
4444
});
45-
}
4645

4746
options.AddPolicy(AuthorizationPolicies.BackOfficeAccess, policy =>
4847
{
@@ -56,39 +55,39 @@ void AddPolicy(string policyName, string claimType, params string[] allowedClaim
5655
policy.RequireRole(Constants.Security.AdminGroupAlias);
5756
});
5857

59-
AddPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content);
60-
AddPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Media);
61-
AddPolicy(AuthorizationPolicies.SectionAccessForContentTree, Constants.Security.AllowedApplicationsClaimType,
58+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Applications.Content);
59+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Applications.Content, Constants.Applications.Media);
60+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForContentTree,
6261
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
6362
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members);
64-
AddPolicy(AuthorizationPolicies.SectionAccessForMediaTree, Constants.Security.AllowedApplicationsClaimType,
63+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMediaTree,
6564
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Users,
6665
Constants.Applications.Settings, Constants.Applications.Packages, Constants.Applications.Members);
67-
AddPolicy(AuthorizationPolicies.SectionAccessForMemberTree, Constants.Security.AllowedApplicationsClaimType,
66+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessForMemberTree,
6867
Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members);
69-
AddPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media);
70-
AddPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members);
71-
AddPolicy(AuthorizationPolicies.SectionAccessPackages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Packages);
72-
AddPolicy(AuthorizationPolicies.SectionAccessSettings, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
73-
AddPolicy(AuthorizationPolicies.SectionAccessUsers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Users);
68+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Applications.Media);
69+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Applications.Members);
70+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessPackages, Constants.Applications.Packages);
71+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessSettings, Constants.Applications.Settings);
72+
AddAllowedApplicationsPolicy(AuthorizationPolicies.SectionAccessUsers, Constants.Applications.Users);
7473

75-
AddPolicy(AuthorizationPolicies.TreeAccessDataTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
76-
AddPolicy(AuthorizationPolicies.TreeAccessDictionary, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Translation);
77-
AddPolicy(AuthorizationPolicies.TreeAccessDictionaryOrTemplates, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Translation, Constants.Applications.Settings);
78-
AddPolicy(AuthorizationPolicies.TreeAccessDocuments, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content);
79-
AddPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Settings);
80-
AddPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
81-
AddPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
82-
AddPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
83-
AddPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media, Constants.Applications.Settings);
84-
AddPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members);
85-
AddPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
86-
AddPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
87-
AddPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
88-
AddPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
89-
AddPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
90-
AddPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
91-
AddPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
74+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDataTypes, Constants.Applications.Settings);
75+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDictionary, Constants.Applications.Translation);
76+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDictionaryOrTemplates, Constants.Applications.Translation, Constants.Applications.Settings);
77+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocuments, Constants.Applications.Content);
78+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, Constants.Applications.Content, Constants.Applications.Settings);
79+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Applications.Settings);
80+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Applications.Settings);
81+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Applications.Settings);
82+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Applications.Media, Constants.Applications.Settings);
83+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Applications.Members);
84+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Applications.Settings);
85+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Applications.Settings);
86+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Applications.Settings);
87+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Applications.Settings);
88+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Applications.Settings);
89+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Applications.Settings);
90+
AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Applications.Settings);
9291

9392
// Contextual permissions
9493
options.AddPolicy(AuthorizationPolicies.ContentPermissionByResource, policy =>

src/Umbraco.Cms.Api.Management/Handlers/RevokeUserAuthenticationTokensNotificationHandler.cs

Lines changed: 0 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -13,77 +13,31 @@
1313
namespace Umbraco.Cms.Api.Management.Handlers;
1414

1515
internal sealed class RevokeUserAuthenticationTokensNotificationHandler :
16-
INotificationAsyncHandler<UserSavingNotification>,
1716
INotificationAsyncHandler<UserSavedNotification>,
1817
INotificationAsyncHandler<UserDeletedNotification>,
19-
INotificationAsyncHandler<UserGroupDeletingNotification>,
20-
INotificationAsyncHandler<UserGroupDeletedNotification>,
2118
INotificationAsyncHandler<UserLoginSuccessNotification>
2219
{
23-
private const string NotificationStateKey = "Umbraco.Cms.Api.Management.Handlers.RevokeUserAuthenticationTokensNotificationHandler";
24-
2520
private readonly IUserService _userService;
26-
private readonly IUserGroupService _userGroupService;
2721
private readonly IOpenIddictTokenManager _tokenManager;
2822
private readonly ILogger<RevokeUserAuthenticationTokensNotificationHandler> _logger;
2923
private readonly SecuritySettings _securitySettings;
3024

3125
public RevokeUserAuthenticationTokensNotificationHandler(
3226
IUserService userService,
33-
IUserGroupService userGroupService,
3427
IOpenIddictTokenManager tokenManager,
3528
ILogger<RevokeUserAuthenticationTokensNotificationHandler> logger,
3629
IOptions<SecuritySettings> securitySettingsOptions)
3730
{
3831
_userService = userService;
39-
_userGroupService = userGroupService;
4032
_tokenManager = tokenManager;
4133
_logger = logger;
4234
_securitySettings = securitySettingsOptions.Value;
4335
}
4436

45-
// We need to know the pre-saving state of the saved users in order to compare if their access has changed
46-
public async Task HandleAsync(UserSavingNotification notification, CancellationToken cancellationToken)
47-
{
48-
try
49-
{
50-
var usersAccess = new Dictionary<Guid, UserStartNodesAndGroupAccess>();
51-
foreach (IUser user in notification.SavedEntities)
52-
{
53-
UserStartNodesAndGroupAccess? priorUserAccess = await GetRelevantUserAccessDataByUserKeyAsync(user.Key);
54-
if (priorUserAccess == null)
55-
{
56-
continue;
57-
}
58-
59-
usersAccess.Add(user.Key, priorUserAccess);
60-
}
61-
62-
notification.State[NotificationStateKey] = usersAccess;
63-
}
64-
catch (DbException e)
65-
{
66-
_logger.LogWarning(e, "This is expected when we upgrade from < Umbraco 14. Otherwise it should not happen");
67-
}
68-
}
69-
7037
public async Task HandleAsync(UserSavedNotification notification, CancellationToken cancellationToken)
7138
{
7239
try
7340
{
74-
Dictionary<Guid, UserStartNodesAndGroupAccess>? preSavingUsersState = null;
75-
76-
if (notification.State.TryGetValue(NotificationStateKey, out var value))
77-
{
78-
preSavingUsersState = value as Dictionary<Guid, UserStartNodesAndGroupAccess>;
79-
}
80-
81-
// If we have a new user, there is no token
82-
if (preSavingUsersState is null || preSavingUsersState.Count == 0)
83-
{
84-
return;
85-
}
86-
8741
foreach (IUser user in notification.SavedEntities)
8842
{
8943
if (user.IsSuper())
@@ -95,23 +49,6 @@ public async Task HandleAsync(UserSavedNotification notification, CancellationTo
9549
if (user.IsLockedOut || user.IsApproved is false)
9650
{
9751
await RevokeTokensAsync(user);
98-
continue;
99-
}
100-
101-
// Don't revoke admin tokens to prevent log out when accidental changes
102-
if (user.IsAdmin())
103-
{
104-
continue;
105-
}
106-
107-
// Check if the user access has changed - we also need to revoke all tokens in this case
108-
if (preSavingUsersState.TryGetValue(user.Key, out UserStartNodesAndGroupAccess? preSavingState))
109-
{
110-
UserStartNodesAndGroupAccess postSavingState = MapToUserStartNodesAndGroupAccess(user);
111-
if (preSavingState.CompareAccess(postSavingState) == false)
112-
{
113-
await RevokeTokensAsync(user);
114-
}
11552
}
11653
}
11754
}
@@ -131,49 +68,6 @@ public async Task HandleAsync(UserDeletedNotification notification, Cancellation
13168
}
13269
}
13370

134-
// We need to know the pre-deleting state of the users part of the deleted group to revoke their tokens
135-
public async Task HandleAsync(UserGroupDeletingNotification notification, CancellationToken cancellationToken)
136-
{
137-
var usersInGroups = new Dictionary<Guid, IEnumerable<IUser>>();
138-
foreach (IUserGroup userGroup in notification.DeletedEntities)
139-
{
140-
var users = await GetUsersByGroupKeyAsync(userGroup.Key);
141-
if (users == null)
142-
{
143-
continue;
144-
}
145-
146-
usersInGroups.Add(userGroup.Key, users);
147-
}
148-
149-
notification.State[NotificationStateKey] = usersInGroups;
150-
}
151-
152-
public async Task HandleAsync(UserGroupDeletedNotification notification, CancellationToken cancellationToken)
153-
{
154-
Dictionary<Guid, IEnumerable<IUser>>? preDeletingUsersInGroups = null;
155-
156-
if (notification.State.TryGetValue(NotificationStateKey, out var value))
157-
{
158-
preDeletingUsersInGroups = value as Dictionary<Guid, IEnumerable<IUser>>;
159-
}
160-
161-
if (preDeletingUsersInGroups is null)
162-
{
163-
return;
164-
}
165-
166-
// since the user group was deleted, we can only use the information we collected before the deletion
167-
// this means that we will not be able to detect users in any groups that were eventually deleted (due to implementor/3th party supplier interference)
168-
// that were not in the initial to be deleted list
169-
foreach (IUser user in preDeletingUsersInGroups
170-
.Where(group => notification.DeletedEntities.Any(entity => group.Key == entity.Key))
171-
.SelectMany(group => group.Value))
172-
{
173-
await RevokeTokensAsync(user);
174-
}
175-
}
176-
17771
public async Task HandleAsync(UserLoginSuccessNotification notification, CancellationToken cancellationToken)
17872
{
17973
if (_securitySettings.AllowConcurrentLogins is false)
@@ -190,29 +84,6 @@ public async Task HandleAsync(UserLoginSuccessNotification notification, Cancell
19084
}
19185
}
19286

193-
// Get data about the user before saving
194-
private async Task<UserStartNodesAndGroupAccess?> GetRelevantUserAccessDataByUserKeyAsync(Guid userKey)
195-
{
196-
IUser? user = await _userService.GetAsync(userKey);
197-
198-
return user is null
199-
? null
200-
: MapToUserStartNodesAndGroupAccess(user);
201-
}
202-
203-
private UserStartNodesAndGroupAccess MapToUserStartNodesAndGroupAccess(IUser user)
204-
=> new(user.Groups.Select(g => g.Key), user.StartContentIds, user.StartMediaIds);
205-
206-
// Get data about the users part of a group before deleting it
207-
private async Task<IEnumerable<IUser>?> GetUsersByGroupKeyAsync(Guid userGroupKey)
208-
{
209-
IUserGroup? userGroup = await _userGroupService.GetAsync(userGroupKey);
210-
211-
return userGroup is null
212-
? null
213-
: _userService.GetAllInGroup(userGroup.Id);
214-
}
215-
21687
private async Task RevokeTokensAsync(IUser user)
21788
{
21889
_logger.LogInformation("Revoking active tokens for user with ID {id}", user.Id);
@@ -236,35 +107,4 @@ private async Task RevokeTokensAsync(IUser user)
236107

237108
return null;
238109
}
239-
240-
private class UserStartNodesAndGroupAccess
241-
{
242-
public IEnumerable<Guid> GroupKeys { get; }
243-
244-
public int[]? StartContentIds { get; }
245-
246-
public int[]? StartMediaIds { get; }
247-
248-
public UserStartNodesAndGroupAccess(IEnumerable<Guid> groupKeys, int[]? startContentIds, int[]? startMediaIds)
249-
{
250-
GroupKeys = groupKeys;
251-
StartContentIds = startContentIds;
252-
StartMediaIds = startMediaIds;
253-
}
254-
255-
public bool CompareAccess(UserStartNodesAndGroupAccess other)
256-
{
257-
var areContentStartNodesEqual = (StartContentIds == null && other.StartContentIds == null) ||
258-
(StartContentIds != null && other.StartContentIds != null &&
259-
StartContentIds.SequenceEqual(other.StartContentIds));
260-
261-
var areMediaStartNodesEqual = (StartMediaIds == null && other.StartMediaIds == null) ||
262-
(StartMediaIds != null && other.StartMediaIds != null &&
263-
StartMediaIds.SequenceEqual(other.StartMediaIds));
264-
265-
return areContentStartNodesEqual &&
266-
areMediaStartNodesEqual &&
267-
GroupKeys.SequenceEqual(other.GroupKeys);
268-
}
269-
}
270110
}

0 commit comments

Comments
 (0)