V14: Add additional validation around manipulating groups (#15834)

* Add additional validation when removing a user from usergroup

* Add additional validation to UpdateUserGroups

* Don't re-implement operation results

* Add additional validation to the update user endpoint

* Complete scopes where it's safe to do so
This commit is contained in:
Mole
2024-03-07 13:21:48 +01:00
committed by GitHub
parent 614c384c47
commit eca13ea011
11 changed files with 159 additions and 46 deletions

View File

@@ -909,22 +909,37 @@ internal class UserService : RepositoryService, IUserService
if (performingUser is null)
{
scope.Complete();
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.MissingUser, existingUser);
}
var userGroups = _userGroupRepository.GetMany().Where(x=>model.UserGroupKeys.Contains(x.Key)).ToHashSet();
IEnumerable<IUserGroup> allUserGroups = _userGroupRepository.GetMany().ToArray();
var userGroups = allUserGroups.Where(x => model.UserGroupKeys.Contains(x.Key)).ToHashSet();
if (userGroups.Count != model.UserGroupKeys.Count)
{
scope.Complete();
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.MissingUserGroup, existingUser);
}
// We're de-admining a user, we need to ensure that this would not leave the admin group empty.
if (existingUser.IsAdmin() && model.UserGroupKeys.Contains(Constants.Security.AdminGroupKey) is false)
{
IUserGroup? adminGroup = allUserGroups.FirstOrDefault(x => x.Key == Constants.Security.AdminGroupKey);
if (adminGroup?.UserCount == 1)
{
scope.Complete();
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.AdminUserGroupMustNotBeEmpty, existingUser);
}
}
// We have to resolve the keys to ids to be compatible with the repository, this could be done in the factory,
// but I'd rather keep the ids out of the service API as much as possible.
int[]? startContentIds = GetIdsFromKeys(model.ContentStartNodeKeys, UmbracoObjectTypes.Document);
if (startContentIds is null || startContentIds.Length != model.ContentStartNodeKeys.Count)
{
scope.Complete();
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.ContentStartNodeNotFound, existingUser);
}
@@ -932,6 +947,7 @@ internal class UserService : RepositoryService, IUserService
if (startMediaIds is null || startMediaIds.Length != model.MediaStartNodeKeys.Count)
{
scope.Complete();
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.MediaStartNodeNotFound, existingUser);
}
@@ -944,12 +960,14 @@ internal class UserService : RepositoryService, IUserService
if (isAuthorized.Success is false)
{
scope.Complete();
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.Unauthorized, existingUser);
}
UserOperationStatus validationStatus = ValidateUserUpdateModel(existingUser, model);
if (validationStatus is not UserOperationStatus.Success)
{
scope.Complete();
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(validationStatus, existingUser);
}
@@ -1055,7 +1073,7 @@ internal class UserService : RepositoryService, IUserService
return UserOperationStatus.UserNameIsNotEmail;
}
if (!IsEmailValid(model.Email))
if (IsEmailValid(model.Email) is false)
{
return UserOperationStatus.InvalidEmail;
}