Merge branch 'RED-5012' into 'main'

RED-5012: delete created user, if no rights to create that user

See merge request fforesight/tenant-user-management-service!63
This commit is contained in:
Ali Oezyetimoglu 2024-01-10 09:25:34 +01:00
commit d39ca21c4b

View File

@ -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,39 @@ public class UserService {
}
public void checkRankOrderForAssigningRole(Set<String> newRoles, Set<String> 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<String> getUserRoles(String userId) {
var userResource = getUserResource(userId);
return userResource.roles().realmLevel().listEffective().stream()
.map(RoleRepresentation::getName)
.filter(r -> tenantUserManagementProperties.getKcRoleMapping().isValidRole(r))
.collect(Collectors.toSet());
}
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
public User setRoles(String userId, Set<String> 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<String> newRoles, Set<String> currentUserRoles) {
@ -139,29 +171,7 @@ public class UserService {
var userResource = getUserResource(userId);
var userRoles = userResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet());
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);
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");
}
if (!newRoles.containsAll(untouchableRoles)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot modify some roles for this user. Insufficient rights");
}
if (userId.equals(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self.");
}
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());
@ -175,17 +185,27 @@ public class UserService {
return userWithNewRoles;
}
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
public User setRoles(String userId, Set<String> roles) {
public void validateSufficientRoles(String userId, Set<String> userRoles, Set<String> newRoles, Set<String> currentUserRoles) {
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 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);
var userWithNewRoles = setRoles(userId, roles, currentUserRoles);
var untouchableRoles = userRoles.stream().filter(roleMapping::isValidRole).map(roleMapping::getRole).filter(r -> r.getRank() > maxRank).map(KCRole::getName).collect(Collectors.toSet());
return userWithNewRoles;
if (maxNewRolesRank > maxRank) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot assign this role to that user. Insufficient rights");
}
if (!newRoles.containsAll(untouchableRoles)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot modify some roles for this user. Insufficient rights");
}
if (userId.equals(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self.");
}
}
@ -201,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");
}
@ -253,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();
@ -358,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");
}