- Ajoute KeycloakRealmSetupService : auto-initialisation des rôles realm (admin, user_manager, user_viewer, role_manager...) et assignation du rôle user_manager au service account unionflow-server au démarrage (idempotent, retries, thread séparé pour ne pas bloquer le démarrage) → Corrige le 403 sur resetPassword / changement de mot de passe premier login - UserResource : étend les @RolesAllowed avec ADMIN/SUPER_ADMIN/USER pour permettre aux appels inter-services unionflow-server d'accéder aux endpoints sans être bloqués par le RBAC LUM ; corrige sendVerificationEmail (retourne Response) - application-dev.properties : service-accounts.user-manager-clients=unionflow-server - application-prod.properties : client-id, credentials.secret, token.audience, auto-setup - application-test.properties : H2 in-memory (plus besoin de Docker pour les tests) - pom.xml : H2 scope test, Jacoco 100% enforcement (exclusions MapStruct/repos/setup), annotation processors MapStruct+Lombok explicites - .gitignore + .env ajouté (.env exclu du commit) - script/docker/.env.example : variables KEYCLOAK_ADMIN_USERNAME/PASSWORD documentées
163 lines
6.7 KiB
Java
163 lines
6.7 KiB
Java
package dev.lions.user.manager.resource;
|
|
|
|
import dev.lions.user.manager.api.UserResourceApi;
|
|
import dev.lions.user.manager.dto.common.ApiErrorDTO;
|
|
import dev.lions.user.manager.dto.importexport.ImportResultDTO;
|
|
import dev.lions.user.manager.dto.user.*;
|
|
import dev.lions.user.manager.service.UserService;
|
|
import jakarta.annotation.security.RolesAllowed;
|
|
import jakarta.inject.Inject;
|
|
import jakarta.validation.Valid;
|
|
import jakarta.validation.constraints.NotBlank;
|
|
import jakarta.validation.constraints.NotNull;
|
|
import jakarta.ws.rs.GET;
|
|
import jakarta.ws.rs.POST;
|
|
import jakarta.ws.rs.QueryParam;
|
|
import jakarta.ws.rs.core.MediaType;
|
|
import jakarta.ws.rs.core.Response;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
import java.util.List;
|
|
|
|
/**
|
|
* REST Resource pour la gestion des utilisateurs
|
|
* Implémente l'interface API commune.
|
|
*/
|
|
@Slf4j
|
|
@jakarta.enterprise.context.ApplicationScoped
|
|
@jakarta.ws.rs.Path("/api/users")
|
|
public class UserResource implements UserResourceApi {
|
|
|
|
@Inject
|
|
UserService userService;
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "ADMIN", "SUPER_ADMIN" })
|
|
public UserSearchResultDTO searchUsers(@Valid @NotNull UserSearchCriteriaDTO criteria) {
|
|
log.info("POST /api/users/search - Recherche d'utilisateurs");
|
|
return userService.searchUsers(criteria);
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "user_viewer", "ADMIN", "SUPER_ADMIN", "USER" })
|
|
public UserDTO getUserById(String userId, String realmName) {
|
|
log.info("GET /api/users/{} - realm: {}", userId, realmName);
|
|
return userService.getUserById(userId, realmName)
|
|
.orElseThrow(() -> new RuntimeException("Utilisateur non trouvé")); // ExceptionMapper should handle/map
|
|
// to 404
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "user_viewer", "ADMIN", "SUPER_ADMIN", "USER" })
|
|
public UserSearchResultDTO getAllUsers(String realmName, int page, int pageSize) {
|
|
log.info("GET /api/users - realm: {}, page: {}, pageSize: {}", realmName, page, pageSize);
|
|
return userService.getAllUsers(realmName, page, pageSize);
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "ADMIN", "SUPER_ADMIN" })
|
|
public Response createUser(@Valid @NotNull UserDTO user, String realmName) {
|
|
log.info("POST /api/users - Création d'un utilisateur: {}", user.getUsername());
|
|
|
|
try {
|
|
UserDTO createdUser = userService.createUser(user, realmName);
|
|
return Response.status(Response.Status.CREATED).entity(createdUser).build();
|
|
} catch (IllegalArgumentException e) {
|
|
log.warn("Données invalides lors de la création: {}", e.getMessage());
|
|
return Response.status(Response.Status.CONFLICT)
|
|
.entity(new ApiErrorDTO(e.getMessage()))
|
|
.build();
|
|
} catch (Exception e) {
|
|
log.error("Erreur lors de la création de l'utilisateur", e);
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "ADMIN", "SUPER_ADMIN" })
|
|
public UserDTO updateUser(String userId, @Valid @NotNull UserDTO user, String realmName) {
|
|
log.info("PUT /api/users/{} - Mise à jour", userId);
|
|
return userService.updateUser(userId, user, realmName);
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "ADMIN", "SUPER_ADMIN" })
|
|
public void deleteUser(String userId, String realmName, boolean hardDelete) {
|
|
log.info("DELETE /api/users/{} - realm: {}, hardDelete: {}", userId, realmName, hardDelete);
|
|
userService.deleteUser(userId, realmName, hardDelete);
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "ADMIN", "SUPER_ADMIN" })
|
|
public void activateUser(String userId, String realmName) {
|
|
log.info("POST /api/users/{}/activate", userId);
|
|
userService.activateUser(userId, realmName);
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "ADMIN", "SUPER_ADMIN" })
|
|
public void deactivateUser(String userId, String realmName, String raison) {
|
|
log.info("POST /api/users/{}/deactivate - raison: {}", userId, raison);
|
|
userService.deactivateUser(userId, realmName, raison);
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager" })
|
|
public void resetPassword(String userId, String realmName, @NotNull PasswordResetRequestDTO request) {
|
|
log.info("POST /api/users/{}/reset-password - temporary: {}", userId, request.isTemporary());
|
|
userService.resetPassword(userId, realmName, request.getPassword(), request.isTemporary());
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager" })
|
|
public Response sendVerificationEmail(String userId, String realmName) {
|
|
log.info("POST /api/users/{}/send-verification-email", userId);
|
|
userService.sendVerificationEmail(userId, realmName);
|
|
return Response.accepted().build();
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager" })
|
|
public SessionsRevokedDTO logoutAllSessions(String userId, String realmName) {
|
|
log.info("POST /api/users/{}/logout-sessions", userId);
|
|
int count = userService.logoutAllSessions(userId, realmName);
|
|
return new SessionsRevokedDTO(count);
|
|
}
|
|
|
|
@Override
|
|
@RolesAllowed({ "admin", "user_manager", "user_viewer" })
|
|
public List<String> getActiveSessions(String userId, String realmName) {
|
|
log.info("GET /api/users/{}/sessions", userId);
|
|
return userService.getActiveSessions(userId, realmName);
|
|
}
|
|
|
|
@Override
|
|
@GET
|
|
@jakarta.ws.rs.Path("/export/csv")
|
|
@jakarta.ws.rs.Produces("text/csv")
|
|
@RolesAllowed({ "admin", "user_manager" })
|
|
public Response exportUsersToCSV(@QueryParam("realm") String realmName) {
|
|
log.info("GET /api/users/export/csv - realm: {}", realmName);
|
|
UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
|
|
.realmName(realmName)
|
|
.page(0)
|
|
.pageSize(10_000)
|
|
.build();
|
|
String csv = userService.exportUsersToCSV(criteria);
|
|
return Response.ok(csv)
|
|
.type(MediaType.valueOf("text/csv"))
|
|
.header("Content-Disposition", "attachment; filename=\"users-" + (realmName != null ? realmName : "export") + ".csv\"")
|
|
.build();
|
|
}
|
|
|
|
@Override
|
|
@POST
|
|
@jakarta.ws.rs.Path("/import/csv")
|
|
@jakarta.ws.rs.Consumes(MediaType.TEXT_PLAIN)
|
|
@RolesAllowed({ "admin", "user_manager" })
|
|
public ImportResultDTO importUsersFromCSV(@QueryParam("realm") String realmName, String csvContent) {
|
|
log.info("POST /api/users/import/csv - realm: {}", realmName);
|
|
return userService.importUsersFromCSV(csvContent, realmName);
|
|
}
|
|
}
|