From 79a465aa40fe8f0a65d3b2e0eeaf44dcebbd601e Mon Sep 17 00:00:00 2001 From: Ali Oezyetimoglu Date: Tue, 9 Jan 2024 22:06:31 +0100 Subject: [PATCH 1/2] 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; } From 1f3c862497c6258e6ac6251d281c635f20e84e54 Mon Sep 17 00:00:00 2001 From: Ali Oezyetimoglu Date: Wed, 10 Jan 2024 08:40:16 +0100 Subject: [PATCH 2/2] RED-5012: catching if user has insufficient rank while creating a user --- .../service/UserService.java | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 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 c3df834..bfea353 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java @@ -140,9 +140,10 @@ public class UserService { 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; + return userResource.roles().realmLevel().listEffective().stream() + .map(RoleRepresentation::getName) + .filter(r -> tenantUserManagementProperties.getKcRoleMapping().isValidRole(r)) + .collect(Collectors.toSet()); } @@ -192,12 +193,7 @@ public class UserService { var newRolesRank = newRoles.stream().map(r -> roleMapping.getRole(r).getRank()).toList(); var maxNewRolesRank = newRolesRank.stream().max(Integer::compare).orElse(-1); - var untouchableRoles = userRoles.stream() - .filter(roleMapping::isValidRole) - .map(roleMapping::getRole) - .filter(r -> r.getRank() > maxRank) - .map(KCRole::getName) - .collect(Collectors.toSet()); + var untouchableRoles = userRoles.stream().filter(roleMapping::isValidRole).map(roleMapping::getRole).filter(r -> r.getRank() > maxRank).map(KCRole::getName).collect(Collectors.toSet()); if (maxNewRolesRank > maxRank) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot assign this role to that user. Insufficient rights"); @@ -225,8 +221,7 @@ public class UserService { var user = this.getUserResource(KeycloakSecurity.getUserId()); var userRepresentation = user.toRepresentation(); - if (userRepresentation.getFederatedIdentities() != null && !userRepresentation.getFederatedIdentities().isEmpty() && !updateProfileRequest.getEmail() - .equals(userRepresentation.getEmail())) { + if (userRepresentation.getFederatedIdentities() != null && !userRepresentation.getFederatedIdentities().isEmpty() && !updateProfileRequest.getEmail().equals(userRepresentation.getEmail())) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "It is not allowed to change the email from a federated identity"); } @@ -277,18 +272,7 @@ public class UserService { var changeEmailClient = KeycloakBuilder.builder() - .serverUrl(tenantUserManagementProperties.getServerUrl()) - .realm(TenantContext.getTenantId()) - .username(username) - .password(password) - .clientId(tenantUserManagementProperties.getApplicationClientId()) - .grantType(OAuth2Constants.PASSWORD) - .resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS) - .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY) - .connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize()) - .disableTrustManager() - .build()) - .build(); + .serverUrl(tenantUserManagementProperties.getServerUrl()).realm(TenantContext.getTenantId()).username(username).password(password).clientId(tenantUserManagementProperties.getApplicationClientId()).grantType(OAuth2Constants.PASSWORD).resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS).hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize()).disableTrustManager().build()).build(); try { changeEmailClient.tokenManager().getAccessTokenString(); @@ -382,8 +366,7 @@ public class UserService { var user = this.getUserResource(userId); var userRepresentation = user.toRepresentation(); - if (userRepresentation.getFederatedIdentities() != null && !userRepresentation.getFederatedIdentities().isEmpty() && !updateProfileRequest.getEmail() - .equals(userRepresentation.getEmail())) { + if (userRepresentation.getFederatedIdentities() != null && !userRepresentation.getFederatedIdentities().isEmpty() && !updateProfileRequest.getEmail().equals(userRepresentation.getEmail())) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to change the email from a federated identity"); }