From 79a465aa40fe8f0a65d3b2e0eeaf44dcebbd601e Mon Sep 17 00:00:00 2001 From: Ali Oezyetimoglu Date: Tue, 9 Jan 2024 22:06:31 +0100 Subject: [PATCH] RED-5012: catching if user has insufficient rank while creating a user --- .../service/UserService.java | 114 +++++++++++------- 1 file changed, 69 insertions(+), 45 deletions(-) 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 bed48df..c3df834 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java @@ -1,26 +1,5 @@ package com.knecon.fforesight.tenantusermanagement.service; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import javax.ws.rs.ClientErrorException; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.core.Response; - -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity; import com.knecon.fforesight.tenantcommons.TenantContext; import com.knecon.fforesight.tenantusermanagement.events.UserCreatedEvent; @@ -49,6 +28,24 @@ import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.NotAuthorizedException; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.Response; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @Slf4j @Service @@ -95,6 +92,8 @@ public class UserService { userRepresentation.setFirstName(user.getFirstName()); userRepresentation.setLastName(user.getLastName()); + checkRankOrderForAssigningRole(user.getRoles(), this.getUserRoles(KeycloakSecurity.getUserId())); + try (var response = this.getTenantUsersResource().create(userRepresentation)) { if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { @@ -126,6 +125,38 @@ public class UserService { } + public void checkRankOrderForAssigningRole(Set newRoles, Set currentUserRoles) { + var roleMapping = tenantUserManagementProperties.getKcRoleMapping(); + var maxRank = currentUserRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1); + var newRolesRank = newRoles.stream().map(r -> roleMapping.getRole(r).getRank()).toList(); + var maxNewRolesRank = newRolesRank.stream().max(Integer::compare).orElse(-1); + + if (maxNewRolesRank > maxRank) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot assign this role to that user. Insufficient rights"); + } + } + + + public Set getUserRoles(String userId) { + + var userResource = getUserResource(userId); + var currentUserRoles = userResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet()); + currentUserRoles = currentUserRoles.stream().filter(r -> tenantUserManagementProperties.getKcRoleMapping().isValidRole(r)).collect(Collectors.toSet()); + return currentUserRoles; + } + + + @CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true) + public User setRoles(String userId, Set roles) { + + var currentUserRoles = this.getUserRoles(KeycloakSecurity.getUserId()); + + var userWithNewRoles = setRoles(userId, roles, currentUserRoles); + + return userWithNewRoles; + } + + @CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true) public User setRoles(String userId, Set newRoles, Set currentUserRoles) { @@ -139,6 +170,23 @@ public class UserService { var userResource = getUserResource(userId); var userRoles = userResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet()); + validateSufficientRoles(userId, userRoles, newRoles, currentUserRoles); + + var currentRolesAsRoleRepresentation = allRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList()); + var newMappedRoles = newRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList()); + + userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation); + userResource.roles().realmLevel().add(newMappedRoles); + + var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername()); + this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, userRoles, newRoles, KeycloakSecurity.getUserId()))); + + return userWithNewRoles; + } + + @CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true) + public void validateSufficientRoles(String userId, Set userRoles, Set newRoles, Set currentUserRoles) { + var roleMapping = tenantUserManagementProperties.getKcRoleMapping(); var maxRank = currentUserRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1); var newRolesRank = newRoles.stream().map(r -> roleMapping.getRole(r).getRank()).toList(); @@ -162,30 +210,6 @@ public class UserService { if (userId.equals(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) { throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self."); } - - var currentRolesAsRoleRepresentation = allRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList()); - var newMappedRoles = newRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList()); - - userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation); - userResource.roles().realmLevel().add(newMappedRoles); - - var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername()); - this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, userRoles, newRoles, KeycloakSecurity.getUserId()))); - - return userWithNewRoles; - } - - - @CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true) - public User setRoles(String userId, Set roles) { - - var currentUserResource = getUserResource(KeycloakSecurity.getUserId()); - var currentUserRoles = currentUserResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet()); - currentUserRoles = currentUserRoles.stream().filter(r -> tenantUserManagementProperties.getKcRoleMapping().isValidRole(r)).collect(Collectors.toSet()); - - var userWithNewRoles = setRoles(userId, roles, currentUserRoles); - - return userWithNewRoles; }