Files
unionflow-client-quarkus-pr…/src/main/java/dev/lions/unionflow/client/view/UserSession.java

535 lines
19 KiB
Java

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<String> roles;
private List<String> 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<String> extractRolesFromList(Object rolesObj) {
List<String> roles = new ArrayList<>();
if (rolesObj instanceof List) {
@SuppressWarnings("unchecked")
List<Object> rolesList = (List<Object>) 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<String> extractRolesFromToken() {
List<String> 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<String, Object> realmMap = (java.util.Map<String, Object>) realmAccess;
Object rolesObj = realmMap.get("roles");
List<String> 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<String> 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<String, Object> resourceMap = (java.util.Map<String, Object>) resourceAccess;
for (Object value : resourceMap.values()) {
if (value instanceof java.util.Map) {
@SuppressWarnings("unchecked")
java.util.Map<String, Object> clientMap = (java.util.Map<String, Object>) value;
Object rolesObj = clientMap.get("roles");
List<String> 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<String> 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<String> 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<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> 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;
}
}
}