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

@@ -398,4 +398,35 @@ internal sealed partial class UserServiceCrudTests
Assert.IsNotNull(updatedUser.StartMediaIds);
Assert.IsEmpty(updatedUser.StartMediaIds);
}
[TestCase(false, false)]
[TestCase(true, true)]
public async Task Cannot_Remove_Admin_Group_From_Only_Admin_User(bool createAdditionalAdminUser, bool expectSuccess)
{
var userService = CreateUserService(securitySettings: new SecuritySettings { UsernameIsEmail = false });
if (createAdditionalAdminUser)
{
var (updateModel, _) = await CreateUserForUpdate(userService);
updateModel.UserGroupKeys = new HashSet<Guid> { Constants.Security.AdminGroupKey };
var updateResult = await userService.UpdateAsync(Constants.Security.SuperUserKey, updateModel);
Assert.IsTrue(updateResult.Success);
}
var adminUser = await userService.GetAsync(Constants.Security.SuperUserKey);
var adminUserUpdateModel = await MapUserToUpdateModel(adminUser);
adminUserUpdateModel.Email = "admin@test.com";
adminUserUpdateModel.UserGroupKeys = new HashSet<Guid> { Constants.Security.EditorGroupKey };
var adminUserUpdateResult = await userService.UpdateAsync(Constants.Security.SuperUserKey, adminUserUpdateModel);
if (expectSuccess)
{
Assert.IsTrue(adminUserUpdateResult.Success);
}
else
{
Assert.IsFalse(adminUserUpdateResult.Success);
Assert.AreEqual(UserOperationStatus.AdminUserGroupMustNotBeEmpty, adminUserUpdateResult.Status);
}
}
}

View File

@@ -1019,6 +1019,42 @@ internal sealed class UserServiceTests : UmbracoIntegrationTest
}
}
[Test]
public async Task Can_Assign_And_Get_Groups_For_User()
{
// Arrange
var (user, userGroup1) = await CreateTestUserAndGroup();
var userGroup2 = await CreateTestUserGroup("testGroup2", "Test Group 2");
// Act & Assert
user = UserService.GetByUsername(user.Username);
Assert.IsNotNull(user);
Assert.AreEqual(1, user.Groups.Count());
Assert.AreEqual(userGroup1.Alias, user.Groups.First().Alias);
// - add second group
user.AddGroup(userGroup2);
UserService.Save(user);
user = UserService.GetByUsername(user.Username);
Assert.AreEqual(2, user.Groups.Count());
// - remove first group
user.RemoveGroup(userGroup1.Alias);
UserService.Save(user);
user = UserService.GetByUsername(user.Username);
Assert.AreEqual(1, user.Groups.Count());
Assert.AreEqual(userGroup2.Alias, user.Groups.First().Alias);
// - remove second group and add first
user.RemoveGroup(userGroup2.Alias);
user.AddGroup(userGroup1.ToReadOnlyGroup());
UserService.Save(user);
user = UserService.GetByUsername(user.Username);
Assert.AreEqual(1, user.Groups.Count());
Assert.AreEqual(userGroup1.Alias, user.Groups.First().Alias);
}
[TestCase(UserKind.Default, UserClientCredentialsOperationStatus.InvalidUser)]
[TestCase(UserKind.Api, UserClientCredentialsOperationStatus.Success)]
public async Task Can_Assign_ClientId_To_Api_User(UserKind userKind, UserClientCredentialsOperationStatus expectedResult)