Merge branch 'RED-3387' into 'main'

RED-3387: No Welcome Email for SSO User

See merge request fforesight/tenant-user-management-service!101
This commit is contained in:
Maverick Studer 2024-05-02 11:36:46 +02:00
commit 1c7698d2de
3 changed files with 79 additions and 36 deletions

View File

@ -25,4 +25,7 @@ public class CreateUserRequest {
@Schema(description = "Roles to assign to user.")
private Set<String> roles = new HashSet<>();
@Schema(description = "Whether a set password mail should be sent")
private boolean sendSetPasswordMail;
}

View File

@ -67,6 +67,9 @@ public class GeneralConfigurationService {
var realmRepresentation = realm.toRepresentation();
realmRepresentation.setResetPasswordAllowed(generalConfigurationModel.isForgotPasswordFunctionEnabled());
realmRepresentation.getAttributes().put("actionTokenGeneratedByUserLifespan.idp-verify-account-via-email", Integer.toString(86400));
if (!StringUtils.isEmpty(generalConfigurationModel.getAuxiliaryName())) {
setDisplayName(realmRepresentation, tenantUserManagementProperties.getApplicationName() + " (" + generalConfigurationModel.getAuxiliaryName() + ")");
} else {

View File

@ -8,11 +8,6 @@ import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.apache.commons.validator.routines.EmailValidator;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.internal.ResteasyClientBuilderImpl;
@ -23,7 +18,6 @@ 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;
@ -48,6 +42,10 @@ import com.knecon.fforesight.tenantusermanagement.model.User;
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
import io.micrometer.common.util.StringUtils;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -112,10 +110,12 @@ public class UserService {
var createdUser = getUserByUsername(username);
try {
sendResetPasswordEmail(createdUser.getUserId());
} catch (Exception e) {
log.debug("Activation E-mail could not be sent!", e);
if (user.isSendSetPasswordMail()) {
try {
sendResetPasswordEmail(createdUser.getUserId());
} catch (Exception e) {
log.debug("Set Password E-mail could not be sent!", e);
}
}
this.rabbitTemplate.convertAndSend(userExchangeName, "user.created", new UserCreatedEvent(createdUser, KeycloakSecurity.getUserId()));
@ -132,9 +132,16 @@ 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);
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");
@ -145,9 +152,7 @@ public class UserService {
public Set<String> getUserRoles(String userId) {
var userResource = getUserResource(userId);
return userResource.roles()
.realmLevel()
.listEffective()
return userResource.roles().realmLevel().listEffective()
.stream()
.map(RoleRepresentation::getName)
.filter(r -> tenantUserManagementProperties.getKcRoleMapping().isValidRole(r))
@ -177,12 +182,19 @@ public class UserService {
});
var userResource = getUserResource(userId);
var userRoles = userResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet());
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());
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);
@ -198,9 +210,16 @@ public class UserService {
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();
var maxNewRolesRank = newRolesRank.stream().max(Integer::compare).orElse(-1);
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)
@ -225,7 +244,10 @@ public class UserService {
public Optional<User> getUserById(String userId) {
return userListingService.getAllUsers(TenantContext.getTenantId()).stream().filter(u -> u.getUserId().equalsIgnoreCase(userId)).findAny();
return userListingService.getAllUsers(TenantContext.getTenantId())
.stream()
.filter(u -> u.getUserId().equalsIgnoreCase(userId))
.findAny();
}
@ -272,7 +294,8 @@ public class UserService {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No id provided.");
}
try {
return this.getTenantUsersResource().get(userId);
return this.getTenantUsersResource()
.get(userId);
} catch (NotFoundException e) {
throw new NotFoundException("User with id: " + userId + " does not exist", e);
}
@ -294,10 +317,10 @@ public class UserService {
.clientId(tenantUserManagementProperties.getApplicationClientId())
.grantType(OAuth2Constants.PASSWORD)
.resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS)
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize())
.disableTrustManager()
.build())
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize())
.disableTrustManager()
.build())
.build();
try {
@ -364,13 +387,17 @@ public class UserService {
private Set<String> getRoles(String id) {
List<RoleRepresentation> realmMappings = this.getTenantUsersResource().get(id).roles().getAll().getRealmMappings();
List<RoleRepresentation> realmMappings = this.getTenantUsersResource()
.get(id).roles().getAll().getRealmMappings();
if (realmMappings == null) {
log.warn("User with id=" + id + " contains null role mappings.");
return new TreeSet<>();
}
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
return realmMappings.stream().map(RoleRepresentation::getName).filter(allRoles::contains).collect(Collectors.toSet());
return realmMappings.stream()
.map(RoleRepresentation::getName)
.filter(allRoles::contains)
.collect(Collectors.toSet());
}
@ -457,7 +484,8 @@ public class UserService {
this.rabbitTemplate.convertAndSend(userExchangeName, "user.statusChanged", (new UserStatusToggleEvent(toggledUser, KeycloakSecurity.getUserId())));
return convert(this.getTenantUsersResource().get(userId).toRepresentation());
return convert(this.getTenantUsersResource()
.get(userId).toRepresentation());
}
@ -474,7 +502,8 @@ public class UserService {
request.setType(CredentialRepresentation.PASSWORD);
request.setTemporary(resetPasswordRequest.isTemporary());
request.setValue(resetPasswordRequest.getPassword());
realmService.realm(TenantContext.getTenantId()).users().get(userId).resetPassword(request);
realmService.realm(TenantContext.getTenantId()).users()
.get(userId).resetPassword(request);
log.info("User {} resetted password for user {}", KeycloakSecurity.getUserId(), userId);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not reset password. It does not match the password policy.", e);
@ -492,7 +521,8 @@ public class UserService {
RoleRepresentation realmRole;
try {
realmRole = realmService.realm(TenantContext.getTenantId()).roles().get(role).toRepresentation();
realmRole = realmService.realm(TenantContext.getTenantId()).roles()
.get(role).toRepresentation();
} catch (NotFoundException e) {
log.warn("The realm role {} is not found.", role);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "The realm role " + role + " is not found.", e);
@ -504,7 +534,8 @@ public class UserService {
private void sendResetPasswordEmail(String userId) {
try {
this.getTenantUsersResource().get(userId).executeActionsEmail(Collections.singletonList("UPDATE_PASSWORD"), 86400);
this.getTenantUsersResource()
.get(userId).executeActionsEmail(Collections.singletonList("UPDATE_PASSWORD"), 86400);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to send email.", e);
}
@ -519,8 +550,14 @@ public class UserService {
var userRoles = getRoles(targetUserId);
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
var maxRank = currentRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
var targetRank = userRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
var maxRank = currentRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
var targetRank = userRoles.stream()
.map(r -> roleMapping.getRole(r).getRank())
.max(Integer::compare)
.orElse(-1);
return targetRank <= maxRank;