diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/UserResource.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/UserResource.java index 7738dff..ae31615 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/UserResource.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/UserResource.java @@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @@ -61,7 +60,7 @@ public interface UserResource { @ResponseBody @ResponseStatus(value = HttpStatus.OK) @Operation(summary = "Update a user profile", description = "None") - @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail invalid")}) + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail invalid"), @ApiResponse(responseCode = "404", description = "The userId cannot be found.")}) @PostMapping(value = UPDATE_USER_PROFILE_PATH + USER_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) User updateProfile(@PathVariable(USER_ID) String userId, @RequestBody UpdateProfileRequest updateProfileRequest); @@ -99,7 +98,7 @@ public interface UserResource { @ResponseBody @Operation(summary = "Gets the user in realm including role info", description = "None") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The " + "userId cannot be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or " + "null.")}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The userId cannot be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or null.")}) @GetMapping(value = USER_REST_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE) User getUserById(@PathVariable(USER_ID) String userId); @@ -120,8 +119,7 @@ public interface UserResource { @ResponseBody @Operation(summary = "Activate/deactivate a user profile", description = "None") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to activate/deactivate profile"), - @ApiResponse(responseCode = "403", description = "Cannot activate/deactivate users with higher rank roles")}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to activate/deactivate profile"), @ApiResponse(responseCode = "403", description = "Cannot activate/deactivate users with higher rank roles"), @ApiResponse(responseCode = "404", description = "The userId cannot be found.")}) @PostMapping(value = ACTIVATE_USER_PROFILE_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE) User activateProfile(@PathVariable(USER_ID) String userId, @RequestParam(IS_ACTIVE_PARAM) boolean isActive); diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java index 3001da7..c438815 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java @@ -1,5 +1,6 @@ package com.knecon.fforesight.tenantusermanagement.controller.external; +import static com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles.KNECON_ROLE_FILTER; import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_ALL_USERS; import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_USERS; import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.UPDATE_MY_PROFILE; @@ -39,19 +40,6 @@ import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor public class UserController implements UserResource, PublicResource { - public static final Predicate KNECON_ROLE_FILTER = user -> { - Set filteredRoles = user.getRoles() - .stream() - .filter(ApplicationRoles::isNoKneconRole) - .collect(Collectors.toSet()); - - if (!user.getRoles().isEmpty() && filteredRoles.isEmpty()) { - return false; - } - - user.setRoles(filteredRoles); - return true; - }; private final UserService userService; private final TenantUserManagementProperties tenantUserManagementProperties; diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/ApplicationRoles.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/ApplicationRoles.java index 488e48f..2116368 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/ApplicationRoles.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/ApplicationRoles.java @@ -1,6 +1,10 @@ package com.knecon.fforesight.tenantusermanagement.permissions; import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.knecon.fforesight.tenantusermanagement.model.User; public final class ApplicationRoles { @@ -15,6 +19,21 @@ public final class ApplicationRoles { public static final Set RED_ROLES = Set.of(RED_USER_ROLE, RED_MANAGER_ROLE, RED_ADMIN_ROLE, RED_USER_ADMIN_ROLE); + public static final Predicate KNECON_ROLE_FILTER = user -> { + Set filteredRoles = user.getRoles() + .stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet()); + + if (!user.getRoles().isEmpty() && filteredRoles.isEmpty()) { + return false; + } + + user.setRoles(filteredRoles); + return true; + }; + + public static boolean isNoKneconRole(String role) { return !KNECON_ROLES.contains(role); diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java index c3565b4..6af19a7 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java @@ -166,9 +166,9 @@ public class UserService { var currentUserRoles = this.getUserRoles(KeycloakSecurity.getUserId()); - if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && roles.stream() - .anyMatch(ApplicationRoles::isKneconRole) && currentUserRoles.stream() - .noneMatch(ApplicationRoles::isKneconRole)) { + Set oldRoles = getRoles(userId); + if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && !oldRoles.isEmpty() && oldRoles.stream() + .allMatch(ApplicationRoles::isKneconRole)) { throw new NotFoundException("User with id: " + userId + " does not exist"); } @@ -182,19 +182,18 @@ public class UserService { var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles(); newRoles.forEach(role -> { - if (!allRoles.contains(role) || ApplicationRoles.isKneconRole(role) && currentUserRoles.stream() - .noneMatch(ApplicationRoles::isKneconRole)) { + if (!allRoles.contains(role) || ApplicationRoles.isKneconRole(role)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role); } }); var userResource = getUserResource(userId); - var userRoles = userResource.roles().realmLevel().listEffective() + var oldRoles = userResource.roles().realmLevel().listEffective() .stream() .map(RoleRepresentation::getName) .collect(Collectors.toSet()); - validateSufficientRoles(userId, userRoles, newRoles, currentUserRoles); + validateSufficientRoles(userId, oldRoles, newRoles, currentUserRoles); var currentRolesAsRoleRepresentation = allRoles.stream() .map(this::getRoleRepresentation) @@ -207,12 +206,33 @@ public class UserService { userResource.roles().realmLevel().add(newMappedRoles); var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername()); - this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, userRoles, newRoles, KeycloakSecurity.getUserId()))); + this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, oldRoles, newRoles, KeycloakSecurity.getUserId()))); return userWithNewRoles; } + @CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true) + public void removeRolesForDeletion(String userId, Set roles) { + + var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles(); + roles.forEach(role -> { + + if (!allRoles.contains(role)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role); + } + }); + + var userResource = getUserResource(userId); + + var currentRolesAsRoleRepresentation = roles.stream() + .map(this::getRoleRepresentation) + .collect(Collectors.toList()); + + userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation); + } + + @CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true) public void validateSufficientRoles(String userId, Set userRoles, Set newRoles, Set currentUserRoles) { @@ -231,7 +251,7 @@ public class UserService { var untouchableRoles = userRoles.stream() .filter(roleMapping::isValidRole) .map(roleMapping::getRole) - .filter(r -> r.getRank() > maxRank) + .filter(r -> r.getRank() > maxRank || ApplicationRoles.isKneconRole(r.getName())) .map(KCRole::getName) .collect(Collectors.toSet()); @@ -288,6 +308,10 @@ public class UserService { } var updatedProfile = getUserByUsername(userRepresentation.getUsername()); + updatedProfile.setRoles(updatedProfile.getRoles() + .stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet())); this.rabbitTemplate.convertAndSend(userExchangeName, "user.ownProfileUpdated", (new UserUpdatedOwnProfileEvent(updatedProfile))); @@ -419,18 +443,25 @@ public class UserService { return; } - var status = validateExecution(KeycloakSecurity.getUserId(), userId); + var status = validateExecutionForDeletion(KeycloakSecurity.getUserId(), userId); - if (status.equals(ValidationStatus.FORBIDDEN)) { + if (status.equals(ValidationResult.FORBIDDEN)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to delete a user with higher ranking roles"); - } else if (status.equals(ValidationStatus.INVALID)) { + } else if (status.equals(ValidationResult.INVALID)) { return; } var userResource = getUserResource(userId); - var userToBeRemoved = getUserByUsername(userResource.toRepresentation().getUsername()); - userResource.remove(); + + if (status.equals(ValidationResult.ROLE_REMOVAL)) { + removeRolesForDeletion(userId, + getRoles(userId).stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet())); + } else { + userResource.remove(); + } this.rabbitTemplate.convertAndSend(userExchangeName, "user.deleted", (new UserRemovedEvent(userToBeRemoved, KeycloakSecurity.getUserId()))); @@ -443,8 +474,9 @@ public class UserService { var user = this.getUserResource(userId); var userRepresentation = user.toRepresentation(); - if (getRoles(userId).stream() - .anyMatch(ApplicationRoles::isKneconRole)) { + Set currentRoles = getRoles(userId); + if (!userExists(userId) || !currentRoles.isEmpty() && currentRoles.stream() + .allMatch(ApplicationRoles::isKneconRole)) { throw new NotFoundException("User with id: " + userId + " does not exist"); } @@ -467,6 +499,10 @@ public class UserService { setRoles(userId, updateProfileRequest.getRoles()); var updatedUser = getUserByUsername(userRepresentation.getUsername()); + updatedUser.setRoles(updatedUser.getRoles() + .stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet())); this.rabbitTemplate.convertAndSend(userExchangeName, "user.updated", (new UserUpdatedEvent(updatedUser, KeycloakSecurity.getUserId()))); @@ -479,9 +515,9 @@ public class UserService { if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) { var status = validateExecution(KeycloakSecurity.getUserId(), userId); - if (status.equals(ValidationStatus.FORBIDDEN)) { + if (status.equals(ValidationResult.FORBIDDEN)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to activate/deactivate a user with higher ranking roles"); - } else if (status.equals(ValidationStatus.INVALID)) { + } else if (status.equals(ValidationResult.INVALID)) { throw new NotFoundException("User with id: " + userId + " does not exist"); } } @@ -499,11 +535,14 @@ public class UserService { } var toggledUser = getUserByUsername(userRepresentation.getUsername()); + toggledUser.setRoles(toggledUser.getRoles() + .stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet())); this.rabbitTemplate.convertAndSend(userExchangeName, "user.statusChanged", (new UserStatusToggleEvent(toggledUser, KeycloakSecurity.getUserId()))); - return convert(this.getTenantUsersResource() - .get(userId).toRepresentation()); + return toggledUser; } @@ -512,9 +551,9 @@ public class UserService { if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) { var status = validateExecution(KeycloakSecurity.getUserId(), userId); - if (status.equals(ValidationStatus.FORBIDDEN)) { + if (status.equals(ValidationResult.FORBIDDEN)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to reset the password of a user with higher ranking roles"); - } else if (status.equals(ValidationStatus.INVALID)) { + } else if (status.equals(ValidationResult.INVALID)) { throw new NotFoundException("User with id: " + userId + " does not exist"); } } @@ -564,22 +603,48 @@ public class UserService { } - private enum ValidationStatus { + private enum ValidationResult { ALLOWED, FORBIDDEN, - INVALID + INVALID, + ROLE_REMOVAL + } - private ValidationStatus validateExecution(String executingUserId, String targetUserId) { + private ValidationResult validateExecution(String executingUserId, String targetUserId) { var currentUserResource = getUserResource(executingUserId); var currentRoles = getRoles(currentUserResource.toRepresentation().getId()); var userRoles = getRoles(targetUserId); + return validateRoleRanks(currentRoles, userRoles); + } + + + private ValidationResult validateExecutionForDeletion(String executingUserId, String targetUserId) { + + var currentUserResource = getUserResource(executingUserId); + var currentRoles = getRoles(currentUserResource.toRepresentation().getId()); + var userRoles = getRoles(targetUserId); + + ValidationResult validationResult = validateRoleRanks(currentRoles, userRoles); + if (validationResult == ValidationResult.ALLOWED) { + if (userRoles.stream() + .anyMatch(ApplicationRoles::isKneconRole) && userRoles.stream() + .anyMatch(ApplicationRoles::isNoKneconRole)) { + return ValidationResult.ROLE_REMOVAL; + } + } + return validationResult; + } + + + private ValidationResult validateRoleRanks(Set currentRoles, Set userRoles) { + if (userRoles.stream() - .anyMatch(ApplicationRoles::isKneconRole)) { - return ValidationStatus.INVALID; + .allMatch(ApplicationRoles::isKneconRole)) { + return ValidationResult.INVALID; } var roleMapping = tenantUserManagementProperties.getKcRoleMapping(); @@ -593,9 +658,9 @@ public class UserService { .orElse(-1); if (targetRank <= maxRank) { - return ValidationStatus.ALLOWED; + return ValidationResult.ALLOWED; } else { - return ValidationStatus.FORBIDDEN; + return ValidationResult.FORBIDDEN; } } diff --git a/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java b/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java index d37b644..0a2da1a 100644 --- a/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java +++ b/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java @@ -9,11 +9,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.beans.factory.annotation.Autowired; import com.knecon.fforesight.tenantcommons.TenantContext; @@ -26,6 +28,7 @@ import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest; import com.knecon.fforesight.tenantusermanagement.model.User; import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles; import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties; +import com.knecon.fforesight.tenantusermanagement.service.RealmService; import feign.FeignException; import lombok.SneakyThrows; @@ -35,6 +38,9 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { @Autowired private UserClient userClient; + @Autowired + private RealmService realmService; + @Autowired private TenantUserManagementProperties tenantUserManagementProperties; @@ -386,24 +392,24 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { createUserRequest.setFirstName("All"); createUserRequest.setLastName("Roles"); createUserRequest.setUsername("AllRoles"); - createUserRequest.setRoles(allRoles); User user = userClient.createUser(createUserRequest); + addRoles(user.getUserId(), allRoles); var createUserRequest2 = new CreateUserRequest(); createUserRequest2.setEmail("nokneconroles@notknecon.com"); createUserRequest2.setFirstName("No Knecon"); createUserRequest2.setLastName("Roles"); createUserRequest2.setUsername("NoKneconRoles"); - createUserRequest2.setRoles(allButKneconRoles); User noKneconUser = userClient.createUser(createUserRequest2); + addRoles(noKneconUser.getUserId(), allButKneconRoles); var createUserRequest3 = new CreateUserRequest(); createUserRequest3.setEmail("onlykneconroles@notknecon.com"); createUserRequest3.setFirstName("Only Knecon"); createUserRequest3.setLastName("Roles"); createUserRequest3.setUsername("OnlyKneconRoles"); - createUserRequest3.setRoles(onlyKneconRoles); User onlyKneconUser = userClient.createUser(createUserRequest3); + addRoles(onlyKneconUser.getUserId(), onlyKneconRoles); var allUsers = userClient.getAllUsers(true); @@ -480,9 +486,13 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23"); // we should not be able to set roles of this user at all as it is not visible to us resulting in a 404 - e = assertThrows(FeignException.class, () -> userClient.setRoles(user.getUserId(), onlyKneconRoles)); + e = assertThrows(FeignException.class, () -> userClient.setRoles(onlyKneconUser.getUserId(), allButKneconRoles)); assertEquals(404, e.status()); + // we can not assign a knecon rule as it is not visible to us + e = assertThrows(FeignException.class, () -> userClient.setRoles(user.getUserId(), onlyKneconRoles)); + assertEquals(400, e.status()); + // we should not be able to assign ourselves a knecon role as it is not visible to us e = assertThrows(FeignException.class, () -> userClient.setRoles(redUserAdmin.getUserId(), allRoles)); assertEquals(400, e.status()); @@ -490,59 +500,99 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { // authenticate as knecon admin again tokenService.setUser("admin@knecon.com", "secret"); - // this should be possible because we now have knecon roles - userClient.setRoles(onlyKneconUser.getUserId(), allRoles); - assertEquals(userClient.getUserById(onlyKneconUser.getUserId()).getRoles().size(), 2); + // this should still not be possible + e = assertThrows(FeignException.class, () -> userClient.setRoles(onlyKneconUser.getUserId(), allRoles)); + assertEquals(404, e.status()); - // and this as well - userClient.setRoles(user.getUserId(), allRoles); + // and also not this + e = assertThrows(FeignException.class, () -> userClient.setRoles(user.getUserId(), allRoles)); + assertEquals(400, e.status()); // we can also poll the user userClient.getUserById(user.getUserId()); + e = assertThrows(FeignException.class, () -> userClient.getUserById(onlyKneconUser.getUserId())); + assertEquals(404, e.status()); + // back to having no rights tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23"); // we can not call update profile - e = assertThrows(FeignException.class, () -> userClient.updateProfile(user.getUserId(), new UpdateProfileRequest())); + e = assertThrows(FeignException.class, () -> userClient.updateProfile(onlyKneconUser.getUserId(), new UpdateProfileRequest())); assertEquals(404, e.status()); // or reset password - e = assertThrows(FeignException.class, () -> userClient.resetPassword(user.getUserId(), new ResetPasswordRequest())); + e = assertThrows(FeignException.class, () -> userClient.resetPassword(onlyKneconUser.getUserId(), new ResetPasswordRequest())); assertEquals(404, e.status()); // now as a knecon admin again tokenService.setUser("admin@knecon.com", "secret"); // we can also not see another knecon account and change their password - e = assertThrows(FeignException.class, () -> userClient.resetPassword(user.getUserId(), new ResetPasswordRequest())); + e = assertThrows(FeignException.class, () -> userClient.resetPassword(onlyKneconUser.getUserId(), new ResetPasswordRequest())); assertEquals(404, e.status()); // or activate the profile - e = assertThrows(FeignException.class, () -> userClient.activateProfile(user.getUserId(), true)); + e = assertThrows(FeignException.class, () -> userClient.activateProfile(onlyKneconUser.getUserId(), true)); assertEquals(404, e.status()); + // but the user with all roles can be processed + userClient.resetPassword(user.getUserId(), new ResetPasswordRequest("Secret@secured!23", false)); + User activated = userClient.activateProfile(user.getUserId(), true); + activated.getRoles() + .stream() + .noneMatch(ApplicationRoles::isKneconRole); + // we create a new user with all roles var createUserRequest4 = new CreateUserRequest(); createUserRequest4.setEmail("allroles2@knecon.com"); createUserRequest4.setFirstName("All"); createUserRequest4.setLastName("Roles2"); createUserRequest4.setUsername("AllRoles2"); - createUserRequest4.setRoles(allRoles); User user4 = userClient.createUser(createUserRequest4); + addRoles(user4.getUserId(), allRoles); - // we attempt to delete it - userClient.deleteUser(user4.getUserId()); + // we attempt to delete it, should not be possible but still return 204 + userClient.deleteUser(onlyKneconUser.getUserId()); // and again using the bulk call - userClient.deleteUsers(List.of(user4.getUserId())); + userClient.deleteUsers(List.of(onlyKneconUser.getUserId())); + // no rights again ... + tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23"); + + // with this user we expect a 204 as well + userClient.deleteUser(onlyKneconUser.getUserId()); + + userClient.deleteUsers(List.of(onlyKneconUser.getUserId())); + + // check users as knecon admin again + tokenService.setUser("admin@knecon.com", "secret"); + + // with this user we expect a 204 as well but the user should have removed red roles + userClient.deleteUser(user4.getUserId()); + + addRoles(onlyKneconUser.getUserId(), allRoles); allUsers = userClient.getAllUsers(true); assertTrue(allUsers.stream() - .anyMatch(u -> u.getUserId().equals(user4.getUserId()))); - // and should not have changed - var user5 = userClient.getUserById(user4.getUserId()); - assertEquals(user4, user5); + .anyMatch(u -> u.getUserId().equals(onlyKneconUser.getUserId()))); + + // hence, 404 when trying to get the user now + e = assertThrows(FeignException.class, () -> userClient.getUserById(user4.getUserId())); + assertEquals(404, e.status()); + // give the user the old roles back + addRoles(user4.getUserId(), allButKneconRoles); + + allUsers = userClient.getAllUsers(true); + var user4AfterShenanigansOpt = allUsers.stream().filter(u -> u.getUserId().equals(user4.getUserId())).findFirst(); + assertTrue(user4AfterShenanigansOpt.isPresent()); + user4AfterShenanigansOpt.get().setRoles(new HashSet<>()); + assertEquals(user4AfterShenanigansOpt.get(), user4); + + var stillOnlyKneconUserOpt = allUsers.stream().filter(u -> u.getUserId().equals(onlyKneconUser.getUserId())).findFirst(); + assertTrue(stillOnlyKneconUserOpt.isPresent()); + stillOnlyKneconUserOpt.get().setRoles(new HashSet<>()); + assertEquals(stillOnlyKneconUserOpt.get(), onlyKneconUser); } @@ -563,8 +613,8 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { createUserRequest.setFirstName("Some Other"); createUserRequest.setLastName("User"); createUserRequest.setUsername("SomeOtherUser"); - createUserRequest.setRoles(allRoles); User user = userClient.createUser(createUserRequest); + addRoles(user.getUserId(), allRoles); var createUserRequest2 = new CreateUserRequest(); createUserRequest2.setEmail("noroles@notknecon.com"); @@ -623,4 +673,30 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { } + + private UsersResource getTenantUsersResource() { + + return realmService.realm(TenantContext.getTenantId()).users(); + } + + + private UserResource getUserResource(String userId) { + + return this.getTenantUsersResource() + .get(userId); + } + + + private RoleRepresentation getRoleRepresentation(String role) { + + return realmService.realm(TenantContext.getTenantId()).roles() + .get(role).toRepresentation(); + } + + + private void addRoles(String userId, Set roles) { + + getUserResource(userId).roles().realmLevel().add(roles.stream().map(this::getRoleRepresentation).toList()); + } + }