Compare commits

...

10 Commits

62 changed files with 2967 additions and 485 deletions

133
.gitignore vendored Normal file
View File

@@ -0,0 +1,133 @@
# ============================================================================
# Lions User Manager - Server Implementation Quarkus - .gitignore
# ============================================================================
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
# Build artifacts
*.jar
*.war
*.ear
*.class
*.idx
# Eclipse
.project
.classpath
.settings/
.metadata/
bin/
# IntelliJ IDEA
.idea/
*.iml
*.iws
*.ipr
out/
# NetBeans
nbproject/
nbbuild/
nbdist/
.nb-gradle/
# VS Code
.vscode/
*.code-workspace
# Mac
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Logs
logs/
*.log
*.log.*
hs_err_pid*.log
# Quarkus
.quarkus/
quarkus-app/
quarkus-run.jar
quarkus-*.dat
# Temporary files
*.tmp
*.bak
*.swp
*~
*.orig
# Test files and reports
test_output*.txt
surefire-reports/
failsafe-reports/
*.dump
*.dumpstream
# Test coverage
.jacoco/
jacoco.exec
coverage/
target/site/jacoco/
# Application specific
application-local.properties
application-*.local.properties
# Configuration files with sensitive data
*.local.json
# Token and authentication files
token.json
token.txt
*.token
# Generated sources
generated-sources/
generated-test-sources/
# Maven status
maven-status/
# Build metrics
build-metrics.json
# Quarkus Dev Services
.devservices/
# Fichiers META-INF générés (reflection-config.json est généré par Quarkus)
**/META-INF/reflection-config.json
# IDE specific
*.sublime-project
*.sublime-workspace
# OS specific
.DS_Store?
._*
.Spotlight-V100
.Trashes
# Lombok configuration (généré automatiquement)
lombok.config
# Environment files
.env
.env.local
.env.*.local

86
Dockerfile.prod Normal file
View File

@@ -0,0 +1,86 @@
####
# Dockerfile de production pour Lions User Manager Server (Backend)
# Multi-stage build optimisé avec sécurité renforcée
# Basé sur la structure de btpxpress-server
####
## Stage 1 : Build avec Maven
FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /app
# Copier pom.xml et télécharger les dépendances (cache Docker)
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Copier le code source
COPY src ./src
# Construire l'application avec profil production
RUN mvn clean package -DskipTests -B -Dquarkus.profile=prod
## Stage 2 : Image de production optimisée
FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
ENV LANGUAGE='en_US:en'
# Configuration des variables d'environnement pour production
ENV QUARKUS_PROFILE=prod
ENV DB_URL=jdbc:postgresql://postgresql:5432/lions_audit
ENV DB_USERNAME=lions_audit_user
ENV DB_PASSWORD=changeme
ENV SERVER_PORT=8080
# Configuration Keycloak/OIDC (production)
ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/master
ENV QUARKUS_OIDC_CLIENT_ID=lions-user-manager
ENV KEYCLOAK_CLIENT_SECRET=changeme
ENV QUARKUS_OIDC_TLS_VERIFICATION=required
# Configuration Keycloak Admin Client
ENV LIONS_KEYCLOAK_SERVER_URL=https://security.lions.dev
ENV LIONS_KEYCLOAK_ADMIN_REALM=master
ENV LIONS_KEYCLOAK_ADMIN_CLIENT_ID=admin-cli
ENV LIONS_KEYCLOAK_ADMIN_USERNAME=admin
ENV LIONS_KEYCLOAK_ADMIN_PASSWORD=changeme
# Configuration CORS pour production
ENV QUARKUS_HTTP_CORS_ORIGINS=https://user-manager.lions.dev,https://admin.lions.dev
ENV QUARKUS_HTTP_CORS_ALLOW_CREDENTIALS=true
# Installer curl pour les health checks
USER root
RUN microdnf install curl -y && microdnf clean all
RUN mkdir -p /app/logs && chown -R 185:185 /app/logs
USER 185
# Copier l'application depuis le builder
COPY --from=builder --chown=185 /app/target/quarkus-app/lib/ /deployments/lib/
COPY --from=builder --chown=185 /app/target/quarkus-app/*.jar /deployments/
COPY --from=builder --chown=185 /app/target/quarkus-app/app/ /deployments/app/
COPY --from=builder --chown=185 /app/target/quarkus-app/quarkus/ /deployments/quarkus/
# Exposer le port
EXPOSE 8080
# Variables JVM optimisées pour production avec sécurité
ENV JAVA_OPTS="-Xmx1g -Xms512m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-XX:+ParallelRefProcEnabled \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/app/logs/heapdump.hprof \
-Djava.security.egd=file:/dev/./urandom \
-Djava.awt.headless=true \
-Dfile.encoding=UTF-8 \
-Djava.util.logging.manager=org.jboss.logmanager.LogManager \
-Dquarkus.profile=${QUARKUS_PROFILE}"
# Point d'entrée avec profil production
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/quarkus-run.jar"]
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/q/health/ready || exit 1

View File

@@ -0,0 +1,85 @@
2025-12-06 19:32:12,544 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.smallrye-fault-tolerance.enabled" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 19:32:12,545 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.security.auth.enabled" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 19:32:12,545 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.security.auth.proactive" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 19:32:12,545 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.oidc.verify-access-token" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 19:32:15,688 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Configuration automatique de Keycloak DÉSACTIVÉE
2025-12-06 19:32:15,697 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Utiliser le script create-roles-and-assign.sh pour configurer Keycloak manuellement
2025-12-06 19:32:15,717 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-06 19:32:15,718 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Initialisation du client Keycloak Admin
2025-12-06 19:32:15,719 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-06 19:32:15,720 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Server URL: http://localhost:8180
2025-12-06 19:32:15,720 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Realm: master
2025-12-06 19:32:15,721 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Client ID: admin-cli
2025-12-06 19:32:15,722 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Username: admin
2025-12-06 19:32:15,723 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Connection Pool Size: 5
2025-12-06 19:32:15,725 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Timeout: 30 secondes
2025-12-06 19:32:15,762 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ✅ Client Keycloak initialisé (connexion lazy)
2025-12-06 19:32:15,763 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) La connexion sera établie lors de la première requête API
2025-12-06 19:32:15,850 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [io.quarkus] (Quarkus Main Thread) lions-user-manager-server 1.0.0 on JVM (powered by Quarkus 3.15.1) started in 12.905s. Listening on: http://localhost:8081
2025-12-06 19:32:15,854 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-12-06 19:32:15,857 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, flyway, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, micrometer, narayana-jta, oidc, rest, rest-client, rest-client-jackson, rest-jackson, security, smallrye-context-propagation, smallrye-fault-tolerance, smallrye-health, smallrye-openapi, swagger-ui, vertx]
2025-12-06 19:32:32,334 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-06 19:32:32,338 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-06 19:32:32,346 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-06 19:32:32,347 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Authorization scheme: Bearer
2025-12-06 19:32:32,352 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Starting creating SecurityIdentity
2025-12-06 19:32:32,360 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Verifying the JWT token with the local JWK keys
2025-12-06 19:32:32,516 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:32:32,517 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:32:32,583 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:32:32,601 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:37:20,766 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-06 19:37:20,767 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-06 19:37:20,768 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-06 19:37:20,769 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-06 19:37:20,769 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-06 19:37:20,770 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-06 19:37:20,778 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:37:20,778 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:37:20,780 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:37:20,781 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:38:43,981 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-06 19:38:43,982 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-06 19:38:43,984 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-06 19:38:43,985 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Authorization scheme: Bearer
2025-12-06 19:38:43,986 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Starting creating SecurityIdentity
2025-12-06 19:38:43,988 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Verifying the JWT token with the local JWK keys
2025-12-06 19:38:44,003 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:38:44,004 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:38:44,009 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:38:44,011 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:44:46,850 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-06 19:44:46,851 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-06 19:44:46,852 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-06 19:44:46,853 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-06 19:44:46,853 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-06 19:44:46,854 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-06 19:44:46,862 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:44:46,863 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:44:46,865 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:44:46,865 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:54:25,525 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-06 19:54:25,526 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-06 19:54:25,527 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-06 19:54:25,527 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Bearer access token is not available
2025-12-06 21:36:12,969 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-06 21:36:12,975 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-06 21:36:12,977 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-06 21:36:12,977 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-06 21:36:12,981 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-06 21:36:12,985 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-06 21:36:13,006 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 21:36:13,009 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 21:36:13,015 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 21:36:13,016 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 21:36:22,446 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-06 21:36:22,447 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-06 21:36:22,448 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-06 21:36:22,448 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Authorization scheme: Bearer
2025-12-06 21:36:22,448 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Starting creating SecurityIdentity
2025-12-06 21:36:22,449 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Verifying the JWT token with the local JWK keys
2025-12-06 21:36:22,455 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/roles/realm
2025-12-06 21:36:22,456 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 21:36:22,469 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.res.RoleResource] (executor-thread-1) GET /api/roles/realm - realm: lions-user-manager
2025-12-06 21:36:22,474 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.ser.imp.RoleServiceImpl] (executor-thread-1) Récupération de tous les rôles realm du realm: lions-user-manager
2025-12-06 21:36:23,100 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[59128] INFO [dev.lio.use.man.ser.imp.RoleServiceImpl] (executor-thread-1) Récupération réussie: 8 rôles trouvés dans le realm lions-user-manager

View File

@@ -0,0 +1,22 @@
2025-12-06 19:31:04,801 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Configuration automatique de Keycloak DÉSACTIVÉE
2025-12-06 19:31:04,802 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Utiliser le script create-roles-and-assign.sh pour configurer Keycloak manuellement
2025-12-06 19:31:04,810 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-06 19:31:04,811 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Initialisation du client Keycloak Admin
2025-12-06 19:31:04,811 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-06 19:31:04,813 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Server URL: http://localhost:8180
2025-12-06 19:31:04,813 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Realm: master
2025-12-06 19:31:04,814 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Client ID: admin-cli
2025-12-06 19:31:04,816 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Username: admin
2025-12-06 19:31:04,817 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Connection Pool Size: 5
2025-12-06 19:31:04,819 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Timeout: 30 secondes
2025-12-06 19:31:04,825 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ✅ Client Keycloak initialisé (connexion lazy)
2025-12-06 19:31:04,826 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) La connexion sera établie lors de la première requête API
2025-12-06 19:31:04,827 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Quarkus Main Thread) lions-user-manager-server 1.0.0 on JVM (powered by Quarkus 3.15.1) started in 4.823s. Listening on: http://localhost:8081
2025-12-06 19:31:04,828 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-12-06 19:31:04,828 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, flyway, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, micrometer, narayana-jta, oidc, rest, rest-client, rest-client-jackson, rest-jackson, security, smallrye-context-propagation, smallrye-fault-tolerance, smallrye-health, smallrye-openapi, swagger-ui, vertx]
2025-12-06 19:31:04,829 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Live reload total time: 5.173s
2025-12-06 19:31:04,854 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] WARNING [org.aes.rea.ter.imp.AbstractWindowsTerminal] (Console Shutdown Hook) Failed to write out.
2025-12-06 19:31:04,910 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] WARNING [org.aes.rea.ter.imp.AbstractWindowsTerminal] (Shutdown thread) Failed to write out.
2025-12-06 19:31:04,910 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Shutdown thread) Fermeture de la connexion Keycloak...
2025-12-06 19:31:04,913 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] WARNING [org.aes.rea.ter.imp.AbstractWindowsTerminal] (Shutdown thread) Failed to write out.
2025-12-06 19:31:04,913 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Shutdown thread) lions-user-manager-server stopped in 0.080s

View File

@@ -0,0 +1,72 @@
2025-12-06 17:30:27,357 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.smallrye-fault-tolerance.enabled" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 17:30:27,358 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.security.auth.enabled" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 17:30:27,358 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.security.auth.proactive" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 17:30:27,358 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] WARN [io.qua.config] (Quarkus Main Thread) Unrecognized configuration key "quarkus.oidc.verify-access-token" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
2025-12-06 17:30:29,096 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Configuration automatique de Keycloak DÉSACTIVÉE
2025-12-06 17:30:29,102 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Utiliser le script create-roles-and-assign.sh pour configurer Keycloak manuellement
2025-12-06 17:30:29,112 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-06 17:30:29,113 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Initialisation du client Keycloak Admin
2025-12-06 17:30:29,113 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-06 17:30:29,113 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Server URL: http://localhost:8180
2025-12-06 17:30:29,114 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Realm: master
2025-12-06 17:30:29,114 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Client ID: admin-cli
2025-12-06 17:30:29,115 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Username: admin
2025-12-06 17:30:29,115 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Connection Pool Size: 5
2025-12-06 17:30:29,115 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Timeout: 30 secondes
2025-12-06 17:30:29,138 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ✅ Client Keycloak initialisé (connexion lazy)
2025-12-06 17:30:29,139 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) La connexion sera établie lors de la première requête API
2025-12-06 17:30:29,175 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Quarkus Main Thread) lions-user-manager-server 1.0.0 on JVM (powered by Quarkus 3.15.1) started in 7.308s. Listening on: http://localhost:8081
2025-12-06 17:30:29,176 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-12-06 17:30:29,176 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, flyway, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, micrometer, narayana-jta, oidc, rest, rest-client, rest-client-jackson, rest-jackson, security, smallrye-context-propagation, smallrye-fault-tolerance, smallrye-health, smallrye-openapi, swagger-ui, vertx]
2025-12-06 17:46:15,854 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-06 17:46:15,855 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-06 17:46:15,855 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-06 17:46:15,856 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Bearer access token is not available
2025-12-06 17:46:16,018 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-06 17:46:16,018 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-06 17:46:16,019 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-06 17:46:16,020 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Bearer access token is not available
2025-12-06 19:15:56,656 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-06 19:15:56,669 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-06 19:15:56,671 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-06 19:15:56,672 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-06 19:15:56,677 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-06 19:15:56,705 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-06 19:15:57,006 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:15:57,011 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:15:57,145 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:15:57,182 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:23:16,924 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-06 19:23:16,925 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-06 19:23:16,927 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-06 19:23:16,927 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Authorization scheme: Bearer
2025-12-06 19:23:16,928 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Starting creating SecurityIdentity
2025-12-06 19:23:16,929 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Verifying the JWT token with the local JWK keys
2025-12-06 19:23:16,941 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:23:16,942 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:23:16,944 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:23:16,945 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:30:12,829 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-06 19:30:12,831 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-06 19:30:12,831 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-06 19:30:12,833 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-06 19:30:12,835 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-06 19:30:12,837 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-06 19:30:12,843 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:30:12,844 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:30:12,845 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:30:12,845 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:30:42,870 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-06 19:30:42,871 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-06 19:30:42,871 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-06 19:30:42,872 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-06 19:30:42,872 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-06 19:30:42,872 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-06 19:30:42,881 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-06 19:30:42,883 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-1) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-06 19:30:42,885 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.res.UserResource] (executor-thread-1) POST /api/users/search - Recherche d'utilisateurs
2025-12-06 19:30:42,886 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-1) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-06 19:30:59,515 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Live reload disabled
2025-12-06 19:30:59,681 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Restarting as requested by the user.
2025-12-06 19:30:59,982 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Fermeture de la connexion Keycloak...
2025-12-06 19:30:59,993 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[53764] INFO [io.quarkus] (Quarkus Main Thread) lions-user-manager-server stopped in 0.306s

View File

@@ -0,0 +1,112 @@
2025-12-05 22:20:06,129 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Configuration automatique de Keycloak DÉSACTIVÉE
2025-12-05 22:20:06,130 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.con.KeycloakTestUserConfig] (Quarkus Main Thread) Utiliser le script create-roles-and-assign.sh pour configurer Keycloak manuellement
2025-12-05 22:20:06,141 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-05 22:20:06,144 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Initialisation du client Keycloak Admin
2025-12-05 22:20:06,144 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ========================================
2025-12-05 22:20:06,144 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Server URL: http://localhost:8180
2025-12-05 22:20:06,146 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Realm: master
2025-12-05 22:20:06,146 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Client ID: admin-cli
2025-12-05 22:20:06,147 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Admin Username: admin
2025-12-05 22:20:06,147 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Connection Pool Size: 5
2025-12-05 22:20:06,147 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) Timeout: 30 secondes
2025-12-05 22:20:06,158 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) ✅ Client Keycloak initialisé (connexion lazy)
2025-12-05 22:20:06,158 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Quarkus Main Thread) La connexion sera établie lors de la première requête API
2025-12-05 22:20:06,159 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [io.quarkus] (Quarkus Main Thread) lions-user-manager-server 1.0.0 on JVM (powered by Quarkus 3.15.1) started in 4.153s. Listening on: http://localhost:8081
2025-12-05 22:20:06,160 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-12-05 22:20:06,162 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, flyway, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, micrometer, narayana-jta, oidc, rest, rest-client, rest-client-jackson, rest-jackson, security, smallrye-context-propagation, smallrye-fault-tolerance, smallrye-health, smallrye-openapi, swagger-ui, vertx]
2025-12-05 22:20:06,164 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-17) Live reload total time: 6.546s
2025-12-05 22:20:06,168 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-05 22:20:06,168 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-05 22:20:06,169 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-05 22:20:06,169 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-05 22:20:06,170 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-05 22:20:06,175 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-05 22:20:06,182 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-2) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c
2025-12-05 22:20:06,184 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-2) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-05 22:20:06,189 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.res.UserResource] (executor-thread-2) GET /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c - realm: master
2025-12-05 22:20:06,194 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-2) Récupération de l'utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c dans le realm master
2025-12-05 22:20:06,198 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-05 22:20:06,200 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-05 22:20:06,201 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-05 22:20:06,203 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Bearer access token is not available
2025-12-05 22:20:06,413 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] WARN [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-2) Utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c non trouvé dans le realm master (404 détecté dans l'exception)
2025-12-05 22:20:06,482 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-05 22:20:06,482 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-05 22:20:06,483 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-05 22:20:06,483 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Bearer access token is not available
2025-12-05 22:20:08,563 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-05 22:20:08,563 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-05 22:20:08,564 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-05 22:20:08,564 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Bearer access token is not available
2025-12-05 22:20:08,578 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-05 22:20:08,579 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-05 22:20:08,580 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-05 22:20:08,580 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Bearer access token is not available
2025-12-05 22:20:42,420 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-05 22:20:42,421 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-05 22:20:42,421 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-05 22:20:42,422 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-05 22:20:42,422 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-05 22:20:42,424 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-05 22:20:42,428 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c
2025-12-05 22:20:42,428 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-05 22:20:42,430 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.res.UserResource] (executor-thread-3) GET /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c - realm: master
2025-12-05 22:20:42,430 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Récupération de l'utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c dans le realm master
2025-12-05 22:20:42,477 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] WARN [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c non trouvé dans le realm master (404 détecté dans l'exception)
2025-12-05 22:23:01,825 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-05 22:23:01,826 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-05 22:23:01,827 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-05 22:23:01,827 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-05 22:23:01,827 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-05 22:23:01,828 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-05 22:23:01,831 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-05 22:23:01,832 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-05 22:23:01,836 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.res.UserResource] (executor-thread-3) POST /api/users/search - Recherche d'utilisateurs
2025-12-05 22:23:01,837 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-05 22:23:09,711 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-05 22:23:09,712 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-05 22:23:09,712 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-05 22:23:09,713 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-05 22:23:09,713 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-05 22:23:09,714 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-05 22:23:09,719 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c
2025-12-05 22:23:09,720 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-05 22:23:09,721 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.res.UserResource] (executor-thread-3) GET /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c - realm: master
2025-12-05 22:23:09,721 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Récupération de l'utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c dans le realm master
2025-12-05 22:23:09,738 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] WARN [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c non trouvé dans le realm master (404 détecté dans l'exception)
2025-12-05 22:24:00,805 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-4) Resolved OIDC tenant id: Default
2025-12-05 22:24:00,806 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-4) Starting a bearer access token authentication
2025-12-05 22:24:00,807 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Looking for a token in the authorization header
2025-12-05 22:24:00,807 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-4) Authorization scheme: Bearer
2025-12-05 22:24:00,808 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Starting creating SecurityIdentity
2025-12-05 22:24:00,810 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-4) Verifying the JWT token with the local JWK keys
2025-12-05 22:24:00,814 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c
2025-12-05 22:24:00,815 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-05 22:24:00,816 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.res.UserResource] (executor-thread-3) GET /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c - realm: master
2025-12-05 22:24:00,817 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Récupération de l'utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c dans le realm master
2025-12-05 22:24:00,869 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] WARN [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c non trouvé dans le realm master (404 détecté dans l'exception)
2025-12-05 22:53:39,460 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-05 22:53:39,461 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-05 22:53:39,462 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-05 22:53:39,463 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Authorization scheme: Bearer
2025-12-05 22:53:39,463 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Starting creating SecurityIdentity
2025-12-05 22:53:39,465 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Verifying the JWT token with the local JWK keys
2025-12-05 22:53:39,472 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c
2025-12-05 22:53:39,474 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-05 22:53:39,475 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.res.UserResource] (executor-thread-3) GET /api/users/672833b5-0c4c-451e-8fe9-86cdae19fb5c - realm: master
2025-12-05 22:53:39,475 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Récupération de l'utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c dans le realm master
2025-12-05 22:53:39,667 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] WARN [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Utilisateur 672833b5-0c4c-451e-8fe9-86cdae19fb5c non trouvé dans le realm master (404 détecté dans l'exception)
2025-12-05 22:53:58,895 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcAuthenticationMechanism] (vert.x-eventloop-thread-3) Resolved OIDC tenant id: Default
2025-12-05 22:53:58,895 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.BearerAuthenticationMechanism] (vert.x-eventloop-thread-3) Starting a bearer access token authentication
2025-12-05 22:53:58,896 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Looking for a token in the authorization header
2025-12-05 22:53:58,896 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcUtils] (vert.x-eventloop-thread-3) Authorization scheme: Bearer
2025-12-05 22:53:58,897 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Starting creating SecurityIdentity
2025-12-05 22:53:58,897 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [io.qua.oid.run.OidcIdentityProvider] (vert.x-eventloop-thread-3) Verifying the JWT token with the local JWK keys
2025-12-05 22:53:58,900 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) Mode dev détecté (profile=dev, oidc.enabled=true): remplacement du SecurityContext pour le chemin /api/users/search
2025-12-05 22:53:58,900 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] DEBUG [dev.lio.use.man.sec.DevSecurityContextProducer] (executor-thread-3) SecurityContext remplacé - isUserInRole('admin')=true, isUserInRole('user_manager')=true
2025-12-05 22:53:58,901 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.res.UserResource] (executor-thread-3) POST /api/users/search - Recherche d'utilisateurs
2025-12-05 22:53:58,902 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.ser.imp.UserServiceImpl] (executor-thread-3) Recherche d'utilisateurs avec critères: UserSearchCriteriaDTO(searchTerm=null, username=null, email=null, prenom=null, nom=null, telephone=null, statut=null, enabled=null, emailVerified=null, realmRoles=null, clientRoles=null, groups=null, clientName=null, organisation=null, departement=null, fonction=null, pays=null, ville=null, dateCreationMin=null, dateCreationMax=null, derniereConnexionMin=null, derniereConnexionMax=null, hasRequiredActions=null, isLocked=null, isExpired=null, hasActiveSessions=null, realmName=lions-user-manager, page=0, pageSize=20, maxResults=null, sortBy=username, sortOrder=ASC, includeRoles=false, includeGroups=false, includeAttributes=false, includeSessionInfo=false)
2025-12-05 23:11:17,932 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] WARNING [org.aes.rea.ter.imp.AbstractWindowsTerminal] (Shutdown thread) Failed to write out.
2025-12-05 23:11:17,739 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [dev.lio.use.man.cli.KeycloakAdminClientImpl] (Shutdown thread) Fermeture de la connexion Keycloak...
2025-12-05 23:11:17,949 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] WARNING [org.aes.rea.ter.imp.AbstractWindowsTerminal] (Shutdown thread) Failed to write out.
2025-12-05 23:11:17,949 gbanedahoud C:\Program Files\Java\jdk-17\bin\java.exe[7920] INFO [io.quarkus] (Shutdown thread) lions-user-manager-server stopped in 0.407s

21
pom.xml
View File

@@ -74,25 +74,10 @@
<artifactId>quarkus-smallrye-fault-tolerance</artifactId>
</dependency>
<!-- Keycloak Admin Client -->
<!-- Keycloak Admin Client - Version Quarkus compatible avec RESTEasy Reactive -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>23.0.3</version>
<exclusions>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
</exclusion>
</exclusions>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-admin-rest-client</artifactId>
</dependency>
<!-- Optional: Database for audit logs -->

View File

@@ -1,5 +1,7 @@
package dev.lions.user.manager.client;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.runtime.Startup;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@@ -52,11 +54,15 @@ public class KeycloakAdminClientImpl implements KeycloakAdminClient {
@PostConstruct
void init() {
log.info("Initialisation du client Keycloak Admin...");
log.info("========================================");
log.info("Initialisation du client Keycloak Admin");
log.info("========================================");
log.info("Server URL: {}", serverUrl);
log.info("Admin Realm: {}", adminRealm);
log.info("Admin Client ID: {}", adminClientId);
log.info("Admin Username: {}", adminUsername);
log.info("Connection Pool Size: {}", connectionPoolSize);
log.info("Timeout: {} secondes", timeoutSeconds);
try {
this.keycloak = KeycloakBuilder.builder()
@@ -67,12 +73,16 @@ public class KeycloakAdminClientImpl implements KeycloakAdminClient {
.password(adminPassword)
.build();
// Test de connexion
keycloak.serverInfo().getInfo();
log.info("✅ Connexion à Keycloak réussie!");
log.info("✅ Client Keycloak initialisé (connexion lazy)");
log.info("La connexion sera établie lors de la première requête API");
} catch (Exception e) {
log.error(" Échec de la connexion à Keycloak: {}", e.getMessage(), e);
throw new RuntimeException("Impossible de se connecter à Keycloak", e);
log.warn("⚠️ Échec de l'initialisation du client Keycloak");
log.warn("URL: {}", serverUrl);
log.warn("Realm: {}", adminRealm);
log.warn("Username: {}", adminUsername);
log.warn("Message: {}", e.getMessage());
// Ne pas bloquer le démarrage - la connexion sera tentée lors du premier appel
this.keycloak = null;
}
}
@@ -134,13 +144,20 @@ public class KeycloakAdminClientImpl implements KeycloakAdminClient {
@Override
public boolean realmExists(String realmName) {
try {
getRealm(realmName).toRepresentation();
// Essayer d'obtenir simplement la liste des rôles du realm
// Si le realm n'existe pas, cela lancera une NotFoundException
// Si le realm existe mais a des problèmes de désérialisation, on suppose qu'il existe
getRealm(realmName).roles().list();
return true;
} catch (NotFoundException e) {
log.debug("Realm {} n'existe pas", realmName);
return false;
} catch (Exception e) {
log.error("Erreur lors de la vérification de l'existence du realm {}: {}", realmName, e.getMessage());
return false;
// En cas d'erreur (comme bruteForceStrategy lors de .toRepresentation()),
// on suppose que le realm existe car l'erreur indique qu'on a pu le contacter
log.debug("Erreur lors de la vérification du realm {} (probablement il existe): {}",
realmName, e.getMessage());
return true;
}
}

View File

@@ -0,0 +1,20 @@
package dev.lions.user.manager.config;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.jackson.ObjectMapperCustomizer;
import jakarta.inject.Singleton;
/**
* Configuration Jackson pour ignorer les propriétés inconnues
* Nécessaire pour la compatibilité avec les versions récentes de Keycloak
*/
@Singleton
public class JacksonConfig implements ObjectMapperCustomizer {
@Override
public void customize(ObjectMapper objectMapper) {
// Ignorer les propriétés inconnues pour compatibilité Keycloak
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
}

View File

@@ -0,0 +1,279 @@
package dev.lions.user.manager.config;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.*;
/**
* Configuration automatique de Keycloak pour l'utilisateur de test
* S'exécute au démarrage de l'application en mode dev
*/
@Singleton
@Slf4j
public class KeycloakTestUserConfig {
@Inject
@ConfigProperty(name = "quarkus.profile", defaultValue = "prod")
String profile;
@Inject
@ConfigProperty(name = "lions.keycloak.server-url")
String keycloakServerUrl;
@Inject
@ConfigProperty(name = "lions.keycloak.admin-realm", defaultValue = "master")
String adminRealm;
@Inject
@ConfigProperty(name = "lions.keycloak.admin-username", defaultValue = "admin")
String adminUsername;
@Inject
@ConfigProperty(name = "lions.keycloak.admin-password", defaultValue = "admin")
String adminPassword;
@Inject
@ConfigProperty(name = "lions.keycloak.authorized-realms")
String authorizedRealms;
private static final String TEST_REALM = "lions-user-manager";
private static final String TEST_USER = "test-user";
private static final String TEST_PASSWORD = "test123";
private static final String TEST_EMAIL = "test@lions.dev";
private static final String CLIENT_ID = "lions-user-manager-client";
private static final List<String> REQUIRED_ROLES = Arrays.asList(
"admin", "user_manager", "user_viewer",
"role_manager", "role_viewer", "auditor", "sync_manager"
);
void onStart(@Observes StartupEvent ev) {
// DÉSACTIVÉ: Configuration manuelle via script create-roles-and-assign.sh
// Cette configuration automatique cause des erreurs de compatibilité Keycloak
// (bruteForceStrategy, cpuInfo non reconnus par la version Keycloak client)
log.info("Configuration automatique de Keycloak DÉSACTIVÉE");
log.info("Utiliser le script create-roles-and-assign.sh pour configurer Keycloak manuellement");
return;
/* ANCIEN CODE DÉSACTIVÉ
// Ne s'exécuter qu'en mode dev
if (!"dev".equals(profile) && !"development".equals(profile)) {
log.debug("Mode non-dev détecté ({}), configuration Keycloak ignorée", profile);
return;
}
log.info("Configuration automatique de Keycloak pour l'utilisateur de test...");
Keycloak adminClient = null;
try {
// Connexion en tant qu'admin
adminClient = KeycloakBuilder.builder()
.serverUrl(keycloakServerUrl)
.realm(adminRealm)
.username(adminUsername)
.password(adminPassword)
.clientId("admin-cli")
.build();
// 1. Vérifier/Créer le realm
ensureRealmExists(adminClient);
// 2. Créer les rôles
ensureRolesExist(adminClient);
// 3. Créer l'utilisateur de test
String userId = ensureTestUserExists(adminClient);
// 4. Assigner les rôles
assignRolesToUser(adminClient, userId);
// 5. Vérifier/Créer le client et le mapper
ensureClientAndMapper(adminClient);
log.info("✓ Configuration Keycloak terminée avec succès");
log.info(" Utilisateur de test: {} / {}", TEST_USER, TEST_PASSWORD);
log.info(" Rôles assignés: {}", String.join(", ", REQUIRED_ROLES));
} catch (Exception e) {
log.error("Erreur lors de la configuration Keycloak: {}", e.getMessage(), e);
} finally {
if (adminClient != null) {
adminClient.close();
}
}
*/
}
private void ensureRealmExists(Keycloak adminClient) {
try {
adminClient.realms().realm(TEST_REALM).toRepresentation();
log.debug("Realm '{}' existe déjà", TEST_REALM);
} catch (jakarta.ws.rs.NotFoundException e) {
log.info("Création du realm '{}'...", TEST_REALM);
RealmRepresentation realm = new RealmRepresentation();
realm.setRealm(TEST_REALM);
realm.setEnabled(true);
adminClient.realms().create(realm);
log.info("✓ Realm '{}' créé", TEST_REALM);
}
}
private void ensureRolesExist(Keycloak adminClient) {
var rolesResource = adminClient.realms().realm(TEST_REALM).roles();
for (String roleName : REQUIRED_ROLES) {
try {
rolesResource.get(roleName).toRepresentation();
log.debug("Rôle '{}' existe déjà", roleName);
} catch (jakarta.ws.rs.NotFoundException e) {
log.info("Création du rôle '{}'...", roleName);
RoleRepresentation role = new RoleRepresentation();
role.setName(roleName);
role.setDescription("Rôle " + roleName + " pour lions-user-manager");
rolesResource.create(role);
log.info("✓ Rôle '{}' créé", roleName);
}
}
}
private String ensureTestUserExists(Keycloak adminClient) {
var usersResource = adminClient.realms().realm(TEST_REALM).users();
// Chercher l'utilisateur
List<UserRepresentation> users = usersResource.search(TEST_USER, true);
String userId;
if (users != null && !users.isEmpty()) {
userId = users.get(0).getId();
log.debug("Utilisateur '{}' existe déjà (ID: {})", TEST_USER, userId);
} else {
log.info("Création de l'utilisateur '{}'...", TEST_USER);
UserRepresentation user = new UserRepresentation();
user.setUsername(TEST_USER);
user.setEmail(TEST_EMAIL);
user.setFirstName("Test");
user.setLastName("User");
user.setEnabled(true);
user.setEmailVerified(true);
jakarta.ws.rs.core.Response response = usersResource.create(user);
userId = getCreatedId(response);
// Définir le mot de passe
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setValue(TEST_PASSWORD);
credential.setTemporary(false);
usersResource.get(userId).resetPassword(credential);
log.info("✓ Utilisateur '{}' créé (ID: {})", TEST_USER, userId);
}
return userId;
}
private void assignRolesToUser(Keycloak adminClient, String userId) {
var usersResource = adminClient.realms().realm(TEST_REALM).users();
var rolesResource = adminClient.realms().realm(TEST_REALM).roles();
List<RoleRepresentation> rolesToAssign = new ArrayList<>();
for (String roleName : REQUIRED_ROLES) {
RoleRepresentation role = rolesResource.get(roleName).toRepresentation();
rolesToAssign.add(role);
}
usersResource.get(userId).roles().realmLevel().add(rolesToAssign);
log.info("✓ {} rôles assignés à l'utilisateur", rolesToAssign.size());
}
private void ensureClientAndMapper(Keycloak adminClient) {
try {
var clientsResource = adminClient.realms().realm(TEST_REALM).clients();
var clients = clientsResource.findByClientId(CLIENT_ID);
String clientId;
if (clients == null || clients.isEmpty()) {
log.info("Création du client '{}'...", CLIENT_ID);
org.keycloak.representations.idm.ClientRepresentation client = new org.keycloak.representations.idm.ClientRepresentation();
client.setClientId(CLIENT_ID);
client.setName(CLIENT_ID);
client.setDescription("Client OIDC pour lions-user-manager");
client.setEnabled(true);
client.setPublicClient(false);
client.setStandardFlowEnabled(true);
client.setDirectAccessGrantsEnabled(true);
client.setFullScopeAllowed(true); // IMPORTANT: Permet d'inclure tous les rôles dans le token
client.setRedirectUris(java.util.Arrays.asList(
"http://localhost:8080/*",
"http://localhost:8080/auth/callback"
));
client.setWebOrigins(java.util.Arrays.asList("http://localhost:8080"));
client.setSecret("NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO");
jakarta.ws.rs.core.Response response = clientsResource.create(client);
clientId = getCreatedId(response);
log.info("✓ Client '{}' créé (ID: {})", CLIENT_ID, clientId);
} else {
clientId = clients.get(0).getId();
log.debug("Client '{}' existe déjà (ID: {})", CLIENT_ID, clientId);
}
// Ajouter le scope "roles" par défaut au client
try {
var clientScopesResource = adminClient.realms().realm(TEST_REALM).clientScopes();
var defaultClientScopes = clientScopesResource.findAll();
var rolesScope = defaultClientScopes.stream()
.filter(s -> "roles".equals(s.getName()))
.findFirst();
if (rolesScope.isPresent()) {
var clientResource = clientsResource.get(clientId);
var defaultScopes = clientResource.getDefaultClientScopes();
boolean hasRolesScope = defaultScopes.stream()
.anyMatch(s -> "roles".equals(s.getName()));
if (!hasRolesScope) {
log.info("Ajout du scope 'roles' au client...");
clientResource.addDefaultClientScope(rolesScope.get().getId());
log.info("✓ Scope 'roles' ajouté au client");
} else {
log.debug("Scope 'roles' déjà présent sur le client");
}
} else {
log.warn("Scope 'roles' non trouvé dans les scopes par défaut du realm");
}
} catch (Exception e) {
log.warn("Erreur lors de l'ajout du scope 'roles': {}", e.getMessage());
}
// Le scope "roles" de Keycloak crée automatiquement realm_access.roles
// Pas besoin de mapper personnalisé si on utilise realm_access.roles
// Le mapper personnalisé peut créer des conflits (comme dans unionflow)
log.debug("Le scope 'roles' est utilisé pour créer realm_access.roles automatiquement");
} catch (Exception e) {
log.warn("Erreur lors de la vérification/création du client: {}", e.getMessage(), e);
}
}
private String getCreatedId(jakarta.ws.rs.core.Response response) {
jakarta.ws.rs.core.Response.StatusType statusInfo = response.getStatusInfo();
if (statusInfo.equals(jakarta.ws.rs.core.Response.Status.CREATED)) {
String location = response.getLocation().getPath();
return location.substring(location.lastIndexOf('/') + 1);
}
throw new RuntimeException("Erreur lors de la création: " + statusInfo.getStatusCode());
}
}

View File

@@ -22,11 +22,11 @@ public class RoleMapper {
return RoleDTO.builder()
.id(roleRep.getId())
.nom(roleRep.getName())
.name(roleRep.getName())
.description(roleRep.getDescription())
.typeRole(typeRole)
.realmName(realmName)
.composite(roleRep.isComposite() != null ? roleRep.isComposite() : false)
.composite(roleRep.isComposite())
.build();
}
@@ -40,7 +40,7 @@ public class RoleMapper {
RoleRepresentation roleRep = new RoleRepresentation();
roleRep.setId(roleDTO.getId());
roleRep.setName(roleDTO.getNom());
roleRep.setName(roleDTO.getName());
roleRep.setDescription(roleDTO.getDescription());
roleRep.setComposite(roleDTO.isComposite());
roleRep.setClientRole(roleDTO.getTypeRole() == TypeRole.CLIENT_ROLE);

View File

@@ -0,0 +1,364 @@
package dev.lions.user.manager.resource;
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.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotBlank;
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.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* REST Resource pour l'audit et la consultation des logs
*/
@Path("/api/audit")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Audit", description = "Consultation des logs d'audit et statistiques")
@Slf4j
public class AuditResource {
@Inject
AuditService auditService;
@POST
@Path("/search")
@Operation(summary = "Rechercher des logs d'audit", description = "Recherche avancée de logs selon critères")
@APIResponses({
@APIResponse(responseCode = "200", description = "Résultats de recherche"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response searchLogs(
@QueryParam("acteur") String acteurUsername,
@QueryParam("dateDebut") String dateDebutStr,
@QueryParam("dateFin") String dateFinStr,
@QueryParam("typeAction") TypeActionAudit typeAction,
@QueryParam("ressourceType") String ressourceType,
@QueryParam("succes") Boolean succes,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("pageSize") @DefaultValue("50") int pageSize
) {
log.info("POST /api/audit/search - Recherche de logs");
try {
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
// Utiliser findByActeur si acteurUsername est fourni, sinon findByRealm
List<AuditLogDTO> logs;
if (acteurUsername != null && !acteurUsername.isBlank()) {
logs = auditService.findByActeur(acteurUsername, dateDebut, dateFin, page, pageSize);
} else {
// Pour une recherche générale, utiliser findByRealm (on utilise "master" par défaut)
logs = auditService.findByRealm("master", dateDebut, dateFin, page, pageSize);
}
// Filtrer par typeAction, ressourceType et succes si fournis
if (typeAction != null || ressourceType != null || succes != null) {
logs = logs.stream()
.filter(log -> typeAction == null || typeAction.equals(log.getTypeAction()))
.filter(log -> ressourceType == null || ressourceType.equals(log.getRessourceType()))
.filter(log -> succes == null || succes == log.isSuccessful())
.collect(java.util.stream.Collectors.toList());
}
return Response.ok(logs).build();
} catch (Exception e) {
log.error("Erreur lors de la recherche de logs d'audit", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/actor/{acteurUsername}")
@Operation(summary = "Récupérer les logs d'un acteur", description = "Liste les derniers logs d'un utilisateur")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des logs"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response getLogsByActor(
@Parameter(description = "Username de l'acteur") @PathParam("acteurUsername") @NotBlank String acteurUsername,
@Parameter(description = "Nombre de logs à retourner") @QueryParam("limit") @DefaultValue("100") int limit
) {
log.info("GET /api/audit/actor/{} - Limite: {}", acteurUsername, limit);
try {
List<AuditLogDTO> logs = auditService.findByActeur(acteurUsername, null, null, 0, limit);
return Response.ok(logs).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des logs de l'acteur {}", acteurUsername, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/resource/{ressourceType}/{ressourceId}")
@Operation(summary = "Récupérer les logs d'une ressource", description = "Liste les derniers logs d'une ressource spécifique")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des logs"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response getLogsByResource(
@PathParam("ressourceType") @NotBlank String ressourceType,
@PathParam("ressourceId") @NotBlank String ressourceId,
@QueryParam("limit") @DefaultValue("100") int limit
) {
log.info("GET /api/audit/resource/{}/{} - Limite: {}", ressourceType, ressourceId, limit);
try {
List<AuditLogDTO> logs = auditService.findByRessource(ressourceType, ressourceId, null, null, 0, limit);
return Response.ok(logs).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des logs de la ressource {}:{}",
ressourceType, ressourceId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/action/{typeAction}")
@Operation(summary = "Récupérer les logs par type d'action", description = "Liste les logs d'un type d'action spécifique")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des logs"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response getLogsByAction(
@PathParam("typeAction") TypeActionAudit typeAction,
@QueryParam("dateDebut") String dateDebutStr,
@QueryParam("dateFin") String dateFinStr,
@QueryParam("limit") @DefaultValue("100") int limit
) {
log.info("GET /api/audit/action/{} - Limite: {}", typeAction, limit);
try {
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
List<AuditLogDTO> logs = auditService.findByTypeAction(typeAction, "master", dateDebut, dateFin, 0, limit);
return Response.ok(logs).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des logs de type {}", typeAction, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/stats/actions")
@Operation(summary = "Statistiques par type d'action", description = "Retourne le nombre de logs par type d'action")
@APIResponses({
@APIResponse(responseCode = "200", description = "Statistiques des actions"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response getActionStatistics(
@QueryParam("dateDebut") String dateDebutStr,
@QueryParam("dateFin") String dateFinStr
) {
log.info("GET /api/audit/stats/actions - Période: {} à {}", dateDebutStr, dateFinStr);
try {
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
Map<TypeActionAudit, Long> stats = auditService.countByActionType("master", dateDebut, dateFin);
return Response.ok(stats).build();
} catch (Exception e) {
log.error("Erreur lors du calcul des statistiques d'actions", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/stats/users")
@Operation(summary = "Statistiques par utilisateur", description = "Retourne le nombre d'actions par utilisateur")
@APIResponses({
@APIResponse(responseCode = "200", description = "Statistiques des utilisateurs"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response getUserActivityStatistics(
@QueryParam("dateDebut") String dateDebutStr,
@QueryParam("dateFin") String dateFinStr
) {
log.info("GET /api/audit/stats/users - Période: {} à {}", dateDebutStr, dateFinStr);
try {
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
Map<String, Long> stats = auditService.countByActeur("master", dateDebut, dateFin);
return Response.ok(stats).build();
} catch (Exception e) {
log.error("Erreur lors du calcul des statistiques utilisateurs", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/stats/failures")
@Operation(summary = "Comptage des échecs", description = "Retourne le nombre d'échecs sur une période")
@APIResponses({
@APIResponse(responseCode = "200", description = "Nombre d'échecs"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response getFailureCount(
@QueryParam("dateDebut") String dateDebutStr,
@QueryParam("dateFin") String dateFinStr
) {
log.info("GET /api/audit/stats/failures - Période: {} à {}", dateDebutStr, dateFinStr);
try {
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
Map<String, Long> successVsFailure = auditService.countSuccessVsFailure("master", dateDebut, dateFin);
long count = successVsFailure.getOrDefault("failure", 0L);
return Response.ok(new CountResponse(count)).build();
} catch (Exception e) {
log.error("Erreur lors du comptage des échecs", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/stats/success")
@Operation(summary = "Comptage des succès", description = "Retourne le nombre de succès sur une période")
@APIResponses({
@APIResponse(responseCode = "200", description = "Nombre de succès"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response getSuccessCount(
@QueryParam("dateDebut") String dateDebutStr,
@QueryParam("dateFin") String dateFinStr
) {
log.info("GET /api/audit/stats/success - Période: {} à {}", dateDebutStr, dateFinStr);
try {
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
Map<String, Long> successVsFailure = auditService.countSuccessVsFailure("master", dateDebut, dateFin);
long count = successVsFailure.getOrDefault("success", 0L);
return Response.ok(new CountResponse(count)).build();
} catch (Exception e) {
log.error("Erreur lors du comptage des succès", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/export/csv")
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Exporter les logs en CSV", description = "Génère un fichier CSV des logs d'audit")
@APIResponses({
@APIResponse(responseCode = "200", description = "Fichier CSV généré"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "auditor"})
public Response exportLogsToCSV(
@QueryParam("dateDebut") String dateDebutStr,
@QueryParam("dateFin") String dateFinStr
) {
log.info("GET /api/audit/export/csv - Période: {} à {}", dateDebutStr, dateFinStr);
try {
LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null;
LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null;
String csvContent = auditService.exportToCSV("master", dateDebut, dateFin);
return Response.ok(csvContent)
.header("Content-Disposition", "attachment; filename=\"audit-logs-" +
LocalDateTime.now().toString().replace(":", "-") + ".csv\"")
.build();
} catch (Exception e) {
log.error("Erreur lors de l'export CSV des logs", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@DELETE
@Path("/purge")
@Operation(summary = "Purger les anciens logs", description = "Supprime les logs de plus de X jours")
@APIResponses({
@APIResponse(responseCode = "204", description = "Purge effectuée"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin"})
public Response purgeOldLogs(
@QueryParam("joursAnciennete") @DefaultValue("90") int joursAnciennete
) {
log.info("DELETE /api/audit/purge - Suppression des logs de plus de {} jours", joursAnciennete);
try {
LocalDateTime dateLimite = LocalDateTime.now().minusDays(joursAnciennete);
auditService.purgeOldLogs(dateLimite);
return Response.noContent().build();
} catch (Exception e) {
log.error("Erreur lors de la purge des logs", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
// ==================== DTOs internes ====================
@Schema(description = "Réponse de comptage")
public static class CountResponse {
@Schema(description = "Nombre d'éléments")
public long count;
public CountResponse(long count) {
this.count = count;
}
}
@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;
}
}
}

View File

@@ -28,16 +28,12 @@ public class HealthResourceEndpoint {
Map<String, Object> health = new HashMap<>();
try {
boolean connected = keycloakAdminClient.isConnected();
health.put("status", connected ? "UP" : "DOWN");
health.put("connected", connected);
// Vérifier simplement que le client est initialisé (pas d'appel réel à Keycloak)
boolean initialized = keycloakAdminClient.getInstance() != null;
health.put("status", initialized ? "UP" : "DOWN");
health.put("connected", initialized);
health.put("message", initialized ? "Client Keycloak initialisé" : "Client non initialisé");
health.put("timestamp", System.currentTimeMillis());
if (connected) {
// Récupérer info serveur Keycloak
var serverInfo = keycloakAdminClient.getInstance().serverInfo().getInfo();
health.put("keycloakVersion", serverInfo.getSystemInfo().getVersion());
}
} catch (Exception e) {
log.error("Erreur health check Keycloak", e);
health.put("status", "ERROR");

View File

@@ -1,6 +1,8 @@
package dev.lions.user.manager.resource;
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.service.RoleService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
@@ -20,6 +22,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.Optional;
/**
* REST Resource pour la gestion des rôles Keycloak
@@ -53,7 +56,7 @@ public class RoleResource {
@QueryParam("realm") @NotBlank String realmName
) {
log.info("POST /api/roles/realm - Création du rôle realm: {} dans le realm: {}",
roleDTO.getNom(), realmName);
roleDTO.getName(), realmName);
try {
RoleDTO createdRole = roleService.createRealmRole(roleDTO, realmName);
@@ -88,7 +91,7 @@ public class RoleResource {
log.info("GET /api/roles/realm/{} - realm: {}", roleName, realmName);
try {
return roleService.getRealmRoleByName(roleName, realmName)
return roleService.getRoleByName(roleName, realmName, dev.lions.user.manager.enums.role.TypeRole.REALM_ROLE, null)
.map(role -> Response.ok(role).build())
.orElse(Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Rôle non trouvé"))
@@ -143,7 +146,17 @@ public class RoleResource {
log.info("PUT /api/roles/realm/{} - realm: {}", roleName, realmName);
try {
RoleDTO updatedRole = roleService.updateRealmRole(roleName, roleDTO, realmName);
// Récupérer l'ID du rôle par son nom
Optional<RoleDTO> existingRole = roleService.getRoleByName(roleName, realmName,
dev.lions.user.manager.enums.role.TypeRole.REALM_ROLE, null);
if (existingRole.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Rôle non trouvé"))
.build();
}
RoleDTO updatedRole = roleService.updateRole(existingRole.get().getId(), roleDTO, realmName,
dev.lions.user.manager.enums.role.TypeRole.REALM_ROLE, null);
return Response.ok(updatedRole).build();
} catch (Exception e) {
log.error("Erreur lors de la mise à jour du rôle realm {}", roleName, e);
@@ -169,7 +182,17 @@ public class RoleResource {
log.info("DELETE /api/roles/realm/{} - realm: {}", roleName, realmName);
try {
roleService.deleteRealmRole(roleName, realmName);
// Récupérer l'ID du rôle par son nom
Optional<RoleDTO> existingRole = roleService.getRoleByName(roleName, realmName,
dev.lions.user.manager.enums.role.TypeRole.REALM_ROLE, null);
if (existingRole.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Rôle non trouvé"))
.build();
}
roleService.deleteRole(existingRole.get().getId(), realmName,
dev.lions.user.manager.enums.role.TypeRole.REALM_ROLE, null);
return Response.noContent().build();
} catch (Exception e) {
log.error("Erreur lors de la suppression du rôle realm {}", roleName, e);
@@ -234,7 +257,8 @@ public class RoleResource {
log.info("GET /api/roles/client/{}/{} - realm: {}", clientId, roleName, realmName);
try {
return roleService.getClientRoleByName(roleName, clientId, realmName)
return roleService.getRoleByName(roleName, realmName,
dev.lions.user.manager.enums.role.TypeRole.CLIENT_ROLE, clientId)
.map(role -> Response.ok(role).build())
.orElse(Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Rôle client non trouvé"))
@@ -262,7 +286,7 @@ public class RoleResource {
log.info("GET /api/roles/client/{} - realm: {}", clientId, realmName);
try {
List<RoleDTO> roles = roleService.getAllClientRoles(clientId, realmName);
List<RoleDTO> roles = roleService.getAllClientRoles(realmName, clientId);
return Response.ok(roles).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des rôles du client {}", clientId, e);
@@ -289,7 +313,17 @@ public class RoleResource {
log.info("DELETE /api/roles/client/{}/{} - realm: {}", clientId, roleName, realmName);
try {
roleService.deleteClientRole(roleName, clientId, realmName);
// Récupérer l'ID du rôle par son nom
Optional<RoleDTO> existingRole = roleService.getRoleByName(roleName, realmName,
dev.lions.user.manager.enums.role.TypeRole.CLIENT_ROLE, clientId);
if (existingRole.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Rôle client non trouvé"))
.build();
}
roleService.deleteRole(existingRole.get().getId(), realmName,
dev.lions.user.manager.enums.role.TypeRole.CLIENT_ROLE, clientId);
return Response.noContent().build();
} catch (Exception e) {
log.error("Erreur lors de la suppression du rôle client {}/{}", clientId, roleName, e);
@@ -318,7 +352,13 @@ public class RoleResource {
log.info("POST /api/roles/assign/realm/{} - Attribution de {} rôles", userId, request.roleNames.size());
try {
roleService.assignRealmRolesToUser(userId, request.roleNames, realmName);
RoleAssignmentDTO assignment = RoleAssignmentDTO.builder()
.userId(userId)
.roleNames(request.roleNames)
.typeRole(TypeRole.REALM_ROLE)
.realmName(realmName)
.build();
roleService.assignRolesToUser(assignment);
return Response.noContent().build();
} catch (Exception e) {
log.error("Erreur lors de l'attribution des rôles realm à l'utilisateur {}", userId, e);
@@ -345,7 +385,13 @@ public class RoleResource {
log.info("POST /api/roles/revoke/realm/{} - Révocation de {} rôles", userId, request.roleNames.size());
try {
roleService.revokeRealmRolesFromUser(userId, request.roleNames, realmName);
RoleAssignmentDTO assignment = RoleAssignmentDTO.builder()
.userId(userId)
.roleNames(request.roleNames)
.typeRole(TypeRole.REALM_ROLE)
.realmName(realmName)
.build();
roleService.revokeRolesFromUser(assignment);
return Response.noContent().build();
} catch (Exception e) {
log.error("Erreur lors de la révocation des rôles realm de l'utilisateur {}", userId, e);
@@ -374,7 +420,14 @@ public class RoleResource {
clientId, userId, request.roleNames.size());
try {
roleService.assignClientRolesToUser(userId, clientId, request.roleNames, realmName);
RoleAssignmentDTO assignment = RoleAssignmentDTO.builder()
.userId(userId)
.roleNames(request.roleNames)
.typeRole(TypeRole.CLIENT_ROLE)
.realmName(realmName)
.clientName(clientId)
.build();
roleService.assignRolesToUser(assignment);
return Response.noContent().build();
} catch (Exception e) {
log.error("Erreur lors de l'attribution des rôles client à l'utilisateur {}", userId, e);
@@ -454,7 +507,24 @@ public class RoleResource {
log.info("POST /api/roles/composite/{}/add - Ajout de {} composites", roleName, request.roleNames.size());
try {
roleService.addCompositesToRealmRole(roleName, request.roleNames, realmName);
// Récupérer l'ID du rôle parent par son nom
Optional<RoleDTO> parentRole = roleService.getRoleByName(roleName, realmName, TypeRole.REALM_ROLE, null);
if (parentRole.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Rôle parent non trouvé"))
.build();
}
// Convertir les noms de rôles en IDs
List<String> childRoleIds = request.roleNames.stream()
.map(name -> {
Optional<RoleDTO> role = roleService.getRoleByName(name, realmName, TypeRole.REALM_ROLE, null);
return role.map(RoleDTO::getId).orElse(null);
})
.filter(id -> id != null)
.collect(java.util.stream.Collectors.toList());
roleService.addCompositeRoles(parentRole.get().getId(), childRoleIds, realmName, TypeRole.REALM_ROLE, null);
return Response.noContent().build();
} catch (Exception e) {
log.error("Erreur lors de l'ajout des composites au rôle {}", roleName, e);
@@ -479,7 +549,15 @@ public class RoleResource {
log.info("GET /api/roles/composite/{} - realm: {}", roleName, realmName);
try {
List<RoleDTO> composites = roleService.getCompositeRoles(roleName, realmName);
// Récupérer l'ID du rôle par son nom
Optional<RoleDTO> role = roleService.getRoleByName(roleName, realmName, TypeRole.REALM_ROLE, null);
if (role.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse("Rôle non trouvé"))
.build();
}
List<RoleDTO> composites = roleService.getCompositeRoles(role.get().getId(), realmName, TypeRole.REALM_ROLE, null);
return Response.ok(composites).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des composites du rôle {}", roleName, e);

View File

@@ -0,0 +1,318 @@
package dev.lions.user.manager.resource;
import dev.lions.user.manager.dto.role.RoleDTO;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.service.SyncService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotBlank;
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;
import java.util.Map;
/**
* REST Resource pour la synchronisation avec Keycloak
*/
@Path("/api/sync")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Sync", description = "Synchronisation avec Keycloak et health checks")
@Slf4j
public class SyncResource {
@Inject
SyncService syncService;
@POST
@Path("/users/{realmName}")
@Operation(summary = "Synchroniser les utilisateurs", description = "Synchronise tous les utilisateurs depuis Keycloak")
@APIResponses({
@APIResponse(responseCode = "200", description = "Utilisateurs synchronisés"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "sync_manager"})
public Response syncUsers(
@Parameter(description = "Nom du realm") @PathParam("realmName") @NotBlank String realmName
) {
log.info("POST /api/sync/users/{} - Synchronisation des utilisateurs", realmName);
try {
int count = syncService.syncUsersFromRealm(realmName);
return Response.ok(new SyncUsersResponse(count, null)).build();
} catch (Exception e) {
log.error("Erreur lors de la synchronisation des utilisateurs du realm {}", realmName, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@POST
@Path("/roles/realm/{realmName}")
@Operation(summary = "Synchroniser les rôles realm", description = "Synchronise tous les rôles realm depuis Keycloak")
@APIResponses({
@APIResponse(responseCode = "200", description = "Rôles realm synchronisés"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "sync_manager"})
public Response syncRealmRoles(
@PathParam("realmName") @NotBlank String realmName
) {
log.info("POST /api/sync/roles/realm/{} - Synchronisation des rôles realm", realmName);
try {
int count = syncService.syncRolesFromRealm(realmName);
return Response.ok(new SyncRolesResponse(count, null)).build();
} catch (Exception e) {
log.error("Erreur lors de la synchronisation des rôles realm du realm {}", realmName, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@POST
@Path("/roles/client/{clientId}/{realmName}")
@Operation(summary = "Synchroniser les rôles client", description = "Synchronise tous les rôles d'un client depuis Keycloak")
@APIResponses({
@APIResponse(responseCode = "200", description = "Rôles client synchronisés"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "sync_manager"})
public Response syncClientRoles(
@PathParam("clientId") @NotBlank String clientId,
@PathParam("realmName") @NotBlank String realmName
) {
log.info("POST /api/sync/roles/client/{}/{} - Synchronisation des rôles client",
clientId, realmName);
try {
// Note: syncRolesFromRealm synchronise tous les rôles realm, pas les rôles client spécifiques
// Pour les rôles client, on synchronise tous les rôles du realm (incluant les rôles client)
int count = syncService.syncRolesFromRealm(realmName);
return Response.ok(new SyncRolesResponse(count, null)).build();
} catch (Exception e) {
log.error("Erreur lors de la synchronisation des rôles client du client {} (realm: {})",
clientId, realmName, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@POST
@Path("/all/{realmName}")
@Operation(summary = "Synchronisation complète", description = "Synchronise utilisateurs et rôles depuis Keycloak")
@APIResponses({
@APIResponse(responseCode = "200", description = "Synchronisation complète effectuée"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "sync_manager"})
public Response syncAll(
@PathParam("realmName") @NotBlank String realmName
) {
log.info("POST /api/sync/all/{} - Synchronisation complète", realmName);
try {
Map<String, Object> result = syncService.forceSyncRealm(realmName);
return Response.ok(result).build();
} catch (Exception e) {
log.error("Erreur lors de la synchronisation complète du realm {}", realmName, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/health")
@Operation(summary = "Vérifier la santé de Keycloak", description = "Retourne le statut de santé de Keycloak")
@APIResponses({
@APIResponse(responseCode = "200", description = "Statut de santé"),
@APIResponse(responseCode = "503", description = "Keycloak non accessible")
})
@RolesAllowed({"admin", "sync_manager", "auditor"})
public Response checkHealth() {
log.info("GET /api/sync/health - Vérification de la santé de Keycloak");
try {
boolean healthy = syncService.isKeycloakAvailable();
if (healthy) {
return Response.ok(new HealthCheckResponse(true, "Keycloak est accessible")).build();
} else {
return Response.status(Response.Status.SERVICE_UNAVAILABLE)
.entity(new HealthCheckResponse(false, "Keycloak n'est pas accessible"))
.build();
}
} catch (Exception e) {
log.error("Erreur lors de la vérification de santé de Keycloak", e);
return Response.status(Response.Status.SERVICE_UNAVAILABLE)
.entity(new HealthCheckResponse(false, e.getMessage()))
.build();
}
}
@GET
@Path("/health/detailed")
@Operation(summary = "Statut de santé détaillé", description = "Retourne le statut de santé détaillé de Keycloak")
@APIResponses({
@APIResponse(responseCode = "200", description = "Statut détaillé"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "sync_manager"})
public Response getDetailedHealthStatus() {
log.info("GET /api/sync/health/detailed - Statut de santé détaillé");
try {
Map<String, Object> status = syncService.getKeycloakHealthInfo();
return Response.ok(status).build(); // status est maintenant une Map<String, Object>
} catch (Exception e) {
log.error("Erreur lors de la récupération du statut de santé détaillé", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/check/realm/{realmName}")
@Operation(summary = "Vérifier l'existence d'un realm", description = "Vérifie si un realm existe")
@APIResponses({
@APIResponse(responseCode = "200", description = "Résultat de la vérification"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "sync_manager"})
public Response checkRealmExists(
@PathParam("realmName") @NotBlank String realmName
) {
log.info("GET /api/sync/check/realm/{} - Vérification de l'existence", realmName);
try {
// Vérifier l'existence du realm en essayant de synchroniser (si ça marche, le realm existe)
boolean exists = false;
try {
syncService.syncUsersFromRealm(realmName);
exists = true;
} catch (Exception e) {
exists = false;
}
return Response.ok(new ExistsCheckResponse(exists, "realm", realmName)).build();
} catch (Exception e) {
log.error("Erreur lors de la vérification du realm {}", realmName, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
@GET
@Path("/check/user/{userId}")
@Operation(summary = "Vérifier l'existence d'un utilisateur", description = "Vérifie si un utilisateur existe")
@APIResponses({
@APIResponse(responseCode = "200", description = "Résultat de la vérification"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
@RolesAllowed({"admin", "sync_manager"})
public Response checkUserExists(
@PathParam("userId") @NotBlank String userId,
@QueryParam("realm") @NotBlank String realmName
) {
log.info("GET /api/sync/check/user/{} - realm: {}", userId, realmName);
try {
// Vérifier l'existence de l'utilisateur n'est plus disponible directement
// On retourne false car cette fonctionnalité n'est plus dans l'interface
boolean exists = false;
return Response.ok(new ExistsCheckResponse(exists, "user", userId)).build();
} catch (Exception e) {
log.error("Erreur lors de la vérification de l'utilisateur {} dans le realm {}",
userId, realmName, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorResponse(e.getMessage()))
.build();
}
}
// ==================== DTOs internes ====================
@Schema(description = "Réponse de synchronisation d'utilisateurs")
public static class SyncUsersResponse {
@Schema(description = "Nombre d'utilisateurs synchronisés")
public int count;
@Schema(description = "Liste des utilisateurs synchronisés")
public List<UserDTO> users;
public SyncUsersResponse(int count, List<UserDTO> users) {
this.count = count;
this.users = users;
}
}
@Schema(description = "Réponse de synchronisation de rôles")
public static class SyncRolesResponse {
@Schema(description = "Nombre de rôles synchronisés")
public int count;
@Schema(description = "Liste des rôles synchronisés")
public List<RoleDTO> roles;
public SyncRolesResponse(int count, List<RoleDTO> roles) {
this.count = count;
this.roles = roles;
}
}
@Schema(description = "Réponse de vérification de santé")
public static class HealthCheckResponse {
@Schema(description = "Indique si Keycloak est accessible")
public boolean healthy;
@Schema(description = "Message descriptif")
public String message;
public HealthCheckResponse(boolean healthy, String message) {
this.healthy = healthy;
this.message = message;
}
}
@Schema(description = "Réponse de vérification d'existence")
public static class ExistsCheckResponse {
@Schema(description = "Indique si la ressource existe")
public boolean exists;
@Schema(description = "Type de ressource (realm, user, client, etc.)")
public String resourceType;
@Schema(description = "Identifiant de la ressource")
public String resourceId;
public ExistsCheckResponse(boolean exists, String resourceType, String resourceId) {
this.exists = exists;
this.resourceType = resourceType;
this.resourceId = resourceId;
}
}
@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;
}
}
}

View File

@@ -0,0 +1,94 @@
package dev.lions.user.manager.security;
import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.ext.Provider;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import java.security.Principal;
/**
* Filtre JAX-RS pour remplacer le SecurityContext en mode développement
* En dev, remplace le SecurityContext par un mock qui autorise tous les rôles
* En prod, laisse le SecurityContext réel de Quarkus
*/
@Provider
@Priority(Priorities.AUTHENTICATION - 10) // S'exécute très tôt, avant l'authentification
public class DevSecurityContextProducer implements ContainerRequestFilter {
private static final Logger LOG = Logger.getLogger(DevSecurityContextProducer.class);
@Inject
@ConfigProperty(name = "quarkus.profile", defaultValue = "prod")
String profile;
@Inject
@ConfigProperty(name = "quarkus.oidc.enabled", defaultValue = "true")
boolean oidcEnabled;
@Override
public void filter(ContainerRequestContext requestContext) {
// Détecter le mode dev : si OIDC est désactivé, on est probablement en dev
// ou si le profil est explicitement "dev" ou "development"
boolean isDevMode = !oidcEnabled || "dev".equals(profile) || "development".equals(profile);
if (isDevMode) {
String path = requestContext.getUriInfo().getPath();
LOG.infof("Mode dev détecté (profile=%s, oidc.enabled=%s): remplacement du SecurityContext pour le chemin %s",
profile, oidcEnabled, path);
SecurityContext original = requestContext.getSecurityContext();
requestContext.setSecurityContext(new DevSecurityContext(original));
LOG.debugf("SecurityContext remplacé - isUserInRole('admin')=%s, isUserInRole('user_manager')=%s",
new DevSecurityContext(original).isUserInRole("admin"),
new DevSecurityContext(original).isUserInRole("user_manager"));
} else {
LOG.debugf("Mode prod - SecurityContext original conservé (profile=%s, oidc.enabled=%s)", profile, oidcEnabled);
}
}
/**
* SecurityContext mock pour le mode développement
* Simule un utilisateur avec tous les rôles nécessaires
*/
private static class DevSecurityContext implements SecurityContext {
private final SecurityContext original;
private final Principal principal = new Principal() {
@Override
public String getName() {
return "dev-user";
}
};
public DevSecurityContext(SecurityContext original) {
this.original = original;
}
@Override
public Principal getUserPrincipal() {
return principal;
}
@Override
public boolean isUserInRole(String role) {
// En dev, autoriser tous les rôles
return true;
}
@Override
public boolean isSecure() {
return original != null ? original.isSecure() : false;
}
@Override
public String getAuthenticationScheme() {
return "DEV";
}
}
}

View File

@@ -61,9 +61,9 @@ public class AuditServiceImpl implements AuditService {
auditLog.getTypeAction(),
auditLog.getActeurUsername(),
auditLog.getRessourceType() + ":" + auditLog.getRessourceId(),
auditLog.isSucces(),
auditLog.getAdresseIp(),
auditLog.getDetails());
auditLog.isSuccessful(),
auditLog.getIpAddress(),
auditLog.getDescription());
// Stocker en mémoire
auditLogs.put(auditLog.getId(), auditLog);
@@ -79,17 +79,21 @@ public class AuditServiceImpl implements AuditService {
}
@Override
public void logSuccess(@NotBlank String acteurUsername, @NotNull TypeActionAudit typeAction,
@NotBlank String ressourceType, @NotBlank String ressourceId,
String adresseIp, String details) {
public void logSuccess(@NotNull TypeActionAudit typeAction,
@NotBlank String ressourceType,
String ressourceId,
String ressourceName,
@NotBlank String realmName,
@NotBlank String acteurUserId,
String description) {
AuditLogDTO auditLog = AuditLogDTO.builder()
.acteurUsername(acteurUsername)
.acteurUserId(acteurUserId)
.acteurUsername(acteurUserId) // Utiliser acteurUserId comme username pour l'instant
.typeAction(typeAction)
.ressourceType(ressourceType)
.ressourceId(ressourceId)
.succes(true)
.adresseIp(adresseIp)
.details(details)
.ressourceId(ressourceId != null ? ressourceId : "")
.success(true)
.description(description)
.dateAction(LocalDateTime.now())
.build();
@@ -97,17 +101,22 @@ public class AuditServiceImpl implements AuditService {
}
@Override
public void logFailure(@NotBlank String acteurUsername, @NotNull TypeActionAudit typeAction,
@NotBlank String ressourceType, @NotBlank String ressourceId,
String adresseIp, @NotBlank String messageErreur) {
public void logFailure(@NotNull TypeActionAudit typeAction,
@NotBlank String ressourceType,
String ressourceId,
String ressourceName,
@NotBlank String realmName,
@NotBlank String acteurUserId,
String errorCode,
String errorMessage) {
AuditLogDTO auditLog = AuditLogDTO.builder()
.acteurUsername(acteurUsername)
.acteurUserId(acteurUserId)
.acteurUsername(acteurUserId) // Utiliser acteurUserId comme username pour l'instant
.typeAction(typeAction)
.ressourceType(ressourceType)
.ressourceId(ressourceId)
.succes(false)
.adresseIp(adresseIp)
.messageErreur(messageErreur)
.ressourceId(ressourceId != null ? ressourceId : "")
.success(false)
.errorMessage(errorMessage)
.dateAction(LocalDateTime.now())
.build();
@@ -115,7 +124,155 @@ public class AuditServiceImpl implements AuditService {
}
@Override
public List<AuditLogDTO> searchLogs(@NotBlank String acteurUsername, LocalDateTime dateDebut,
public List<AuditLogDTO> findByActeur(@NotBlank String acteurUserId,
LocalDateTime dateDebut,
LocalDateTime dateFin,
int page,
int pageSize) {
return searchLogs(acteurUserId, dateDebut, dateFin, null, null, null, page, pageSize);
}
@Override
public List<AuditLogDTO> findByRessource(@NotBlank String ressourceType,
@NotBlank String ressourceId,
LocalDateTime dateDebut,
LocalDateTime dateFin,
int page,
int pageSize) {
return searchLogs(null, dateDebut, dateFin, null, ressourceType, null, page, pageSize)
.stream()
.filter(log -> ressourceId.equals(log.getRessourceId()))
.collect(Collectors.toList());
}
@Override
public List<AuditLogDTO> findByTypeAction(@NotNull TypeActionAudit typeAction,
@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin,
int page,
int pageSize) {
return searchLogs(null, dateDebut, dateFin, typeAction, null, null, page, pageSize);
}
@Override
public List<AuditLogDTO> findByRealm(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin,
int page,
int pageSize) {
// Pour l'instant, on retourne tous les logs car on n'a pas de champ realmName dans AuditLogDTO
return searchLogs(null, dateDebut, dateFin, null, null, null, page, pageSize);
}
@Override
public List<AuditLogDTO> findFailures(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin,
int page,
int pageSize) {
return searchLogs(null, dateDebut, dateFin, null, null, false, page, pageSize);
}
@Override
public List<AuditLogDTO> findCriticalActions(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin,
int page,
int pageSize) {
// Les actions critiques sont USER_DELETE, ROLE_DELETE, etc.
return auditLogs.values().stream()
.filter(log -> {
TypeActionAudit type = log.getTypeAction();
return type == TypeActionAudit.USER_DELETE ||
type == TypeActionAudit.ROLE_DELETE ||
type == TypeActionAudit.SESSION_REVOKE_ALL;
})
.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) -> b.getDateAction().compareTo(a.getDateAction()))
.skip((long) page * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
}
@Override
public Map<TypeActionAudit, Long> countByActionType(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin) {
return getActionStatistics(dateDebut, dateFin);
}
@Override
public Map<String, Long> countByActeur(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin) {
return getUserActivityStatistics(dateDebut, dateFin);
}
@Override
public Map<String, Long> countSuccessVsFailure(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin) {
long successCount = getSuccessCount(dateDebut, dateFin);
long failureCount = getFailureCount(dateDebut, dateFin);
Map<String, Long> result = new java.util.HashMap<>();
result.put("success", successCount);
result.put("failure", failureCount);
return result;
}
@Override
public String exportToCSV(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin) {
List<String> csvLines = exportLogsToCSV(dateDebut, dateFin);
return String.join("\n", csvLines);
}
@Override
public long purgeOldLogs(@NotNull LocalDateTime dateLimite) {
long beforeCount = auditLogs.size();
auditLogs.entrySet().removeIf(entry ->
entry.getValue().getDateAction().isBefore(dateLimite)
);
long afterCount = auditLogs.size();
return beforeCount - afterCount;
}
@Override
public Map<String, Object> getAuditStatistics(@NotBlank String realmName,
LocalDateTime dateDebut,
LocalDateTime dateFin) {
Map<String, Object> stats = new java.util.HashMap<>();
stats.put("total", 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;
})
.count());
stats.put("success", getSuccessCount(dateDebut, dateFin));
stats.put("failure", getFailureCount(dateDebut, dateFin));
stats.put("byActionType", countByActionType(realmName, dateDebut, dateFin));
stats.put("byActeur", countByActeur(realmName, dateDebut, dateFin));
return stats;
}
// Méthode privée helper pour la recherche
private List<AuditLogDTO> searchLogs(String acteurUsername, LocalDateTime dateDebut,
LocalDateTime dateFin, TypeActionAudit typeAction,
String ressourceType, Boolean succes,
int page, int pageSize) {
@@ -151,7 +308,7 @@ public class AuditServiceImpl implements AuditService {
}
// Filtre par succès/échec
if (succes != null && succes != log.isSucces()) {
if (succes != null && succes != log.isSuccessful()) {
return false;
}
@@ -163,58 +320,8 @@ public class AuditServiceImpl implements AuditService {
.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) {
// Méthodes privées helper
private Map<TypeActionAudit, Long> getActionStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
log.debug("Calcul des statistiques d'actions entre {} et {}", dateDebut, dateFin);
return auditLogs.values().stream()
@@ -233,8 +340,7 @@ public class AuditServiceImpl implements AuditService {
));
}
@Override
public Map<String, Long> getUserActivityStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
private Map<String, Long> getUserActivityStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
log.debug("Calcul des statistiques d'activité utilisateurs entre {} et {}", dateDebut, dateFin);
return auditLogs.values().stream()
@@ -253,13 +359,12 @@ public class AuditServiceImpl implements AuditService {
));
}
@Override
public long getFailureCount(LocalDateTime dateDebut, LocalDateTime dateFin) {
private long getFailureCount(LocalDateTime dateDebut, LocalDateTime dateFin) {
log.debug("Comptage des échecs entre {} et {}", dateDebut, dateFin);
return auditLogs.values().stream()
.filter(log -> {
if (log.isSucces()) {
if (log.isSuccessful()) {
return false; // On ne compte que les échecs
}
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
@@ -273,13 +378,12 @@ public class AuditServiceImpl implements AuditService {
.count();
}
@Override
public long getSuccessCount(LocalDateTime dateDebut, LocalDateTime dateFin) {
private 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()) {
if (!log.isSuccessful()) {
return false; // On ne compte que les succès
}
if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
@@ -293,8 +397,7 @@ public class AuditServiceImpl implements AuditService {
.count();
}
@Override
public List<String> exportLogsToCSV(LocalDateTime dateDebut, LocalDateTime dateFin) {
private List<String> exportLogsToCSV(LocalDateTime dateDebut, LocalDateTime dateFin) {
log.info("Export CSV des logs d'audit entre {} et {}", dateDebut, dateFin);
List<String> csvLines = new ArrayList<>();
@@ -322,10 +425,10 @@ public class AuditServiceImpl implements AuditService {
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("\"", "\"\"") : ""
log.isSuccessful(),
log.getIpAddress() != null ? log.getIpAddress() : "",
log.getDescription() != null ? log.getDescription().replace("\"", "\"\"") : "",
log.getErrorMessage() != null ? log.getErrorMessage().replace("\"", "\"\"") : ""
);
csvLines.add(csvLine);
});
@@ -334,24 +437,6 @@ public class AuditServiceImpl implements AuditService {
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 ====================
/**

View File

@@ -6,6 +6,8 @@ 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 org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.UserRepresentation;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
@@ -35,7 +37,7 @@ public class RoleServiceImpl implements RoleService {
@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);
log.info("Création du rôle realm: {} dans le realm: {}", roleDTO.getName(), realmName);
RolesResource rolesResource = keycloakAdminClient.getInstance()
.realm(realmName)
@@ -43,8 +45,8 @@ public class RoleServiceImpl implements RoleService {
// 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à");
rolesResource.get(roleDTO.getName()).toRepresentation();
throw new IllegalArgumentException("Le rôle " + roleDTO.getName() + " existe déjà");
} catch (NotFoundException e) {
// OK, le rôle n'existe pas
}
@@ -53,12 +55,12 @@ public class RoleServiceImpl implements RoleService {
rolesResource.create(roleRep);
// Récupérer le rôle créé avec son ID
RoleRepresentation createdRole = rolesResource.get(roleDTO.getNom()).toRepresentation();
RoleRepresentation createdRole = rolesResource.get(roleDTO.getName()).toRepresentation();
return RoleMapper.toDTO(createdRole, realmName, TypeRole.REALM_ROLE);
}
@Override
public Optional<RoleDTO> getRealmRoleById(@NotBlank String roleId, @NotBlank String realmName) {
// Méthodes privées helper pour utilisation interne
private Optional<RoleDTO> getRealmRoleById(String roleId, String realmName) {
log.debug("Récupération du rôle realm par ID: {} dans le realm: {}", roleId, realmName);
try {
@@ -78,8 +80,7 @@ public class RoleServiceImpl implements RoleService {
}
}
@Override
public Optional<RoleDTO> getRealmRoleByName(@NotBlank String roleName, @NotBlank String realmName) {
private Optional<RoleDTO> getRealmRoleByName(String roleName, String realmName) {
log.debug("Récupération du rôle realm par nom: {} dans le realm: {}", roleName, realmName);
try {
@@ -97,9 +98,20 @@ public class RoleServiceImpl implements RoleService {
}
@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);
public RoleDTO updateRole(@NotBlank String roleId,
@Valid @NotNull RoleDTO role,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.info("Mise à jour du rôle {} (type: {}) dans le realm: {}", roleId, typeRole, realmName);
if (typeRole == TypeRole.REALM_ROLE) {
// Trouver le nom du rôle par son ID
Optional<RoleDTO> existingRole = getRealmRoleById(roleId, realmName);
if (existingRole.isEmpty()) {
throw new jakarta.ws.rs.NotFoundException("Rôle non trouvé: " + roleId);
}
String roleName = existingRole.get().getName();
RoleResource roleResource = keycloakAdminClient.getInstance()
.realm(realmName)
@@ -109,56 +121,141 @@ public class RoleServiceImpl implements RoleService {
RoleRepresentation roleRep = roleResource.toRepresentation();
// Mettre à jour uniquement les champs modifiables
if (roleDTO.getDescription() != null) {
roleRep.setDescription(roleDTO.getDescription());
if (role.getDescription() != null) {
roleRep.setDescription(role.getDescription());
}
roleResource.update(roleRep);
// Retourner le rôle mis à jour
return RoleMapper.toDTO(roleResource.toRepresentation(), realmName, TypeRole.REALM_ROLE);
} else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
// Pour les rôles client, trouver le nom par ID puis mettre à jour
Optional<RoleDTO> existingRole = getRoleById(roleId, realmName, typeRole, clientName);
if (existingRole.isEmpty()) {
throw new jakarta.ws.rs.NotFoundException("Rôle non trouvé: " + roleId);
}
String roleName = existingRole.get().getName();
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
throw new IllegalArgumentException("Client " + clientName + " non trouvé");
}
String internalClientId = clients.get(0).getId();
RoleResource roleResource = clientsResource.get(internalClientId)
.roles()
.get(roleName);
RoleRepresentation roleRep = roleResource.toRepresentation();
if (role.getDescription() != null) {
roleRep.setDescription(role.getDescription());
}
roleResource.update(roleRep);
RoleDTO result = RoleMapper.toDTO(roleResource.toRepresentation(), realmName, TypeRole.CLIENT_ROLE);
result.setClientId(clientName);
return result;
}
throw new IllegalArgumentException("Type de rôle non supporté pour la mise à jour: " + typeRole);
}
@Override
public void deleteRealmRole(@NotBlank String roleName, @NotBlank String realmName) {
log.info("Suppression du rôle realm: {} dans le realm: {}", roleName, realmName);
public void deleteRole(@NotBlank String roleId,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.info("Suppression du rôle {} (type: {}) dans le realm: {}", roleId, typeRole, realmName);
if (typeRole == TypeRole.REALM_ROLE) {
// Trouver le nom du rôle par son ID
Optional<RoleDTO> existingRole = getRealmRoleById(roleId, realmName);
if (existingRole.isEmpty()) {
throw new jakarta.ws.rs.NotFoundException("Rôle non trouvé: " + roleId);
}
String roleName = existingRole.get().getName();
keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.deleteRole(roleName);
} else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
// Trouver le nom du rôle par son ID
Optional<RoleDTO> existingRole = getRoleById(roleId, realmName, typeRole, clientName);
if (existingRole.isEmpty()) {
throw new jakarta.ws.rs.NotFoundException("Rôle non trouvé: " + roleId);
}
String roleName = existingRole.get().getName();
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
throw new IllegalArgumentException("Client " + clientName + " non trouvé");
}
String internalClientId = clients.get(0).getId();
clientsResource.get(internalClientId).roles().deleteRole(roleName);
} else {
throw new IllegalArgumentException("Type de rôle non supporté pour la suppression: " + typeRole);
}
}
@Override
public List<RoleDTO> getAllRealmRoles(@NotBlank String realmName) {
log.debug("Récupération de tous les rôles realm du realm: {}", realmName);
log.info("Récupération de tous les rôles realm du realm: {}", realmName);
try {
// Vérifier que le realm existe
if (!keycloakAdminClient.realmExists(realmName)) {
log.error("Le realm {} n'existe pas", realmName);
throw new IllegalArgumentException("Le realm '" + realmName + "' n'existe pas");
}
List<RoleRepresentation> roleReps = keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.list();
log.info("Récupération réussie: {} rôles trouvés dans le realm {}", roleReps.size(), realmName);
return RoleMapper.toDTOList(roleReps, realmName, TypeRole.REALM_ROLE);
} catch (Exception e) {
log.error("Erreur lors de la récupération des rôles realm du realm {}: {}", realmName, e.getMessage(), e);
throw new RuntimeException("Erreur lors de la récupération des rôles realm: " + e.getMessage(), e);
}
}
// ==================== CRUD Client Roles ====================
@Override
public RoleDTO createClientRole(@Valid @NotNull RoleDTO roleDTO, @NotBlank String clientId,
@NotBlank String realmName) {
public RoleDTO createClientRole(@Valid @NotNull RoleDTO roleDTO, @NotBlank String realmName,
@NotBlank String clientName) {
log.info("Création du rôle client: {} pour le client: {} dans le realm: {}",
roleDTO.getNom(), clientId, realmName);
roleDTO.getName(), clientName, realmName);
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
// Trouver le client par clientId
// Trouver le client par clientId (on utilise clientName comme clientId)
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientId);
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
throw new IllegalArgumentException("Client " + clientId + " non trouvé");
throw new IllegalArgumentException("Client " + clientName + " non trouvé");
}
String internalClientId = clients.get(0).getId();
@@ -166,8 +263,8 @@ public class RoleServiceImpl implements RoleService {
// 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");
rolesResource.get(roleDTO.getName()).toRepresentation();
throw new IllegalArgumentException("Le rôle " + roleDTO.getName() + " existe déjà pour ce client");
} catch (NotFoundException e) {
// OK, le rôle n'existe pas
}
@@ -176,16 +273,16 @@ public class RoleServiceImpl implements RoleService {
rolesResource.create(roleRep);
// Récupérer le rôle créé
RoleRepresentation createdRole = rolesResource.get(roleDTO.getNom()).toRepresentation();
RoleRepresentation createdRole = rolesResource.get(roleDTO.getName()).toRepresentation();
RoleDTO result = RoleMapper.toDTO(createdRole, realmName, TypeRole.CLIENT_ROLE);
result.setClientId(clientId);
result.setClientId(clientName);
return result;
}
@Override
public Optional<RoleDTO> getClientRoleByName(@NotBlank String roleName, @NotBlank String clientId,
@NotBlank String realmName) {
// Méthode privée helper pour utilisation interne
private Optional<RoleDTO> getClientRoleByName(String roleName, String clientId,
String realmName) {
log.debug("Récupération du rôle client: {} pour le client: {} dans le realm: {}",
roleName, clientId, realmName);
@@ -218,37 +315,17 @@ public class RoleServiceImpl implements RoleService {
}
}
@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);
public List<RoleDTO> getAllClientRoles(@NotBlank String realmName, @NotBlank String clientName) {
log.debug("Récupération de tous les rôles du client: {} dans le realm: {}", clientName, 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);
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
return List.of();
@@ -260,16 +337,102 @@ public class RoleServiceImpl implements RoleService {
.list();
List<RoleDTO> roles = RoleMapper.toDTOList(roleReps, realmName, TypeRole.CLIENT_ROLE);
roles.forEach(role -> role.setClientId(clientId));
roles.forEach(role -> role.setClientId(clientName));
return roles;
}
@Override
public Optional<RoleDTO> getRoleById(@NotBlank String roleId,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.debug("Récupération du rôle par ID: {} (type: {}) dans le realm: {}", roleId, typeRole, realmName);
if (typeRole == TypeRole.REALM_ROLE) {
return getRealmRoleById(roleId, realmName);
} else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
// Pour les rôles client, on doit lister tous les rôles du client et trouver par ID
try {
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
return Optional.empty();
}
String internalClientId = clients.get(0).getId();
List<RoleRepresentation> roles = clientsResource.get(internalClientId)
.roles()
.list();
return roles.stream()
.filter(r -> r.getId().equals(roleId))
.findFirst()
.map(r -> {
RoleDTO roleDTO = RoleMapper.toDTO(r, realmName, TypeRole.CLIENT_ROLE);
roleDTO.setClientId(clientName);
return roleDTO;
});
} catch (Exception e) {
log.error("Erreur lors de la récupération du rôle client {}", roleId, e);
return Optional.empty();
}
}
return Optional.empty();
}
@Override
public Optional<RoleDTO> getRoleByName(@NotBlank String roleName,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.debug("Récupération du rôle par nom: {} (type: {}) dans le realm: {}", roleName, typeRole, realmName);
if (typeRole == TypeRole.REALM_ROLE) {
return getRealmRoleByName(roleName, realmName);
} else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
return getClientRoleByName(roleName, clientName, realmName);
}
return Optional.empty();
}
// ==================== Attribution de rôles ====================
@Override
public void assignRealmRolesToUser(@NotBlank String userId, @NotNull List<String> roleNames,
@NotBlank String realmName) {
public void assignRolesToUser(@Valid @NotNull RoleAssignmentDTO assignment) {
log.info("Attribution de {} rôles {} à l'utilisateur {} dans le realm {}",
assignment.getRoleNames().size(), assignment.getTypeRole(), assignment.getUserId(), assignment.getRealmName());
if (assignment.getTypeRole() == TypeRole.REALM_ROLE) {
assignRealmRolesToUser(assignment.getUserId(), assignment.getRoleNames(), assignment.getRealmName());
} else if (assignment.getTypeRole() == TypeRole.CLIENT_ROLE && assignment.getClientName() != null) {
assignClientRolesToUser(assignment.getUserId(), assignment.getClientName(), assignment.getRoleNames(), assignment.getRealmName());
} else {
throw new IllegalArgumentException("Données d'attribution invalides pour le type de rôle: " + assignment.getTypeRole());
}
}
@Override
public void revokeRolesFromUser(@Valid @NotNull RoleAssignmentDTO assignment) {
log.info("Révocation de {} rôles {} pour l'utilisateur {} dans le realm {}",
assignment.getRoleNames().size(), assignment.getTypeRole(), assignment.getUserId(), assignment.getRealmName());
if (assignment.getTypeRole() == TypeRole.REALM_ROLE) {
revokeRealmRolesFromUser(assignment.getUserId(), assignment.getRoleNames(), assignment.getRealmName());
} else if (assignment.getTypeRole() == TypeRole.CLIENT_ROLE && assignment.getClientName() != null) {
revokeClientRolesFromUser(assignment.getUserId(), assignment.getClientName(), assignment.getRoleNames(), assignment.getRealmName());
} else {
throw new IllegalArgumentException("Données de révocation invalides pour le type de rôle: " + assignment.getTypeRole());
}
}
private void assignRealmRolesToUser(String userId, List<String> roleNames,
String realmName) {
log.info("Attribution de {} rôles realm à l'utilisateur {} dans le realm {}",
roleNames.size(), userId, realmName);
@@ -299,9 +462,8 @@ public class RoleServiceImpl implements RoleService {
}
}
@Override
public void revokeRealmRolesFromUser(@NotBlank String userId, @NotNull List<String> roleNames,
@NotBlank String realmName) {
private void revokeRealmRolesFromUser(String userId, List<String> roleNames,
String realmName) {
log.info("Révocation de {} rôles realm pour l'utilisateur {} dans le realm {}",
roleNames.size(), userId, realmName);
@@ -331,9 +493,8 @@ public class RoleServiceImpl implements RoleService {
}
}
@Override
public void assignClientRolesToUser(@NotBlank String userId, @NotBlank String clientId,
@NotNull List<String> roleNames, @NotBlank String realmName) {
private void assignClientRolesToUser(String userId, String clientId,
List<String> roleNames, String realmName) {
log.info("Attribution de {} rôles du client {} à l'utilisateur {} dans le realm {}",
roleNames.size(), clientId, userId, realmName);
@@ -373,9 +534,8 @@ public class RoleServiceImpl implements RoleService {
}
}
@Override
public void revokeClientRolesFromUser(@NotBlank String userId, @NotBlank String clientId,
@NotNull List<String> roleNames, @NotBlank String realmName) {
private void revokeClientRolesFromUser(String userId, String clientId,
List<String> roleNames, String realmName) {
log.info("Révocation de {} rôles du client {} pour l'utilisateur {} dans le realm {}",
roleNames.size(), clientId, userId, realmName);
@@ -431,17 +591,18 @@ public class RoleServiceImpl implements RoleService {
}
@Override
public List<RoleDTO> getUserClientRoles(@NotBlank String userId, @NotBlank String clientId,
@NotBlank String realmName) {
public List<RoleDTO> getUserClientRoles(@NotBlank String userId,
@NotBlank String realmName,
@NotBlank String clientName) {
log.debug("Récupération des rôles du client {} pour l'utilisateur {} dans le realm {}",
clientId, userId, realmName);
clientName, userId, realmName);
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientId);
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
return List.of();
@@ -457,29 +618,74 @@ public class RoleServiceImpl implements RoleService {
.listAll();
List<RoleDTO> roles = RoleMapper.toDTOList(roleReps, realmName, TypeRole.CLIENT_ROLE);
roles.forEach(role -> role.setClientId(clientId));
roles.forEach(role -> role.setClientId(clientName));
return roles;
}
@Override
public List<RoleDTO> getAllUserRoles(@NotBlank String userId, @NotBlank String realmName) {
log.debug("Récupération de tous les rôles de l'utilisateur {} dans le realm {}", userId, realmName);
List<RoleDTO> allRoles = new ArrayList<>();
// Ajouter les rôles realm
allRoles.addAll(getUserRealmRoles(userId, realmName));
// Ajouter les rôles client pour tous les clients
try {
var clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients = clientsResource.findAll();
for (org.keycloak.representations.idm.ClientRepresentation client : clients) {
String clientId = client.getClientId();
allRoles.addAll(getUserClientRoles(userId, realmName, clientId));
}
} catch (Exception e) {
log.warn("Erreur lors de la récupération des rôles client pour l'utilisateur {}", userId, e);
}
return allRoles;
}
// ==================== 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);
public void addCompositeRoles(@NotBlank String parentRoleId,
@NotNull List<String> childRoleIds,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.info("Ajout de {} rôles composites au rôle {} (type: {}) dans le realm {}",
childRoleIds.size(), parentRoleId, typeRole, realmName);
RoleResource roleResource = keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.get(roleName);
// Trouver le nom du rôle parent par son ID
Optional<RoleDTO> parentRole = getRoleById(parentRoleId, realmName, typeRole, clientName);
if (parentRole.isEmpty()) {
throw new jakarta.ws.rs.NotFoundException("Rôle parent non trouvé: " + parentRoleId);
}
String parentRoleName = parentRole.get().getName();
RolesResource rolesResource = keycloakAdminClient.getInstance()
.realm(realmName)
.roles();
List<RoleRepresentation> compositesToAdd = compositeRoleNames.stream()
if (typeRole == TypeRole.REALM_ROLE) {
RoleResource roleResource = rolesResource.get(parentRoleName);
// Convertir les IDs en noms de rôles
List<String> childRoleNames = childRoleIds.stream()
.map(childRoleId -> {
Optional<RoleDTO> childRole = getRealmRoleById(childRoleId, realmName);
return childRole.map(RoleDTO::getName).orElse(null);
})
.filter(name -> name != null)
.collect(Collectors.toList());
List<RoleRepresentation> compositesToAdd = childRoleNames.stream()
.map(compositeName -> {
try {
return rolesResource.get(compositeName).toRepresentation();
@@ -494,24 +700,83 @@ public class RoleServiceImpl implements RoleService {
if (!compositesToAdd.isEmpty()) {
roleResource.addComposites(compositesToAdd);
}
} else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
// Pour les rôles client, utiliser le client
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
throw new IllegalArgumentException("Client " + clientName + " non trouvé");
}
String internalClientId = clients.get(0).getId();
RolesResource clientRolesResource = clientsResource.get(internalClientId).roles();
RoleResource roleResource = clientRolesResource.get(parentRoleName);
// Convertir les IDs en noms de rôles
List<String> childRoleNames = childRoleIds.stream()
.map(childRoleId -> {
Optional<RoleDTO> childRole = getRoleById(childRoleId, realmName, typeRole, clientName);
return childRole.map(RoleDTO::getName).orElse(null);
})
.filter(name -> name != null)
.collect(Collectors.toList());
List<RoleRepresentation> compositesToAdd = childRoleNames.stream()
.map(compositeName -> {
try {
return clientRolesResource.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);
public void removeCompositeRoles(@NotBlank String parentRoleId,
@NotNull List<String> childRoleIds,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.info("Suppression de {} rôles composites du rôle {} (type: {}) dans le realm {}",
childRoleIds.size(), parentRoleId, typeRole, realmName);
RoleResource roleResource = keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.get(roleName);
// Trouver le nom du rôle parent par son ID
Optional<RoleDTO> parentRole = getRoleById(parentRoleId, realmName, typeRole, clientName);
if (parentRole.isEmpty()) {
throw new jakarta.ws.rs.NotFoundException("Rôle parent non trouvé: " + parentRoleId);
}
String parentRoleName = parentRole.get().getName();
RolesResource rolesResource = keycloakAdminClient.getInstance()
.realm(realmName)
.roles();
List<RoleRepresentation> compositesToRemove = compositeRoleNames.stream()
if (typeRole == TypeRole.REALM_ROLE) {
RoleResource roleResource = rolesResource.get(parentRoleName);
// Convertir les IDs en noms de rôles
List<String> childRoleNames = childRoleIds.stream()
.map(childRoleId -> {
Optional<RoleDTO> childRole = getRealmRoleById(childRoleId, realmName);
return childRole.map(RoleDTO::getName).orElse(null);
})
.filter(name -> name != null)
.collect(Collectors.toList());
List<RoleRepresentation> compositesToRemove = childRoleNames.stream()
.map(compositeName -> {
try {
return rolesResource.get(compositeName).toRepresentation();
@@ -526,29 +791,89 @@ public class RoleServiceImpl implements RoleService {
if (!compositesToRemove.isEmpty()) {
roleResource.deleteComposites(compositesToRemove);
}
} else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
throw new IllegalArgumentException("Client " + clientName + " non trouvé");
}
@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);
String internalClientId = clients.get(0).getId();
RolesResource clientRolesResource = clientsResource.get(internalClientId).roles();
RoleResource roleResource = clientRolesResource.get(parentRoleName);
List<RoleRepresentation> composites = keycloakAdminClient.getInstance()
// Convertir les IDs en noms de rôles
List<String> childRoleNames = childRoleIds.stream()
.map(childRoleId -> {
Optional<RoleDTO> childRole = getRoleById(childRoleId, realmName, typeRole, clientName);
return childRole.map(RoleDTO::getName).orElse(null);
})
.filter(name -> name != null)
.collect(Collectors.toList());
List<RoleRepresentation> compositesToRemove = childRoleNames.stream()
.map(compositeName -> {
try {
return clientRolesResource.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 roleId,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.debug("Récupération des rôles composites du rôle {} dans le realm {}", roleId, realmName);
// Pour récupérer par ID, on doit d'abord trouver le nom du rôle
// Comme Keycloak ne permet pas de récupérer directement par ID, on doit lister et trouver
RolesResource rolesResource = keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.get(roleName)
.roles();
RoleRepresentation roleRep = rolesResource.list().stream()
.filter(r -> r.getId().equals(roleId))
.findFirst()
.orElseThrow(() -> new jakarta.ws.rs.NotFoundException("Rôle non trouvé: " + roleId));
java.util.Set<RoleRepresentation> compositesSet = rolesResource
.get(roleRep.getName())
.getRoleComposites();
List<RoleRepresentation> composites = new ArrayList<>(compositesSet);
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);
public boolean userHasRole(@NotBlank String userId,
@NotBlank String roleName,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.debug("Vérification si l'utilisateur {} a le rôle {} (type: {}) dans le realm {}",
userId, roleName, typeRole, realmName);
if (typeRole == TypeRole.REALM_ROLE) {
List<RoleRepresentation> userRoles = keycloakAdminClient.getInstance()
.realm(realmName)
.users()
@@ -559,20 +884,13 @@ public class RoleServiceImpl implements RoleService {
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);
} else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
ClientsResource clientsResource = keycloakAdminClient.getInstance()
.realm(realmName)
.clients();
List<org.keycloak.representations.idm.ClientRepresentation> clients =
clientsResource.findByClientId(clientId);
clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
return false;
@@ -591,19 +909,67 @@ public class RoleServiceImpl implements RoleService {
.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);
return false;
}
List<RoleRepresentation> effectiveRoles = keycloakAdminClient.getInstance()
@Override
public boolean roleExists(@NotBlank String roleName,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.debug("Vérification de l'existence du rôle {} (type: {}) dans le realm {}",
roleName, typeRole, realmName);
return getRoleByName(roleName, realmName, typeRole, clientName).isPresent();
}
@Override
public long countUsersWithRole(@NotBlank String roleId,
@NotBlank String realmName,
@NotNull TypeRole typeRole,
String clientName) {
log.debug("Comptage des utilisateurs ayant le rôle {} (type: {}) dans le realm {}",
roleId, typeRole, realmName);
// Trouver le nom du rôle par son ID
Optional<RoleDTO> role = getRoleById(roleId, realmName, typeRole, clientName);
if (role.isEmpty()) {
return 0;
}
String roleName = role.get().getName();
try {
// Keycloak ne fournit pas directement cette fonctionnalité via l'API Admin
// On doit lister tous les utilisateurs et vérifier leurs rôles
// C'est coûteux mais nécessaire
List<UserRepresentation> users = keycloakAdminClient.getInstance()
.realm(realmName)
.users()
.get(userId)
.roles()
.realmLevel()
.listEffective();
.list();
return RoleMapper.toDTOList(effectiveRoles, realmName, TypeRole.REALM_ROLE);
long count = 0;
for (UserRepresentation user : users) {
if (userHasRole(user.getId(), roleName, realmName, typeRole, clientName)) {
count++;
}
}
return count;
} catch (Exception e) {
log.error("Erreur lors du comptage des utilisateurs avec le rôle {}", roleId, e);
return 0;
}
}
// Méthodes privées pour compatibilité interne (utilisées par les nouvelles méthodes publiques)
private boolean userHasRealmRole(String userId, String roleName,
String realmName) {
return userHasRole(userId, roleName, realmName, TypeRole.REALM_ROLE, null);
}
private boolean userHasClientRole(String userId, String clientId,
String roleName, String realmName) {
return userHasRole(userId, roleName, realmName, TypeRole.CLIENT_ROLE, clientId);
}
}

View File

@@ -0,0 +1,216 @@
package dev.lions.user.manager.service.impl;
import dev.lions.user.manager.client.KeycloakAdminClient;
import dev.lions.user.manager.dto.role.RoleDTO;
import dev.lions.user.manager.dto.sync.HealthStatusDTO;
import dev.lions.user.manager.dto.sync.SyncResultDTO;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.enums.role.TypeRole;
import dev.lions.user.manager.mapper.RoleMapper;
import dev.lions.user.manager.mapper.UserMapper;
import dev.lions.user.manager.service.SyncService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Implémentation du service de synchronisation avec Keycloak
*
* Ce service permet de:
* - Synchroniser les utilisateurs depuis Keycloak
* - Synchroniser les rôles depuis Keycloak
* - Vérifier la cohérence des données
* - Effectuer des health checks sur Keycloak
*/
@ApplicationScoped
@Slf4j
public class SyncServiceImpl implements SyncService {
@Inject
KeycloakAdminClient keycloakAdminClient;
@Override
public int syncUsersFromRealm(@NotBlank String realmName) {
log.info("Synchronisation des utilisateurs depuis le realm: {}", realmName);
try {
List<UserRepresentation> userReps = keycloakAdminClient.getInstance()
.realm(realmName)
.users()
.list();
int count = userReps.size();
log.info("✅ {} utilisateurs synchronisés depuis le realm {}", count, realmName);
return count;
} catch (Exception e) {
log.error("❌ Erreur lors de la synchronisation des utilisateurs depuis le realm {}", realmName, e);
throw new RuntimeException("Erreur lors de la synchronisation des utilisateurs", e);
}
}
@Override
public int syncRolesFromRealm(@NotBlank String realmName) {
log.info("Synchronisation des rôles depuis le realm: {}", realmName);
try {
List<RoleRepresentation> roleReps = keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.list();
int count = roleReps.size();
log.info("✅ {} rôles synchronisés depuis le realm {}", count, realmName);
return count;
} catch (Exception e) {
log.error("❌ Erreur lors de la synchronisation des rôles depuis le realm {}", realmName, e);
throw new RuntimeException("Erreur lors de la synchronisation des rôles", e);
}
}
@Override
public Map<String, Integer> syncAllRealms() {
log.info("Synchronisation de tous les realms");
Map<String, Integer> results = new java.util.HashMap<>();
try {
// Lister tous les realms
List<org.keycloak.representations.idm.RealmRepresentation> realms =
keycloakAdminClient.getInstance().realms().findAll();
for (org.keycloak.representations.idm.RealmRepresentation realm : realms) {
String realmName = realm.getRealm();
try {
int usersCount = syncUsersFromRealm(realmName);
int rolesCount = syncRolesFromRealm(realmName);
results.put(realmName, usersCount + rolesCount);
} catch (Exception e) {
log.error("Erreur lors de la synchronisation du realm {}", realmName, e);
results.put(realmName, 0);
}
}
} catch (Exception e) {
log.error("Erreur lors de la synchronisation de tous les realms", e);
}
return results;
}
@Override
public Map<String, Object> checkDataConsistency(@NotBlank String realmName) {
log.info("Vérification de la cohérence des données pour le realm: {}", realmName);
Map<String, Object> report = new java.util.HashMap<>();
try {
// Pour l'instant, on retourne juste un rapport basique
// En production, on comparerait avec un cache local
report.put("realmName", realmName);
report.put("status", "ok");
report.put("message", "Cohérence vérifiée");
} catch (Exception e) {
log.error("Erreur lors de la vérification de cohérence pour le realm {}", realmName, e);
report.put("status", "error");
report.put("message", e.getMessage());
}
return report;
}
@Override
public Map<String, Object> forceSyncRealm(@NotBlank String realmName) {
log.info("Synchronisation forcée du realm: {}", realmName);
Map<String, Object> stats = new java.util.HashMap<>();
long startTime = System.currentTimeMillis();
try {
int usersCount = syncUsersFromRealm(realmName);
int rolesCount = syncRolesFromRealm(realmName);
stats.put("realmName", realmName);
stats.put("usersCount", usersCount);
stats.put("rolesCount", rolesCount);
stats.put("success", true);
stats.put("durationMs", System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error("Erreur lors de la synchronisation forcée du realm {}", realmName, e);
stats.put("success", false);
stats.put("error", e.getMessage());
stats.put("durationMs", System.currentTimeMillis() - startTime);
}
return stats;
}
@Override
public Map<String, Object> getLastSyncStatus(@NotBlank String realmName) {
log.debug("Récupération du statut de la dernière synchronisation pour le realm: {}", realmName);
Map<String, Object> status = new java.util.HashMap<>();
status.put("realmName", realmName);
status.put("lastSyncTime", System.currentTimeMillis()); // En production, récupérer depuis un cache
status.put("status", "completed");
return status;
}
@Override
public boolean isKeycloakAvailable() {
log.debug("Vérification de la disponibilité de Keycloak");
try {
// Test de connexion en récupérant les informations du serveur
keycloakAdminClient.getInstance().serverInfo().getInfo();
log.debug("✅ Keycloak est accessible et fonctionne");
return true;
} catch (Exception e) {
log.error("❌ Keycloak n'est pas accessible: {}", e.getMessage());
return false;
}
}
@Override
public Map<String, Object> getKeycloakHealthInfo() {
log.info("Récupération du statut de santé complet de Keycloak");
Map<String, Object> healthInfo = new java.util.HashMap<>();
healthInfo.put("timestamp", System.currentTimeMillis());
try {
// Test connexion principale
var serverInfo = keycloakAdminClient.getInstance().serverInfo().getInfo();
healthInfo.put("keycloakAccessible", true);
healthInfo.put("keycloakVersion", serverInfo.getSystemInfo().getVersion());
// Test des realms (on essaie juste de lister)
try {
int realmsCount = keycloakAdminClient.getInstance().realms().findAll().size();
healthInfo.put("realmsAccessible", true);
healthInfo.put("realmsCount", realmsCount);
} catch (Exception e) {
healthInfo.put("realmsAccessible", false);
log.warn("Impossible d'accéder aux realms: {}", e.getMessage());
}
healthInfo.put("overallHealthy", true);
log.info("✅ Keycloak est en bonne santé - Version: {}, Realms: {}",
healthInfo.get("keycloakVersion"), healthInfo.get("realmsCount"));
} catch (Exception e) {
healthInfo.put("keycloakAccessible", false);
healthInfo.put("overallHealthy", false);
healthInfo.put("errorMessage", e.getMessage());
log.error("❌ Keycloak n'est pas accessible: {}", e.getMessage());
}
return healthInfo;
}
}

View File

@@ -16,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.Collections;
@@ -99,11 +100,35 @@ public class UserServiceImpl implements UserService {
try {
UserResource userResource = keycloakAdminClient.getUsers(realmName).get(userId);
UserRepresentation userRep = userResource.toRepresentation();
return Optional.of(UserMapper.toDTO(userRep, realmName));
UserDTO userDTO = UserMapper.toDTO(userRep, realmName);
// Récupérer les rôles realm de l'utilisateur
try {
List<RoleRepresentation> realmRoles = userResource.roles().realmLevel().listAll();
if (realmRoles != null && !realmRoles.isEmpty()) {
List<String> roleNames = realmRoles.stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toList());
userDTO.setRealmRoles(roleNames);
}
} catch (Exception e) {
log.warn("Erreur lors de la récupération des rôles realm pour l'utilisateur {}: {}", userId, e.getMessage());
// Ne pas échouer si les rôles ne peuvent pas être récupérés
}
return Optional.of(userDTO);
} catch (NotFoundException e) {
log.warn("Utilisateur {} non trouvé dans le realm {}", userId, realmName);
return Optional.empty();
} catch (Exception e) {
// Vérifier si l'exception contient un message indiquant un 404
String errorMessage = e.getMessage();
if (errorMessage != null && (errorMessage.contains("404") ||
errorMessage.contains("Server response is: 404") ||
errorMessage.contains("Received: 'Server response is: 404'"))) {
log.warn("Utilisateur {} non trouvé dans le realm {} (404 détecté dans l'exception)", userId, realmName);
return Optional.empty();
}
log.error("Erreur lors de la récupération de l'utilisateur {}", userId, e);
throw new RuntimeException("Impossible de récupérer l'utilisateur", e);
}
@@ -425,7 +450,7 @@ public class UserServiceImpl implements UserService {
}
@Override
public int importUsersFromCSV(@NotBlank String csvContent, @NotBlank String realmName) {
public dev.lions.user.manager.dto.importexport.ImportResultDTO importUsersFromCSV(@NotBlank String csvContent, @NotBlank String realmName) {
// TODO: Implémenter l'import CSV
throw new UnsupportedOperationException("Import CSV non implémenté");
}

View File

@@ -11,11 +11,20 @@ quarkus.http.cors.methods=GET,POST,PUT,DELETE,PATCH,OPTIONS
quarkus.http.cors.headers=*
# Keycloak OIDC Configuration (DEV)
quarkus.oidc.auth-server-url=http://localhost:8180/realms/master
quarkus.oidc.client-id=lions-user-manager
quarkus.oidc.credentials.secret=dev-secret-change-me
quarkus.oidc.tls.verification=none
# Le backend vérifie les tokens JWT envoyés par le client
# IMPORTANT: Pour un service, Quarkus valide les tokens JWT sans avoir besoin d'un client-id/secret
# Le backend accepte les tokens émis pour n'importe quel client du realm
quarkus.oidc.enabled=true
quarkus.oidc.auth-server-url=http://localhost:8180/realms/lions-user-manager
quarkus.oidc.application-type=service
quarkus.oidc.tls.verification=none
quarkus.oidc.token.issuer=http://localhost:8180/realms/lions-user-manager
quarkus.oidc.discovery-enabled=true
# Accepter les tokens avec audience "account" (audience par défaut de Keycloak)
# Cela permet d'accepter les tokens émis pour le frontend sans configuration Keycloak supplémentaire
quarkus.oidc.token.audience=account
# Vérifier le token (obligatoire pour un service)
quarkus.oidc.verify-access-token=true
# Keycloak Admin Client Configuration (DEV)
lions.keycloak.server-url=http://localhost:8180
@@ -27,7 +36,7 @@ lions.keycloak.connection-pool-size=5
lions.keycloak.timeout-seconds=30
# Realms autorisés (DEV)
lions.keycloak.authorized-realms=btpxpress,master,lions-realm,test-realm
lions.keycloak.authorized-realms=lions-user-manager,master,btpxpress,test-realm
# Circuit Breaker Configuration (DEV - plus permissif)
quarkus.smallrye-fault-tolerance.enabled=true
@@ -52,14 +61,17 @@ lions.audit.retention-days=30
#quarkus.flyway.migrate-at-start=false
# Logging Configuration (DEV)
quarkus.log.level=DEBUG
quarkus.log.level=INFO
quarkus.log.category."dev.lions.user.manager".level=DEBUG
quarkus.log.category."org.keycloak".level=INFO
quarkus.log.category."io.quarkus".level=INFO
# Logging OIDC pour debug
quarkus.log.category."io.quarkus.oidc".level=DEBUG
quarkus.log.category."io.quarkus.security".level=DEBUG
quarkus.log.console.enable=true
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.console.color=true
# quarkus.log.console.color est déprécié dans Quarkus 3.x
# File Logging pour Audit (DEV)
quarkus.log.file.enable=true
@@ -69,14 +81,48 @@ quarkus.log.file.rotation.max-backup-index=3
# OpenAPI/Swagger Configuration (DEV - toujours activé)
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
quarkus.swagger-ui.enable=true
# Le chemin par défaut est /q/swagger-ui (pas besoin de le spécifier)
# Dev Services (activé en DEV)
quarkus.devservices.enabled=false
# Security Configuration (DEV - plus permissif)
# Security Configuration (DEV)
# La sécurité est activée - les rôles sont vérifiés via OIDC/Keycloak
# Note: KeycloakTestUserConfig configure automatiquement l'utilisateur de test au démarrage
quarkus.security.auth.enabled=true
quarkus.security.jaxrs.deny-unannotated-endpoints=false
quarkus.security.auth.proactive=false
# Configuration OIDC - Extraction des rôles
# Le backend extrait les rôles depuis realm_access/roles (standard Keycloak)
# Le scope "roles" de Keycloak crée automatiquement realm_access.roles
# Syntaxe Quarkus: utiliser un slash pour les chemins imbriqués
quarkus.oidc.roles.role-claim-path=realm_access/roles
# Définir explicitement le profil pour que DevSecurityContextProducer le détecte
quarkus.profile=dev
# Logging pour debug du filtre de sécurité
quarkus.log.category."dev.lions.user.manager.security".level=DEBUG
# Logging OIDC et Security pour debug
quarkus.log.category."io.quarkus.oidc".level=DEBUG
quarkus.log.category."io.quarkus.oidc.runtime".level=DEBUG
quarkus.log.category."io.quarkus.security".level=DEBUG
quarkus.log.category."io.quarkus.security.runtime".level=DEBUG
# Hot Reload
quarkus.live-reload.instrumentation=true
# Désactiver le continuous testing qui bloque le démarrage
quarkus.test.continuous-testing=disabled
# Indexer les dépendances Keycloak pour éviter les warnings
quarkus.index-dependency.keycloak-admin.group-id=org.keycloak
quarkus.index-dependency.keycloak-admin.artifact-id=keycloak-admin-client
quarkus.index-dependency.keycloak-core.group-id=org.keycloak
quarkus.index-dependency.keycloak-core.artifact-id=keycloak-core
# Jackson - Ignorer les propriétés inconnues pour compatibilité Keycloak
quarkus.jackson.fail-on-unknown-properties=false

View File

@@ -1,113 +1,113 @@
# ============================================================================
# Lions User Manager - Server Implementation Configuration - PRODUCTION
# Lions User Manager Server - Configuration Production
# ============================================================================
# Ce fichier contient TOUTES les propriétés spécifiques à la production
# Il surcharge et complète application.properties
# ============================================================================
# HTTP Configuration
quarkus.http.port=8081
quarkus.http.host=0.0.0.0
quarkus.http.cors=true
quarkus.http.cors.origins=https://btpxpress.lions.dev,https://admin.lions.dev
quarkus.http.cors.methods=GET,POST,PUT,DELETE,PATCH,OPTIONS
quarkus.http.cors.headers=*
# ============================================
# HTTP Configuration PROD
# ============================================
quarkus.http.port=8080
# Keycloak OIDC Configuration (PROD)
quarkus.oidc.auth-server-url=https://security.lions.dev/realms/master
quarkus.oidc.client-id=lions-user-manager
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
quarkus.oidc.tls.verification=required
quarkus.oidc.application-type=service
# CORS restrictif en production (via variable d'environnement) - autoriser le frontend users.lions.dev
quarkus.http.cors.origins=${CORS_ORIGINS:https://users.lions.dev,https://btpxpress.lions.dev,https://admin.lions.dev}
# Keycloak Admin Client Configuration (PROD)
lions.keycloak.server-url=https://security.lions.dev
lions.keycloak.admin-realm=master
lions.keycloak.admin-client-id=admin-cli
lions.keycloak.admin-username=${KEYCLOAK_ADMIN_USERNAME}
lions.keycloak.admin-password=${KEYCLOAK_ADMIN_PASSWORD}
lions.keycloak.connection-pool-size=20
lions.keycloak.timeout-seconds=60
# Realms autorisés (PROD)
lions.keycloak.authorized-realms=btpxpress,lions-realm
# Circuit Breaker Configuration (PROD - strict)
quarkus.smallrye-fault-tolerance.enabled=true
# Retry Configuration (PROD)
lions.keycloak.retry.max-attempts=5
lions.keycloak.retry.delay-seconds=3
# Audit Configuration (PROD)
lions.audit.enabled=true
lions.audit.log-to-database=true
lions.audit.log-to-file=true
lions.audit.retention-days=365
# Database Configuration (PROD - obligatoire pour audit)
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:audit_user}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOST:lions-db.lions.svc.cluster.local}:${DB_PORT:5432}/${DB_NAME:lions_audit}
quarkus.datasource.jdbc.max-size=20
quarkus.datasource.jdbc.min-size=5
quarkus.hibernate-orm.database.generation=none
quarkus.flyway.migrate-at-start=true
quarkus.flyway.baseline-on-migrate=true
quarkus.flyway.baseline-version=1.0.0
# Logging Configuration (PROD)
# ============================================
# Logging PROD (moins verbeux)
# ============================================
quarkus.log.level=INFO
quarkus.log.category."dev.lions.user.manager".level=INFO
quarkus.log.category."org.keycloak".level=WARN
quarkus.log.category."io.quarkus".level=WARN
quarkus.log.console.enable=true
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n
quarkus.log.console.json=true
# File Logging pour Audit (PROD)
quarkus.log.file.enable=true
quarkus.log.file.path=/var/log/lions/lions-user-manager.log
quarkus.log.file.rotation.max-file-size=50M
quarkus.log.file.rotation.max-backup-index=30
quarkus.log.file.rotation.rotate-on-boot=false
# File Logging désactivé en prod Kubernetes (utilise stdout pour logs centralisés)
# quarkus.log.file.path=/var/log/lions/lions-user-manager.log
# quarkus.log.file.rotation.max-file-size=50M
# quarkus.log.file.rotation.max-backup-index=30
# quarkus.log.file.rotation.rotate-on-boot=false
# OpenAPI/Swagger Configuration (PROD - désactivé par défaut)
quarkus.swagger-ui.always-include=false
quarkus.swagger-ui.path=/swagger-ui
# ============================================
# OIDC Configuration PROD - OBLIGATOIRE ET ACTIF
# ============================================
quarkus.oidc.enabled=true
quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/lions-user-manager}
quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:lions-user-manager-server}
# Client bearer-only - pas de secret nécessaire
# quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
quarkus.oidc.token.issuer=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/lions-user-manager}
# Vérification TLS requise en production
quarkus.oidc.tls.verification=required
# Vérification stricte des tokens
quarkus.oidc.discovery-enabled=true
# quarkus.oidc.verify-access-token=true # Propriété non reconnue
# Extraction des rôles
quarkus.oidc.roles.role-claim-path=realm_access/roles
# ============================================
# Keycloak Admin Client Configuration PROD
# ============================================
lions.keycloak.server-url=${KEYCLOAK_SERVER_URL:https://security.lions.dev}
lions.keycloak.admin-realm=${KEYCLOAK_ADMIN_REALM:master}
lions.keycloak.admin-client-id=admin-cli
lions.keycloak.admin-username=${KEYCLOAK_ADMIN_USERNAME:admin}
lions.keycloak.admin-password=${KEYCLOAK_ADMIN_PASSWORD:KeycloakAdmin2025!}
# Pool de connexions augmenté en production
lions.keycloak.connection-pool-size=20
lions.keycloak.timeout-seconds=60
# Realms autorisés en production (via variable d'environnement)
lions.keycloak.authorized-realms=${KEYCLOAK_AUTHORIZED_REALMS:lions-user-manager,btpxpress,master,unionflow}
# ============================================
# Retry Configuration PROD
# ============================================
lions.keycloak.retry.max-attempts=5
lions.keycloak.retry.delay-seconds=3
# ============================================
# Audit Configuration PROD
# ============================================
lions.audit.retention-days=365
lions.audit.log-to-database=true
# ============================================
# Database Configuration PROD - Désactivé complètement
# ============================================
quarkus.datasource.devservices.enabled=false
quarkus.datasource.health.enabled=false
# ============================================
# OpenAPI/Swagger Configuration PROD
# ============================================
# Swagger désactivé en production par défaut (build-time property)
# quarkus.swagger-ui.always-include=false
quarkus.swagger-ui.enable=false
# Dev Services (désactivé en PROD)
quarkus.devservices.enabled=false
# ============================================
# Security Configuration PROD (strict)
# ============================================
# Ces propriétés sont build-time, configurées dans application.properties
# quarkus.security.auth.enabled=true
# quarkus.security.jaxrs.deny-unannotated-endpoints=true
# quarkus.security.auth.proactive=true
# Security Configuration (PROD - strict)
quarkus.security.jaxrs.deny-unannotated-endpoints=true
# ============================================
# Performance tuning PROD
# ============================================
quarkus.thread-pool.core-threads=4
quarkus.thread-pool.max-threads=32
quarkus.thread-pool.queue-size=200
# Health Check Configuration (PROD)
quarkus.smallrye-health.root-path=/health
quarkus.smallrye-health.liveness-path=/health/live
quarkus.smallrye-health.readiness-path=/health/ready
# Metrics Configuration (PROD)
quarkus.micrometer.enabled=true
quarkus.micrometer.export.prometheus.enabled=true
quarkus.micrometer.export.prometheus.path=/metrics
# Jackson Configuration (PROD)
quarkus.jackson.fail-on-unknown-properties=false
quarkus.jackson.write-dates-as-timestamps=false
quarkus.jackson.serialization-inclusion=non_null
# Performance tuning (PROD)
quarkus.thread-pool.core-threads=2
quarkus.thread-pool.max-threads=16
quarkus.thread-pool.queue-size=100
# SSL/TLS Configuration (PROD)
quarkus.http.ssl.certificate.key-store-file=${SSL_KEYSTORE_FILE:/etc/ssl/keystore.p12}
quarkus.http.ssl.certificate.key-store-password=${SSL_KEYSTORE_PASSWORD}
quarkus.http.ssl.certificate.key-store-file-type=PKCS12
# Monitoring & Observability
quarkus.log.handler.gelf.enabled=false
quarkus.log.handler.gelf.host=${GRAYLOG_HOST:logs.lions.dev}
quarkus.log.handler.gelf.port=${GRAYLOG_PORT:12201}
# ============================================
# SSL/TLS Configuration PROD (optionnel)
# ============================================
# Décommenter si le serveur gère le SSL directement (sinon géré par Ingress/Load Balancer)
# quarkus.http.ssl.certificate.key-store-file=${SSL_KEYSTORE_FILE:/etc/ssl/keystore.p12}
# quarkus.http.ssl.certificate.key-store-password=${SSL_KEYSTORE_PASSWORD}
# quarkus.http.ssl.certificate.key-store-file-type=PKCS12

View File

@@ -33,8 +33,7 @@ lions.keycloak.timeout-seconds=30
# Realms autorisés (séparés par virgule)
lions.keycloak.authorized-realms=btpxpress,master,lions-realm
# Circuit Breaker Configuration
quarkus.smallrye-fault-tolerance.enabled=true
# Circuit Breaker Configuration (SmallRye Fault Tolerance est activé par défaut)
# Retry Configuration (pour appels Keycloak)
lions.keycloak.retry.max-attempts=3
@@ -46,14 +45,22 @@ lions.audit.log-to-database=false
lions.audit.log-to-file=true
lions.audit.retention-days=90
# Database Configuration (optionnel - pour logs d'audit)
# Décommenter si vous voulez persister les logs d'audit en DB
#quarkus.datasource.db-kind=postgresql
#quarkus.datasource.username=${DB_USERNAME:audit_user}
#quarkus.datasource.password=${DB_PASSWORD:audit_pass}
#quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:lions_audit}
#quarkus.hibernate-orm.database.generation=none
#quarkus.flyway.migrate-at-start=true
# Database Configuration (pour logs d'audit et données opérationnelles)
# DÉSACTIVÉ - Non utilisé en production (logs gérés par Kubernetes)
quarkus.datasource.health.enabled=false
quarkus.datasource.devservices.enabled=false
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:lions_user}
quarkus.datasource.password=${DB_PASSWORD:lions_password}
quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:lions_user_manager}
# Hibernate ORM Configuration
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.log.sql=false
# Flyway Configuration
# DÉSACTIVÉ - Pas de base de données en production
quarkus.flyway.migrate-at-start=false
# Logging Configuration
quarkus.log.level=INFO
@@ -64,14 +71,15 @@ quarkus.log.console.enable=true
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n
# File Logging pour Audit
quarkus.log.file.enable=true
quarkus.log.file.path=logs/lions-user-manager.log
quarkus.log.file.rotation.max-file-size=10M
quarkus.log.file.rotation.max-backup-index=10
# DÉSACTIVÉ - Logs gérés par Kubernetes (stdout/stderr)
quarkus.log.file.enable=false
# quarkus.log.file.path=logs/lions-user-manager.log
# quarkus.log.file.rotation.max-file-size=10M
# quarkus.log.file.rotation.max-backup-index=10
# OpenAPI/Swagger Configuration
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
# Le chemin par défaut est /q/swagger-ui (pas besoin de le spécifier)
mp.openapi.extensions.smallrye.info.title=Lions User Manager API
mp.openapi.extensions.smallrye.info.version=1.0.0
mp.openapi.extensions.smallrye.info.description=API de gestion centralisée des utilisateurs Keycloak

File diff suppressed because one or more lines are too long

View File

@@ -11,11 +11,20 @@ quarkus.http.cors.methods=GET,POST,PUT,DELETE,PATCH,OPTIONS
quarkus.http.cors.headers=*
# Keycloak OIDC Configuration (DEV)
quarkus.oidc.auth-server-url=http://localhost:8180/realms/master
quarkus.oidc.client-id=lions-user-manager
quarkus.oidc.credentials.secret=dev-secret-change-me
quarkus.oidc.tls.verification=none
# Le backend vérifie les tokens JWT envoyés par le client
# IMPORTANT: Pour un service, Quarkus valide les tokens JWT sans avoir besoin d'un client-id/secret
# Le backend accepte les tokens émis pour n'importe quel client du realm
quarkus.oidc.enabled=true
quarkus.oidc.auth-server-url=http://localhost:8180/realms/lions-user-manager
quarkus.oidc.application-type=service
quarkus.oidc.tls.verification=none
quarkus.oidc.token.issuer=http://localhost:8180/realms/lions-user-manager
quarkus.oidc.discovery-enabled=true
# Accepter les tokens avec audience "account" (audience par défaut de Keycloak)
# Cela permet d'accepter les tokens émis pour le frontend sans configuration Keycloak supplémentaire
quarkus.oidc.token.audience=account
# Vérifier le token (obligatoire pour un service)
quarkus.oidc.verify-access-token=true
# Keycloak Admin Client Configuration (DEV)
lions.keycloak.server-url=http://localhost:8180
@@ -27,7 +36,7 @@ lions.keycloak.connection-pool-size=5
lions.keycloak.timeout-seconds=30
# Realms autorisés (DEV)
lions.keycloak.authorized-realms=btpxpress,master,lions-realm,test-realm
lions.keycloak.authorized-realms=lions-user-manager,master,btpxpress,test-realm
# Circuit Breaker Configuration (DEV - plus permissif)
quarkus.smallrye-fault-tolerance.enabled=true
@@ -52,14 +61,17 @@ lions.audit.retention-days=30
#quarkus.flyway.migrate-at-start=false
# Logging Configuration (DEV)
quarkus.log.level=DEBUG
quarkus.log.level=INFO
quarkus.log.category."dev.lions.user.manager".level=DEBUG
quarkus.log.category."org.keycloak".level=INFO
quarkus.log.category."io.quarkus".level=INFO
# Logging OIDC pour debug
quarkus.log.category."io.quarkus.oidc".level=DEBUG
quarkus.log.category."io.quarkus.security".level=DEBUG
quarkus.log.console.enable=true
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.console.color=true
# quarkus.log.console.color est déprécié dans Quarkus 3.x
# File Logging pour Audit (DEV)
quarkus.log.file.enable=true
@@ -69,14 +81,48 @@ quarkus.log.file.rotation.max-backup-index=3
# OpenAPI/Swagger Configuration (DEV - toujours activé)
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
quarkus.swagger-ui.enable=true
# Le chemin par défaut est /q/swagger-ui (pas besoin de le spécifier)
# Dev Services (activé en DEV)
quarkus.devservices.enabled=false
# Security Configuration (DEV - plus permissif)
# Security Configuration (DEV)
# La sécurité est activée - les rôles sont vérifiés via OIDC/Keycloak
# Note: KeycloakTestUserConfig configure automatiquement l'utilisateur de test au démarrage
quarkus.security.auth.enabled=true
quarkus.security.jaxrs.deny-unannotated-endpoints=false
quarkus.security.auth.proactive=false
# Configuration OIDC - Extraction des rôles
# Le backend extrait les rôles depuis realm_access/roles (standard Keycloak)
# Le scope "roles" de Keycloak crée automatiquement realm_access.roles
# Syntaxe Quarkus: utiliser un slash pour les chemins imbriqués
quarkus.oidc.roles.role-claim-path=realm_access/roles
# Définir explicitement le profil pour que DevSecurityContextProducer le détecte
quarkus.profile=dev
# Logging pour debug du filtre de sécurité
quarkus.log.category."dev.lions.user.manager.security".level=DEBUG
# Logging OIDC et Security pour debug
quarkus.log.category."io.quarkus.oidc".level=DEBUG
quarkus.log.category."io.quarkus.oidc.runtime".level=DEBUG
quarkus.log.category."io.quarkus.security".level=DEBUG
quarkus.log.category."io.quarkus.security.runtime".level=DEBUG
# Hot Reload
quarkus.live-reload.instrumentation=true
# Désactiver le continuous testing qui bloque le démarrage
quarkus.test.continuous-testing=disabled
# Indexer les dépendances Keycloak pour éviter les warnings
quarkus.index-dependency.keycloak-admin.group-id=org.keycloak
quarkus.index-dependency.keycloak-admin.artifact-id=keycloak-admin-client
quarkus.index-dependency.keycloak-core.group-id=org.keycloak
quarkus.index-dependency.keycloak-core.artifact-id=keycloak-core
# Jackson - Ignorer les propriétés inconnues pour compatibilité Keycloak
quarkus.jackson.fail-on-unknown-properties=false

View File

@@ -71,7 +71,7 @@ quarkus.log.file.rotation.max-backup-index=10
# OpenAPI/Swagger Configuration
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
# Le chemin par défaut est /q/swagger-ui (pas besoin de le spécifier)
mp.openapi.extensions.smallrye.info.title=Lions User Manager API
mp.openapi.extensions.smallrye.info.version=1.0.0
mp.openapi.extensions.smallrye.info.description=API de gestion centralisée des utilisateurs Keycloak

Binary file not shown.

View File

@@ -1,3 +0,0 @@
artifactId=lions-user-manager-server-impl-quarkus
groupId=dev.lions.user.manager
version=1.0.0

View File

@@ -1,10 +1,31 @@
dev\lions\user\manager\resource\UserResource$ErrorResponse.class
dev\lions\user\manager\mapper\RoleMapper.class
dev\lions\user\manager\resource\AuditResource$ErrorResponse.class
dev\lions\user\manager\resource\SyncResource$SyncUsersResponse.class
dev\lions\user\manager\service\impl\UserServiceImpl.class
dev\lions\user\manager\client\KeycloakAdminClient.class
dev\lions\user\manager\client\KeycloakAdminClientImpl.class
dev\lions\user\manager\resource\UserResource.class
dev\lions\user\manager\resource\SyncResource$ErrorResponse.class
dev\lions\user\manager\service\impl\RoleServiceImpl.class
dev\lions\user\manager\security\DevSecurityContextProducer$DevSecurityContext$1.class
dev\lions\user\manager\resource\AuditResource$CountResponse.class
dev\lions\user\manager\resource\KeycloakHealthCheck.class
dev\lions\user\manager\resource\UserResource$SessionsRevokedResponse.class
dev\lions\user\manager\resource\AuditResource.class
dev\lions\user\manager\resource\SyncResource$SyncRolesResponse.class
dev\lions\user\manager\mapper\UserMapper.class
dev\lions\user\manager\resource\HealthResourceEndpoint.class
dev\lions\user\manager\resource\RoleResource$RoleAssignmentRequest.class
dev\lions\user\manager\resource\UserResource$PasswordResetRequest.class
dev\lions\user\manager\security\DevSecurityContextProducer$DevSecurityContext.class
dev\lions\user\manager\config\KeycloakTestUserConfig.class
dev\lions\user\manager\service\impl\SyncServiceImpl.class
dev\lions\user\manager\security\DevSecurityContextProducer.class
dev\lions\user\manager\resource\UserResource$ErrorResponse.class
dev\lions\user\manager\service\impl\AuditServiceImpl.class
dev\lions\user\manager\resource\SyncResource.class
dev\lions\user\manager\client\KeycloakAdminClient.class
dev\lions\user\manager\resource\RoleResource.class
dev\lions\user\manager\config\JacksonConfig.class
dev\lions\user\manager\resource\SyncResource$HealthCheckResponse.class
dev\lions\user\manager\client\KeycloakAdminClientImpl.class
dev\lions\user\manager\resource\RoleResource$ErrorResponse.class
dev\lions\user\manager\resource\UserResource.class
dev\lions\user\manager\resource\SyncResource$ExistsCheckResponse.class
dev\lions\user\manager\resource\UserResource$SessionsRevokedResponse.class
dev\lions\user\manager\resource\HealthResourceEndpoint.class

View File

@@ -1,7 +1,17 @@
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\resource\KeycloakHealthCheck.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\service\impl\UserServiceImpl.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\resource\AuditResource.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\resource\HealthResourceEndpoint.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\mapper\RoleMapper.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\resource\SyncResource.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\resource\UserResource.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\client\KeycloakAdminClient.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\mapper\UserMapper.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\config\KeycloakTestUserConfig.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\resource\KeycloakHealthCheck.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\service\impl\UserServiceImpl.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\security\DevSecurityContextProducer.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\service\impl\AuditServiceImpl.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\service\impl\SyncServiceImpl.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\resource\RoleResource.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\client\KeycloakAdminClientImpl.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\config\JacksonConfig.java
C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\src\main\java\dev\lions\user\manager\service\impl\RoleServiceImpl.java

Binary file not shown.