Compare commits
3 Commits
main
...
RED-8491-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bac3fa640 | ||
|
|
5ad75932d8 | ||
|
|
13ff6ced57 |
@ -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);
|
||||
|
||||
|
||||
@ -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<User> KNECON_ROLE_FILTER = user -> {
|
||||
Set<String> 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;
|
||||
|
||||
@ -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<String> RED_ROLES = Set.of(RED_USER_ROLE, RED_MANAGER_ROLE, RED_ADMIN_ROLE, RED_USER_ADMIN_ROLE);
|
||||
|
||||
|
||||
public static final Predicate<User> KNECON_ROLE_FILTER = user -> {
|
||||
Set<String> 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);
|
||||
|
||||
@ -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<String> 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<String> 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<String> userRoles, Set<String> newRoles, Set<String> 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<String> 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<String> currentRoles, Set<String> userRoles) {
|
||||
|
||||
if (userRoles.stream()
|
||||
.anyMatch(ApplicationRoles::isKneconRole)) {
|
||||
return ValidationStatus.INVALID;
|
||||
.allMatch(ApplicationRoles::isKneconRole)) {
|
||||
return ValidationResult.INVALID;
|
||||
}
|
||||
|
||||
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
|
||||
@ -588,14 +653,15 @@ public class UserService {
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
var targetRank = userRoles.stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
|
||||
if (targetRank <= maxRank) {
|
||||
return ValidationStatus.ALLOWED;
|
||||
return ValidationResult.ALLOWED;
|
||||
} else {
|
||||
return ValidationStatus.FORBIDDEN;
|
||||
return ValidationResult.FORBIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,69 +486,157 @@ 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());
|
||||
|
||||
// reset password for authentication
|
||||
userClient.resetPassword(noKneconUser.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
|
||||
|
||||
// authenticate with user without knecon roles
|
||||
tokenService.setUser("nokneconroles@notknecon.com", "Secret@secured!23");
|
||||
|
||||
e = assertThrows(FeignException.class, () -> userClient.resetPassword(onlyKneconUser.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build()));
|
||||
assertEquals(404, e.status());
|
||||
|
||||
userClient.resetPassword(user.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
|
||||
|
||||
userClient.activateProfile(user.getUserId(), true);
|
||||
|
||||
userClient.deleteUser(user.getUserId());
|
||||
|
||||
e = assertThrows(FeignException.class, () -> userClient.getUserById(user.getUserId()));
|
||||
assertEquals(404, e.status());
|
||||
// give the user the old roles back
|
||||
addRoles(user.getUserId(), allButKneconRoles);
|
||||
|
||||
// create several users with different roles for testing
|
||||
createUserRequest = new CreateUserRequest();
|
||||
createUserRequest.setEmail("lesseruser@user.com");
|
||||
createUserRequest.setFirstName("Lesser");
|
||||
createUserRequest.setLastName("User");
|
||||
createUserRequest.setUsername("LesserSuperUser");
|
||||
var lesserUser = userClient.createUser(createUserRequest);
|
||||
addRoles(lesserUser.getUserId(), Set.of("LESS_SUPER_USER"));
|
||||
|
||||
// reset password for authentication
|
||||
userClient.resetPassword(lesserUser.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build());
|
||||
|
||||
// authenticate with user without knecon roles
|
||||
tokenService.setUser("lesseruser@user.com", "Secret@secured!23");
|
||||
|
||||
e = assertThrows(FeignException.class, () -> userClient.resetPassword(user.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build()));
|
||||
assertEquals(403, e.status());
|
||||
|
||||
e = assertThrows(FeignException.class, () -> userClient.activateProfile(user.getUserId(), true));
|
||||
assertEquals(403, e.status());
|
||||
|
||||
e = assertThrows(FeignException.class, () -> userClient.deleteUser(user.getUserId()));
|
||||
assertEquals(403, e.status());
|
||||
|
||||
// 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 +657,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 +717,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<String> roles) {
|
||||
|
||||
getUserResource(userId).roles().realmLevel().add(roles.stream().map(this::getRoleRepresentation).toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user