RED-5012: catching if user has insufficient rank while creating a user

This commit is contained in:
Ali Oezyetimoglu 2024-01-09 22:06:31 +01:00
parent d655ea958b
commit 79a465aa40

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,38 @@ 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);
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<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,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<String> userRoles, 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();
@ -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<String> 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;
}