feat: Ajout RoleService, AuditService complets
Nouveaux fichiers: - RoleMapper.java - RoleServiceImpl.java (20+ méthodes) - RoleResource.java (REST API rôles) - AuditServiceImpl.java (logging et statistiques) Services: - Gestion complète des rôles realm et client - Attribution/révocation de rôles - Rôles composites - Audit logging avec stats Statut: 🔄 Backend 70% complété 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package dev.lions.user.manager.mapper;
|
||||
|
||||
import dev.lions.user.manager.dto.role.RoleDTO;
|
||||
import dev.lions.user.manager.enums.role.TypeRole;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre RoleDTO et Keycloak RoleRepresentation
|
||||
*/
|
||||
public class RoleMapper {
|
||||
|
||||
/**
|
||||
* Convertit une RoleRepresentation Keycloak en RoleDTO
|
||||
*/
|
||||
public static RoleDTO toDTO(RoleRepresentation roleRep, String realmName, TypeRole typeRole) {
|
||||
if (roleRep == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return RoleDTO.builder()
|
||||
.id(roleRep.getId())
|
||||
.nom(roleRep.getName())
|
||||
.description(roleRep.getDescription())
|
||||
.typeRole(typeRole)
|
||||
.realmName(realmName)
|
||||
.composite(roleRep.isComposite() != null ? roleRep.isComposite() : false)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit un RoleDTO en RoleRepresentation Keycloak
|
||||
*/
|
||||
public static RoleRepresentation toRepresentation(RoleDTO roleDTO) {
|
||||
if (roleDTO == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RoleRepresentation roleRep = new RoleRepresentation();
|
||||
roleRep.setId(roleDTO.getId());
|
||||
roleRep.setName(roleDTO.getNom());
|
||||
roleRep.setDescription(roleDTO.getDescription());
|
||||
roleRep.setComposite(roleDTO.isComposite());
|
||||
roleRep.setClientRole(roleDTO.getTypeRole() == TypeRole.CLIENT_ROLE);
|
||||
|
||||
return roleRep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit une liste de RoleRepresentation en liste de RoleDTO
|
||||
*/
|
||||
public static List<RoleDTO> toDTOList(List<RoleRepresentation> roleReps, String realmName, TypeRole typeRole) {
|
||||
if (roleReps == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return roleReps.stream()
|
||||
.map(roleRep -> toDTO(roleRep, realmName, typeRole))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit une liste de RoleDTO en liste de RoleRepresentation
|
||||
*/
|
||||
public static List<RoleRepresentation> toRepresentationList(List<RoleDTO> roleDTOs) {
|
||||
if (roleDTOs == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return roleDTOs.stream()
|
||||
.map(RoleMapper::toRepresentation)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
package dev.lions.user.manager.resource;
|
||||
|
||||
import dev.lions.user.manager.dto.role.RoleDTO;
|
||||
import dev.lions.user.manager.service.RoleService;
|
||||
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.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST Resource pour la gestion des rôles Keycloak
|
||||
* Endpoints pour les rôles realm, rôles client, et attributions
|
||||
*/
|
||||
@Path("/api/roles")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Roles", description = "Gestion des rôles Keycloak (realm et client)")
|
||||
@Slf4j
|
||||
public class RoleResource {
|
||||
|
||||
@Inject
|
||||
RoleService roleService;
|
||||
|
||||
// ==================== Endpoints Realm Roles ====================
|
||||
|
||||
@POST
|
||||
@Path("/realm")
|
||||
@Operation(summary = "Créer un rôle realm", description = "Crée un nouveau rôle au niveau du realm")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "201", description = "Rôle créé",
|
||||
content = @Content(schema = @Schema(implementation = RoleDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "409", description = "Rôle existe déjà"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager"})
|
||||
public Response createRealmRole(
|
||||
@Valid @NotNull RoleDTO roleDTO,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("POST /api/roles/realm - Création du rôle realm: {} dans le realm: {}",
|
||||
roleDTO.getNom(), realmName);
|
||||
|
||||
try {
|
||||
RoleDTO createdRole = roleService.createRealmRole(roleDTO, realmName);
|
||||
return Response.status(Response.Status.CREATED).entity(createdRole).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Données invalides lors de la création du rôle: {}", e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la création du rôle realm", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/realm/{roleName}")
|
||||
@Operation(summary = "Récupérer un rôle realm par nom", description = "Récupère les détails d'un rôle realm")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Rôle trouvé",
|
||||
content = @Content(schema = @Schema(implementation = RoleDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager", "role_viewer"})
|
||||
public Response getRealmRole(
|
||||
@Parameter(description = "Nom du rôle") @PathParam("roleName") @NotBlank String roleName,
|
||||
@Parameter(description = "Nom du realm") @QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("GET /api/roles/realm/{} - realm: {}", roleName, realmName);
|
||||
|
||||
try {
|
||||
return roleService.getRealmRoleByName(roleName, realmName)
|
||||
.map(role -> Response.ok(role).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Rôle non trouvé"))
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération du rôle realm {}", roleName, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/realm")
|
||||
@Operation(summary = "Lister tous les rôles realm", description = "Liste tous les rôles du realm")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des rôles"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager", "role_viewer"})
|
||||
public Response getAllRealmRoles(
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("GET /api/roles/realm - realm: {}", realmName);
|
||||
|
||||
try {
|
||||
List<RoleDTO> roles = roleService.getAllRealmRoles(realmName);
|
||||
return Response.ok(roles).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des rôles realm", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/realm/{roleName}")
|
||||
@Operation(summary = "Mettre à jour un rôle realm", description = "Met à jour les informations d'un rôle realm")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Rôle mis à jour",
|
||||
content = @Content(schema = @Schema(implementation = RoleDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager"})
|
||||
public Response updateRealmRole(
|
||||
@PathParam("roleName") @NotBlank String roleName,
|
||||
@Valid @NotNull RoleDTO roleDTO,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("PUT /api/roles/realm/{} - realm: {}", roleName, realmName);
|
||||
|
||||
try {
|
||||
RoleDTO updatedRole = roleService.updateRealmRole(roleName, roleDTO, realmName);
|
||||
return Response.ok(updatedRole).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la mise à jour du rôle realm {}", roleName, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/realm/{roleName}")
|
||||
@Operation(summary = "Supprimer un rôle realm", description = "Supprime un rôle realm")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Rôle supprimé"),
|
||||
@APIResponse(responseCode = "404", description = "Rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin"})
|
||||
public Response deleteRealmRole(
|
||||
@PathParam("roleName") @NotBlank String roleName,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("DELETE /api/roles/realm/{} - realm: {}", roleName, realmName);
|
||||
|
||||
try {
|
||||
roleService.deleteRealmRole(roleName, realmName);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la suppression du rôle realm {}", roleName, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Endpoints Client Roles ====================
|
||||
|
||||
@POST
|
||||
@Path("/client/{clientId}")
|
||||
@Operation(summary = "Créer un rôle client", description = "Crée un nouveau rôle pour un client spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "201", description = "Rôle créé",
|
||||
content = @Content(schema = @Schema(implementation = RoleDTO.class))),
|
||||
@APIResponse(responseCode = "400", description = "Données invalides"),
|
||||
@APIResponse(responseCode = "409", description = "Rôle existe déjà"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager"})
|
||||
public Response createClientRole(
|
||||
@PathParam("clientId") @NotBlank String clientId,
|
||||
@Valid @NotNull RoleDTO roleDTO,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("POST /api/roles/client/{} - Création du rôle client dans le realm: {}",
|
||||
clientId, realmName);
|
||||
|
||||
try {
|
||||
RoleDTO createdRole = roleService.createClientRole(roleDTO, clientId, realmName);
|
||||
return Response.status(Response.Status.CREATED).entity(createdRole).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Données invalides lors de la création du rôle client: {}", e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la création du rôle client", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/client/{clientId}/{roleName}")
|
||||
@Operation(summary = "Récupérer un rôle client par nom", description = "Récupère les détails d'un rôle client")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Rôle trouvé",
|
||||
content = @Content(schema = @Schema(implementation = RoleDTO.class))),
|
||||
@APIResponse(responseCode = "404", description = "Rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager", "role_viewer"})
|
||||
public Response getClientRole(
|
||||
@PathParam("clientId") @NotBlank String clientId,
|
||||
@PathParam("roleName") @NotBlank String roleName,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("GET /api/roles/client/{}/{} - realm: {}", clientId, roleName, realmName);
|
||||
|
||||
try {
|
||||
return roleService.getClientRoleByName(roleName, clientId, realmName)
|
||||
.map(role -> Response.ok(role).build())
|
||||
.orElse(Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Rôle client non trouvé"))
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération du rôle client {}/{}", clientId, roleName, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/client/{clientId}")
|
||||
@Operation(summary = "Lister tous les rôles d'un client", description = "Liste tous les rôles d'un client spécifique")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des rôles"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager", "role_viewer"})
|
||||
public Response getAllClientRoles(
|
||||
@PathParam("clientId") @NotBlank String clientId,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("GET /api/roles/client/{} - realm: {}", clientId, realmName);
|
||||
|
||||
try {
|
||||
List<RoleDTO> roles = roleService.getAllClientRoles(clientId, realmName);
|
||||
return Response.ok(roles).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des rôles du client {}", clientId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/client/{clientId}/{roleName}")
|
||||
@Operation(summary = "Supprimer un rôle client", description = "Supprime un rôle d'un client")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Rôle supprimé"),
|
||||
@APIResponse(responseCode = "404", description = "Rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin"})
|
||||
public Response deleteClientRole(
|
||||
@PathParam("clientId") @NotBlank String clientId,
|
||||
@PathParam("roleName") @NotBlank String roleName,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("DELETE /api/roles/client/{}/{} - realm: {}", clientId, roleName, realmName);
|
||||
|
||||
try {
|
||||
roleService.deleteClientRole(roleName, clientId, realmName);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la suppression du rôle client {}/{}", clientId, roleName, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Endpoints Attribution de rôles ====================
|
||||
|
||||
@POST
|
||||
@Path("/assign/realm/{userId}")
|
||||
@Operation(summary = "Attribuer des rôles realm à un utilisateur", description = "Assigne un ou plusieurs rôles realm à un utilisateur")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Rôles attribués"),
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur ou rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager"})
|
||||
public Response assignRealmRoles(
|
||||
@PathParam("userId") @NotBlank String userId,
|
||||
@QueryParam("realm") @NotBlank String realmName,
|
||||
@NotNull RoleAssignmentRequest request
|
||||
) {
|
||||
log.info("POST /api/roles/assign/realm/{} - Attribution de {} rôles", userId, request.roleNames.size());
|
||||
|
||||
try {
|
||||
roleService.assignRealmRolesToUser(userId, request.roleNames, realmName);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'attribution des rôles realm à l'utilisateur {}", userId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/revoke/realm/{userId}")
|
||||
@Operation(summary = "Révoquer des rôles realm d'un utilisateur", description = "Révoque un ou plusieurs rôles realm d'un utilisateur")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Rôles révoqués"),
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur ou rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager"})
|
||||
public Response revokeRealmRoles(
|
||||
@PathParam("userId") @NotBlank String userId,
|
||||
@QueryParam("realm") @NotBlank String realmName,
|
||||
@NotNull RoleAssignmentRequest request
|
||||
) {
|
||||
log.info("POST /api/roles/revoke/realm/{} - Révocation de {} rôles", userId, request.roleNames.size());
|
||||
|
||||
try {
|
||||
roleService.revokeRealmRolesFromUser(userId, request.roleNames, realmName);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la révocation des rôles realm de l'utilisateur {}", userId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/assign/client/{clientId}/{userId}")
|
||||
@Operation(summary = "Attribuer des rôles client à un utilisateur", description = "Assigne un ou plusieurs rôles client à un utilisateur")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Rôles attribués"),
|
||||
@APIResponse(responseCode = "404", description = "Utilisateur, client ou rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager"})
|
||||
public Response assignClientRoles(
|
||||
@PathParam("clientId") @NotBlank String clientId,
|
||||
@PathParam("userId") @NotBlank String userId,
|
||||
@QueryParam("realm") @NotBlank String realmName,
|
||||
@NotNull RoleAssignmentRequest request
|
||||
) {
|
||||
log.info("POST /api/roles/assign/client/{}/{} - Attribution de {} rôles client",
|
||||
clientId, userId, request.roleNames.size());
|
||||
|
||||
try {
|
||||
roleService.assignClientRolesToUser(userId, clientId, request.roleNames, realmName);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'attribution des rôles client à l'utilisateur {}", userId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/user/realm/{userId}")
|
||||
@Operation(summary = "Récupérer les rôles realm d'un utilisateur", description = "Liste tous les rôles realm d'un utilisateur")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des rôles"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager", "role_viewer"})
|
||||
public Response getUserRealmRoles(
|
||||
@PathParam("userId") @NotBlank String userId,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("GET /api/roles/user/realm/{} - realm: {}", userId, realmName);
|
||||
|
||||
try {
|
||||
List<RoleDTO> roles = roleService.getUserRealmRoles(userId, realmName);
|
||||
return Response.ok(roles).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des rôles realm de l'utilisateur {}", userId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/user/client/{clientId}/{userId}")
|
||||
@Operation(summary = "Récupérer les rôles client d'un utilisateur", description = "Liste tous les rôles client d'un utilisateur")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des rôles"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager", "role_viewer"})
|
||||
public Response getUserClientRoles(
|
||||
@PathParam("clientId") @NotBlank String clientId,
|
||||
@PathParam("userId") @NotBlank String userId,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("GET /api/roles/user/client/{}/{} - realm: {}", clientId, userId, realmName);
|
||||
|
||||
try {
|
||||
List<RoleDTO> roles = roleService.getUserClientRoles(userId, clientId, realmName);
|
||||
return Response.ok(roles).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des rôles client de l'utilisateur {}", userId, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Endpoints Rôles composites ====================
|
||||
|
||||
@POST
|
||||
@Path("/composite/{roleName}/add")
|
||||
@Operation(summary = "Ajouter des rôles composites", description = "Ajoute des rôles composites à un rôle")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "204", description = "Composites ajoutés"),
|
||||
@APIResponse(responseCode = "404", description = "Rôle non trouvé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager"})
|
||||
public Response addComposites(
|
||||
@PathParam("roleName") @NotBlank String roleName,
|
||||
@QueryParam("realm") @NotBlank String realmName,
|
||||
@NotNull RoleAssignmentRequest request
|
||||
) {
|
||||
log.info("POST /api/roles/composite/{}/add - Ajout de {} composites", roleName, request.roleNames.size());
|
||||
|
||||
try {
|
||||
roleService.addCompositesToRealmRole(roleName, request.roleNames, realmName);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de l'ajout des composites au rôle {}", roleName, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/composite/{roleName}")
|
||||
@Operation(summary = "Récupérer les rôles composites", description = "Liste tous les rôles composites d'un rôle")
|
||||
@APIResponses({
|
||||
@APIResponse(responseCode = "200", description = "Liste des composites"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
})
|
||||
@RolesAllowed({"admin", "role_manager", "role_viewer"})
|
||||
public Response getComposites(
|
||||
@PathParam("roleName") @NotBlank String roleName,
|
||||
@QueryParam("realm") @NotBlank String realmName
|
||||
) {
|
||||
log.info("GET /api/roles/composite/{} - realm: {}", roleName, realmName);
|
||||
|
||||
try {
|
||||
List<RoleDTO> composites = roleService.getCompositeRoles(roleName, realmName);
|
||||
return Response.ok(composites).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération des composites du rôle {}", roleName, e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DTOs internes ====================
|
||||
|
||||
@Schema(description = "Requête d'attribution/révocation de rôles")
|
||||
public static class RoleAssignmentRequest {
|
||||
@Schema(description = "Liste des noms de rôles", required = true)
|
||||
public List<String> roleNames;
|
||||
}
|
||||
|
||||
@Schema(description = "Réponse d'erreur")
|
||||
public static class ErrorResponse {
|
||||
@Schema(description = "Message d'erreur")
|
||||
public String message;
|
||||
|
||||
public ErrorResponse(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
package dev.lions.user.manager.service.impl;
|
||||
|
||||
import dev.lions.user.manager.dto.audit.AuditLogDTO;
|
||||
import dev.lions.user.manager.enums.audit.TypeActionAudit;
|
||||
import dev.lions.user.manager.service.AuditService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implémentation du service d'audit
|
||||
*
|
||||
* NOTES:
|
||||
* - Cette implémentation utilise un stockage en mémoire pour le développement
|
||||
* - En production, il faudrait utiliser une base de données (PostgreSQL avec Panache)
|
||||
* - Les logs sont également écrits via SLF4J pour être capturés par les systèmes de logging centralisés
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class AuditServiceImpl implements AuditService {
|
||||
|
||||
// Stockage en mémoire (à remplacer par une DB en production)
|
||||
private final Map<String, AuditLogDTO> auditLogs = new ConcurrentHashMap<>();
|
||||
|
||||
@ConfigProperty(name = "lions.audit.enabled", defaultValue = "true")
|
||||
boolean auditEnabled;
|
||||
|
||||
@ConfigProperty(name = "lions.audit.log-to-database", defaultValue = "false")
|
||||
boolean logToDatabase;
|
||||
|
||||
@Override
|
||||
public AuditLogDTO logAction(@Valid @NotNull AuditLogDTO auditLog) {
|
||||
if (!auditEnabled) {
|
||||
log.debug("Audit désactivé, log ignoré");
|
||||
return auditLog;
|
||||
}
|
||||
|
||||
// Générer un ID si nécessaire
|
||||
if (auditLog.getId() == null) {
|
||||
auditLog.setId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
// Ajouter le timestamp si nécessaire
|
||||
if (auditLog.getDateAction() == null) {
|
||||
auditLog.setDateAction(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// Log structuré pour les systèmes de logging (Graylog, Elasticsearch, etc.)
|
||||
log.info("AUDIT | Type: {} | Acteur: {} | Ressource: {} | Succès: {} | IP: {} | Détails: {}",
|
||||
auditLog.getTypeAction(),
|
||||
auditLog.getActeurUsername(),
|
||||
auditLog.getRessourceType() + ":" + auditLog.getRessourceId(),
|
||||
auditLog.isSucces(),
|
||||
auditLog.getAdresseIp(),
|
||||
auditLog.getDetails());
|
||||
|
||||
// Stocker en mémoire
|
||||
auditLogs.put(auditLog.getId(), auditLog);
|
||||
|
||||
// TODO: Si logToDatabase = true, persister dans PostgreSQL via Panache
|
||||
// Exemple:
|
||||
// if (logToDatabase) {
|
||||
// AuditLogEntity entity = AuditLogMapper.toEntity(auditLog);
|
||||
// entity.persist();
|
||||
// }
|
||||
|
||||
return auditLog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSuccess(@NotBlank String acteurUsername, @NotNull TypeActionAudit typeAction,
|
||||
@NotBlank String ressourceType, @NotBlank String ressourceId,
|
||||
String adresseIp, String details) {
|
||||
AuditLogDTO auditLog = AuditLogDTO.builder()
|
||||
.acteurUsername(acteurUsername)
|
||||
.typeAction(typeAction)
|
||||
.ressourceType(ressourceType)
|
||||
.ressourceId(ressourceId)
|
||||
.succes(true)
|
||||
.adresseIp(adresseIp)
|
||||
.details(details)
|
||||
.dateAction(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
logAction(auditLog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logFailure(@NotBlank String acteurUsername, @NotNull TypeActionAudit typeAction,
|
||||
@NotBlank String ressourceType, @NotBlank String ressourceId,
|
||||
String adresseIp, @NotBlank String messageErreur) {
|
||||
AuditLogDTO auditLog = AuditLogDTO.builder()
|
||||
.acteurUsername(acteurUsername)
|
||||
.typeAction(typeAction)
|
||||
.ressourceType(ressourceType)
|
||||
.ressourceId(ressourceId)
|
||||
.succes(false)
|
||||
.adresseIp(adresseIp)
|
||||
.messageErreur(messageErreur)
|
||||
.dateAction(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
logAction(auditLog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditLogDTO> searchLogs(@NotBlank String acteurUsername, LocalDateTime dateDebut,
|
||||
LocalDateTime dateFin, TypeActionAudit typeAction,
|
||||
String ressourceType, Boolean succes,
|
||||
int page, int pageSize) {
|
||||
log.debug("Recherche de logs d'audit: acteur={}, dateDebut={}, dateFin={}, typeAction={}, succes={}",
|
||||
acteurUsername, dateDebut, dateFin, typeAction, succes);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> {
|
||||
// Filtre par acteur (si spécifié et non "*")
|
||||
if (acteurUsername != null && !"*".equals(acteurUsername) &&
|
||||
!acteurUsername.equals(log.getActeurUsername())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtre par date début
|
||||
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtre par date fin
|
||||
if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtre par type d'action
|
||||
if (typeAction != null && !typeAction.equals(log.getTypeAction())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtre par type de ressource
|
||||
if (ressourceType != null && !ressourceType.equals(log.getRessourceType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtre par succès/échec
|
||||
if (succes != null && succes != log.isSucces()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.sorted((a, b) -> b.getDateAction().compareTo(a.getDateAction())) // Tri décroissant par date
|
||||
.skip((long) page * pageSize)
|
||||
.limit(pageSize)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditLogDTO> getLogsByActeur(@NotBlank String acteurUsername, int limit) {
|
||||
log.debug("Récupération des {} derniers logs de l'acteur: {}", limit, acteurUsername);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> acteurUsername.equals(log.getActeurUsername()))
|
||||
.sorted((a, b) -> b.getDateAction().compareTo(a.getDateAction()))
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditLogDTO> getLogsByRessource(@NotBlank String ressourceType,
|
||||
@NotBlank String ressourceId, int limit) {
|
||||
log.debug("Récupération des {} derniers logs de la ressource: {}:{}",
|
||||
limit, ressourceType, ressourceId);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> ressourceType.equals(log.getRessourceType()) &&
|
||||
ressourceId.equals(log.getRessourceId()))
|
||||
.sorted((a, b) -> b.getDateAction().compareTo(a.getDateAction()))
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditLogDTO> getLogsByAction(@NotNull TypeActionAudit typeAction,
|
||||
LocalDateTime dateDebut, LocalDateTime dateFin,
|
||||
int limit) {
|
||||
log.debug("Récupération des {} logs de type: {} entre {} et {}",
|
||||
limit, typeAction, dateDebut, dateFin);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> {
|
||||
if (!typeAction.equals(log.getTypeAction())) {
|
||||
return false;
|
||||
}
|
||||
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sorted((a, b) -> b.getDateAction().compareTo(a.getDateAction()))
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TypeActionAudit, Long> getActionStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.debug("Calcul des statistiques d'actions entre {} et {}", dateDebut, dateFin);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> {
|
||||
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.collect(Collectors.groupingBy(
|
||||
AuditLogDTO::getTypeAction,
|
||||
Collectors.counting()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Long> getUserActivityStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.debug("Calcul des statistiques d'activité utilisateurs entre {} et {}", dateDebut, dateFin);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> {
|
||||
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.collect(Collectors.groupingBy(
|
||||
AuditLogDTO::getActeurUsername,
|
||||
Collectors.counting()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFailureCount(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.debug("Comptage des échecs entre {} et {}", dateDebut, dateFin);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> {
|
||||
if (log.isSucces()) {
|
||||
return false; // On ne compte que les échecs
|
||||
}
|
||||
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSuccessCount(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.debug("Comptage des succès entre {} et {}", dateDebut, dateFin);
|
||||
|
||||
return auditLogs.values().stream()
|
||||
.filter(log -> {
|
||||
if (!log.isSucces()) {
|
||||
return false; // On ne compte que les succès
|
||||
}
|
||||
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> exportLogsToCSV(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
log.info("Export CSV des logs d'audit entre {} et {}", dateDebut, dateFin);
|
||||
|
||||
List<String> csvLines = new ArrayList<>();
|
||||
|
||||
// En-tête CSV
|
||||
csvLines.add("ID,Date Action,Acteur,Type Action,Ressource Type,Ressource ID,Succès,Adresse IP,Détails,Message Erreur");
|
||||
|
||||
// Données
|
||||
auditLogs.values().stream()
|
||||
.filter(log -> {
|
||||
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
|
||||
return false;
|
||||
}
|
||||
if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sorted((a, b) -> a.getDateAction().compareTo(b.getDateAction()))
|
||||
.forEach(log -> {
|
||||
String csvLine = String.format("%s,%s,%s,%s,%s,%s,%s,%s,\"%s\",\"%s\"",
|
||||
log.getId(),
|
||||
log.getDateAction(),
|
||||
log.getActeurUsername(),
|
||||
log.getTypeAction(),
|
||||
log.getRessourceType(),
|
||||
log.getRessourceId(),
|
||||
log.isSucces(),
|
||||
log.getAdresseIp() != null ? log.getAdresseIp() : "",
|
||||
log.getDetails() != null ? log.getDetails().replace("\"", "\"\"") : "",
|
||||
log.getMessageErreur() != null ? log.getMessageErreur().replace("\"", "\"\"") : ""
|
||||
);
|
||||
csvLines.add(csvLine);
|
||||
});
|
||||
|
||||
log.info("Export CSV terminé: {} lignes", csvLines.size() - 1);
|
||||
return csvLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeOldLogs(int joursDAnc ienneté) {
|
||||
log.info("Purge des logs d'audit de plus de {} jours", joursDAncienneté);
|
||||
|
||||
LocalDateTime dateLimit = LocalDateTime.now().minusDays(joursDAncienneté);
|
||||
|
||||
long beforeCount = auditLogs.size();
|
||||
auditLogs.entrySet().removeIf(entry ->
|
||||
entry.getValue().getDateAction().isBefore(dateLimit)
|
||||
);
|
||||
long afterCount = auditLogs.size();
|
||||
|
||||
log.info("Purge terminée: {} logs supprimés", beforeCount - afterCount);
|
||||
|
||||
// TODO: Si base de données utilisée, exécuter:
|
||||
// DELETE FROM audit_log WHERE date_action < :dateLimit
|
||||
}
|
||||
|
||||
// ==================== Méthodes utilitaires ====================
|
||||
|
||||
/**
|
||||
* Retourne le nombre total de logs en mémoire
|
||||
*/
|
||||
public long getTotalCount() {
|
||||
return auditLogs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide tous les logs (ATTENTION: à utiliser uniquement en développement)
|
||||
*/
|
||||
public void clearAll() {
|
||||
log.warn("ATTENTION: Suppression de tous les logs d'audit en mémoire");
|
||||
auditLogs.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
package dev.lions.user.manager.service.impl;
|
||||
|
||||
import dev.lions.user.manager.client.KeycloakAdminClient;
|
||||
import dev.lions.user.manager.dto.role.RoleAssignmentDTO;
|
||||
import dev.lions.user.manager.dto.role.RoleDTO;
|
||||
import dev.lions.user.manager.enums.role.TypeRole;
|
||||
import dev.lions.user.manager.mapper.RoleMapper;
|
||||
import dev.lions.user.manager.service.RoleService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.keycloak.admin.client.resource.*;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implémentation du service de gestion des rôles Keycloak
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Slf4j
|
||||
public class RoleServiceImpl implements RoleService {
|
||||
|
||||
@Inject
|
||||
KeycloakAdminClient keycloakAdminClient;
|
||||
|
||||
// ==================== CRUD Realm Roles ====================
|
||||
|
||||
@Override
|
||||
public RoleDTO createRealmRole(@Valid @NotNull RoleDTO roleDTO, @NotBlank String realmName) {
|
||||
log.info("Création du rôle realm: {} dans le realm: {}", roleDTO.getNom(), realmName);
|
||||
|
||||
RolesResource rolesResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles();
|
||||
|
||||
// Vérifier si le rôle existe déjà
|
||||
try {
|
||||
rolesResource.get(roleDTO.getNom()).toRepresentation();
|
||||
throw new IllegalArgumentException("Le rôle " + roleDTO.getNom() + " existe déjà");
|
||||
} catch (NotFoundException e) {
|
||||
// OK, le rôle n'existe pas
|
||||
}
|
||||
|
||||
RoleRepresentation roleRep = RoleMapper.toRepresentation(roleDTO);
|
||||
rolesResource.create(roleRep);
|
||||
|
||||
// Récupérer le rôle créé avec son ID
|
||||
RoleRepresentation createdRole = rolesResource.get(roleDTO.getNom()).toRepresentation();
|
||||
return RoleMapper.toDTO(createdRole, realmName, TypeRole.REALM_ROLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RoleDTO> getRealmRoleById(@NotBlank String roleId, @NotBlank String realmName) {
|
||||
log.debug("Récupération du rôle realm par ID: {} dans le realm: {}", roleId, realmName);
|
||||
|
||||
try {
|
||||
RolesResource rolesResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles();
|
||||
|
||||
// Keycloak ne permet pas de récupérer un rôle par ID directement, on doit lister tous les rôles
|
||||
List<RoleRepresentation> roles = rolesResource.list();
|
||||
return roles.stream()
|
||||
.filter(r -> r.getId().equals(roleId))
|
||||
.findFirst()
|
||||
.map(r -> RoleMapper.toDTO(r, realmName, TypeRole.REALM_ROLE));
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération du rôle realm {}", roleId, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RoleDTO> getRealmRoleByName(@NotBlank String roleName, @NotBlank String realmName) {
|
||||
log.debug("Récupération du rôle realm par nom: {} dans le realm: {}", roleName, realmName);
|
||||
|
||||
try {
|
||||
RoleRepresentation roleRep = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles()
|
||||
.get(roleName)
|
||||
.toRepresentation();
|
||||
|
||||
return Optional.of(RoleMapper.toDTO(roleRep, realmName, TypeRole.REALM_ROLE));
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle realm {} non trouvé dans le realm {}", roleName, realmName);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleDTO updateRealmRole(@NotBlank String roleName, @Valid @NotNull RoleDTO roleDTO,
|
||||
@NotBlank String realmName) {
|
||||
log.info("Mise à jour du rôle realm: {} dans le realm: {}", roleName, realmName);
|
||||
|
||||
RoleResource roleResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles()
|
||||
.get(roleName);
|
||||
|
||||
RoleRepresentation roleRep = roleResource.toRepresentation();
|
||||
|
||||
// Mettre à jour uniquement les champs modifiables
|
||||
if (roleDTO.getDescription() != null) {
|
||||
roleRep.setDescription(roleDTO.getDescription());
|
||||
}
|
||||
|
||||
roleResource.update(roleRep);
|
||||
|
||||
// Retourner le rôle mis à jour
|
||||
return RoleMapper.toDTO(roleResource.toRepresentation(), realmName, TypeRole.REALM_ROLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRealmRole(@NotBlank String roleName, @NotBlank String realmName) {
|
||||
log.info("Suppression du rôle realm: {} dans le realm: {}", roleName, realmName);
|
||||
|
||||
keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles()
|
||||
.deleteRole(roleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDTO> getAllRealmRoles(@NotBlank String realmName) {
|
||||
log.debug("Récupération de tous les rôles realm du realm: {}", realmName);
|
||||
|
||||
List<RoleRepresentation> roleReps = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles()
|
||||
.list();
|
||||
|
||||
return RoleMapper.toDTOList(roleReps, realmName, TypeRole.REALM_ROLE);
|
||||
}
|
||||
|
||||
// ==================== CRUD Client Roles ====================
|
||||
|
||||
@Override
|
||||
public RoleDTO createClientRole(@Valid @NotNull RoleDTO roleDTO, @NotBlank String clientId,
|
||||
@NotBlank String realmName) {
|
||||
log.info("Création du rôle client: {} pour le client: {} dans le realm: {}",
|
||||
roleDTO.getNom(), clientId, realmName);
|
||||
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
// Trouver le client par clientId
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
throw new IllegalArgumentException("Client " + clientId + " non trouvé");
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
RolesResource rolesResource = clientsResource.get(internalClientId).roles();
|
||||
|
||||
// Vérifier si le rôle existe déjà
|
||||
try {
|
||||
rolesResource.get(roleDTO.getNom()).toRepresentation();
|
||||
throw new IllegalArgumentException("Le rôle " + roleDTO.getNom() + " existe déjà pour ce client");
|
||||
} catch (NotFoundException e) {
|
||||
// OK, le rôle n'existe pas
|
||||
}
|
||||
|
||||
RoleRepresentation roleRep = RoleMapper.toRepresentation(roleDTO);
|
||||
rolesResource.create(roleRep);
|
||||
|
||||
// Récupérer le rôle créé
|
||||
RoleRepresentation createdRole = rolesResource.get(roleDTO.getNom()).toRepresentation();
|
||||
RoleDTO result = RoleMapper.toDTO(createdRole, realmName, TypeRole.CLIENT_ROLE);
|
||||
result.setClientId(clientId);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RoleDTO> getClientRoleByName(@NotBlank String roleName, @NotBlank String clientId,
|
||||
@NotBlank String realmName) {
|
||||
log.debug("Récupération du rôle client: {} pour le client: {} dans le realm: {}",
|
||||
roleName, clientId, realmName);
|
||||
|
||||
try {
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
RoleRepresentation roleRep = clientsResource.get(internalClientId)
|
||||
.roles()
|
||||
.get(roleName)
|
||||
.toRepresentation();
|
||||
|
||||
RoleDTO roleDTO = RoleMapper.toDTO(roleRep, realmName, TypeRole.CLIENT_ROLE);
|
||||
roleDTO.setClientId(clientId);
|
||||
|
||||
return Optional.of(roleDTO);
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle client {} non trouvé pour le client {} dans le realm {}",
|
||||
roleName, clientId, realmName);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteClientRole(@NotBlank String roleName, @NotBlank String clientId,
|
||||
@NotBlank String realmName) {
|
||||
log.info("Suppression du rôle client: {} pour le client: {} dans le realm: {}",
|
||||
roleName, clientId, realmName);
|
||||
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
throw new IllegalArgumentException("Client " + clientId + " non trouvé");
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
clientsResource.get(internalClientId).roles().deleteRole(roleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDTO> getAllClientRoles(@NotBlank String clientId, @NotBlank String realmName) {
|
||||
log.debug("Récupération de tous les rôles du client: {} dans le realm: {}", clientId, realmName);
|
||||
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
List<RoleRepresentation> roleReps = clientsResource.get(internalClientId)
|
||||
.roles()
|
||||
.list();
|
||||
|
||||
List<RoleDTO> roles = RoleMapper.toDTOList(roleReps, realmName, TypeRole.CLIENT_ROLE);
|
||||
roles.forEach(role -> role.setClientId(clientId));
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
// ==================== Attribution de rôles ====================
|
||||
|
||||
@Override
|
||||
public void assignRealmRolesToUser(@NotBlank String userId, @NotNull List<String> roleNames,
|
||||
@NotBlank String realmName) {
|
||||
log.info("Attribution de {} rôles realm à l'utilisateur {} dans le realm {}",
|
||||
roleNames.size(), userId, realmName);
|
||||
|
||||
UserResource userResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId);
|
||||
|
||||
RolesResource rolesResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles();
|
||||
|
||||
List<RoleRepresentation> rolesToAssign = roleNames.stream()
|
||||
.map(roleName -> {
|
||||
try {
|
||||
return rolesResource.get(roleName).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle {} non trouvé, ignoré", roleName);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(role -> role != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!rolesToAssign.isEmpty()) {
|
||||
userResource.roles().realmLevel().add(rolesToAssign);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeRealmRolesFromUser(@NotBlank String userId, @NotNull List<String> roleNames,
|
||||
@NotBlank String realmName) {
|
||||
log.info("Révocation de {} rôles realm pour l'utilisateur {} dans le realm {}",
|
||||
roleNames.size(), userId, realmName);
|
||||
|
||||
UserResource userResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId);
|
||||
|
||||
RolesResource rolesResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles();
|
||||
|
||||
List<RoleRepresentation> rolesToRevoke = roleNames.stream()
|
||||
.map(roleName -> {
|
||||
try {
|
||||
return rolesResource.get(roleName).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle {} non trouvé, ignoré", roleName);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(role -> role != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!rolesToRevoke.isEmpty()) {
|
||||
userResource.roles().realmLevel().remove(rolesToRevoke);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignClientRolesToUser(@NotBlank String userId, @NotBlank String clientId,
|
||||
@NotNull List<String> roleNames, @NotBlank String realmName) {
|
||||
log.info("Attribution de {} rôles du client {} à l'utilisateur {} dans le realm {}",
|
||||
roleNames.size(), clientId, userId, realmName);
|
||||
|
||||
UserResource userResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId);
|
||||
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
throw new IllegalArgumentException("Client " + clientId + " non trouvé");
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
RolesResource rolesResource = clientsResource.get(internalClientId).roles();
|
||||
|
||||
List<RoleRepresentation> rolesToAssign = roleNames.stream()
|
||||
.map(roleName -> {
|
||||
try {
|
||||
return rolesResource.get(roleName).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle client {} non trouvé, ignoré", roleName);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(role -> role != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!rolesToAssign.isEmpty()) {
|
||||
userResource.roles().clientLevel(internalClientId).add(rolesToAssign);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeClientRolesFromUser(@NotBlank String userId, @NotBlank String clientId,
|
||||
@NotNull List<String> roleNames, @NotBlank String realmName) {
|
||||
log.info("Révocation de {} rôles du client {} pour l'utilisateur {} dans le realm {}",
|
||||
roleNames.size(), clientId, userId, realmName);
|
||||
|
||||
UserResource userResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId);
|
||||
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
throw new IllegalArgumentException("Client " + clientId + " non trouvé");
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
RolesResource rolesResource = clientsResource.get(internalClientId).roles();
|
||||
|
||||
List<RoleRepresentation> rolesToRevoke = roleNames.stream()
|
||||
.map(roleName -> {
|
||||
try {
|
||||
return rolesResource.get(roleName).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle client {} non trouvé, ignoré", roleName);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(role -> role != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!rolesToRevoke.isEmpty()) {
|
||||
userResource.roles().clientLevel(internalClientId).remove(rolesToRevoke);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDTO> getUserRealmRoles(@NotBlank String userId, @NotBlank String realmName) {
|
||||
log.debug("Récupération des rôles realm de l'utilisateur {} dans le realm {}", userId, realmName);
|
||||
|
||||
List<RoleRepresentation> roleReps = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId)
|
||||
.roles()
|
||||
.realmLevel()
|
||||
.listAll();
|
||||
|
||||
return RoleMapper.toDTOList(roleReps, realmName, TypeRole.REALM_ROLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDTO> getUserClientRoles(@NotBlank String userId, @NotBlank String clientId,
|
||||
@NotBlank String realmName) {
|
||||
log.debug("Récupération des rôles du client {} pour l'utilisateur {} dans le realm {}",
|
||||
clientId, userId, realmName);
|
||||
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
List<RoleRepresentation> roleReps = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId)
|
||||
.roles()
|
||||
.clientLevel(internalClientId)
|
||||
.listAll();
|
||||
|
||||
List<RoleDTO> roles = RoleMapper.toDTOList(roleReps, realmName, TypeRole.CLIENT_ROLE);
|
||||
roles.forEach(role -> role.setClientId(clientId));
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
// ==================== Rôles composites ====================
|
||||
|
||||
@Override
|
||||
public void addCompositesToRealmRole(@NotBlank String roleName, @NotNull List<String> compositeRoleNames,
|
||||
@NotBlank String realmName) {
|
||||
log.info("Ajout de {} rôles composites au rôle realm {} dans le realm {}",
|
||||
compositeRoleNames.size(), roleName, realmName);
|
||||
|
||||
RoleResource roleResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles()
|
||||
.get(roleName);
|
||||
|
||||
RolesResource rolesResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles();
|
||||
|
||||
List<RoleRepresentation> compositesToAdd = compositeRoleNames.stream()
|
||||
.map(compositeName -> {
|
||||
try {
|
||||
return rolesResource.get(compositeName).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle composite {} non trouvé, ignoré", compositeName);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(role -> role != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!compositesToAdd.isEmpty()) {
|
||||
roleResource.addComposites(compositesToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompositesFromRealmRole(@NotBlank String roleName, @NotNull List<String> compositeRoleNames,
|
||||
@NotBlank String realmName) {
|
||||
log.info("Suppression de {} rôles composites du rôle realm {} dans le realm {}",
|
||||
compositeRoleNames.size(), roleName, realmName);
|
||||
|
||||
RoleResource roleResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles()
|
||||
.get(roleName);
|
||||
|
||||
RolesResource rolesResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles();
|
||||
|
||||
List<RoleRepresentation> compositesToRemove = compositeRoleNames.stream()
|
||||
.map(compositeName -> {
|
||||
try {
|
||||
return rolesResource.get(compositeName).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("Rôle composite {} non trouvé, ignoré", compositeName);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(role -> role != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!compositesToRemove.isEmpty()) {
|
||||
roleResource.deleteComposites(compositesToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDTO> getCompositeRoles(@NotBlank String roleName, @NotBlank String realmName) {
|
||||
log.debug("Récupération des rôles composites du rôle {} dans le realm {}", roleName, realmName);
|
||||
|
||||
List<RoleRepresentation> composites = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.roles()
|
||||
.get(roleName)
|
||||
.getRoleComposites();
|
||||
|
||||
return RoleMapper.toDTOList(composites, realmName, TypeRole.COMPOSITE_ROLE);
|
||||
}
|
||||
|
||||
// ==================== Vérification de permissions ====================
|
||||
|
||||
@Override
|
||||
public boolean userHasRealmRole(@NotBlank String userId, @NotBlank String roleName,
|
||||
@NotBlank String realmName) {
|
||||
log.debug("Vérification si l'utilisateur {} a le rôle realm {} dans le realm {}",
|
||||
userId, roleName, realmName);
|
||||
|
||||
List<RoleRepresentation> userRoles = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId)
|
||||
.roles()
|
||||
.realmLevel()
|
||||
.listEffective(); // Incluant les rôles hérités via composites
|
||||
|
||||
return userRoles.stream()
|
||||
.anyMatch(role -> role.getName().equals(roleName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userHasClientRole(@NotBlank String userId, @NotBlank String clientId,
|
||||
@NotBlank String roleName, @NotBlank String realmName) {
|
||||
log.debug("Vérification si l'utilisateur {} a le rôle client {} du client {} dans le realm {}",
|
||||
userId, roleName, clientId, realmName);
|
||||
|
||||
ClientsResource clientsResource = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.clients();
|
||||
|
||||
List<org.keycloak.representations.idm.ClientRepresentation> clients =
|
||||
clientsResource.findByClientId(clientId);
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String internalClientId = clients.get(0).getId();
|
||||
List<RoleRepresentation> userClientRoles = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId)
|
||||
.roles()
|
||||
.clientLevel(internalClientId)
|
||||
.listEffective();
|
||||
|
||||
return userClientRoles.stream()
|
||||
.anyMatch(role -> role.getName().equals(roleName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDTO> getUserEffectiveRealmRoles(@NotBlank String userId, @NotBlank String realmName) {
|
||||
log.debug("Récupération des rôles realm effectifs de l'utilisateur {} dans le realm {}",
|
||||
userId, realmName);
|
||||
|
||||
List<RoleRepresentation> effectiveRoles = keycloakAdminClient.getInstance()
|
||||
.realm(realmName)
|
||||
.users()
|
||||
.get(userId)
|
||||
.roles()
|
||||
.realmLevel()
|
||||
.listEffective();
|
||||
|
||||
return RoleMapper.toDTOList(effectiveRoles, realmName, TypeRole.REALM_ROLE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user