package dev.lions.unionflow.client.view; import jakarta.enterprise.context.SessionScoped; import jakarta.inject.Inject; import jakarta.inject.Named; import org.eclipse.microprofile.jwt.JsonWebToken; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.logging.Logger; /** * Gestion de la session utilisateur avec Keycloak OIDC * * @author UnionFlow Team * @version 2.0 */ @Named("userSession") @SessionScoped public class UserSession implements Serializable { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(UserSession.class.getName()); @Inject private JsonWebToken jwt; private String username; private boolean authenticated = false; private String typeCompte; private List roles; private List permissions; private CurrentUser currentUser; private EntiteInfo entite; public UserSession() { // Session par défaut non authentifiée clearSession(); } /** * Initialise la session depuis le token OIDC Keycloak * Appelé automatiquement après l'authentification */ public void initializeFromOidcToken() { if (jwt != null && jwt.getName() != null) { this.authenticated = true; this.username = jwt.getClaim("preferred_username"); if (this.username == null) { this.username = jwt.getName(); } // Récupérer les informations du token String email = jwt.getClaim("email"); String givenName = jwt.getClaim("given_name"); String familyName = jwt.getClaim("family_name"); // Récupérer les rôles depuis le token this.roles = extractRolesFromToken(); LOGGER.info("Rôles assignés à this.roles: " + this.roles); LOGGER.info("Vérification contains('SUPER_ADMIN'): " + (this.roles != null && this.roles.contains("SUPER_ADMIN"))); this.typeCompte = determineTypeCompte(); LOGGER.info("Type de compte déterminé: " + this.typeCompte); // Mettre à jour les informations utilisateur this.currentUser = new CurrentUser(); this.currentUser.setUsername(this.username); this.currentUser.setEmail(email); this.currentUser.setPrenom(givenName); this.currentUser.setNom(familyName); // Générer un ID depuis le subject du token String subject = jwt.getSubject(); if (subject != null) { try { this.currentUser.setId(UUID.fromString(subject)); } catch (IllegalArgumentException e) { // Si le subject n'est pas un UUID, générer un UUID déterministe this.currentUser.setId(UUID.nameUUIDFromBytes(subject.getBytes())); } } LOGGER.info("Session utilisateur initialisée depuis Keycloak pour: " + this.username + " (Type: " + typeCompte + ")"); } } /** * Convertit un objet JSON en String de manière sécurisée * Gère les cas où l'objet est un JsonStringImpl, String, ou autre type */ private String convertToString(Object obj) { if (obj == null) { return null; } if (obj instanceof String) { String str = (String) obj; // Nettoyer les guillemets qui pourraient être présents str = str.trim(); if (str.startsWith("'") && str.endsWith("'") && str.length() > 1) { str = str.substring(1, str.length() - 1); } if (str.startsWith("\"") && str.endsWith("\"") && str.length() > 1) { str = str.substring(1, str.length() - 1); } return str.trim(); } // Gérer JsonStringImpl et autres types JSON String str = obj.toString(); // Nettoyer les guillemets qui pourraient être présents str = str.trim(); if (str.startsWith("'") && str.endsWith("'") && str.length() > 1) { str = str.substring(1, str.length() - 1); } if (str.startsWith("\"") && str.endsWith("\"") && str.length() > 1) { str = str.substring(1, str.length() - 1); } return str.trim(); } /** * Extrait et convertit une liste de rôles depuis un objet JSON */ private List extractRolesFromList(Object rolesObj) { List roles = new ArrayList<>(); if (rolesObj instanceof List) { @SuppressWarnings("unchecked") List rolesList = (List) rolesObj; for (Object roleObj : rolesList) { String role = convertToString(roleObj); if (role != null && !role.isEmpty()) { // S'assurer que c'est vraiment un String en créant une nouvelle instance roles.add(new String(role)); LOGGER.fine("Rôle converti: '" + role + "' (type: " + role.getClass().getName() + ")"); } } } return roles; } /** * Extrait les rôles depuis le token JWT */ private List extractRolesFromToken() { List extractedRoles = new ArrayList<>(); // Rôles dans "realm_access.roles" try { Object realmAccess = jwt.getClaim("realm_access"); if (realmAccess instanceof java.util.Map) { @SuppressWarnings("unchecked") java.util.Map realmMap = (java.util.Map) realmAccess; Object rolesObj = realmMap.get("roles"); List realmRoles = extractRolesFromList(rolesObj); if (!realmRoles.isEmpty()) { extractedRoles.addAll(realmRoles); LOGGER.info("Rôles extraits depuis realm_access.roles: " + realmRoles); } } else if (realmAccess instanceof List) { // Fallback: si realm_access est directement une liste de rôles List realmRoles = extractRolesFromList(realmAccess); if (!realmRoles.isEmpty()) { extractedRoles.addAll(realmRoles); LOGGER.info("Rôles extraits depuis realm_access (liste): " + realmRoles); } } } catch (Exception e) { LOGGER.warning("Erreur lors de l'extraction des rôles realm: " + e.getMessage()); } // Rôles dans "resource_access" try { Object resourceAccess = jwt.getClaim("resource_access"); if (resourceAccess instanceof java.util.Map) { @SuppressWarnings("unchecked") java.util.Map resourceMap = (java.util.Map) resourceAccess; for (Object value : resourceMap.values()) { if (value instanceof java.util.Map) { @SuppressWarnings("unchecked") java.util.Map clientMap = (java.util.Map) value; Object rolesObj = clientMap.get("roles"); List clientRoles = extractRolesFromList(rolesObj); if (!clientRoles.isEmpty()) { extractedRoles.addAll(clientRoles); LOGGER.info("Rôles extraits depuis resource_access: " + clientRoles); } } } } } catch (Exception e) { LOGGER.warning("Erreur lors de l'extraction des rôles client: " + e.getMessage()); } // Fallback: essayer d'extraire les rôles depuis le claim "roles" directement if (extractedRoles.isEmpty()) { try { Object rolesClaim = jwt.getClaim("roles"); List directRoles = extractRolesFromList(rolesClaim); if (!directRoles.isEmpty()) { extractedRoles.addAll(directRoles); LOGGER.info("Rôles extraits depuis claim 'roles': " + directRoles); } } catch (Exception e) { LOGGER.warning("Erreur lors de l'extraction des rôles depuis claim 'roles': " + e.getMessage()); } } LOGGER.info("Total des rôles extraits: " + extractedRoles); return extractedRoles; } /** * Détermine le type de compte depuis les rôles */ private String determineTypeCompte() { // Utiliser this.roles pour s'assurer qu'on utilise la bonne variable d'instance List rolesToCheck = this.roles; if (rolesToCheck == null || rolesToCheck.isEmpty()) { LOGGER.warning("Aucun rôle trouvé, type de compte par défaut: MEMBRE"); return "MEMBRE"; } LOGGER.info("Détermination du type de compte depuis les rôles: " + rolesToCheck); LOGGER.info("Nombre de rôles: " + rolesToCheck.size()); // Vérifier le type des éléments de la liste if (!rolesToCheck.isEmpty()) { Object firstRole = rolesToCheck.get(0); LOGGER.info("Type du premier rôle: " + (firstRole != null ? firstRole.getClass().getName() : "null")); LOGGER.info("Premier rôle (toString): '" + firstRole + "'"); LOGGER.info("Premier rôle (length): " + (firstRole != null ? firstRole.toString().length() : 0)); // Vérifier les caractères du premier rôle if (firstRole != null) { String firstRoleStr = firstRole.toString(); LOGGER.info("Premier rôle (bytes): " + java.util.Arrays.toString(firstRoleStr.getBytes())); } } // Vérifier SUPER_ADMIN en parcourant la liste (plus robuste que contains) for (String role : rolesToCheck) { if (role != null) { // Nettoyer la chaîne : retirer les guillemets et espaces String roleStr = role.toString().trim(); // Retirer les guillemets simples et doubles au début et à la fin if (roleStr.startsWith("'") && roleStr.endsWith("'")) { roleStr = roleStr.substring(1, roleStr.length() - 1); } if (roleStr.startsWith("\"") && roleStr.endsWith("\"")) { roleStr = roleStr.substring(1, roleStr.length() - 1); } roleStr = roleStr.trim(); LOGGER.info("Vérification du rôle: '" + roleStr + "' (longueur: " + roleStr.length() + ", original: '" + role + "')"); if ("SUPER_ADMIN".equals(roleStr) || "super-admin".equalsIgnoreCase(roleStr)) { LOGGER.info("✅ Type de compte détecté: SUPER_ADMIN (rôle trouvé: '" + roleStr + "')"); return "SUPER_ADMIN"; } } } // Fallback: utiliser contains() pour compatibilité boolean hasSuperAdmin = rolesToCheck.contains("SUPER_ADMIN"); boolean hasSuperAdminLower = rolesToCheck.contains("super-admin"); LOGGER.info("Contient 'SUPER_ADMIN' (contains): " + hasSuperAdmin); LOGGER.info("Contient 'super-admin' (contains): " + hasSuperAdminLower); if (hasSuperAdmin || hasSuperAdminLower) { LOGGER.info("✅ Type de compte détecté: SUPER_ADMIN (via contains)"); return "SUPER_ADMIN"; } // Vérifier ADMIN_ENTITE (mais pas si c'est juste "ADMIN" qui pourrait être ambigu) if (rolesToCheck.contains("ADMIN_ENTITE")) { LOGGER.info("Type de compte détecté: ADMIN_ENTITE"); return "ADMIN_ENTITE"; } // Vérifier les autres rôles admin (avec précaution pour éviter les faux positifs) for (String role : rolesToCheck) { if (role != null && (role.equals("ADMIN") || role.equalsIgnoreCase("admin"))) { LOGGER.info("Type de compte détecté: ADMIN_ENTITE (via rôle ADMIN)"); return "ADMIN_ENTITE"; } } LOGGER.warning("Aucun rôle admin trouvé, type de compte par défaut: MEMBRE"); return "MEMBRE"; } public void clearSession() { this.authenticated = false; this.username = null; this.typeCompte = null; this.roles = null; this.permissions = null; this.currentUser = null; this.entite = null; LOGGER.info("Session utilisateur effacée"); } // Méthodes de vérification des rôles et permissions public boolean hasRole(String role) { return roles != null && roles.contains(role); } public boolean hasPermission(String permission) { return permissions != null && permissions.contains(permission); } public boolean isSuperAdmin() { return "SUPER_ADMIN".equals(typeCompte) || hasRole("SUPER_ADMIN"); } public boolean isAdmin() { return isSuperAdmin() || "ADMIN_ENTITE".equals(typeCompte) || hasRole("ADMIN_ENTITE"); } public boolean isMembre() { return "MEMBRE".equals(typeCompte) || hasRole("MEMBRE"); } // Méthode pour obtenir le rôle principal public String getRole() { if (isSuperAdmin()) { return "SUPER_ADMIN"; } if (isAdmin()) { return "ADMIN"; } if (typeCompte != null) { return typeCompte; } if (roles != null && !roles.isEmpty()) { return roles.get(0); } return "MEMBER"; } // Getters et Setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public boolean isAuthenticated() { // Vérifier via JsonWebToken if (jwt != null && jwt.getName() != null && !authenticated) { initializeFromOidcToken(); } return authenticated || (jwt != null && jwt.getName() != null); } public void setAuthenticated(boolean authenticated) { this.authenticated = authenticated; } public String getTypeCompte() { // Si le type de compte n'est pas encore déterminé, l'initialiser if (typeCompte == null && jwt != null && jwt.getName() != null) { LOGGER.info("getTypeCompte() appelé avant initialisation, initialisation en cours..."); initializeFromOidcToken(); } return typeCompte; } public void setTypeCompte(String typeCompte) { this.typeCompte = typeCompte; } public List getRoles() { return roles; } public void setRoles(List roles) { this.roles = roles; } public List getPermissions() { return permissions; } public void setPermissions(List permissions) { this.permissions = permissions; } public CurrentUser getCurrentUser() { return currentUser; } public void setCurrentUser(CurrentUser currentUser) { this.currentUser = currentUser; } public EntiteInfo getEntite() { return entite; } public void setEntite(EntiteInfo entite) { this.entite = entite; } // Classes internes public static class CurrentUser implements Serializable { private UUID id; private String nom; private String prenom; private String email; private String username; public String getNomComplet() { if (prenom != null && nom != null) { return prenom + " " + nom; } return nom != null ? nom : username; } public String getInitiales() { StringBuilder initiales = new StringBuilder(); if (prenom != null && !prenom.isEmpty()) { initiales.append(prenom.charAt(0)); } if (nom != null && !nom.isEmpty()) { initiales.append(nom.charAt(0)); } return initiales.toString().toUpperCase(); } // Getters et Setters public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String getPrenom() { return prenom; } public void setPrenom(String prenom) { this.prenom = prenom; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } public static class EntiteInfo implements Serializable { private UUID id; private String nom; private String type; private String pays; private String ville; public String getDescription() { StringBuilder desc = new StringBuilder(); if (nom != null) { desc.append(nom); } if (ville != null && pays != null) { desc.append(" (").append(ville).append(", ").append(pays).append(")"); } return desc.toString(); } // Getters et Setters public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getPays() { return pays; } public void setPays(String pays) { this.pays = pays; } public String getVille() { return ville; } public void setVille(String ville) { this.ville = ville; } } }