Permissions: Fix removal of check on removal the final admin user (closes #19917) (#19921)

Reworks update of user groups on a user by updating in place rather than deleting and re-adding.
Ensure user groups affected by the update are invalidated in the repository cache.

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
This commit is contained in:
Andy Butland
2025-09-22 12:51:27 +02:00
committed by GitHub
parent 79de4e3871
commit d7d39658e0
3 changed files with 138 additions and 14 deletions

View File

@@ -671,6 +671,15 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
protected override void PersistDeletedItem(IUser entity)
{
// Clear user group caches for any user groups associated with the deleted user.
// We need to do this because the count of the number of users in the user group is cached
// along with the user group, and if we've made changes to the user groups assigned to the user,
// the count for the groups need to be refreshed.
foreach (IReadOnlyUserGroup group in entity.Groups)
{
ClearRepositoryCacheForUserGroup(group.Id);
}
IEnumerable<string> deletes = GetDeleteClauses();
foreach (var delete in deletes)
{
@@ -713,8 +722,8 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
if (entity.IsPropertyDirty("Groups"))
{
// lookup all assigned
List<UserGroupDto>? assigned = entity.Groups == null || entity.Groups.Any() == false
? new List<UserGroupDto>()
List<UserGroupDto>? assigned = entity.Groups.Any() is false
? []
: Database.Fetch<UserGroupDto>(
"SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)",
new { aliases = entity.Groups.Select(x => x.Alias) });
@@ -724,6 +733,15 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
var dto = new User2UserGroupDto { UserGroupId = groupDto.Id, UserId = entity.Id };
Database.Insert(dto);
}
// Clear user group caches for the user groups associated with the new user.
// We need to do this because the count of the number of users in the user group is cached
// along with the user group, and if we've made changes to the user groups assigned to the user,
// the count for the groups need to be refreshed.
foreach (IReadOnlyUserGroup group in entity.Groups)
{
ClearRepositoryCacheForUserGroup(group.Id);
}
}
entity.ResetDirtyProperties();
@@ -836,27 +854,66 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
if (entity.IsPropertyDirty("Groups"))
{
//lookup all assigned
List<UserGroupDto>? assigned = entity.Groups == null || entity.Groups.Any() == false
? new List<UserGroupDto>()
: Database.Fetch<UserGroupDto>(
"SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)",
new { aliases = entity.Groups.Select(x => x.Alias) });
// Get all user groups Ids currently assigned to the user.
var existingUserGroupIds = Database.Fetch<User2UserGroupDto>(
"WHERE UserId = @UserId",
new { UserId = entity.Id })
.Select(x => x.UserGroupId)
.ToList();
//first delete all
// TODO: We could do this a nicer way instead of "Nuke and Pave"
Database.Delete<User2UserGroupDto>("WHERE UserId = @UserId", new { UserId = entity.Id });
// Get the user groups Ids that need to be removed and added.
var userGroupsIdsToRemove = existingUserGroupIds
.Except(entity.Groups.Select(x => x.Id))
.ToList();
var userGroupIdsToAdd = entity.Groups
.Select(x => x.Id)
.Except(existingUserGroupIds)
.ToList();
foreach (UserGroupDto? groupDto in assigned)
// Remove user groups that are no longer assigned to the user.
if (userGroupsIdsToRemove.Count > 0)
{
var dto = new User2UserGroupDto { UserGroupId = groupDto.Id, UserId = entity.Id };
Database.Insert(dto);
Database.Delete<User2UserGroupDto>(
"WHERE UserId = @UserId AND UserGroupId IN (@userGroupIds)",
new { UserId = entity.Id, userGroupIds = userGroupsIdsToRemove });
}
// Add user groups that are newly assigned to the user.
if (userGroupIdsToAdd.Count > 0)
{
IEnumerable<User2UserGroupDto> user2UserGroupDtos = userGroupIdsToAdd
.Select(userGroupId => new User2UserGroupDto
{
UserGroupId = userGroupId,
UserId = entity.Id,
});
Database.InsertBulk(user2UserGroupDtos);
}
// Clear user group caches for any user group that have been removed or added.
// We need to do this because the count of the number of users in the user group is cached
// along with the user group, and if we've made changes to the user groups assigned to the user,
// the count for the groups need to be refreshed.
var userGroupIdsToRefresh = userGroupsIdsToRemove
.Union(userGroupIdsToAdd)
.ToList();
foreach (int userGroupIdToRefresh in userGroupIdsToRefresh)
{
ClearRepositoryCacheForUserGroup(userGroupIdToRefresh);
}
}
entity.ResetDirtyProperties();
}
private void ClearRepositoryCacheForUserGroup(int id)
{
IAppPolicyCache userGroupCache = AppCaches.IsolatedCaches.GetOrCreate<IUserGroup>();
string cacheKey = RepositoryCacheKeys.GetKey<IUserGroup, int>(id);
userGroupCache.Clear(cacheKey);
}
private void AddingOrUpdateStartNodes(IEntity entity, IEnumerable<UserStartNodeDto> current,
UserStartNodeDto.StartNodeTypeValue startNodeType, int[]? entityStartIds)
{