Architecture modifiée pour Frontend-Centric Authentication: 1. **Suppression des dépendances OIDC** - quarkus-oidc → quarkus-smallrye-jwt - quarkus-keycloak-authorization → quarkus-smallrye-jwt-build - Le backend ne gère plus l'authentification OAuth 2. **Configuration JWT simple** - Validation des tokens JWT envoyés par le frontend - mp.jwt.verify.publickey.location (JWKS de Keycloak) - mp.jwt.verify.issuer (Keycloak realm) - Authentification via Authorization: Bearer header 3. **Suppression configurations OIDC** - application.properties: Suppression %dev.quarkus.oidc.* - application.properties: Suppression %prod.quarkus.oidc.* - application-prod.properties: Remplacement par mp.jwt.* - Logging: io.quarkus.oidc → io.quarkus.smallrye.jwt 4. **Sécurité simplifiée** - quarkus.security.auth.proactive=false - @Authenticated sur les endpoints - CORS configuré pour le frontend - Endpoints publics: /q/*, /openapi, /swagger-ui/* Flux d'authentification: 1️⃣ Frontend → Keycloak (OAuth login) 2️⃣ Frontend ← Keycloak (access_token) 3️⃣ Frontend → Backend (Authorization: Bearer token) 4️⃣ Backend valide le token JWT (signature + issuer) 5️⃣ Backend → Frontend (données API) Avantages: ✅ Pas de secret backend à gérer ✅ Pas de client btpxpress-backend dans Keycloak ✅ Séparation claire frontend/backend ✅ Backend devient une API REST stateless ✅ Tokens gérés par le frontend (localStorage/sessionStorage) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
355 lines
14 KiB
Java
355 lines
14 KiB
Java
package dev.lions.btpxpress.adapter.http;
|
|
|
|
import dev.lions.btpxpress.application.service.PermissionService;
|
|
import dev.lions.btpxpress.domain.core.entity.Permission;
|
|
import dev.lions.btpxpress.domain.core.entity.Permission.PermissionCategory;
|
|
import dev.lions.btpxpress.domain.core.entity.UserRole;
|
|
import jakarta.inject.Inject;
|
|
import jakarta.ws.rs.*;
|
|
import jakarta.ws.rs.core.MediaType;
|
|
import jakarta.ws.rs.core.Response;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
/**
|
|
* API REST pour la gestion des permissions EXPOSITION: Consultation des droits d'accès et
|
|
* permissions par rôle
|
|
*/
|
|
@Path("/api/permissions")
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
@Consumes(MediaType.APPLICATION_JSON)
|
|
public class PermissionResource {
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(PermissionResource.class);
|
|
|
|
@Inject PermissionService permissionService;
|
|
|
|
// === ENDPOINTS DE CONSULTATION ===
|
|
|
|
/** Récupère toutes les permissions disponibles */
|
|
@GET
|
|
@Path("/all")
|
|
public Response getAllPermissions() {
|
|
try {
|
|
logger.debug("GET /api/permissions/all");
|
|
|
|
List<Object> permissions =
|
|
Arrays.stream(Permission.values())
|
|
.map(
|
|
p ->
|
|
Map.of(
|
|
"code", p.getCode(),
|
|
"description", p.getDescription(),
|
|
"category", p.getCategory().name(),
|
|
"categoryDisplay", p.getCategory().getDisplayName()))
|
|
.collect(Collectors.toList());
|
|
|
|
return Response.ok(permissions).build();
|
|
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la récupération des permissions", e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
/** Récupère les permissions par catégorie */
|
|
@GET
|
|
@Path("/categories")
|
|
public Response getPermissionsByCategory() {
|
|
try {
|
|
logger.debug("GET /api/permissions/categories");
|
|
|
|
Map<String, Object> result =
|
|
Arrays.stream(PermissionCategory.values())
|
|
.collect(
|
|
Collectors.toMap(
|
|
category -> category.name(),
|
|
category ->
|
|
Map.of(
|
|
"displayName", category.getDisplayName(),
|
|
"permissions",
|
|
Permission.getByCategory(category).stream()
|
|
.map(
|
|
p ->
|
|
Map.of(
|
|
"code", p.getCode(),
|
|
"description", p.getDescription()))
|
|
.collect(Collectors.toList()))));
|
|
|
|
return Response.ok(result).build();
|
|
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la récupération des permissions par catégorie", e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
/** Récupère les permissions d'un rôle spécifique */
|
|
@GET
|
|
@Path("/role/{role}")
|
|
public Response getPermissionsByRole(@PathParam("role") String roleStr) {
|
|
try {
|
|
logger.debug("GET /api/permissions/role/{}", roleStr);
|
|
|
|
UserRole role = UserRole.valueOf(roleStr.toUpperCase());
|
|
Map<String, Object> summary = permissionService.getPermissionSummary(role);
|
|
|
|
return Response.ok(summary).build();
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
return Response.status(Response.Status.BAD_REQUEST)
|
|
.entity("Rôle invalide: " + roleStr)
|
|
.build();
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la récupération des permissions pour le rôle: " + roleStr, e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
/** Vérifie si un rôle a une permission spécifique */
|
|
@GET
|
|
@Path("/check/{role}/{permission}")
|
|
public Response checkPermission(
|
|
@PathParam("role") String roleStr, @PathParam("permission") String permissionCode) {
|
|
try {
|
|
logger.debug("GET /api/permissions/check/{}/{}", roleStr, permissionCode);
|
|
|
|
UserRole role = UserRole.valueOf(roleStr.toUpperCase());
|
|
boolean hasPermission = permissionService.hasPermission(role, permissionCode);
|
|
|
|
return Response.ok(
|
|
Map.of(
|
|
"role", role.getDisplayName(),
|
|
"permission", permissionCode,
|
|
"hasPermission", hasPermission))
|
|
.build();
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
return Response.status(Response.Status.BAD_REQUEST)
|
|
.entity("Rôle invalide: " + roleStr)
|
|
.build();
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la vérification de permission", e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la vérification de permission: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
/** Récupère tous les rôles avec leurs permissions */
|
|
@GET
|
|
@Path("/roles")
|
|
public Response getAllRolesWithPermissions() {
|
|
try {
|
|
logger.debug("GET /api/permissions/roles");
|
|
|
|
Map<String, Object> result =
|
|
Arrays.stream(UserRole.values())
|
|
.collect(
|
|
Collectors.toMap(
|
|
role -> role.name(),
|
|
role ->
|
|
Map.of(
|
|
"displayName", role.getDisplayName(),
|
|
"description", role.getDescription(),
|
|
"hierarchyLevel", role.getHierarchyLevel(),
|
|
"isManagementRole", role.isManagementRole(),
|
|
"isFieldRole", role.isFieldRole(),
|
|
"isAdministrativeRole", role.isAdministrativeRole(),
|
|
"permissions",
|
|
permissionService.getPermissions(role).stream()
|
|
.map(Permission::getCode)
|
|
.collect(Collectors.toList()),
|
|
"permissionCount", permissionService.getPermissions(role).size())));
|
|
|
|
return Response.ok(result).build();
|
|
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la récupération des rôles et permissions", e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la récupération des rôles: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
/** Compare les permissions entre deux rôles */
|
|
@GET
|
|
@Path("/compare/{role1}/{role2}")
|
|
public Response compareRoles(
|
|
@PathParam("role1") String role1Str, @PathParam("role2") String role2Str) {
|
|
try {
|
|
logger.debug("GET /api/permissions/compare/{}/{}", role1Str, role2Str);
|
|
|
|
UserRole role1 = UserRole.valueOf(role1Str.toUpperCase());
|
|
UserRole role2 = UserRole.valueOf(role2Str.toUpperCase());
|
|
|
|
Set<Permission> permissions1 = permissionService.getPermissions(role1);
|
|
Set<Permission> permissions2 = permissionService.getPermissions(role2);
|
|
Set<Permission> missing1to2 = permissionService.getMissingPermissions(role1, role2);
|
|
Set<Permission> missing2to1 = permissionService.getMissingPermissions(role2, role1);
|
|
|
|
Set<Permission> common =
|
|
permissions1.stream().filter(permissions2::contains).collect(Collectors.toSet());
|
|
|
|
return Response.ok(
|
|
Map.of(
|
|
"role1",
|
|
Map.of(
|
|
"name", role1.getDisplayName(),
|
|
"permissionCount", permissions1.size(),
|
|
"permissions",
|
|
permissions1.stream()
|
|
.map(Permission::getCode)
|
|
.collect(Collectors.toList())),
|
|
"role2",
|
|
Map.of(
|
|
"name", role2.getDisplayName(),
|
|
"permissionCount", permissions2.size(),
|
|
"permissions",
|
|
permissions2.stream()
|
|
.map(Permission::getCode)
|
|
.collect(Collectors.toList())),
|
|
"common",
|
|
Map.of(
|
|
"count", common.size(),
|
|
"permissions",
|
|
common.stream()
|
|
.map(Permission::getCode)
|
|
.collect(Collectors.toList())),
|
|
"onlyInRole1",
|
|
Map.of(
|
|
"count", missing2to1.size(),
|
|
"permissions",
|
|
missing2to1.stream()
|
|
.map(Permission::getCode)
|
|
.collect(Collectors.toList())),
|
|
"onlyInRole2",
|
|
Map.of(
|
|
"count", missing1to2.size(),
|
|
"permissions",
|
|
missing1to2.stream()
|
|
.map(Permission::getCode)
|
|
.collect(Collectors.toList()))))
|
|
.build();
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
return Response.status(Response.Status.BAD_REQUEST).entity("Rôle invalide").build();
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la comparaison des rôles", e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la comparaison: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
/** Récupère les permissions spécifiques au gestionnaire de projet */
|
|
@GET
|
|
@Path("/gestionnaire")
|
|
public Response getGestionnairePermissions() {
|
|
try {
|
|
logger.debug("GET /api/permissions/gestionnaire");
|
|
|
|
UserRole gestionnaireRole = UserRole.GESTIONNAIRE_PROJET;
|
|
Map<String, Object> summary = permissionService.getPermissionSummary(gestionnaireRole);
|
|
|
|
// Ajout d'informations spécifiques au gestionnaire
|
|
Map<PermissionCategory, List<Permission>> byCategory =
|
|
permissionService.getPermissionsByCategory(gestionnaireRole);
|
|
|
|
return Response.ok(
|
|
Map.of(
|
|
"role", gestionnaireRole.getDisplayName(),
|
|
"description", gestionnaireRole.getDescription(),
|
|
"summary", summary,
|
|
"specificities",
|
|
Map.of(
|
|
"clientManagement", "Gestion limitée aux clients assignés",
|
|
"projectScope", "Chantiers et projets sous sa responsabilité uniquement",
|
|
"budgetAccess", "Consultation et planification budgétaire",
|
|
"materialReservation", "Réservation de matériel pour ses chantiers",
|
|
"reportingLevel", "Rapports et statistiques de ses projets"),
|
|
"categoriesDetails",
|
|
byCategory.entrySet().stream()
|
|
.collect(
|
|
Collectors.toMap(
|
|
entry -> entry.getKey().getDisplayName(),
|
|
entry ->
|
|
entry.getValue().stream()
|
|
.map(
|
|
p ->
|
|
Map.of(
|
|
"code", p.getCode(),
|
|
"description", p.getDescription()))
|
|
.collect(Collectors.toList())))))
|
|
.build();
|
|
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la récupération des permissions gestionnaire", e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la récupération des permissions: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
/** Valide les permissions requises pour une fonctionnalité */
|
|
@POST
|
|
@Path("/validate")
|
|
public Response validatePermissions(ValidationRequest request) {
|
|
try {
|
|
logger.debug("POST /api/permissions/validate");
|
|
|
|
UserRole role = UserRole.valueOf(request.role.toUpperCase());
|
|
Set<Permission> requiredPermissions =
|
|
request.requiredPermissions.stream()
|
|
.map(Permission::fromCode)
|
|
.collect(Collectors.toSet());
|
|
|
|
boolean hasMinimum = permissionService.hasMinimumPermissions(role, requiredPermissions);
|
|
|
|
Map<String, Boolean> permissionChecks =
|
|
request.requiredPermissions.stream()
|
|
.collect(
|
|
Collectors.toMap(
|
|
code -> code, code -> permissionService.hasPermission(role, code)));
|
|
|
|
return Response.ok(
|
|
Map.of(
|
|
"role",
|
|
role.getDisplayName(),
|
|
"hasMinimumPermissions",
|
|
hasMinimum,
|
|
"permissionChecks",
|
|
permissionChecks,
|
|
"missingPermissions",
|
|
request.requiredPermissions.stream()
|
|
.filter(code -> !permissionService.hasPermission(role, code))
|
|
.collect(Collectors.toList())))
|
|
.build();
|
|
|
|
} catch (Exception e) {
|
|
logger.error("Erreur lors de la validation des permissions", e);
|
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
.entity("Erreur lors de la validation: " + e.getMessage())
|
|
.build();
|
|
}
|
|
}
|
|
|
|
// === CLASSES DE REQUÊTE ===
|
|
|
|
public static class ValidationRequest {
|
|
public String role;
|
|
public List<String> requiredPermissions;
|
|
}
|
|
}
|