diff --git a/Dockerfile.prod b/Dockerfile.prod
new file mode 100644
index 0000000..ead8e81
--- /dev/null
+++ b/Dockerfile.prod
@@ -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
+
diff --git a/logs/dev/lions-user-manager.log b/logs/dev/lions-user-manager.log
new file mode 100644
index 0000000..e6cbfb7
--- /dev/null
+++ b/logs/dev/lions-user-manager.log
@@ -0,0 +1,4 @@
+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
diff --git a/logs/dev/lions-user-manager.log.1 b/logs/dev/lions-user-manager.log.1
new file mode 100644
index 0000000..570596c
--- /dev/null
+++ b/logs/dev/lions-user-manager.log.1
@@ -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
diff --git a/logs/dev/lions-user-manager.log.2 b/logs/dev/lions-user-manager.log.2
new file mode 100644
index 0000000..5162a71
--- /dev/null
+++ b/logs/dev/lions-user-manager.log.2
@@ -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
diff --git a/logs/dev/lions-user-manager.log.3 b/logs/dev/lions-user-manager.log.3
new file mode 100644
index 0000000..d32cacf
--- /dev/null
+++ b/logs/dev/lions-user-manager.log.3
@@ -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
diff --git a/pom.xml b/pom.xml
index cb22eeb..834730f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,25 +74,10 @@
quarkus-smallrye-fault-tolerance
-
+
- org.keycloak
- keycloak-admin-client
- 23.0.3
-
-
- org.jboss.resteasy
- resteasy-client
-
-
- org.jboss.resteasy
- resteasy-multipart-provider
-
-
- org.jboss.resteasy
- resteasy-jackson2-provider
-
-
+ io.quarkus
+ quarkus-keycloak-admin-rest-client
diff --git a/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java b/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java
index cf581b9..1858e5c 100644
--- a/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java
+++ b/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java
@@ -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;
}
}
diff --git a/src/main/java/dev/lions/user/manager/config/JacksonConfig.java b/src/main/java/dev/lions/user/manager/config/JacksonConfig.java
new file mode 100644
index 0000000..b59e643
--- /dev/null
+++ b/src/main/java/dev/lions/user/manager/config/JacksonConfig.java
@@ -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);
+ }
+}
diff --git a/src/main/java/dev/lions/user/manager/config/KeycloakTestUserConfig.java b/src/main/java/dev/lions/user/manager/config/KeycloakTestUserConfig.java
new file mode 100644
index 0000000..f242814
--- /dev/null
+++ b/src/main/java/dev/lions/user/manager/config/KeycloakTestUserConfig.java
@@ -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 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 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 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());
+ }
+}
+
diff --git a/src/main/java/dev/lions/user/manager/mapper/RoleMapper.java b/src/main/java/dev/lions/user/manager/mapper/RoleMapper.java
index f70e04b..46385ea 100644
--- a/src/main/java/dev/lions/user/manager/mapper/RoleMapper.java
+++ b/src/main/java/dev/lions/user/manager/mapper/RoleMapper.java
@@ -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);
diff --git a/src/main/java/dev/lions/user/manager/resource/AuditResource.java b/src/main/java/dev/lions/user/manager/resource/AuditResource.java
new file mode 100644
index 0000000..aaedd35
--- /dev/null
+++ b/src/main/java/dev/lions/user/manager/resource/AuditResource.java
@@ -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 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 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 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 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 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 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 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 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;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java b/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java
index 21bf146..e4691e0 100644
--- a/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java
+++ b/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java
@@ -28,16 +28,12 @@ public class HealthResourceEndpoint {
Map 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");
diff --git a/src/main/java/dev/lions/user/manager/resource/RoleResource.java b/src/main/java/dev/lions/user/manager/resource/RoleResource.java
index 826ec1d..1180116 100644
--- a/src/main/java/dev/lions/user/manager/resource/RoleResource.java
+++ b/src/main/java/dev/lions/user/manager/resource/RoleResource.java
@@ -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 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 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 roles = roleService.getAllClientRoles(clientId, realmName);
+ List 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 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 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 childRoleIds = request.roleNames.stream()
+ .map(name -> {
+ Optional 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 composites = roleService.getCompositeRoles(roleName, realmName);
+ // Récupérer l'ID du rôle par son nom
+ Optional 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 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);
diff --git a/src/main/java/dev/lions/user/manager/resource/SyncResource.java b/src/main/java/dev/lions/user/manager/resource/SyncResource.java
new file mode 100644
index 0000000..db8c9fa
--- /dev/null
+++ b/src/main/java/dev/lions/user/manager/resource/SyncResource.java
@@ -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 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 status = syncService.getKeycloakHealthInfo();
+ return Response.ok(status).build(); // status est maintenant une Map
+ } 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 users;
+
+ public SyncUsersResponse(int count, List 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 roles;
+
+ public SyncRolesResponse(int count, List 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;
+ }
+ }
+}
diff --git a/src/main/java/dev/lions/user/manager/security/DevSecurityContextProducer.java b/src/main/java/dev/lions/user/manager/security/DevSecurityContextProducer.java
new file mode 100644
index 0000000..9c86fdb
--- /dev/null
+++ b/src/main/java/dev/lions/user/manager/security/DevSecurityContextProducer.java
@@ -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";
+ }
+ }
+}
+
diff --git a/src/main/java/dev/lions/user/manager/service/impl/AuditServiceImpl.java b/src/main/java/dev/lions/user/manager/service/impl/AuditServiceImpl.java
index 06ebf1f..26a2827 100644
--- a/src/main/java/dev/lions/user/manager/service/impl/AuditServiceImpl.java
+++ b/src/main/java/dev/lions/user/manager/service/impl/AuditServiceImpl.java
@@ -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 searchLogs(@NotBlank String acteurUsername, LocalDateTime dateDebut,
+ public List 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 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 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 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 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 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 countByActionType(@NotBlank String realmName,
+ LocalDateTime dateDebut,
+ LocalDateTime dateFin) {
+ return getActionStatistics(dateDebut, dateFin);
+ }
+
+ @Override
+ public Map countByActeur(@NotBlank String realmName,
+ LocalDateTime dateDebut,
+ LocalDateTime dateFin) {
+ return getUserActivityStatistics(dateDebut, dateFin);
+ }
+
+ @Override
+ public Map countSuccessVsFailure(@NotBlank String realmName,
+ LocalDateTime dateDebut,
+ LocalDateTime dateFin) {
+ long successCount = getSuccessCount(dateDebut, dateFin);
+ long failureCount = getFailureCount(dateDebut, dateFin);
+
+ Map 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 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 getAuditStatistics(@NotBlank String realmName,
+ LocalDateTime dateDebut,
+ LocalDateTime dateFin) {
+ Map 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 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 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 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 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 getActionStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
+ // Méthodes privées helper
+ private Map 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 getUserActivityStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
+ private Map 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 exportLogsToCSV(LocalDateTime dateDebut, LocalDateTime dateFin) {
+ private List exportLogsToCSV(LocalDateTime dateDebut, LocalDateTime dateFin) {
log.info("Export CSV des logs d'audit entre {} et {}", dateDebut, dateFin);
List 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 ====================
/**
diff --git a/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java b/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java
index d465e4d..9a33c18 100644
--- a/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java
+++ b/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java
@@ -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 getRealmRoleById(@NotBlank String roleId, @NotBlank String realmName) {
+ // Méthodes privées helper pour utilisation interne
+ private Optional 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 getRealmRoleByName(@NotBlank String roleName, @NotBlank String realmName) {
+ private Optional getRealmRoleByName(String roleName, String realmName) {
log.debug("Récupération du rôle realm par nom: {} dans le realm: {}", roleName, realmName);
try {
@@ -97,68 +98,164 @@ 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);
- RoleResource roleResource = keycloakAdminClient.getInstance()
- .realm(realmName)
- .roles()
- .get(roleName);
+ if (typeRole == TypeRole.REALM_ROLE) {
+ // Trouver le nom du rôle par son ID
+ Optional existingRole = getRealmRoleById(roleId, realmName);
+ if (existingRole.isEmpty()) {
+ throw new jakarta.ws.rs.NotFoundException("Rôle non trouvé: " + roleId);
+ }
+ String roleName = existingRole.get().getName();
- RoleRepresentation roleRep = roleResource.toRepresentation();
+ RoleResource roleResource = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .roles()
+ .get(roleName);
- // Mettre à jour uniquement les champs modifiables
- if (roleDTO.getDescription() != null) {
- roleRep.setDescription(roleDTO.getDescription());
+ RoleRepresentation roleRep = roleResource.toRepresentation();
+
+ // Mettre à jour uniquement les champs modifiables
+ 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 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 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;
}
- roleResource.update(roleRep);
-
- // Retourner le rôle mis à jour
- return RoleMapper.toDTO(roleResource.toRepresentation(), realmName, TypeRole.REALM_ROLE);
+ 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);
- keycloakAdminClient.getInstance()
- .realm(realmName)
- .roles()
- .deleteRole(roleName);
+ if (typeRole == TypeRole.REALM_ROLE) {
+ // Trouver le nom du rôle par son ID
+ Optional 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 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 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 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);
- List roleReps = keycloakAdminClient.getInstance()
- .realm(realmName)
- .roles()
- .list();
+ 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");
+ }
- return RoleMapper.toDTOList(roleReps, realmName, TypeRole.REALM_ROLE);
+ List 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 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 getClientRoleByName(@NotBlank String roleName, @NotBlank String clientId,
- @NotBlank String realmName) {
+ // Méthode privée helper pour utilisation interne
+ private Optional 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 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 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 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 clients =
- clientsResource.findByClientId(clientId);
+ clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
return List.of();
@@ -260,16 +337,102 @@ public class RoleServiceImpl implements RoleService {
.list();
List 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 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 clients =
+ clientsResource.findByClientId(clientName);
+
+ if (clients.isEmpty()) {
+ return Optional.empty();
+ }
+
+ String internalClientId = clients.get(0).getId();
+ List 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 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 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 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 roleNames,
- @NotBlank String realmName) {
+ private void revokeRealmRolesFromUser(String userId, List 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 roleNames, @NotBlank String realmName) {
+ private void assignClientRolesToUser(String userId, String clientId,
+ List 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 roleNames, @NotBlank String realmName) {
+ private void revokeClientRolesFromUser(String userId, String clientId,
+ List 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 getUserClientRoles(@NotBlank String userId, @NotBlank String clientId,
- @NotBlank String realmName) {
+ public List 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 clients =
- clientsResource.findByClientId(clientId);
+ clientsResource.findByClientId(clientName);
if (clients.isEmpty()) {
return List.of();
@@ -457,86 +618,246 @@ public class RoleServiceImpl implements RoleService {
.listAll();
List 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 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 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 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 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 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 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 compositesToAdd = compositeRoleNames.stream()
- .map(compositeName -> {
- try {
- return rolesResource.get(compositeName).toRepresentation();
- } catch (NotFoundException e) {
- log.warn("Rôle composite {} non trouvé, ignoré", compositeName);
- return null;
- }
- })
- .filter(role -> role != null)
- .collect(Collectors.toList());
+ if (typeRole == TypeRole.REALM_ROLE) {
+ RoleResource roleResource = rolesResource.get(parentRoleName);
- if (!compositesToAdd.isEmpty()) {
- roleResource.addComposites(compositesToAdd);
+ // Convertir les IDs en noms de rôles
+ List childRoleNames = childRoleIds.stream()
+ .map(childRoleId -> {
+ Optional childRole = getRealmRoleById(childRoleId, realmName);
+ return childRole.map(RoleDTO::getName).orElse(null);
+ })
+ .filter(name -> name != null)
+ .collect(Collectors.toList());
+
+ List compositesToAdd = childRoleNames.stream()
+ .map(compositeName -> {
+ try {
+ return rolesResource.get(compositeName).toRepresentation();
+ } catch (NotFoundException e) {
+ log.warn("Rôle composite {} non trouvé, ignoré", compositeName);
+ return null;
+ }
+ })
+ .filter(role -> role != null)
+ .collect(Collectors.toList());
+
+ if (!compositesToAdd.isEmpty()) {
+ roleResource.addComposites(compositesToAdd);
+ }
+ } else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
+ // Pour les rôles client, utiliser le client
+ ClientsResource clientsResource = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .clients();
+
+ List 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 childRoleNames = childRoleIds.stream()
+ .map(childRoleId -> {
+ Optional childRole = getRoleById(childRoleId, realmName, typeRole, clientName);
+ return childRole.map(RoleDTO::getName).orElse(null);
+ })
+ .filter(name -> name != null)
+ .collect(Collectors.toList());
+
+ List 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 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 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 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 compositesToRemove = compositeRoleNames.stream()
- .map(compositeName -> {
- try {
- return rolesResource.get(compositeName).toRepresentation();
- } catch (NotFoundException e) {
- log.warn("Rôle composite {} non trouvé, ignoré", compositeName);
- return null;
- }
- })
- .filter(role -> role != null)
- .collect(Collectors.toList());
+ if (typeRole == TypeRole.REALM_ROLE) {
+ RoleResource roleResource = rolesResource.get(parentRoleName);
- if (!compositesToRemove.isEmpty()) {
- roleResource.deleteComposites(compositesToRemove);
+ // Convertir les IDs en noms de rôles
+ List childRoleNames = childRoleIds.stream()
+ .map(childRoleId -> {
+ Optional childRole = getRealmRoleById(childRoleId, realmName);
+ return childRole.map(RoleDTO::getName).orElse(null);
+ })
+ .filter(name -> name != null)
+ .collect(Collectors.toList());
+
+ List compositesToRemove = childRoleNames.stream()
+ .map(compositeName -> {
+ try {
+ return rolesResource.get(compositeName).toRepresentation();
+ } catch (NotFoundException e) {
+ log.warn("Rôle composite {} non trouvé, ignoré", compositeName);
+ return null;
+ }
+ })
+ .filter(role -> role != null)
+ .collect(Collectors.toList());
+
+ if (!compositesToRemove.isEmpty()) {
+ roleResource.deleteComposites(compositesToRemove);
+ }
+ } else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
+ ClientsResource clientsResource = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .clients();
+
+ List 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 childRoleNames = childRoleIds.stream()
+ .map(childRoleId -> {
+ Optional childRole = getRoleById(childRoleId, realmName, typeRole, clientName);
+ return childRole.map(RoleDTO::getName).orElse(null);
+ })
+ .filter(name -> name != null)
+ .collect(Collectors.toList());
+
+ List 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 getCompositeRoles(@NotBlank String roleName, @NotBlank String realmName) {
- log.debug("Récupération des rôles composites du rôle {} dans le realm {}", roleName, realmName);
- List composites = keycloakAdminClient.getInstance()
+ @Override
+ public List 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 compositesSet = rolesResource
+ .get(roleRep.getName())
.getRoleComposites();
+
+ List composites = new ArrayList<>(compositesSet);
return RoleMapper.toDTOList(composites, realmName, TypeRole.COMPOSITE_ROLE);
}
@@ -544,66 +865,111 @@ public class RoleServiceImpl implements RoleService {
// ==================== 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);
- List userRoles = keycloakAdminClient.getInstance()
- .realm(realmName)
- .users()
- .get(userId)
- .roles()
- .realmLevel()
- .listEffective(); // Incluant les rôles hérités via composites
+ if (typeRole == TypeRole.REALM_ROLE) {
+ List userRoles = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .users()
+ .get(userId)
+ .roles()
+ .realmLevel()
+ .listEffective(); // Incluant les rôles hérités via composites
- return userRoles.stream()
- .anyMatch(role -> role.getName().equals(roleName));
- }
+ return userRoles.stream()
+ .anyMatch(role -> role.getName().equals(roleName));
+ } else if (typeRole == TypeRole.CLIENT_ROLE && clientName != null) {
+ ClientsResource clientsResource = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .clients();
- @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);
+ List clients =
+ clientsResource.findByClientId(clientName);
- ClientsResource clientsResource = keycloakAdminClient.getInstance()
- .realm(realmName)
- .clients();
+ if (clients.isEmpty()) {
+ return false;
+ }
- List clients =
- clientsResource.findByClientId(clientId);
+ String internalClientId = clients.get(0).getId();
+ List userClientRoles = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .users()
+ .get(userId)
+ .roles()
+ .clientLevel(internalClientId)
+ .listEffective();
- if (clients.isEmpty()) {
- return false;
+ return userClientRoles.stream()
+ .anyMatch(role -> role.getName().equals(roleName));
}
- String internalClientId = clients.get(0).getId();
- List userClientRoles = keycloakAdminClient.getInstance()
- .realm(realmName)
- .users()
- .get(userId)
- .roles()
- .clientLevel(internalClientId)
- .listEffective();
-
- return userClientRoles.stream()
- .anyMatch(role -> role.getName().equals(roleName));
+ return false;
}
@Override
- public List 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);
+ 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);
- List effectiveRoles = keycloakAdminClient.getInstance()
- .realm(realmName)
- .users()
- .get(userId)
- .roles()
- .realmLevel()
- .listEffective();
+ return getRoleByName(roleName, realmName, typeRole, clientName).isPresent();
+ }
- return RoleMapper.toDTOList(effectiveRoles, realmName, TypeRole.REALM_ROLE);
+ @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 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 users = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .users()
+ .list();
+
+ 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);
}
}
diff --git a/src/main/java/dev/lions/user/manager/service/impl/SyncServiceImpl.java b/src/main/java/dev/lions/user/manager/service/impl/SyncServiceImpl.java
new file mode 100644
index 0000000..6463e2c
--- /dev/null
+++ b/src/main/java/dev/lions/user/manager/service/impl/SyncServiceImpl.java
@@ -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 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 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 syncAllRealms() {
+ log.info("Synchronisation de tous les realms");
+
+ Map results = new java.util.HashMap<>();
+
+ try {
+ // Lister tous les realms
+ List 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 checkDataConsistency(@NotBlank String realmName) {
+ log.info("Vérification de la cohérence des données pour le realm: {}", realmName);
+
+ Map 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 forceSyncRealm(@NotBlank String realmName) {
+ log.info("Synchronisation forcée du realm: {}", realmName);
+
+ Map 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 getLastSyncStatus(@NotBlank String realmName) {
+ log.debug("Récupération du statut de la dernière synchronisation pour le realm: {}", realmName);
+
+ Map 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 getKeycloakHealthInfo() {
+ log.info("Récupération du statut de santé complet de Keycloak");
+
+ Map 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;
+ }
+}
diff --git a/src/main/java/dev/lions/user/manager/service/impl/UserServiceImpl.java b/src/main/java/dev/lions/user/manager/service/impl/UserServiceImpl.java
index fba8898..ec64b81 100644
--- a/src/main/java/dev/lions/user/manager/service/impl/UserServiceImpl.java
+++ b/src/main/java/dev/lions/user/manager/service/impl/UserServiceImpl.java
@@ -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 realmRoles = userResource.roles().realmLevel().listAll();
+ if (realmRoles != null && !realmRoles.isEmpty()) {
+ List 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);
}
diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties
index 836e8d5..ad9475f 100644
--- a/src/main/resources/application-dev.properties
+++ b/src/main/resources/application-dev.properties
@@ -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
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 0cfcd12..88055f9 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -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
diff --git a/target/build-metrics.json b/target/build-metrics.json
new file mode 100644
index 0000000..f6fc123
--- /dev/null
+++ b/target/build-metrics.json
@@ -0,0 +1 @@
+{"duration":7149,"records":[{"duration":2412,"stepId":"io.quarkus.oidc.deployment.devservices.OidcDevUIProcessor#prepareOidcDevConsole","started":"19:32:05.437","dependents":[606,504,532,503,564,505],"id":502,"thread":"build-22"},{"duration":1959,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#build","started":"19:32:07.181","dependents":[606],"id":568,"thread":"build-33"},{"duration":1442,"stepId":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor#setupClientProxies","started":"19:32:09.713","dependents":[604,606,602,603],"id":601,"thread":"build-33"},{"duration":1139,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#enhancerDomainObjects","started":"19:32:07.822","dependents":[602,561,562],"id":560,"thread":"build-30"},{"duration":934,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#setupEndpoints","started":"19:32:08.776","dependents":[604,606,602,584,603,578,580,586],"id":577,"thread":"build-154"},{"duration":919,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#generateConfigClass","started":"19:32:05.260","dependents":[],"id":356,"thread":"build-74"},{"duration":863,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#logConsoleCommand","started":"19:32:05.166","dependents":[581],"id":346,"thread":"build-56"},{"duration":851,"stepId":"io.quarkus.deployment.steps.ApplicationIndexBuildStep#build","started":"19:32:05.250","dependents":[601,357,492,355,577,483,495],"id":354,"thread":"build-75"},{"duration":839,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#setupConsole","started":"19:32:05.248","dependents":[352,361,351,353],"id":350,"thread":"build-76"},{"duration":809,"stepId":"io.quarkus.devui.deployment.menu.ConfigurationProcessor#registerJsonRpcService","started":"19:32:05.063","dependents":[426,606,332,504,337,503,505],"id":331,"thread":"build-21"},{"duration":779,"stepId":"io.quarkus.arc.deployment.ArcProcessor#buildCompatibleExtensions","started":"19:32:05.063","dependents":[483,460],"id":324,"thread":"build-28"},{"duration":777,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#build","started":"19:32:11.389","dependents":[],"id":606,"thread":"build-13"},{"duration":752,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#createVertxThreadFactory","started":"19:32:05.069","dependents":[606,329],"id":310,"thread":"build-24"},{"duration":731,"stepId":"io.quarkus.micrometer.deployment.binder.VertxBinderProcessor#build","started":"19:32:05.092","dependents":[606,344],"id":319,"thread":"build-32"},{"duration":718,"stepId":"io.quarkus.virtual.threads.VirtualThreadsProcessor#setup","started":"19:32:05.241","dependents":[606,504,483,503,460,505],"id":340,"thread":"build-4"},{"duration":700,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#build","started":"19:32:05.144","dependents":[606,432,483,460],"id":325,"thread":"build-33"},{"duration":679,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#getAllExtensions","started":"19:32:08.317","dependents":[565,566,570,567,569],"id":564,"thread":"build-62"},{"duration":674,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#quitCommand","started":"19:32:05.063","dependents":[581],"id":302,"thread":"build-2"},{"duration":648,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#ioThreadDetector","started":"19:32:05.174","dependents":[606,321],"id":312,"thread":"build-68"},{"duration":624,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#helpCommand","started":"19:32:05.108","dependents":[581],"id":301,"thread":"build-35"},{"duration":619,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#bodyHandler","started":"19:32:05.438","dependents":[606,596],"id":348,"thread":"build-57"},{"duration":595,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#buildStatic","started":"19:32:05.264","dependents":[606],"id":327,"thread":"build-67"},{"duration":589,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#createVertxContextHandlers","started":"19:32:05.233","dependents":[606,334,329],"id":314,"thread":"build-6"},{"duration":581,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#currentContextFactory","started":"19:32:05.241","dependents":[606,534],"id":313,"thread":"build-72"},{"duration":572,"stepId":"io.quarkus.micrometer.deployment.export.PrometheusRegistryProcessor#createPrometheusRoute","started":"19:32:05.301","dependents":[606,336,593,594],"id":333,"thread":"build-71"},{"duration":558,"stepId":"io.quarkus.deployment.steps.ConfigDescriptionBuildStep#createConfigDescriptions","started":"19:32:05.241","dependents":[553,559],"id":305,"thread":"build-48"},{"duration":550,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#parsePersistenceXmlDescriptors","started":"19:32:05.247","dependents":[447,304],"id":303,"thread":"build-3"},{"duration":546,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#shutdownConfigValidator","started":"19:32:05.045","dependents":[606],"id":293,"thread":"build-7"},{"duration":537,"stepId":"io.quarkus.deployment.dev.io.NioThreadPoolDevModeProcessor#setupTCCL","started":"19:32:05.045","dependents":[606],"id":289,"thread":"build-8"},{"duration":529,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmCdiProcessor#generateJpaConfigBean","started":"19:32:05.434","dependents":[606,504,503,505],"id":342,"thread":"build-29"},{"duration":527,"stepId":"io.quarkus.netty.deployment.NettyProcessor#eagerlyInitClass","started":"19:32:05.060","dependents":[606],"id":292,"thread":"build-10"},{"duration":521,"stepId":"io.quarkus.mutiny.deployment.MutinyProcessor#buildTimeInit","started":"19:32:05.061","dependents":[606],"id":286,"thread":"build-19"},{"duration":518,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#setupAdditionalBeans","started":"19:32:05.100","dependents":[606,483,460],"id":295,"thread":"build-38"},{"duration":499,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#createHttpAuthenticationHandler","started":"19:32:05.322","dependents":[606,338,537],"id":311,"thread":"build-12"},{"duration":497,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#addRoutingCtxToSecurityEventsForCdiBeans","started":"19:32:05.325","dependents":[606,341],"id":317,"thread":"build-51"},{"duration":468,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#build","started":"19:32:05.341","dependents":[604,309,328,335,483,460,445],"id":308,"thread":"build-37"},{"duration":463,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#registerMetrics","started":"19:32:05.344","dependents":[606,550,551,458],"id":306,"thread":"build-83"},{"duration":447,"stepId":"io.quarkus.arc.deployment.ArcProcessor#generateResources","started":"19:32:08.317","dependents":[604,602,534],"id":533,"thread":"build-83"},{"duration":434,"stepId":"io.quarkus.arc.deployment.ArcProcessor#registerBeans","started":"19:32:07.366","dependents":[501,486,504,489,503,495,488,490,487,497,496,492,494,584,511,498,491,505,493],"id":485,"thread":"build-48"},{"duration":428,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthDevUiProcessor#create","started":"19:32:05.301","dependents":[606,532,564],"id":300,"thread":"build-44"},{"duration":418,"stepId":"io.quarkus.deployment.index.ApplicationArchiveBuildStep#build","started":"19:32:06.102","dependents":[363,361,360,602,561,516,447,358,359,595,397,460],"id":357,"thread":"build-29"},{"duration":412,"stepId":"io.quarkus.deployment.steps.BannerProcessor#recordBanner","started":"19:32:05.434","dependents":[606,458],"id":326,"thread":"build-20"},{"duration":411,"stepId":"io.quarkus.devui.deployment.build.BuildMetricsDevUIProcessor#create","started":"19:32:05.172","dependents":[606],"id":290,"thread":"build-65"},{"duration":402,"stepId":"io.quarkus.deployment.steps.PreloadClassesBuildStep#preInit","started":"19:32:05.131","dependents":[606],"id":281,"thread":"build-15"},{"duration":402,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#produceNamedHttpSecurityPolicies","started":"19:32:05.131","dependents":[606,504,503,505],"id":279,"thread":"build-5"},{"duration":399,"stepId":"io.quarkus.keycloak.admin.client.reactive.KeycloakAdminClientReactiveProcessor#registerKeycloakAdminClientBeans","started":"19:32:05.434","dependents":[606,504,503,505],"id":323,"thread":"build-30"},{"duration":389,"stepId":"io.quarkus.datasource.deployment.DataSourcesExcludedFromHealthChecksProcessor#produceBean","started":"19:32:05.434","dependents":[606,504,503,505],"id":320,"thread":"build-31"},{"duration":389,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#cors","started":"19:32:05.434","dependents":[606,596,591],"id":316,"thread":"build-62"},{"duration":388,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#eventLoopCount","started":"19:32:05.434","dependents":[606,600],"id":315,"thread":"build-25"},{"duration":386,"stepId":"io.quarkus.micrometer.deployment.binder.HttpBinderProcessor#enableHttpBinders","started":"19:32:05.437","dependents":[606,504,483,503,460,505],"id":318,"thread":"build-64"},{"duration":372,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#checkForBuildTimeConfigChange","started":"19:32:05.437","dependents":[606],"id":307,"thread":"build-58"},{"duration":357,"stepId":"io.quarkus.devui.deployment.ide.IdeProcessor#createOpenInIDEService","started":"19:32:05.475","dependents":[606,593,332,594],"id":322,"thread":"build-43"},{"duration":349,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initFormAuth","started":"19:32:05.260","dependents":[606,593,594,483,460],"id":294,"thread":"build-53"},{"duration":341,"stepId":"io.quarkus.keycloak.admin.client.reactive.KeycloakAdminClientReactiveProcessor#integrate","started":"19:32:05.241","dependents":[606,597,599],"id":288,"thread":"build-47"},{"duration":340,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#classLoaderHack","started":"19:32:05.241","dependents":[606],"id":284,"thread":"build-54"},{"duration":338,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#additionalBeans","started":"19:32:05.081","dependents":[604,483,460],"id":269,"thread":"build-18"},{"duration":321,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupLoggingStaticInit","started":"19:32:05.261","dependents":[606],"id":287,"thread":"build-11"},{"duration":318,"stepId":"io.quarkus.vertx.deployment.VertxJsonProcessor#registerJacksonSerDeser","started":"19:32:05.048","dependents":[419],"id":260,"thread":"build-20"},{"duration":316,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#addMpClientEnricher","started":"19:32:05.058","dependents":[601],"id":262,"thread":"build-25"},{"duration":312,"stepId":"io.quarkus.arc.deployment.ArcProcessor#validate","started":"19:32:07.894","dependents":[530,521,516,527,519,528,515,526,533,522,602,518,517,520],"id":514,"thread":"build-4"},{"duration":302,"stepId":"io.quarkus.deployment.ide.IdeProcessor#detectRunningIdeProcesses","started":"19:32:05.158","dependents":[274],"id":273,"thread":"build-43"},{"duration":294,"stepId":"io.quarkus.vertx.http.deployment.ManagementInterfaceSecurityProcessor#createManagementAuthMechHandler","started":"19:32:05.326","dependents":[606,297,538],"id":296,"thread":"build-40"},{"duration":287,"stepId":"io.quarkus.devui.deployment.logstream.LogStreamProcessor#createJsonRPCService","started":"19:32:05.075","dependents":[426,332,337],"id":257,"thread":"build-31"},{"duration":276,"stepId":"io.quarkus.vertx.http.deployment.GeneratedStaticResourcesProcessor#process","started":"19:32:05.437","dependents":[606,593,594,595],"id":299,"thread":"build-45"},{"duration":274,"stepId":"io.quarkus.rest.client.reactive.jackson.deployment.RestClientReactiveJacksonProcessor#additionalProviders_3f333413be4c0802e30f75e67ce4dd421dc2e40b","started":"19:32:05.148","dependents":[601,583,585,584,483,460],"id":270,"thread":"build-41"},{"duration":270,"stepId":"io.quarkus.security.deployment.SecurityProcessor#recordBouncyCastleProviders","started":"19:32:05.263","dependents":[606],"id":280,"thread":"build-79"},{"duration":266,"stepId":"io.quarkus.deployment.steps.RuntimeConfigSetupBuildStep#setupRuntimeConfig","started":"19:32:05.167","dependents":[510,500,316,445,599,588,285,298,591,349,323,600,329,541,278,282,318,453,509,299,508,538,598,544,606,502,315,291,326,474,478,543,537,515,347,307,596,359,342,344,501,594,459,458,443,571,320,572,348,587],"id":272,"thread":"build-60"},{"duration":254,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#vertxIntegration","started":"19:32:05.168","dependents":[601,583,585,584],"id":271,"thread":"build-62"},{"duration":253,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#autoAddScope","started":"19:32:05.144","dependents":[469],"id":268,"thread":"build-30"},{"duration":252,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#registerAdditionalBeans","started":"19:32:05.324","dependents":[606,514,522,469,504,483,503,460,505],"id":283,"thread":"build-9"},{"duration":250,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createBuildTimeConstJsTemplate","started":"19:32:09.033","dependents":[574,575],"id":573,"thread":"build-69"},{"duration":249,"stepId":"io.quarkus.devui.deployment.menu.ContinuousTestingProcessor#createJsonRPCService","started":"19:32:05.106","dependents":[426,332,337],"id":252,"thread":"build-39"},{"duration":241,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#unremovableBeans","started":"19:32:05.149","dependents":[514,522],"id":267,"thread":"build-14"},{"duration":234,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#buildTimeRunTimeConfig","started":"19:32:05.247","dependents":[604,557],"id":275,"thread":"build-52"},{"duration":232,"stepId":"io.quarkus.keycloak.admin.client.reactive.KeycloakAdminClientReactiveProcessor#nativeImage","started":"19:32:05.147","dependents":[604,603],"id":264,"thread":"build-45"},{"duration":229,"stepId":"io.quarkus.vertx.http.deployment.console.ConsoleProcessor#setupConsole","started":"19:32:05.301","dependents":[597,599],"id":276,"thread":"build-78"},{"duration":229,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#setupPersistenceProvider","started":"19:32:05.434","dependents":[606,548,546],"id":298,"thread":"build-18"},{"duration":211,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#setupConfigOverride","started":"19:32:05.132","dependents":[],"id":243,"thread":"build-23"},{"duration":211,"stepId":"io.quarkus.narayana.jta.deployment.NarayanaJtaProcessor#build","started":"19:32:06.544","dependents":[604,606,509,458,483,460,445,444],"id":443,"thread":"build-37"},{"duration":209,"stepId":"io.quarkus.vertx.http.deployment.webjar.WebJarProcessor#processWebJarDevMode","started":"19:32:08.996","dependents":[606,572,570,571],"id":569,"thread":"build-30"},{"duration":198,"stepId":"io.quarkus.netty.deployment.NettyProcessor#setNettyMachineId","started":"19:32:05.147","dependents":[606],"id":246,"thread":"build-27"},{"duration":183,"stepId":"io.quarkus.deployment.steps.ClassPathSystemPropBuildStep#set","started":"19:32:05.349","dependents":[606],"id":277,"thread":"build-27"},{"duration":182,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#fileHandling","started":"19:32:05.147","dependents":[601,585,584],"id":233,"thread":"build-36"},{"duration":176,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#pregenProxies","started":"19:32:11.213","dependents":[606],"id":605,"thread":"build-130"},{"duration":172,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#defineJpaEntities","started":"19:32:06.545","dependents":[604,605,454,452,453,447,560,457,546,440,439],"id":438,"thread":"build-51"},{"duration":163,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#contextInjection","started":"19:32:05.167","dependents":[469,466,483,460],"id":236,"thread":"build-61"},{"duration":163,"stepId":"io.quarkus.arc.deployment.ConfigStaticInitBuildSteps#transformConfigProducer","started":"19:32:05.217","dependents":[483],"id":265,"thread":"build-64"},{"duration":159,"stepId":"io.quarkus.deployment.DockerStatusProcessor#IsDockerWorking","started":"19:32:05.172","dependents":[555],"id":237,"thread":"build-66"},{"duration":149,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#beanValidationAnnotations","started":"19:32:06.991","dependents":[482,480],"id":479,"thread":"build-62"},{"duration":149,"stepId":"io.quarkus.security.deployment.SecurityProcessor#recordRuntimeConfigReady","started":"19:32:05.434","dependents":[606],"id":291,"thread":"build-17"},{"duration":148,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#build","started":"19:32:07.141","dependents":[604,606,514,522,535,483],"id":482,"thread":"build-37"},{"duration":148,"stepId":"io.quarkus.deployment.steps.ReflectiveHierarchyStep#build","started":"19:32:11.156","dependents":[604],"id":603,"thread":"build-62"},{"duration":148,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#releaseConfigOnShutdown","started":"19:32:05.434","dependents":[606],"id":285,"thread":"build-41"},{"duration":147,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initBasicAuth","started":"19:32:05.233","dependents":[481,483,478,460],"id":266,"thread":"build-73"},{"duration":144,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#build_68c59e5d5fe4deeaa2b750dd2b2f234cee36c063","started":"19:32:05.862","dependents":[606,501,594,504,349,503,600,543,597,347,596,345,505,599,499],"id":344,"thread":"build-20"},{"duration":144,"stepId":"io.quarkus.arc.deployment.CommandLineArgumentsProcessor#commandLineArgs","started":"19:32:05.095","dependents":[504,483,503,460,505],"id":168,"thread":"build-37"},{"duration":141,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#registerDevUiHandlers","started":"19:32:09.372","dependents":[606,593,594],"id":576,"thread":"build-33"},{"duration":140,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#beans","started":"19:32:05.217","dependents":[483,460],"id":253,"thread":"build-42"},{"duration":140,"stepId":"io.quarkus.oidc.deployment.devservices.keycloak.KeycloakDevUIProcessor#produceProviderComponent","started":"19:32:05.434","dependents":[606,504,532,503,564,505],"id":282,"thread":"build-73"},{"duration":139,"stepId":"io.quarkus.security.deployment.SecurityProcessor#registerSecurityInterceptors","started":"19:32:05.823","dependents":[606,504,483,503,460,505],"id":341,"thread":"build-62"},{"duration":138,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#filterMultipleVertxInstancesWarning","started":"19:32:05.184","dependents":[458,444],"id":221,"thread":"build-17"},{"duration":135,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#exceptionMapper","started":"19:32:05.097","dependents":[604,436],"id":153,"thread":"build-16"},{"duration":133,"stepId":"io.quarkus.arc.deployment.BeanArchiveProcessor#build","started":"19:32:06.857","dependents":[530,482,467,468,577,461,472,473,479,471,545,464,601,470,526,465,496,469,463,584,483,462,498],"id":460,"thread":"build-30"},{"duration":131,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#exceptionMappers","started":"19:32:05.048","dependents":[436],"id":102,"thread":"build-17"},{"duration":128,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#securityExceptionMappers","started":"19:32:05.162","dependents":[436],"id":216,"thread":"build-49"},{"duration":126,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#addAutoFilters","started":"19:32:07.054","dependents":[568],"id":481,"thread":"build-48"},{"duration":122,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#setupAuthenticationMechanisms","started":"19:32:05.823","dependents":[606,596,591,481,483,478,460],"id":338,"thread":"build-58"},{"duration":120,"stepId":"io.quarkus.deployment.ide.IdeProcessor#detectIdeFiles","started":"19:32:05.060","dependents":[274],"id":109,"thread":"build-13"},{"duration":116,"stepId":"io.quarkus.netty.deployment.NettyProcessor#cleanupUnsafeLog","started":"19:32:05.209","dependents":[458,444],"id":226,"thread":"build-46"},{"duration":116,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#overrideContextInternalInterfaceToAddSafeGuards","started":"19:32:05.127","dependents":[602],"id":174,"thread":"build-12"},{"duration":109,"stepId":"io.quarkus.smallrye.faulttolerance.deployment.SmallRyeFaultToleranceProcessor#processFaultToleranceAnnotations","started":"19:32:08.207","dependents":[604,606,533,531],"id":530,"thread":"build-28"},{"duration":106,"stepId":"io.quarkus.arc.deployment.ArcProcessor#setupExecutor","started":"19:32:05.862","dependents":[606],"id":343,"thread":"build-33"},{"duration":105,"stepId":"io.quarkus.caffeine.deployment.devui.CaffeineDevUIProcessor#createCard","started":"19:32:05.075","dependents":[532,564],"id":105,"thread":"build-6"},{"duration":102,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerProvidersFromAnnotations","started":"19:32:06.544","dependents":[604,514,522,460,434],"id":433,"thread":"build-25"},{"duration":100,"stepId":"io.quarkus.deployment.pkg.steps.JarResultBuildStep#outputTarget","started":"19:32:05.233","dependents":[249,361,568,250],"id":238,"thread":"build-16"},{"duration":99,"stepId":"io.quarkus.deployment.steps.CompiledJavaVersionBuildStep#compiledJavaVersion","started":"19:32:05.264","dependents":[577],"id":259,"thread":"build-58"},{"duration":99,"stepId":"io.quarkus.arc.deployment.devui.ArcDevModeApiProcessor#collectBeanInfo","started":"19:32:08.207","dependents":[529],"id":528,"thread":"build-24"},{"duration":99,"stepId":"io.quarkus.arc.deployment.ShutdownBuildSteps#addScope","started":"19:32:05.223","dependents":[469],"id":220,"thread":"build-70"},{"duration":98,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#setMtlsCertificateRoleProperties","started":"19:32:05.434","dependents":[606],"id":278,"thread":"build-14"},{"duration":96,"stepId":"io.quarkus.smallrye.faulttolerance.deployment.SmallRyeFaultToleranceProcessor#build","started":"19:32:06.543","dependents":[604,606,557,432,483,460],"id":430,"thread":"build-62"},{"duration":92,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupLoggingRuntimeInit","started":"19:32:06.756","dependents":[604,606,459,598],"id":458,"thread":"build-30"},{"duration":90,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#overrideStandardValidationFactoryResolution","started":"19:32:05.147","dependents":[602],"id":163,"thread":"build-3"},{"duration":87,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createIndexHtmlTemplate","started":"19:32:09.284","dependents":[575],"id":574,"thread":"build-33"},{"duration":87,"stepId":"io.quarkus.security.deployment.SecurityProcessor#gatherSecurityChecks","started":"19:32:06.992","dependents":[604,606,557,474,577,476,483],"id":473,"thread":"build-6"},{"duration":87,"stepId":"io.quarkus.devui.deployment.menu.ConfigurationProcessor#registerConfigs","started":"19:32:08.810","dependents":[606],"id":559,"thread":"build-13"},{"duration":87,"stepId":"io.quarkus.security.deployment.SecurityProcessor#registerAdditionalBeans","started":"19:32:05.241","dependents":[483,460],"id":230,"thread":"build-13"},{"duration":85,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#recordableConstructor","started":"19:32:05.131","dependents":[606],"id":139,"thread":"build-42"},{"duration":84,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#build","started":"19:32:05.861","dependents":[606,504,503,505],"id":339,"thread":"build-83"},{"duration":84,"stepId":"io.quarkus.rest.client.reactive.jackson.deployment.RestClientReactiveJacksonProcessor#additionalProviders_d467f0796ff6c3d28e57d6f18c92f27cf1d4298e","started":"19:32:05.108","dependents":[433],"id":121,"thread":"build-4"},{"duration":84,"stepId":"io.quarkus.deployment.ExtensionLoader#config","started":"19:32:05.131","dependents":[300,510,558,195,238,357,157,452,212,534,383,397,314,526,258,169,602,248,500,156,203,193,576,529,191,380,547,588,266,285,550,591,172,401,323,403,207,170,200,528,329,541,278,336,205,453,299,429,508,538,544,333,606,309,354,315,291,230,527,326,476,474,478,171,537,347,249,307,263,359,460,379,340,305,288,342,341,384,344,594,215,274,458,495,443,571,406,572,308,427,540,296,483,567,587,218,280,455,245,284,313,197,577,456,597,433,350,275,199,178,360,533,462,208,179,316,445,599,206,180,298,482,349,467,396,600,303,234,399,471,311,282,601,362,192,185,477,318,509,214,598,502,352,548,568,188,187,481,543,569,232,186,557,204,338,492,522,596,201,306,466,447,434,213,586,196,294,501,459,198,473,320,287,361,356,202,551,518,348,432,210],"id":155,"thread":"build-9"},{"duration":83,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#generateDataSourceSupportBean","started":"19:32:05.810","dependents":[606,514,522,504,483,503,460,505],"id":335,"thread":"build-48"},{"duration":83,"stepId":"io.quarkus.deployment.steps.ClassPathSystemPropBuildStep#produce","started":"19:32:05.264","dependents":[277],"id":247,"thread":"build-80"},{"duration":82,"stepId":"io.quarkus.vertx.http.deployment.GeneratedStaticResourcesProcessor#produceResources","started":"19:32:05.163","dependents":[254],"id":177,"thread":"build-52"},{"duration":80,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#generateMappings","started":"19:32:06.543","dependents":[604,482,431,522,480,494,518,490],"id":429,"thread":"build-30"},{"duration":78,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setMinLevelForInitialConfigurator","started":"19:32:05.250","dependents":[606],"id":232,"thread":"build-55"},{"duration":76,"stepId":"io.quarkus.smallrye.jwt.build.deployment.SmallRyeJwtBuildProcessor#addClassesForReflection","started":"19:32:05.107","dependents":[604],"id":114,"thread":"build-40"},{"duration":76,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#defaultUnwrappedExceptions","started":"19:32:05.161","dependents":[436],"id":162,"thread":"build-48"},{"duration":74,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#registerUiExtension","started":"19:32:05.301","dependents":[569],"id":263,"thread":"build-22"},{"duration":74,"stepId":"io.quarkus.jsonp.deployment.JsonpProcessor#build","started":"19:32:05.108","dependents":[604,606],"id":112,"thread":"build-29"},{"duration":73,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#handleCustomAnnotatedMethods","started":"19:32:06.585","dependents":[483,460,436,437],"id":435,"thread":"build-6"},{"duration":73,"stepId":"io.quarkus.devui.deployment.menu.DependenciesProcessor#createAppDeps","started":"19:32:05.264","dependents":[567],"id":240,"thread":"build-37"},{"duration":70,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#additionalBean","started":"19:32:05.872","dependents":[363,483,460],"id":337,"thread":"build-12"},{"duration":69,"stepId":"io.quarkus.devui.deployment.menu.DependenciesProcessor#createBuildTimeActions","started":"19:32:05.264","dependents":[332],"id":239,"thread":"build-69"},{"duration":66,"stepId":"io.quarkus.flyway.deployment.devui.FlywayDevUIProcessor#registerJsonRpcBackend","started":"19:32:05.184","dependents":[426,337],"id":184,"thread":"build-67"},{"duration":63,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#additionalBeans","started":"19:32:05.063","dependents":[483,460],"id":61,"thread":"build-12"},{"duration":63,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#smallryeOpenApiIndex","started":"19:32:06.991","dependents":[568,477,475,481,478],"id":472,"thread":"build-43"},{"duration":62,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#testConsoleCommand","started":"19:32:06.543","dependents":[581],"id":428,"thread":"build-12"},{"duration":62,"stepId":"io.quarkus.smallrye.openapi.deployment.devui.OpenApiDevUIProcessor#pages","started":"19:32:05.301","dependents":[532,564],"id":258,"thread":"build-29"},{"duration":61,"stepId":"io.quarkus.arc.deployment.ArcProcessor#initialize","started":"19:32:07.297","dependents":[485,484,528,498],"id":483,"thread":"build-48"},{"duration":59,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#frameworkRoot","started":"19:32:05.241","dependents":[245,333,300,558,502,527,580,322,258,263,596,576,573,276,235,591,594,574,219,592,471,571,282,572,540,567],"id":218,"thread":"build-59"},{"duration":58,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#jsonDefault","started":"19:32:05.184","dependents":[577],"id":173,"thread":"build-34"},{"duration":56,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#registerAutoSecurityFilter","started":"19:32:07.054","dependents":[606,504,503,505],"id":478,"thread":"build-37"},{"duration":55,"stepId":"io.quarkus.deployment.steps.ClassTransformingBuildStep#handleClassTransformation","started":"19:32:11.156","dependents":[605],"id":602,"thread":"build-13"},{"duration":54,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#deprioritizeLegacyProviders","started":"19:32:05.166","dependents":[601,585],"id":147,"thread":"build-58"},{"duration":54,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#registerAnnotatedUserDefinedRuntimeFilters","started":"19:32:07.054","dependents":[604,606,504,503,505],"id":477,"thread":"build-33"},{"duration":54,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#provideSecurityInformation","started":"19:32:05.184","dependents":[481,478],"id":164,"thread":"build-63"},{"duration":52,"stepId":"io.quarkus.deployment.dev.IsolatedDevModeMain$AddApplicationClassPredicateBuildStep$1@5e342c1f","started":"19:32:05.163","dependents":[577,483],"id":140,"thread":"build-53"},{"duration":52,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#preinitializeRouter","started":"19:32:06.007","dependents":[606,594,504,503,505],"id":349,"thread":"build-33"},{"duration":52,"stepId":"io.quarkus.rest.client.reactive.deployment.devservices.DevServicesRestClientHttpProxyProcessor#registerDefaultProvider","started":"19:32:05.217","dependents":[390],"id":209,"thread":"build-57"},{"duration":52,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#defineHealthRoutes","started":"19:32:06.991","dependents":[593,594],"id":471,"thread":"build-33"},{"duration":51,"stepId":"io.quarkus.deployment.SecureRandomProcessor#registerReflectiveMethods","started":"19:32:05.131","dependents":[604],"id":111,"thread":"build-26"},{"duration":51,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#findAllJsonRPCMethods","started":"19:32:06.543","dependents":[573,549],"id":426,"thread":"build-33"},{"duration":51,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#transformEndpoints","started":"19:32:06.991","dependents":[483],"id":470,"thread":"build-12"},{"duration":50,"stepId":"io.quarkus.resteasy.reactive.server.deployment.devui.ResteasyReactiveDevUIProcessor#createPages","started":"19:32:05.048","dependents":[532,564],"id":44,"thread":"build-15"},{"duration":50,"stepId":"io.quarkus.hibernate.orm.panache.deployment.PanacheHibernateResourceProcessor#ensureBeanLookupAvailable","started":"19:32:05.184","dependents":[514,522],"id":158,"thread":"build-54"},{"duration":49,"stepId":"io.quarkus.flyway.deployment.FlywayProcessor#reflection","started":"19:32:06.543","dependents":[604,603],"id":424,"thread":"build-83"},{"duration":49,"stepId":"io.quarkus.deployment.steps.NativeImageConfigBuildStep#build","started":"19:32:05.810","dependents":[606],"id":328,"thread":"build-83"},{"duration":49,"stepId":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor#initializeStorkFilter","started":"19:32:05.186","dependents":[363,604,483,460],"id":160,"thread":"build-71"},{"duration":48,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#addRestClientBeans","started":"19:32:06.549","dependents":[606,460],"id":427,"thread":"build-43"},{"duration":48,"stepId":"io.quarkus.narayana.jta.deployment.NarayanaJtaProcessor#unremovableBean","started":"19:32:05.081","dependents":[514,522],"id":70,"thread":"build-27"},{"duration":48,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#additionalBean","started":"19:32:05.132","dependents":[483,460],"id":107,"thread":"build-11"},{"duration":47,"stepId":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor#registerInvocationCallbacks","started":"19:32:06.543","dependents":[606],"id":423,"thread":"build-24"},{"duration":47,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#httpRoot","started":"19:32:05.241","dependents":[276,596,568,576,580,595,592],"id":215,"thread":"build-71"},{"duration":46,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#jwtClaimIntegration","started":"19:32:05.325","dependents":[483,460],"id":261,"thread":"build-17"},{"duration":45,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#cacheControlSupport","started":"19:32:05.166","dependents":[577],"id":134,"thread":"build-59"},{"duration":45,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#responseHeaderSupport","started":"19:32:05.084","dependents":[577],"id":69,"thread":"build-34"},{"duration":44,"stepId":"io.quarkus.micrometer.deployment.MicrometerProcessor#metricsCapabilityPrometheusBuildItem","started":"19:32:05.301","dependents":[589,601,548,306,430,251],"id":245,"thread":"build-77"},{"duration":44,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#generateCustomizer","started":"19:32:06.544","dependents":[460],"id":419,"thread":"build-74"},{"duration":44,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#serverSerializers","started":"19:32:09.714","dependents":[604,606,586],"id":585,"thread":"build-69"},{"duration":44,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#brandingFiles","started":"19:32:05.172","dependents":[457],"id":143,"thread":"build-64"},{"duration":44,"stepId":"io.quarkus.arc.deployment.StartupBuildSteps#unremovableBeans","started":"19:32:05.149","dependents":[514,522],"id":122,"thread":"build-47"},{"duration":43,"stepId":"io.quarkus.devui.deployment.menu.ReadmeProcessor#createJsonRPCServiceForCache","started":"19:32:05.213","dependents":[426,337],"id":194,"thread":"build-50"},{"duration":42,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#scanResources","started":"19:32:06.543","dependents":[413,422,579,577,415,545,412,601,414,470,420,416,483,586,435],"id":411,"thread":"build-31"},{"duration":42,"stepId":"io.quarkus.rest.client.reactive.deployment.devconsole.RestClientReactiveDevUIProcessor#createJsonRPCServiceForCache","started":"19:32:05.048","dependents":[426,337],"id":32,"thread":"build-22"},{"duration":42,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#getSwaggerUiFinalDestination","started":"19:32:08.810","dependents":[569],"id":558,"thread":"build-69"},{"duration":42,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#setupRequestCollectingFilter","started":"19:32:05.063","dependents":[437],"id":51,"thread":"build-9"},{"duration":40,"stepId":"io.quarkus.hibernate.orm.deployment.GraalVMFeatures#registerJdbcArrayTypesForReflection","started":"19:32:05.213","dependents":[604],"id":189,"thread":"build-22"},{"duration":40,"stepId":"io.quarkus.arc.deployment.HotDeploymentConfigBuildStep#startup","started":"19:32:05.177","dependents":[154],"id":146,"thread":"build-69"},{"duration":40,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#responseStatusSupport","started":"19:32:05.167","dependents":[577],"id":124,"thread":"build-57"},{"duration":40,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#transformInjectionPoint","started":"19:32:05.182","dependents":[483],"id":148,"thread":"build-70"},{"duration":39,"stepId":"io.quarkus.vertx.http.deployment.ManagementInterfaceSecurityProcessor#setupAuthenticationMechanisms","started":"19:32:05.621","dependents":[606,596,483,460],"id":297,"thread":"build-38"},{"duration":39,"stepId":"io.quarkus.flyway.deployment.FlywayProcessor#build","started":"19:32:06.759","dependents":[604,606,459,457,541],"id":456,"thread":"build-37"},{"duration":38,"stepId":"io.quarkus.deployment.steps.ThreadPoolSetup#createExecutor","started":"19:32:05.823","dependents":[339,606,330,344,343,596,334],"id":329,"thread":"build-12"},{"duration":38,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#logging","started":"19:32:05.144","dependents":[182],"id":113,"thread":"build-34"},{"duration":37,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#build_9d6b7122fb368970c50c3a870d1f672392cd8afb","started":"19:32:05.218","dependents":[604,328],"id":190,"thread":"build-74"},{"duration":37,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#providersFromClasspath","started":"19:32:05.178","dependents":[601,583,585,584],"id":137,"thread":"build-51"},{"duration":36,"stepId":"io.quarkus.deployment.steps.AdditionalClassLoaderResourcesBuildStep#appendAdditionalClassloaderResources","started":"19:32:05.108","dependents":[363],"id":84,"thread":"build-36"},{"duration":36,"stepId":"io.quarkus.arc.deployment.devui.ArcDevUIProcessor#createJsonRPCService","started":"19:32:05.092","dependents":[426,337],"id":67,"thread":"build-5"},{"duration":35,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#checkMixingStacks","started":"19:32:05.322","dependents":[597,599],"id":255,"thread":"build-50"},{"duration":35,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#registerSecurityInterceptors","started":"19:32:05.324","dependents":[483,460],"id":256,"thread":"build-57"},{"duration":35,"stepId":"io.quarkus.vertx.http.deployment.StaticResourcesProcessor#collectStaticResources","started":"19:32:05.322","dependents":[543],"id":254,"thread":"build-70"},{"duration":35,"stepId":"io.quarkus.arc.deployment.AutoAddScopeProcessor#annotationTransformer","started":"19:32:06.992","dependents":[514,522,483],"id":469,"thread":"build-48"},{"duration":34,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#addDefaultCacheBean","started":"19:32:06.007","dependents":[606,504,503,505],"id":347,"thread":"build-29"},{"duration":34,"stepId":"io.quarkus.devui.deployment.menu.ReadmeProcessor#createReadmePage","started":"19:32:05.097","dependents":[567],"id":75,"thread":"build-22"},{"duration":33,"stepId":"io.quarkus.arc.deployment.ConfigStaticInitBuildSteps#registerBeans","started":"19:32:05.059","dependents":[483,460],"id":36,"thread":"build-26"},{"duration":33,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#applicationReflection","started":"19:32:05.047","dependents":[604],"id":24,"thread":"build-11"},{"duration":33,"stepId":"io.quarkus.vertx.deployment.EventBusCodecProcessor#registerCodecs","started":"19:32:06.991","dependents":[604,499],"id":468,"thread":"build-37"},{"duration":33,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#setupDeployment","started":"19:32:09.759","dependents":[604,606,589,593,596,591,594,587,590,588],"id":586,"thread":"build-13"},{"duration":33,"stepId":"io.quarkus.resteasy.reactive.server.deployment.devui.ResteasyReactiveDevUIProcessor#createJsonRPCService","started":"19:32:05.184","dependents":[426,337],"id":142,"thread":"build-29"},{"duration":32,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createBuildTimeData","started":"19:32:09.000","dependents":[573,574],"id":567,"thread":"build-13"},{"duration":32,"stepId":"io.quarkus.deployment.execannotations.ExecutionModelAnnotationsProcessor#devuiJsonRpcServices","started":"19:32:05.106","dependents":[434],"id":78,"thread":"build-3"},{"duration":32,"stepId":"io.quarkus.micrometer.deployment.MicrometerProcessor#createCard","started":"19:32:05.874","dependents":[532,564],"id":336,"thread":"build-67"},{"duration":32,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#additionalBeans","started":"19:32:05.063","dependents":[483,460],"id":40,"thread":"build-16"},{"duration":32,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#addQualifiers","started":"19:32:05.184","dependents":[483,461],"id":141,"thread":"build-55"},{"duration":32,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#registerSafeDuplicatedContextInterceptor","started":"19:32:05.149","dependents":[483,460],"id":110,"thread":"build-44"},{"duration":32,"stepId":"io.quarkus.netty.deployment.NettyProcessor#cleanupMacDNSInLog","started":"19:32:05.148","dependents":[458,444],"id":106,"thread":"build-46"},{"duration":31,"stepId":"io.quarkus.micrometer.deployment.binder.NettyBinderProcessor#createVertxNettyAllocatorMetrics","started":"19:32:05.184","dependents":[483,460],"id":136,"thread":"build-11"},{"duration":31,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#beanDefiningAnnotations","started":"19:32:05.075","dependents":[432,483,460],"id":53,"thread":"build-4"},{"duration":31,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#watchConfigFiles","started":"19:32:05.050","dependents":[457],"id":26,"thread":"build-23"},{"duration":30,"stepId":"io.quarkus.hibernate.orm.deployment.GraalVMFeatures#registerGeneratorClassesForReflections","started":"19:32:05.213","dependents":[604],"id":175,"thread":"build-26"},{"duration":30,"stepId":"io.quarkus.resteasy.reactive.common.deployment.JaxrsMethodsProcessor#jaxrsMethods","started":"19:32:05.217","dependents":[434],"id":181,"thread":"build-55"},{"duration":30,"stepId":"io.quarkus.arc.deployment.ArcProcessor#registerContextPropagation","started":"19:32:05.233","dependents":[327],"id":203,"thread":"build-69"},{"duration":30,"stepId":"io.quarkus.arc.deployment.SyntheticBeansProcessor#initRuntime","started":"19:32:07.850","dependents":[544,606,510,506,511,509,507,546,508,597,599],"id":505,"thread":"build-24"},{"duration":30,"stepId":"io.quarkus.vertx.http.deployment.devmode.NotFoundProcessor#resourceNotFoundDataAvailable","started":"19:32:05.072","dependents":[483,460],"id":49,"thread":"build-30"},{"duration":30,"stepId":"io.quarkus.devui.deployment.build.BuildMetricsDevUIProcessor#createJsonRPCService","started":"19:32:05.097","dependents":[426,337],"id":64,"thread":"build-11"},{"duration":29,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#buildExclusions","started":"19:32:06.557","dependents":[472],"id":418,"thread":"build-75"},{"duration":29,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createKnownInternalImportMap","started":"19:32:05.301","dependents":[574],"id":235,"thread":"build-81"},{"duration":29,"stepId":"io.quarkus.security.deployment.SecurityProcessor#createSecurityCheckStorage","started":"19:32:07.079","dependents":[606,504,577,483,503,505],"id":476,"thread":"build-51"},{"duration":29,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#createJsonRpcRouter","started":"19:32:08.776","dependents":[606],"id":549,"thread":"build-83"},{"duration":29,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#generateBuilders","started":"19:32:08.810","dependents":[604],"id":557,"thread":"build-122"},{"duration":29,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#registerAuthMechanismSelectionInterceptor","started":"19:32:06.543","dependents":[606,402,400,401,495,473],"id":399,"thread":"build-71"},{"duration":29,"stepId":"io.quarkus.deployment.steps.CapabilityAggregationStep#aggregateCapabilities","started":"19:32:05.293","dependents":[589,223,254,452,256,382,451,577,242,283,446,393,445,298,482,231,225,335,600,234,241,399,311,282,394,454,601,224,251,436,255,502,351,568,475,476,358,338,447,586,229,227,342,384,317,603,443,320,406,261,308,427,244,296,483,228],"id":222,"thread":"build-49"},{"duration":28,"stepId":"io.quarkus.deployment.steps.CapabilityAggregationStep#provideCapabilities","started":"19:32:05.264","dependents":[222],"id":217,"thread":"build-77"},{"duration":28,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#registerCustomExceptionMappers","started":"19:32:05.210","dependents":[435],"id":165,"thread":"build-13"},{"duration":27,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#registerOpenApiSchemaClassesForReflection","started":"19:32:07.054","dependents":[604,603],"id":475,"thread":"build-12"},{"duration":27,"stepId":"io.quarkus.arc.deployment.LoggingBeanSupportProcessor#discoveredComponents","started":"19:32:05.079","dependents":[432,483,460],"id":52,"thread":"build-14"},{"duration":26,"stepId":"io.quarkus.arc.deployment.ArcProcessor#loggerProducer","started":"19:32:05.213","dependents":[483,460],"id":167,"thread":"build-4"},{"duration":26,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#setUpDefaultMediaType","started":"19:32:05.233","dependents":[601],"id":195,"thread":"build-29"},{"duration":26,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#compressionSupport","started":"19:32:05.241","dependents":[577],"id":207,"thread":"build-40"},{"duration":26,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#asyncSupport","started":"19:32:05.108","dependents":[577],"id":76,"thread":"build-30"},{"duration":26,"stepId":"io.quarkus.arc.deployment.StartupBuildSteps#addScope","started":"19:32:05.223","dependents":[469],"id":183,"thread":"build-58"},{"duration":26,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#registerRowSetSupport","started":"19:32:05.066","dependents":[604],"id":34,"thread":"build-29"},{"duration":25,"stepId":"io.quarkus.hibernate.orm.deployment.ResteasyReactiveServerIntegrationProcessor#unwrappedExceptions","started":"19:32:05.213","dependents":[436],"id":166,"thread":"build-44"},{"duration":25,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#vetoMPConfigProperties","started":"19:32:05.081","dependents":[483],"id":54,"thread":"build-23"},{"duration":25,"stepId":"io.quarkus.devui.deployment.menu.ContinuousTestingProcessor#createContinuousTestingPages","started":"19:32:05.184","dependents":[567],"id":125,"thread":"build-44"},{"duration":25,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#feature","started":"19:32:05.260","dependents":[606],"id":213,"thread":"build-29"},{"duration":25,"stepId":"io.quarkus.oidc.deployment.devservices.OidcDevUIProcessor#produceOidcDevJsonRpcService","started":"19:32:05.241","dependents":[426,337],"id":205,"thread":"build-63"},{"duration":25,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#reinitializeClassesForNetty","started":"19:32:05.066","dependents":[328],"id":30,"thread":"build-5"},{"duration":24,"stepId":"io.quarkus.arc.deployment.SyntheticBeansProcessor#initRegular","started":"19:32:07.850","dependents":[511],"id":504,"thread":"build-4"},{"duration":23,"stepId":"io.quarkus.security.deployment.SecurityProcessor#prepareBouncyCastleProviders","started":"19:32:05.264","dependents":[604],"id":214,"thread":"build-44"},{"duration":23,"stepId":"io.quarkus.smallrye.faulttolerance.deployment.devui.FaultToleranceDevUIProcessor#jsonRPCService","started":"19:32:05.106","dependents":[426,337],"id":71,"thread":"build-33"},{"duration":22,"stepId":"io.quarkus.vertx.http.deployment.devmode.ArcDevProcessor#registerRoutes","started":"19:32:08.207","dependents":[606,593,533,594,595],"id":527,"thread":"build-83"},{"duration":22,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigRootsAsBeans","started":"19:32:05.233","dependents":[504,503,505],"id":193,"thread":"build-53"},{"duration":22,"stepId":"io.quarkus.deployment.steps.DevModeBuildStep#watchChanges","started":"19:32:05.233","dependents":[457],"id":191,"thread":"build-11"},{"duration":22,"stepId":"io.quarkus.arc.deployment.AutoProducerMethodsProcessor#annotationTransformer","started":"19:32:06.991","dependents":[483],"id":467,"thread":"build-51"},{"duration":22,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#shouldNotRemoveHttpServerOptionsCustomizers","started":"19:32:05.213","dependents":[514,522],"id":159,"thread":"build-59"},{"duration":22,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createRelocationMap","started":"19:32:05.184","dependents":[574],"id":123,"thread":"build-22"},{"duration":22,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#runtimeOverrideConfig","started":"19:32:05.213","dependents":[557],"id":161,"thread":"build-47"},{"duration":21,"stepId":"io.quarkus.deployment.steps.CombinedIndexBuildStep#build","started":"19:32:06.520","dependents":[388,365,419,480,452,579,382,395,405,383,397,456,407,410,433,389,562,430,380,387,393,392,364,369,482,403,385,366,377,396,370,399,488,394,441,404,425,429,378,436,408,468,423,561,367,490,426,386,373,376,368,400,375,372,494,447,434,435,379,384,371,409,424,603,519,458,472,443,374,479,406,381,427,428,483,440],"id":363,"thread":"build-74"},{"duration":21,"stepId":"io.quarkus.security.deployment.SecurityProcessor#registerJCAProvidersForReflection","started":"19:32:05.262","dependents":[604],"id":212,"thread":"build-22"},{"duration":21,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#mainClassBuildStep","started":"19:32:06.543","dependents":[602],"id":397,"thread":"build-56"},{"duration":21,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#registerHttpAuthMechanismAnnotations","started":"19:32:05.108","dependents":[399],"id":68,"thread":"build-15"},{"duration":20,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#contributeQuarkusConfigToJpaModel","started":"19:32:05.247","dependents":[438],"id":208,"thread":"build-26"},{"duration":20,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#sharedStateListener","started":"19:32:05.063","dependents":[352],"id":28,"thread":"build-3"},{"duration":20,"stepId":"io.quarkus.micrometer.deployment.MicrometerProcessor#processAnnotatedMetrics","started":"19:32:05.241","dependents":[483],"id":200,"thread":"build-44"},{"duration":20,"stepId":"io.quarkus.deployment.pkg.steps.FileSystemResourcesBuildStep#notNormalMode","started":"19:32:05.333","dependents":[],"id":250,"thread":"build-61"},{"duration":20,"stepId":"io.quarkus.devui.deployment.logstream.LogStreamProcessor#additionalBean","started":"19:32:05.106","dependents":[483,460],"id":62,"thread":"build-26"},{"duration":20,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerQueryParamStyleForConfig","started":"19:32:05.108","dependents":[356],"id":65,"thread":"build-23"},{"duration":19,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#healthCheck","started":"19:32:05.329","dependents":[483,460],"id":248,"thread":"build-36"},{"duration":19,"stepId":"io.quarkus.micrometer.deployment.MicrometerProcessor#registerAdditionalBeans","started":"19:32:06.543","dependents":[604,514,522,551,483,460],"id":396,"thread":"build-48"},{"duration":19,"stepId":"io.quarkus.deployment.CollectionClassProcessor#setupCollectionClasses","started":"19:32:05.061","dependents":[604],"id":25,"thread":"build-27"},{"duration":19,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#configFiles","started":"19:32:05.333","dependents":[457],"id":249,"thread":"build-66"},{"duration":19,"stepId":"io.quarkus.jdbc.postgresql.deployment.JDBCPostgreSQLProcessor#configureAgroalConnection","started":"19:32:05.322","dependents":[483,460],"id":242,"thread":"build-59"},{"duration":19,"stepId":"io.quarkus.deployment.recording.substitutions.AdditionalSubstitutionsBuildStep#additionalSubstitutions","started":"19:32:05.060","dependents":[606],"id":23,"thread":"build-18"},{"duration":19,"stepId":"io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsProcessor#collectInterceptedStaticMethods","started":"19:32:07.802","dependents":[514,522,539,498],"id":496,"thread":"build-4"},{"duration":19,"stepId":"io.quarkus.netty.deployment.NettyProcessor#build","started":"19:32:05.241","dependents":[604,328],"id":197,"thread":"build-51"},{"duration":19,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#generateRestClientConfigBuilder","started":"19:32:06.550","dependents":[557],"id":398,"thread":"build-28"},{"duration":19,"stepId":"io.quarkus.hibernate.orm.deployment.metrics.HibernateOrmMetricsProcessor#metrics","started":"19:32:08.786","dependents":[606,550,551],"id":548,"thread":"build-60"},{"duration":19,"stepId":"io.quarkus.devservices.deployment.DevServicesProcessor#config","started":"19:32:08.810","dependents":[581,556],"id":555,"thread":"build-83"},{"duration":19,"stepId":"io.quarkus.oidc.deployment.devservices.keycloak.KeycloakDevUIProcessor#produceOidcDevJsonRpcService","started":"19:32:05.247","dependents":[426,337],"id":206,"thread":"build-34"},{"duration":19,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#reflection","started":"19:32:05.161","dependents":[604],"id":104,"thread":"build-22"},{"duration":18,"stepId":"io.quarkus.vertx.deployment.EventConsumerMethodsProcessor#eventConsumerMethods","started":"19:32:05.084","dependents":[434],"id":48,"thread":"build-3"},{"duration":18,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#handleApplication","started":"19:32:06.557","dependents":[408,409,585,405,577,437,604,545,407,601,410,404,425,586,436],"id":403,"thread":"build-32"},{"duration":18,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#detectBasicAuthImplicitlyRequired","started":"19:32:07.802","dependents":[606],"id":495,"thread":"build-51"},{"duration":18,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#register","started":"19:32:06.543","dependents":[604,603,483,460],"id":395,"thread":"build-57"},{"duration":18,"stepId":"io.quarkus.security.deployment.SecurityProcessor#validateStartUpObserversNotSecured","started":"19:32:08.207","dependents":[533],"id":526,"thread":"build-62"},{"duration":18,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#collectEventConsumers","started":"19:32:07.802","dependents":[511,499],"id":497,"thread":"build-30"},{"duration":18,"stepId":"io.quarkus.undertow.deployment.UndertowStaticResourcesBuildStep#handleGeneratedWebResources","started":"19:32:05.326","dependents":[],"id":244,"thread":"build-83"},{"duration":17,"stepId":"io.quarkus.mutiny.deployment.MutinyProcessor#runtimeInit","started":"19:32:05.862","dependents":[606],"id":334,"thread":"build-28"},{"duration":17,"stepId":"io.quarkus.arc.deployment.LookupConditionsProcessor#suppressConditionsGenerators","started":"19:32:06.991","dependents":[483],"id":465,"thread":"build-4"},{"duration":17,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#doNotRemoveVertxOptionsCustomizers","started":"19:32:05.045","dependents":[514,522],"id":14,"thread":"build-4"},{"duration":17,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigMappingsBean","started":"19:32:07.802","dependents":[511],"id":494,"thread":"build-24"},{"duration":17,"stepId":"io.quarkus.jdbc.postgresql.deployment.JDBCPostgreSQLProcessor#registerServiceBinding","started":"19:32:05.324","dependents":[308],"id":241,"thread":"build-63"},{"duration":17,"stepId":"io.quarkus.arc.deployment.AutoInjectFieldProcessor#annotationTransformer","started":"19:32:06.991","dependents":[483],"id":466,"thread":"build-30"},{"duration":16,"stepId":"io.quarkus.smallrye.faulttolerance.deployment.SmallRyeFaultToleranceProcessor#transformInterceptorPriority","started":"19:32:06.991","dependents":[483],"id":464,"thread":"build-25"},{"duration":16,"stepId":"io.quarkus.micrometer.deployment.binder.VertxBinderProcessor#unremoveableAdditionalHttpServerMetrics","started":"19:32:05.045","dependents":[514,522],"id":13,"thread":"build-9"},{"duration":16,"stepId":"io.quarkus.arc.deployment.ReflectiveBeanClassesProcessor#implicitReflectiveBeanClasses","started":"19:32:07.802","dependents":[533],"id":493,"thread":"build-28"},{"duration":16,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForContextResolvers","started":"19:32:06.576","dependents":[604,582,483,460,586],"id":425,"thread":"build-4"},{"duration":15,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#resolveRolesAllowedConfigExpressions","started":"19:32:06.543","dependents":[515,606,504,503,505,473],"id":394,"thread":"build-21"},{"duration":15,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#registerVerticleClasses","started":"19:32:06.543","dependents":[604],"id":392,"thread":"build-4"},{"duration":15,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#registerBean","started":"19:32:05.213","dependents":[483,460],"id":152,"thread":"build-72"},{"duration":15,"stepId":"io.quarkus.credentials.CredentialsProcessor#unremoveable","started":"19:32:05.047","dependents":[514,522],"id":12,"thread":"build-14"},{"duration":15,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#setupEndpoints","started":"19:32:08.776","dependents":[604,601,583,585,584],"id":545,"thread":"build-69"},{"duration":15,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#unremovable","started":"19:32:06.543","dependents":[514,522,483,460],"id":393,"thread":"build-20"},{"duration":15,"stepId":"io.quarkus.micrometer.deployment.export.PrometheusRegistryProcessor#registerEmptyExamplarProvider","started":"19:32:05.213","dependents":[483,460],"id":151,"thread":"build-6"},{"duration":15,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#indexAdditionalConstrainedClasses","started":"19:32:06.624","dependents":[482,479],"id":431,"thread":"build-12"},{"duration":15,"stepId":"io.quarkus.deployment.dev.ConfigureDisableInstrumentationBuildStep#configure","started":"19:32:05.218","dependents":[597,599],"id":154,"thread":"build-29"},{"duration":15,"stepId":"io.quarkus.datasource.deployment.devui.DevUIDatasourceProcessor#registerJsonRpcBackend","started":"19:32:05.131","dependents":[426,337],"id":89,"thread":"build-41"},{"duration":15,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmCdiProcessor#registerAnnotations","started":"19:32:05.045","dependents":[432,483,460],"id":10,"thread":"build-5"},{"duration":14,"stepId":"io.quarkus.deployment.ide.IdeProcessor#effectiveIde","started":"19:32:05.460","dependents":[361,353,567,322],"id":274,"thread":"build-60"},{"duration":14,"stepId":"io.quarkus.rest.client.reactive.deployment.devconsole.RestClientReactiveDevUIProcessor#beans","started":"19:32:05.081","dependents":[483,460],"id":41,"thread":"build-11"},{"duration":14,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#registerVerticleClasses","started":"19:32:06.543","dependents":[604],"id":388,"thread":"build-75"},{"duration":14,"stepId":"io.quarkus.hibernate.orm.deployment.dev.HibernateOrmDevServicesProcessor#devServicesAutoGenerateByDefault","started":"19:32:08.786","dependents":[552],"id":547,"thread":"build-122"},{"duration":14,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#logCleanup","started":"19:32:05.163","dependents":[458,444],"id":99,"thread":"build-50"},{"duration":14,"stepId":"io.quarkus.arc.deployment.UnremovableAnnotationsProcessor#unremovableBeans","started":"19:32:05.077","dependents":[514,522],"id":31,"thread":"build-32"},{"duration":14,"stepId":"io.quarkus.flyway.deployment.devui.FlywayDevUIProcessor#create","started":"19:32:06.765","dependents":[606,532,564],"id":455,"thread":"build-51"},{"duration":14,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#finalizeRouter","started":"19:32:09.824","dependents":[606,598,597,599],"id":596,"thread":"build-69"},{"duration":14,"stepId":"io.quarkus.security.deployment.SecurityProcessor#authorizationController","started":"19:32:05.250","dependents":[483,460],"id":204,"thread":"build-37"},{"duration":14,"stepId":"io.quarkus.hibernate.orm.deployment.dev.HibernateOrmDevUIProcessor#createJsonRPCService","started":"19:32:05.080","dependents":[426,337],"id":37,"thread":"build-33"},{"duration":14,"stepId":"io.quarkus.micrometer.deployment.binder.NettyBinderProcessor#createReactiveNettyAllocatorMetrics","started":"19:32:05.165","dependents":[604,483,460],"id":103,"thread":"build-54"},{"duration":13,"stepId":"io.quarkus.netty.deployment.NettyProcessor#registerQualifiers","started":"19:32:05.130","dependents":[483,460],"id":79,"thread":"build-43"},{"duration":13,"stepId":"io.quarkus.stork.deployment.SmallRyeStorkProcessor#unremoveableBeans","started":"19:32:05.048","dependents":[514,522],"id":11,"thread":"build-21"},{"duration":13,"stepId":"io.quarkus.micrometer.deployment.binder.StorkBinderProcessor#addStorkObservationCollector","started":"19:32:05.165","dependents":[483,460],"id":100,"thread":"build-55"},{"duration":13,"stepId":"io.quarkus.deployment.steps.ReflectiveHierarchyStep#ignoreJavaClassWarnings","started":"19:32:05.060","dependents":[603],"id":21,"thread":"build-6"},{"duration":13,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveVertxWebSocketIntegrationProcessor#scanner","started":"19:32:05.094","dependents":[577],"id":58,"thread":"build-29"},{"duration":13,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#additionalAsyncTypeMethodScanners","started":"19:32:05.131","dependents":[577],"id":85,"thread":"build-44"},{"duration":13,"stepId":"io.quarkus.vertx.http.deployment.devmode.NotFoundProcessor#routeNotFound","started":"19:32:09.824","dependents":[606],"id":595,"thread":"build-13"},{"duration":12,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#unlessBuildProperty","started":"19:32:06.545","dependents":[418,391,403],"id":389,"thread":"build-2"},{"duration":12,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#buildResourceInterceptors","started":"19:32:06.660","dependents":[470,584,577,483,460,586],"id":437,"thread":"build-25"},{"duration":12,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#logCleanup","started":"19:32:05.045","dependents":[458,444],"id":7,"thread":"build-10"},{"duration":12,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateRuntimeConfigProperty","started":"19:32:08.212","dependents":[604,606],"id":524,"thread":"build-6"},{"duration":12,"stepId":"io.quarkus.vertx.http.deployment.StaticResourcesProcessor#runtimeInit","started":"19:32:08.776","dependents":[606,596],"id":543,"thread":"build-13"},{"duration":12,"stepId":"io.quarkus.smallrye.faulttolerance.deployment.FaultToleranceMethodsProcessor#eventConsumerMethods","started":"19:32:05.094","dependents":[434],"id":55,"thread":"build-36"},{"duration":12,"stepId":"io.quarkus.keycloak.admin.client.reactive.KeycloakAdminClientReactiveProcessor#marker","started":"19:32:05.044","dependents":[357],"id":2,"thread":"build-3"},{"duration":12,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#build_7a4403d699506d83ac39616f3c11e5e1b448d863","started":"19:32:06.765","dependents":[606,535],"id":454,"thread":"build-6"},{"duration":12,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#setupVersionField","started":"19:32:05.131","dependents":[604],"id":80,"thread":"build-34"},{"duration":12,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#jacksonSupport","started":"19:32:06.543","dependents":[606,504,503,505],"id":383,"thread":"build-29"},{"duration":12,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthFeatureProcessor#defineFeature","started":"19:32:05.054","dependents":[606],"id":19,"thread":"build-24"},{"duration":11,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#cleanupVertxWarnings","started":"19:32:05.047","dependents":[458,444],"id":8,"thread":"build-12"},{"duration":11,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateConfigMappingsInjectionPoints","started":"19:32:08.207","dependents":[557,525],"id":522,"thread":"build-12"},{"duration":11,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#unlessBuildProfile","started":"19:32:06.545","dependents":[418,391,403],"id":385,"thread":"build-32"},{"duration":11,"stepId":"io.quarkus.hibernate.orm.panache.deployment.PanacheHibernateResourceProcessor#build","started":"19:32:08.962","dependents":[563,602],"id":562,"thread":"build-69"},{"duration":11,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#ifBuildProperty","started":"19:32:06.545","dependents":[418,391,403],"id":387,"thread":"build-72"},{"duration":11,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#registerTenantResolverInterceptor","started":"19:32:06.544","dependents":[606,402,400,495],"id":384,"thread":"build-68"},{"duration":11,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#makeTenantIdentityProviderInjectionPointsNamed","started":"19:32:05.048","dependents":[483],"id":9,"thread":"build-19"},{"duration":11,"stepId":"io.quarkus.deployment.dev.HotDeploymentWatchedFileBuildStep#setupWatchedFileHotDeployment","started":"19:32:06.798","dependents":[597,599],"id":457,"thread":"build-51"},{"duration":11,"stepId":"io.quarkus.deployment.steps.RegisterForReflectionBuildStep#build","started":"19:32:06.543","dependents":[604,603],"id":382,"thread":"build-76"},{"duration":11,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#ifBuildProfile","started":"19:32:06.545","dependents":[418,391,403],"id":386,"thread":"build-64"},{"duration":11,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#build_d182d2fe7ae008890806ec353e99fa052582ee2d","started":"19:32:06.766","dependents":[606,562],"id":453,"thread":"build-43"},{"duration":11,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForExceptionMappers","started":"19:32:06.660","dependents":[604,483,460,586],"id":436,"thread":"build-30"},{"duration":11,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmCdiProcessor#validatePersistenceUnitExtensions","started":"19:32:08.207","dependents":[533],"id":521,"thread":"build-22"},{"duration":10,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForParamConverters_dcdfdd2a310a09abe5ee3f0ed2b2bc49f36f3d07","started":"19:32:06.576","dependents":[604,577,483,460,586],"id":417,"thread":"build-32"},{"duration":10,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmCdiProcessor#registerBeans","started":"19:32:06.766","dependents":[514,522,483,460],"id":452,"thread":"build-62"},{"duration":10,"stepId":"io.quarkus.devui.deployment.menu.ContinuousTestingProcessor#continuousTestingState","started":"19:32:08.776","dependents":[606],"id":542,"thread":"build-107"},{"duration":10,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupStackTraceFormatter","started":"19:32:06.520","dependents":[362,596,458],"id":361,"thread":"build-33"},{"duration":10,"stepId":"io.quarkus.agroal.deployment.AgroalMetricsProcessor#registerMetrics","started":"19:32:05.810","dependents":[606,550,551],"id":309,"thread":"build-58"},{"duration":9,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmCdiProcessor#generateDataSourceBeans","started":"19:32:06.764","dependents":[606,504,483,503,460,505],"id":451,"thread":"build-33"},{"duration":9,"stepId":"io.quarkus.deployment.steps.CurateOutcomeBuildStep#removeResources","started":"19:32:05.250","dependents":[602],"id":199,"thread":"build-58"},{"duration":9,"stepId":"io.quarkus.flyway.deployment.FlywayProcessor#startActions","started":"19:32:08.776","dependents":[544,606,548,546,547,597,599],"id":541,"thread":"build-191"},{"duration":9,"stepId":"io.quarkus.arc.deployment.ArcProcessor#registerSyntheticObservers","started":"19:32:07.880","dependents":[604,512,513,533,514,522],"id":511,"thread":"build-22"},{"duration":9,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#brandingFiles","started":"19:32:05.213","dependents":[457],"id":150,"thread":"build-40"},{"duration":9,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#registerBean","started":"19:32:05.048","dependents":[483,460],"id":6,"thread":"build-18"},{"duration":9,"stepId":"io.quarkus.deployment.steps.BlockingOperationControlBuildStep#blockingOP","started":"19:32:05.823","dependents":[606],"id":321,"thread":"build-24"},{"duration":9,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#runtimeOnly","started":"19:32:05.063","dependents":[557],"id":20,"thread":"build-4"},{"duration":9,"stepId":"io.quarkus.flyway.deployment.FlywayProcessor#createBeans","started":"19:32:06.848","dependents":[544,606,510,504,503,546,506,509,483,507,460,505,508],"id":459,"thread":"build-51"},{"duration":8,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#integrateEagerSecurity","started":"19:32:06.572","dependents":[577],"id":406,"thread":"build-71"},{"duration":8,"stepId":"io.quarkus.micrometer.deployment.binder.NettyBinderProcessor#createNettyNettyAllocatorMetrics","started":"19:32:05.171","dependents":[483,460],"id":101,"thread":"build-63"},{"duration":8,"stepId":"io.quarkus.arc.deployment.SplitPackageProcessor#splitPackageDetection","started":"19:32:06.520","dependents":[533],"id":360,"thread":"build-75"},{"duration":8,"stepId":"io.quarkus.deployment.steps.DevServicesConfigBuildStep#setup","started":"19:32:08.801","dependents":[558,557,555,554,553,559,597,599],"id":552,"thread":"build-130"},{"duration":8,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#config","started":"19:32:05.070","dependents":[557],"id":22,"thread":"build-14"},{"duration":8,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateStaticInitConfigProperty","started":"19:32:08.212","dependents":[604,606],"id":523,"thread":"build-51"},{"duration":8,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#suppressNonRuntimeConfigChanged","started":"19:32:05.172","dependents":[307],"id":108,"thread":"build-67"},{"duration":8,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#gatherMvnpmJars","started":"19:32:05.264","dependents":[574,576],"id":211,"thread":"build-78"},{"duration":8,"stepId":"io.quarkus.arc.deployment.WrongAnnotationUsageProcessor#detect","started":"19:32:07.802","dependents":[533],"id":492,"thread":"build-25"},{"duration":8,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ObservabilityProcessor#methodScanner","started":"19:32:05.344","dependents":[577],"id":251,"thread":"build-23"},{"duration":7,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#configValidator","started":"19:32:07.141","dependents":[604,557],"id":480,"thread":"build-33"},{"duration":7,"stepId":"io.quarkus.narayana.jta.deployment.NarayanaJtaProcessor#transactionContext","started":"19:32:07.359","dependents":[485],"id":484,"thread":"build-37"},{"duration":7,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForFeatures","started":"19:32:06.576","dependents":[421,586],"id":410,"thread":"build-21"},{"duration":7,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForDynamicFeatures","started":"19:32:06.576","dependents":[421,586],"id":409,"thread":"build-28"},{"duration":7,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#generateConfigProperties","started":"19:32:06.544","dependents":[604,482,431,522,480,494,518,490],"id":381,"thread":"build-3"},{"duration":7,"stepId":"io.quarkus.stork.deployment.SmallRyeStorkProcessor#initializeStork","started":"19:32:07.880","dependents":[606],"id":510,"thread":"build-4"},{"duration":7,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmCdiProcessor#convertJpaResourceAnnotationsToQualifier","started":"19:32:06.766","dependents":[483],"id":450,"thread":"build-25"},{"duration":7,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#setup","started":"19:32:07.829","dependents":[606,504,503,505],"id":501,"thread":"build-4"},{"duration":7,"stepId":"io.quarkus.arc.deployment.ArcProcessor#exposeCustomScopeNames","started":"19:32:05.215","dependents":[492,469,467,427,432,220,483,183,460],"id":149,"thread":"build-51"},{"duration":7,"stepId":"io.quarkus.arc.deployment.ArcProcessor#launchMode","started":"19:32:05.100","dependents":[483,460],"id":57,"thread":"build-35"},{"duration":7,"stepId":"io.quarkus.rest.client.reactive.deployment.devservices.DevServicesRestClientHttpProxyProcessor#start","started":"19:32:06.550","dependents":[555,552,556],"id":390,"thread":"build-6"},{"duration":7,"stepId":"io.quarkus.deployment.SslProcessor#setupNativeSsl","started":"19:32:05.233","dependents":[328,335,176,445],"id":169,"thread":"build-40"},{"duration":6,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#additionalBeans","started":"19:32:06.583","dependents":[604,483,460],"id":421,"thread":"build-28"},{"duration":6,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setUpDarkeningDefault","started":"19:32:05.094","dependents":[557],"id":46,"thread":"build-33"},{"duration":6,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#determineRegisteredRestClients","started":"19:32:06.543","dependents":[427,380,398],"id":379,"thread":"build-6"},{"duration":6,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForInterceptors","started":"19:32:06.576","dependents":[437],"id":408,"thread":"build-48"},{"duration":6,"stepId":"io.quarkus.panache.common.deployment.PanacheHibernateCommonResourceProcessor#replaceFieldAccesses","started":"19:32:08.962","dependents":[602],"id":561,"thread":"build-13"},{"duration":6,"stepId":"io.quarkus.narayana.jta.deployment.NarayanaJtaProcessor#startRecoveryService","started":"19:32:07.880","dependents":[606],"id":509,"thread":"build-28"},{"duration":6,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ObservabilityProcessor#preAuthFailureFilter","started":"19:32:09.793","dependents":[606,596,591,590],"id":589,"thread":"build-69"},{"duration":6,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#customExceptionMappers","started":"19:32:05.045","dependents":[435],"id":5,"thread":"build-6"},{"duration":6,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#unknownConfigFiles","started":"19:32:06.520","dependents":[606],"id":359,"thread":"build-76"},{"duration":6,"stepId":"io.quarkus.narayana.jta.deployment.NarayanaJtaProcessor#registerScope","started":"19:32:05.209","dependents":[149],"id":138,"thread":"build-57"},{"duration":6,"stepId":"io.quarkus.arc.deployment.ShutdownBuildSteps#unremovableBeans","started":"19:32:05.044","dependents":[514,522],"id":3,"thread":"build-2"},{"duration":5,"stepId":"io.quarkus.micrometer.deployment.binder.NettyBinderProcessor#createVertxNettyEventExecutorMetrics","started":"19:32:05.322","dependents":[483,460],"id":231,"thread":"build-26"},{"duration":5,"stepId":"io.quarkus.arc.deployment.init.InitializationTaskProcessor#startApplicationInitializer","started":"19:32:08.786","dependents":[606],"id":544,"thread":"build-121"},{"duration":5,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#scanForParameterContainers","started":"19:32:06.576","dependents":[601,577],"id":405,"thread":"build-56"},{"duration":5,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#registerHibernateOrmMetadataForCoreDialects","started":"19:32:05.100","dependents":[447],"id":50,"thread":"build-15"},{"duration":5,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#reduceLogging","started":"19:32:05.131","dependents":[182],"id":77,"thread":"build-33"},{"duration":5,"stepId":"io.quarkus.deployment.steps.DevServicesConfigBuildStep#deprecated","started":"19:32:05.092","dependents":[552],"id":43,"thread":"build-35"},{"duration":5,"stepId":"io.quarkus.arc.deployment.SyntheticBeansProcessor#initStatic","started":"19:32:07.850","dependents":[606,511],"id":503,"thread":"build-51"},{"duration":5,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#startPersistenceUnits","started":"19:32:08.787","dependents":[606,597,599],"id":546,"thread":"build-130"},{"duration":5,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigClasses","started":"19:32:08.219","dependents":[606],"id":525,"thread":"build-22"},{"duration":5,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#addHealthCheck","started":"19:32:05.324","dependents":[248],"id":234,"thread":"build-34"},{"duration":5,"stepId":"io.quarkus.micrometer.deployment.export.PrometheusRegistryProcessor#createPrometheusRegistry","started":"19:32:05.250","dependents":[551,483,460],"id":192,"thread":"build-77"},{"duration":5,"stepId":"io.quarkus.arc.deployment.ArcProcessor#initializeContainer","started":"19:32:08.766","dependents":[606,535],"id":534,"thread":"build-138"},{"duration":5,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#scanForIOInterceptors","started":"19:32:06.576","dependents":[437],"id":407,"thread":"build-57"},{"duration":4,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#handler","started":"19:32:09.805","dependents":[606,595,592],"id":591,"thread":"build-69"},{"duration":4,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#initializeRouter","started":"19:32:09.819","dependents":[606,596,595],"id":594,"thread":"build-154"},{"duration":4,"stepId":"io.quarkus.panache.common.deployment.PanacheHibernateCommonResourceProcessor#findEntityClasses","started":"19:32:06.719","dependents":[561],"id":441,"thread":"build-51"},{"duration":4,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#shutdownListener","started":"19:32:05.047","dependents":[598],"id":4,"thread":"build-13"},{"duration":4,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#supportMixins","started":"19:32:06.543","dependents":[604,606,504,503,505],"id":378,"thread":"build-43"},{"duration":4,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#handleClassLevelExceptionMappers","started":"19:32:06.585","dependents":[604,577],"id":422,"thread":"build-48"},{"duration":4,"stepId":"io.quarkus.tls.CertificatesProcessor#initializeCertificate","started":"19:32:07.824","dependents":[606,501,596,504,503,505],"id":500,"thread":"build-51"},{"duration":4,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#registerSwaggerUiHandler","started":"19:32:09.206","dependents":[606,593,594],"id":572,"thread":"build-33"},{"duration":4,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#configurationDescriptorBuilding","started":"19:32:06.759","dependents":[606,605,450,454,449,452,453,451,457,448,547],"id":447,"thread":"build-51"},{"duration":4,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#mapPageBuildTimeData","started":"19:32:08.317","dependents":[573],"id":532,"thread":"build-28"},{"duration":4,"stepId":"io.quarkus.arc.deployment.devui.ArcDevUIProcessor#pages","started":"19:32:08.306","dependents":[532,564],"id":529,"thread":"build-83"},{"duration":4,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#configPropertyInjectionPoints","started":"19:32:08.208","dependents":[604,523,524],"id":520,"thread":"build-48"},{"duration":4,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigPropertiesBean","started":"19:32:07.802","dependents":[511],"id":490,"thread":"build-43"},{"duration":3,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#setupExceptionHandler","started":"19:32:06.090","dependents":[361],"id":353,"thread":"build-29"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#jacksonRegistered","started":"19:32:05.130","dependents":[270],"id":73,"thread":"build-14"},{"duration":3,"stepId":"io.quarkus.devui.deployment.logstream.LogStreamProcessor#handler","started":"19:32:06.532","dependents":[606,458],"id":362,"thread":"build-75"},{"duration":3,"stepId":"io.quarkus.netty.deployment.NettyProcessor#registerEventLoopBeans","started":"19:32:06.007","dependents":[606,504,503,505],"id":345,"thread":"build-62"},{"duration":3,"stepId":"io.quarkus.undertow.deployment.UndertowStaticResourcesBuildStep#scanStaticResources","started":"19:32:06.520","dependents":[],"id":358,"thread":"build-57"},{"duration":3,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#registerHealthUiHandler","started":"19:32:09.206","dependents":[606,593,594],"id":571,"thread":"build-62"},{"duration":3,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#createSynthBeansForConfiguredInjectionPoints","started":"19:32:07.802","dependents":[606,504,503,505],"id":491,"thread":"build-83"},{"duration":3,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#generateDataSourceBeans","started":"19:32:06.756","dependents":[606,504,509,451,446,447,459,503,546,456,505,541],"id":445,"thread":"build-51"},{"duration":3,"stepId":"io.quarkus.arc.deployment.StartupBuildSteps#registerStartupObservers","started":"19:32:07.890","dependents":[514],"id":513,"thread":"build-28"},{"duration":3,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#produceTenantIdentityProviders","started":"19:32:07.802","dependents":[606,504,503,505],"id":488,"thread":"build-12"},{"duration":3,"stepId":"io.quarkus.micrometer.deployment.MicrometerProcessor#configureRegistry","started":"19:32:08.805","dependents":[606],"id":551,"thread":"build-69"},{"duration":3,"stepId":"io.quarkus.jdbc.postgresql.deployment.JDBCPostgreSQLProcessor#registerDriver","started":"19:32:05.240","dependents":[308],"id":176,"thread":"build-37"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#additionalReflection","started":"19:32:09.713","dependents":[604],"id":584,"thread":"build-13"},{"duration":3,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#build","started":"19:32:07.821","dependents":[606,510,500,597,599],"id":499,"thread":"build-4"},{"duration":3,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#detectAccessTokenVerificationRequired","started":"19:32:07.802","dependents":[557],"id":489,"thread":"build-62"},{"duration":3,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#openSocket","started":"19:32:09.840","dependents":[604,606],"id":600,"thread":"build-13"},{"duration":2,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#findEnablementStereotypes","started":"19:32:06.543","dependents":[386,389,385,387],"id":377,"thread":"build-67"},{"duration":2,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#runtimeConfiguration","started":"19:32:09.794","dependents":[606,588],"id":587,"thread":"build-154"},{"duration":2,"stepId":"io.quarkus.hibernate.orm.panache.common.deployment.PanacheJpaCommonResourceProcessor#lookupNamedQueries_5a86a91ed8ef1aa483288c8239df231983eeb766","started":"19:32:06.720","dependents":[442],"id":440,"thread":"build-25"},{"duration":2,"stepId":"io.quarkus.arc.deployment.ShutdownBuildSteps#registerShutdownObservers","started":"19:32:07.890","dependents":[514],"id":512,"thread":"build-4"},{"duration":2,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#unremovableBeans","started":"19:32:06.585","dependents":[514,522],"id":420,"thread":"build-20"},{"duration":2,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#loadAllBuildTimeTemplates","started":"19:32:09.371","dependents":[576],"id":575,"thread":"build-69"},{"duration":2,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#startTesting","started":"19:32:06.088","dependents":[458,597,599],"id":352,"thread":"build-33"},{"duration":2,"stepId":"io.quarkus.arc.deployment.ArcProcessor#notifyBeanContainerListeners","started":"19:32:08.771","dependents":[606,536],"id":535,"thread":"build-83"},{"duration":2,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#unremovableBeans","started":"19:32:05.097","dependents":[514,522],"id":45,"thread":"build-26"},{"duration":2,"stepId":"io.quarkus.hibernate.orm.panache.deployment.PanacheHibernateResourceProcessor#validate","started":"19:32:08.207","dependents":[533],"id":519,"thread":"build-51"},{"duration":2,"stepId":"io.quarkus.devui.deployment.welcome.WelcomeProcessor#createWelcomePages","started":"19:32:08.996","dependents":[567],"id":566,"thread":"build-69"},{"duration":2,"stepId":"io.quarkus.devservices.postgresql.deployment.PostgresqlDevServicesProcessor#psqlCommand","started":"19:32:08.810","dependents":[581],"id":554,"thread":"build-60"},{"duration":2,"stepId":"io.quarkus.arc.deployment.TestsAsBeansProcessor#testAnnotations","started":"19:32:05.144","dependents":[432,483,460],"id":86,"thread":"build-22"},{"duration":2,"stepId":"io.quarkus.arc.deployment.ArcProcessor#quarkusMain","started":"19:32:05.061","dependents":[432,483,460],"id":15,"thread":"build-3"},{"duration":2,"stepId":"io.quarkus.deployment.steps.BannerProcessor#watchBannerChanges","started":"19:32:05.268","dependents":[457],"id":210,"thread":"build-81"},{"duration":2,"stepId":"io.quarkus.micrometer.deployment.MicrometerProcessor#createRootRegistry","started":"19:32:08.776","dependents":[606,550,551],"id":540,"thread":"build-121"},{"duration":2,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateConfigPropertiesInjectionPoints","started":"19:32:08.207","dependents":[525],"id":518,"thread":"build-6"},{"duration":2,"stepId":"io.quarkus.arc.deployment.ArcProcessor#signalBeanContainerReady","started":"19:32:08.774","dependents":[606,585,549,577,546,537,543,541,542,545,601,596,540,539,595,586,538,599],"id":536,"thread":"build-138"},{"duration":2,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#jpaEntitiesIndexer","started":"19:32:06.543","dependents":[605,438],"id":376,"thread":"build-64"},{"duration":2,"stepId":"io.quarkus.arc.deployment.devui.ArcDevUIProcessor#registerMonitoringComponents","started":"19:32:06.639","dependents":[483,460],"id":432,"thread":"build-30"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveDevModeProcessor#openCommand","started":"19:32:09.710","dependents":[581],"id":580,"thread":"build-69"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#handleJsonAnnotations","started":"19:32:09.710","dependents":[604,606,582],"id":579,"thread":"build-154"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#addDefaultAuthFailureHandler","started":"19:32:09.800","dependents":[606,596,591],"id":590,"thread":"build-13"},{"duration":1,"stepId":"io.quarkus.hibernate.orm.panache.deployment.PanacheHibernateResourceProcessor#produceModel","started":"19:32:05.065","dependents":[605,376,560],"id":18,"thread":"build-14"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#additionalProviders","started":"19:32:09.712","dependents":[601,583,585,584],"id":582,"thread":"build-62"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#applicationSpecificUnwrappedExceptions","started":"19:32:06.543","dependents":[436],"id":375,"thread":"build-51"},{"duration":1,"stepId":"io.quarkus.flyway.deployment.FlywayAlwaysEnabledProcessor#indexFlyway","started":"19:32:05.063","dependents":[357],"id":17,"thread":"build-5"},{"duration":1,"stepId":"io.quarkus.hibernate.orm.panache.common.deployment.PanacheJpaCommonResourceProcessor#buildNamedQueryMap","started":"19:32:06.722","dependents":[606],"id":442,"thread":"build-30"},{"duration":1,"stepId":"io.quarkus.arc.deployment.LifecycleEventsBuildStep#startupEvent","started":"19:32:09.839","dependents":[606,600],"id":599,"thread":"build-62"},{"duration":1,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#registerHttpAuthMechanismAnnotation","started":"19:32:05.048","dependents":[399],"id":1,"thread":"build-16"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#configureHandlers","started":"19:32:09.796","dependents":[606],"id":588,"thread":"build-13"},{"duration":1,"stepId":"io.quarkus.hibernate.orm.panache.deployment.PanacheHibernateResourceProcessor#recordEntityToPersistenceUnit","started":"19:32:08.974","dependents":[606],"id":563,"thread":"build-13"},{"duration":1,"stepId":"io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsProcessor#callInitializer","started":"19:32:08.776","dependents":[606],"id":539,"thread":"build-60"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ArcProcessor#validateAsyncObserverExceptionHandlers","started":"19:32:08.207","dependents":[533],"id":517,"thread":"build-37"},{"duration":1,"stepId":"io.quarkus.deployment.steps.ShutdownListenerBuildStep#setupShutdown","started":"19:32:09.839","dependents":[606],"id":598,"thread":"build-13"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#notFoundRoutes","started":"19:32:09.815","dependents":[595],"id":593,"thread":"build-69"},{"duration":1,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#addPersistenceUnitAnnotationToIndex","started":"19:32:05.106","dependents":[363],"id":56,"thread":"build-30"},{"duration":1,"stepId":"io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsProcessor#processInterceptedStaticMethods","started":"19:32:07.821","dependents":[604,602,561,562,560],"id":498,"thread":"build-51"},{"duration":1,"stepId":"io.quarkus.deployment.steps.ApplicationInfoBuildStep#create","started":"19:32:05.260","dependents":[606],"id":202,"thread":"build-22"},{"duration":1,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setUpDefaultLevels","started":"19:32:05.247","dependents":[557,458],"id":182,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.deployment.JniProcessor#setupJni","started":"19:32:05.247","dependents":[328],"id":178,"thread":"build-37"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#produceEagerSecurityInterceptorStorage","started":"19:32:06.572","dependents":[606,504,503,505],"id":402,"thread":"build-48"},{"duration":0,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#installCliCommands","started":"19:32:09.712","dependents":[597,599],"id":581,"thread":"build-33"},{"duration":0,"stepId":"io.quarkus.micrometer.deployment.binder.HttpBinderProcessor#registerProvider","started":"19:32:05.184","dependents":[363,483,460],"id":116,"thread":"build-46"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#convertRoutes","started":"19:32:09.814","dependents":[593,594],"id":592,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.deployment.steps.ProfileBuildStep#defaultProfile","started":"19:32:05.144","dependents":[557],"id":82,"thread":"build-3"},{"duration":0,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#mapDeploymentMethods","started":"19:32:05.872","dependents":[426,549],"id":332,"thread":"build-67"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.EndpointsProcessor#createEndpointsPage","started":"19:32:05.301","dependents":[567],"id":219,"thread":"build-57"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#candidatesForFieldAccess","started":"19:32:06.719","dependents":[441],"id":439,"thread":"build-30"},{"duration":0,"stepId":"io.quarkus.deployment.index.ApplicationArchiveBuildStep#addConfiguredIndexedDependencies","started":"19:32:05.261","dependents":[357],"id":201,"thread":"build-78"},{"duration":0,"stepId":"io.quarkus.datasource.deployment.devui.DevUIDatasourceProcessor#create","started":"19:32:05.241","dependents":[532,564],"id":172,"thread":"build-75"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#handleSseEventFilter","started":"19:32:06.991","dependents":[604],"id":463,"thread":"build-83"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#quarkusApplication","started":"19:32:06.543","dependents":[483,460],"id":370,"thread":"build-28"},{"duration":0,"stepId":"io.quarkus.deployment.execannotations.ExecutionModelAnnotationsProcessor#check","started":"19:32:06.646","dependents":[],"id":434,"thread":"build-30"},{"duration":0,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#initTenantConfigBean","started":"19:32:07.880","dependents":[606],"id":506,"thread":"build-51"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#activateSslNativeSupport","started":"19:32:05.165","dependents":[328],"id":95,"thread":"build-55"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#marker","started":"19:32:05.097","dependents":[357],"id":42,"thread":"build-38"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setProperty","started":"19:32:05.108","dependents":[606],"id":59,"thread":"build-14"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerProviderBeans","started":"19:32:06.543","dependents":[483,460],"id":368,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#perClassExceptionMapperSupport","started":"19:32:06.585","dependents":[483],"id":415,"thread":"build-57"},{"duration":0,"stepId":"io.quarkus.arc.deployment.AutoInjectFieldProcessor#autoInjectQualifiers","started":"19:32:06.991","dependents":[469,466],"id":461,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#multitenancy","started":"19:32:06.765","dependents":[606,514,522,504,503,505],"id":449,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.dev.HibernateOrmDevUIProcessor#handleInitialSql","started":"19:32:06.764","dependents":[455,459],"id":448,"thread":"build-4"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#addAllWriteableMarker","started":"19:32:09.714","dependents":[602],"id":583,"thread":"build-154"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingWithPanacheProcessor#process","started":"19:32:06.543","dependents":[602],"id":365,"thread":"build-33"},{"duration":0,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#missingDevUIMessageHandler","started":"19:32:06.090","dependents":[597,599],"id":351,"thread":"build-57"},{"duration":0,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#preventLoggerContention","started":"19:32:05.209","dependents":[182],"id":126,"thread":"build-4"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.dev.HibernateOrmDevUIProcessor#additionalBeans","started":"19:32:05.166","dependents":[483,460],"id":97,"thread":"build-60"},{"duration":0,"stepId":"io.quarkus.deployment.ForkJoinPoolProcessor#setProperty","started":"19:32:05.217","dependents":[606],"id":144,"thread":"build-53"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.GeneratedStaticResourcesProcessor#devMode","started":"19:32:05.162","dependents":[177,299,457],"id":93,"thread":"build-50"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#generateCustomProducer","started":"19:32:06.585","dependents":[483,460],"id":413,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#initializeRolesAllowedConfigExp","started":"19:32:08.207","dependents":[606],"id":515,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#featureAndCapability","started":"19:32:05.169","dependents":[606,222],"id":98,"thread":"build-63"},{"duration":0,"stepId":"io.quarkus.micrometer.deployment.binder.HttpBinderProcessor#enableHttpServerSupport","started":"19:32:05.325","dependents":[483,460],"id":229,"thread":"build-82"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initMtlsClientAuth","started":"19:32:05.241","dependents":[483,460],"id":170,"thread":"build-4"},{"duration":0,"stepId":"io.quarkus.micrometer.deployment.binder.VertxBinderProcessor#setVertxConfig","started":"19:32:07.880","dependents":[606],"id":507,"thread":"build-25"},{"duration":0,"stepId":"io.quarkus.narayana.jta.deployment.NarayanaJtaProcessor#logCleanupFilters","started":"19:32:05.214","dependents":[458,444],"id":135,"thread":"build-73"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.devservices.DevServicesRestClientHttpProxyProcessor#determineRequiredProxies","started":"19:32:06.550","dependents":[390],"id":380,"thread":"build-67"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#feature","started":"19:32:05.144","dependents":[606],"id":81,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.deployment.recording.AnnotationProxyBuildStep#build","started":"19:32:06.102","dependents":[530,499],"id":355,"thread":"build-33"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#unremovableAsyncObserverExceptionHandlers","started":"19:32:05.130","dependents":[514,522],"id":72,"thread":"build-42"},{"duration":0,"stepId":"io.quarkus.smallrye.faulttolerance.deployment.devui.FaultToleranceDevUIProcessor#cardPage","started":"19:32:08.317","dependents":[532,564],"id":531,"thread":"build-24"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#enrollBeanValidationTypeSafeActivatorForReflection","started":"19:32:05.324","dependents":[604],"id":225,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ExecutorServiceProcessor#executorServiceBean","started":"19:32:05.861","dependents":[504,503,505],"id":330,"thread":"build-67"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#buildSetup","started":"19:32:05.209","dependents":[606],"id":127,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#includeArchivesHostingEntityPackagesInIndex","started":"19:32:05.250","dependents":[357],"id":188,"thread":"build-58"},{"duration":0,"stepId":"io.quarkus.deployment.ConstructorPropertiesProcessor#build","started":"19:32:06.544","dependents":[604],"id":374,"thread":"build-2"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.panache.deployment.PanacheHibernateResourceProcessor#featureBuildItem","started":"19:32:05.092","dependents":[606],"id":33,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#pathInterfaceImpls","started":"19:32:06.585","dependents":[483,460],"id":416,"thread":"build-72"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.BuildMetricsProcessor#createBuildMetricsPages","started":"19:32:05.210","dependents":[567],"id":132,"thread":"build-26"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.ConfigurationProcessor#createConfigurationPages","started":"19:32:08.810","dependents":[567],"id":553,"thread":"build-121"},{"duration":0,"stepId":"io.quarkus.security.deployment.SecurityProcessor#feature","started":"19:32:05.233","dependents":[606],"id":157,"thread":"build-72"},{"duration":0,"stepId":"io.quarkus.deployment.ExtensionLoader#booleanSupplierFactory","started":"19:32:05.166","dependents":[217],"id":96,"thread":"build-57"},{"duration":0,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#processSmallRyeHealthConfigValues","started":"19:32:05.260","dependents":[557],"id":196,"thread":"build-50"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateLogFilterBuildStep#setupLogFilters","started":"19:32:05.129","dependents":[458,444],"id":66,"thread":"build-41"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#feature","started":"19:32:05.093","dependents":[606],"id":35,"thread":"build-36"},{"duration":0,"stepId":"io.quarkus.jdbc.postgresql.deployment.PostgreSQLJDBCReflections#build","started":"19:32:05.209","dependents":[604],"id":129,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerProvidersInstances","started":"19:32:06.544","dependents":[433],"id":373,"thread":"build-32"},{"duration":0,"stepId":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor#configFile","started":"19:32:05.149","dependents":[457],"id":90,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#shutdownHealthCheck","started":"19:32:05.247","dependents":[483,460],"id":179,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateUserTypeProcessor#build","started":"19:32:06.543","dependents":[604],"id":367,"thread":"build-83"},{"duration":0,"stepId":"io.quarkus.jdbc.postgresql.deployment.JDBCPostgreSQLProcessor#feature","started":"19:32:05.094","dependents":[606],"id":38,"thread":"build-26"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.ExtensionsProcessor#createExtensionsPages","started":"19:32:08.996","dependents":[567],"id":565,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.security.deployment.SecurityProcessor#transformSecurityAnnotations","started":"19:32:06.572","dependents":[483],"id":401,"thread":"build-56"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#searchForProviders","started":"19:32:05.325","dependents":[357],"id":227,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.jackson.deployment.RestClientReactiveJacksonProcessor#feature","started":"19:32:05.145","dependents":[606],"id":83,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#defineTypeOfImpliedPU","started":"19:32:06.759","dependents":[450,451,447],"id":446,"thread":"build-25"},{"duration":0,"stepId":"io.quarkus.smallrye.health.deployment.SmallRyeHealthProcessor#processSmallRyeHealthRuntimeConfig","started":"19:32:07.880","dependents":[606],"id":508,"thread":"build-83"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.devconsole.RestClientReactiveDevUIProcessor#create","started":"19:32:05.108","dependents":[532,564],"id":60,"thread":"build-9"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#resourceIndex","started":"19:32:06.543","dependents":[578,460,411],"id":364,"thread":"build-83"},{"duration":0,"stepId":"io.quarkus.netty.deployment.NettyProcessor#limitArenaSize","started":"19:32:05.250","dependents":[606],"id":186,"thread":"build-75"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.ManagementInterfaceSecurityProcessor#initializeAuthMechanismHandler","started":"19:32:08.776","dependents":[606],"id":538,"thread":"build-130"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#setMinimalNettyMaxOrderSize","started":"19:32:05.149","dependents":[186,197],"id":91,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.arc.deployment.TestsAsBeansProcessor#testClassBeans","started":"19:32:05.217","dependents":[483,460],"id":145,"thread":"build-29"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerHeaderFactoryBeans","started":"19:32:06.544","dependents":[483,460],"id":372,"thread":"build-25"},{"duration":0,"stepId":"io.quarkus.security.deployment.SecurityProcessor#produceJcaSecurityProviders","started":"19:32:05.250","dependents":[280,212,214],"id":185,"thread":"build-67"},{"duration":0,"stepId":"io.quarkus.deployment.steps.PreloadClassesBuildStep#registerPreInitClasses","started":"19:32:05.184","dependents":[],"id":115,"thread":"build-26"},{"duration":0,"stepId":"io.quarkus.stork.deployment.SmallRyeStorkProcessor#checkThatTheKubernetesExtensionIsUsedWhenKubernetesServiceDiscoveryInOnTheClasspath","started":"19:32:05.324","dependents":[510],"id":224,"thread":"build-51"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.EndpointsProcessor#createJsonRPCService","started":"19:32:05.209","dependents":[426,337],"id":131,"thread":"build-50"},{"duration":0,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#createAllRoutes","started":"19:32:09.206","dependents":[576],"id":570,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#announceFeature","started":"19:32:05.158","dependents":[606],"id":92,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#contributePersistenceXmlToJpaModel","started":"19:32:05.798","dependents":[438],"id":304,"thread":"build-2"},{"duration":0,"stepId":"io.quarkus.micrometer.deployment.MicrometerConfigAlwaysEnabledProcessor#mpConfigAsBean","started":"19:32:05.085","dependents":[514,522],"id":29,"thread":"build-35"},{"duration":0,"stepId":"io.quarkus.micrometer.deployment.MicrometerProcessor#registerExtensionMetrics","started":"19:32:08.806","dependents":[606],"id":550,"thread":"build-122"},{"duration":0,"stepId":"io.quarkus.security.deployment.SecurityProcessor#resolveConfigExpressionRoles","started":"19:32:07.079","dependents":[606],"id":474,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.micrometer.deployment.MicrometerConfigAlwaysEnabledProcessor#feature","started":"19:32:05.147","dependents":[606],"id":87,"thread":"build-44"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#produceLoggingCategories","started":"19:32:05.247","dependents":[182],"id":180,"thread":"build-75"},{"duration":0,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#contributeClassesToIndex","started":"19:32:05.209","dependents":[363],"id":128,"thread":"build-47"},{"duration":0,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#handle","started":"19:32:05.082","dependents":[458,444],"id":27,"thread":"build-34"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#setUpDenyAllJaxRs","started":"19:32:05.233","dependents":[476],"id":156,"thread":"build-51"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerCustomConfigBeanTypes","started":"19:32:07.802","dependents":[604,504,503,505],"id":486,"thread":"build-37"},{"duration":0,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#conditionTransformer","started":"19:32:06.558","dependents":[483],"id":391,"thread":"build-72"},{"duration":0,"stepId":"io.quarkus.deployment.steps.CurateOutcomeBuildStep#curateOutcome","started":"19:32:05.250","dependents":[455,255,357,354,566,222,395,564,569,426,199,277,602,247,573,240,239,211,308,361,332,532,567,214,259,217],"id":187,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerCompressionInterceptors","started":"19:32:05.063","dependents":[604],"id":16,"thread":"build-14"},{"duration":0,"stepId":"io.quarkus.deployment.pkg.steps.NativeImageBuildStep#ignoreBuildPropertyChanges","started":"19:32:05.210","dependents":[307],"id":133,"thread":"build-72"},{"duration":0,"stepId":"io.quarkus.flyway.deployment.FlywayAlwaysEnabledProcessor#build","started":"19:32:05.095","dependents":[606],"id":39,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ObserverValidationProcessor#validateApplicationObserver","started":"19:32:08.207","dependents":[533],"id":516,"thread":"build-25"},{"duration":0,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#agroal","started":"19:32:05.131","dependents":[606],"id":74,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.dev.HibernateOrmDevUIProcessor#create","started":"19:32:05.184","dependents":[532,564],"id":117,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#unremovableContextMethodParams","started":"19:32:06.585","dependents":[514,522],"id":412,"thread":"build-71"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForParamConverters_59e3169e3a646b7fcf3083416f558434b73816c5","started":"19:32:06.576","dependents":[417],"id":404,"thread":"build-20"},{"duration":0,"stepId":"io.quarkus.arc.deployment.HotDeploymentConfigBuildStep#configFile","started":"19:32:05.147","dependents":[457],"id":88,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupLogFilters","started":"19:32:05.184","dependents":[458,444],"id":118,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.devui.deployment.build.BuildMetricsDevUIProcessor#additionalBeans","started":"19:32:05.185","dependents":[483,460],"id":120,"thread":"build-50"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#subResourcesAsBeans","started":"19:32:06.585","dependents":[514,522,483,460],"id":414,"thread":"build-56"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.DevServicesProcessor#createDevServicesPages","started":"19:32:08.830","dependents":[567],"id":556,"thread":"build-60"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#handleFieldSecurity","started":"19:32:09.710","dependents":[579],"id":578,"thread":"build-33"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#checkTransactionsSupport","started":"19:32:05.322","dependents":[533],"id":223,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initializeAuthenticationHandler","started":"19:32:08.776","dependents":[606],"id":537,"thread":"build-122"},{"duration":0,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#autoRegisterModules","started":"19:32:06.543","dependents":[419],"id":369,"thread":"build-58"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.panache.deployment.PanacheHibernateResourceProcessor#collectEntityClasses","started":"19:32:06.543","dependents":[562],"id":371,"thread":"build-72"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setUpDefaultLogCleanupFilters","started":"19:32:06.756","dependents":[557],"id":444,"thread":"build-25"},{"duration":0,"stepId":"io.quarkus.security.deployment.SecurityProcessor#gatherClassSecurityChecks","started":"19:32:06.991","dependents":[473],"id":462,"thread":"build-24"},{"duration":0,"stepId":"io.quarkus.smallrye.faulttolerance.deployment.SmallRyeFaultToleranceProcessor#registerTypes","started":"19:32:05.209","dependents":[356],"id":130,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#filterNettyHostsFileParsingWarn","started":"19:32:05.164","dependents":[458,444],"id":94,"thread":"build-54"},{"duration":0,"stepId":"io.quarkus.oidc.deployment.OidcAlwaysEnabledProcessor#featureBuildItem","started":"19:32:05.100","dependents":[606],"id":47,"thread":"build-39"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmAlwaysEnabledProcessor#featureBuildItem","started":"19:32:05.184","dependents":[606],"id":119,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.security.deployment.SecurityProcessor#transformAdditionalSecuredClassesToMethods","started":"19:32:05.241","dependents":[401,473],"id":171,"thread":"build-3"},{"duration":0,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#enableSslInNative","started":"19:32:05.127","dependents":[328],"id":63,"thread":"build-26"},{"duration":0,"stepId":"io.quarkus.agroal.deployment.AgroalProcessor#adaptOpenTelemetryJdbcInstrumentationForNative","started":"19:32:05.326","dependents":[602],"id":228,"thread":"build-46"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#collectInterceptedMethods","started":"19:32:06.572","dependents":[406,402],"id":400,"thread":"build-28"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#warnOfSchemaProblems","started":"19:32:09.839","dependents":[606],"id":597,"thread":"build-154"},{"duration":0,"stepId":"io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#hotDeploymentWatchedFiles","started":"19:32:05.260","dependents":[457],"id":198,"thread":"build-77"},{"duration":0,"stepId":"io.quarkus.deployment.steps.ReflectionDiagnosticProcessor#writeReflectionData","started":"19:32:11.306","dependents":[],"id":604,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.caffeine.deployment.CaffeineProcessor#cacheLoaders","started":"19:32:06.543","dependents":[604],"id":366,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.oidc.deployment.OidcBuildStep#checkClaim","started":"19:32:07.802","dependents":[511],"id":487,"thread":"build-6"}],"started":"2025-12-06T19:32:05.04","items":[{"count":4357,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem"},{"count":1703,"class":"io.quarkus.deployment.builditem.GeneratedClassBuildItem"},{"count":1636,"class":"io.quarkus.deployment.builditem.ConfigDescriptionBuildItem"},{"count":755,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem"},{"count":741,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem"},{"count":101,"class":"io.quarkus.arc.deployment.AdditionalBeanBuildItem"},{"count":82,"class":"io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem"},{"count":66,"class":"io.quarkus.hibernate.validator.spi.AdditionalConstrainedClassBuildItem"},{"count":65,"class":"io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem"},{"count":62,"class":"io.quarkus.vertx.http.deployment.RouteBuildItem"},{"count":58,"class":"io.quarkus.arc.deployment.SyntheticBeanBuildItem"},{"count":52,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem"},{"count":39,"class":"io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem"},{"count":32,"class":"io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem"},{"count":28,"class":"io.quarkus.arc.deployment.ConfigPropertyBuildItem"},{"count":27,"class":"io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem"},{"count":23,"class":"io.quarkus.arc.deployment.UnremovableBeanBuildItem"},{"count":22,"class":"io.quarkus.deployment.builditem.CapabilityBuildItem"},{"count":21,"class":"io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem"},{"count":21,"class":"io.quarkus.deployment.builditem.FeatureBuildItem"},{"count":20,"class":"io.quarkus.deployment.builditem.ConfigClassBuildItem"},{"count":19,"class":"io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem"},{"count":18,"class":"io.quarkus.deployment.logging.LogCleanupFilterBuildItem"},{"count":17,"class":"io.quarkus.deployment.builditem.BytecodeTransformerBuildItem"},{"count":16,"class":"io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem"},{"count":15,"class":"io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem"},{"count":15,"class":"io.quarkus.devui.spi.JsonRPCProvidersBuildItem"},{"count":13,"class":"io.quarkus.devui.deployment.DevUIWebJarBuildItem"},{"count":13,"class":"io.quarkus.devui.deployment.DevUIRoutesBuildItem"},{"count":13,"class":"io.quarkus.arc.deployment.AnnotationsTransformerBuildItem"},{"count":12,"class":"io.quarkus.devui.spi.page.CardPageBuildItem"},{"count":11,"class":"io.quarkus.deployment.builditem.SuppressNonRuntimeConfigChangedWarningBuildItem"},{"count":10,"class":"io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem"},{"count":10,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem"},{"count":8,"class":"io.quarkus.hibernate.orm.deployment.spi.DatabaseKindDialectBuildItem"},{"count":8,"class":"io.quarkus.devui.deployment.InternalPageBuildItem"},{"count":8,"class":"io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem"},{"count":8,"class":"io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem"},{"count":7,"class":"io.quarkus.deployment.builditem.SystemPropertyBuildItem"},{"count":7,"class":"io.quarkus.deployment.builditem.ConsoleCommandBuildItem"},{"count":7,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyReaderBuildItem"},{"count":6,"class":"io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem"},{"count":6,"class":"io.quarkus.vertx.http.deployment.FilterBuildItem"},{"count":6,"class":"io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem"},{"count":6,"class":"io.quarkus.devui.deployment.BuildTimeConstBuildItem"},{"count":6,"class":"io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem"},{"count":5,"class":"io.quarkus.vertx.http.deployment.HttpAuthMechanismAnnotationBuildItem"},{"count":5,"class":"io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem"},{"count":5,"class":"io.quarkus.deployment.builditem.ServiceStartBuildItem"},{"count":5,"class":"io.quarkus.deployment.execannotations.ExecutionModelAnnotationsAllowedBuildItem"},{"count":5,"class":"io.quarkus.devui.spi.buildtime.BuildTimeActionBuildItem"},{"count":5,"class":"io.quarkus.arc.deployment.GeneratedBeanBuildItem"},{"count":5,"class":"io.quarkus.deployment.builditem.ConfigMappingBuildItem"},{"count":4,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyWriterOverrideBuildItem"},{"count":4,"class":"io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem$BeanConfiguratorBuildItem"},{"count":4,"class":"io.quarkus.arc.deployment.AutoAddScopeBuildItem"},{"count":4,"class":"io.quarkus.vertx.http.deployment.spi.RouteBuildItem"},{"count":4,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyReaderOverrideBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.ShutdownListenerBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.StaticInitConfigBuilderBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem"},{"count":3,"class":"io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem"},{"count":3,"class":"io.quarkus.smallrye.openapi.deployment.spi.AddToOpenAPIDefinitionBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.LogCategoryBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.IndexDependencyBuildItem"},{"count":3,"class":"io.quarkus.resteasy.reactive.server.spi.UnwrappedExceptionBuildItem"},{"count":3,"class":"io.quarkus.resteasy.reactive.spi.CustomExceptionMapperBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.GeneratedResourceBuildItem"},{"count":2,"class":"io.quarkus.resteasy.reactive.common.deployment.ResourceInterceptorsContributorBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem"},{"count":2,"class":"io.quarkus.devui.spi.buildtime.QuteTemplateBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.RecordableConstructorBuildItem"},{"count":2,"class":"io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem"},{"count":2,"class":"io.quarkus.devui.spi.buildtime.StaticContentBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.ConfigurationTypeBuildItem"},{"count":2,"class":"io.quarkus.arc.deployment.InjectionPointTransformerBuildItem"},{"count":2,"class":"io.quarkus.hibernate.orm.panache.common.deployment.PanacheNamedQueryEntityClassBuildStep"},{"count":2,"class":"io.quarkus.devui.deployment.InternalImportMapBuildItem"},{"count":2,"class":"io.quarkus.arc.deployment.AutoInjectAnnotationBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.MvnpmBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.AnnotationProxyBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.BytecodeRecorderConstantDefinitionBuildItem"},{"count":1,"class":"io.quarkus.deployment.console.ConsoleInstalledBuildItem"},{"count":1,"class":"io.quarkus.deployment.metrics.MetricsCapabilityBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.DevServicesAdditionalConfigBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.SynthesisFinishedBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ObservabilityIntegrationBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.RemovedResourceBuildItem"},{"count":1,"class":"io.quarkus.vertx.core.deployment.EventLoopCountBuildItem"},{"count":1,"class":"io.quarkus.vertx.core.deployment.CoreVertxBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ContextResolversBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.DockerStatusBuildItem"},{"count":1,"class":"io.quarkus.vertx.deployment.LocalCodecSelectorTypesBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.InitialRouterBuildItem"},{"count":1,"class":"io.quarkus.panache.common.deployment.HibernateMetamodelForFieldAccessBuildItem"},{"count":1,"class":"io.quarkus.smallrye.openapi.deployment.spi.OpenApiDocumentBuildItem"},{"count":1,"class":"io.quarkus.deployment.dev.ExceptionNotificationBuildItem"},{"count":1,"class":"io.quarkus.swaggerui.deployment.SwaggerUiBuildItem"},{"count":1,"class":"io.quarkus.deployment.pkg.builditem.CompiledJavaVersionBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ValidationPhaseBuildItem"},{"count":1,"class":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveEnricherBuildItem"},{"count":1,"class":"io.quarkus.netty.deployment.EventLoopSupplierBuildItem"},{"count":1,"class":"io.quarkus.deployment.BooleanSupplierFactoryBuildItem"},{"count":1,"class":"io.quarkus.hibernate.validator.spi.BeanValidationAnnotationsBuildItem"},{"count":1,"class":"io.quarkus.tls.TlsRegistryBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ParamConverterProvidersBuildItem"},{"count":1,"class":"io.quarkus.hibernate.orm.deployment.ImpliedBlockingPersistenceUnitTypeBuildItem"},{"count":1,"class":"io.quarkus.rest.client.reactive.spi.DevServicesRestClientProxyProvider$BuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.spi.HandlerConfigurationProviderBuildItem"},{"count":1,"class":"io.quarkus.smallrye.health.deployment.SmallRyeHealthBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem"},{"count":1,"class":"io.quarkus.security.spi.AdditionalSecurityConstrainerEventPropsBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ExcludedTypeBuildItem"},{"count":1,"class":"io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ThreadFactoryBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationIndexBuildItem"},{"count":1,"class":"io.quarkus.panache.common.deployment.HibernateModelClassCandidatesForFieldAccessBuildItem"},{"count":1,"class":"io.quarkus.agroal.spi.JdbcDriverBuildItem"},{"count":1,"class":"io.quarkus.smallrye.health.deployment.spi.HealthBuildItem"},{"count":1,"class":"io.quarkus.micrometer.deployment.MicrometerRegistryProviderBuildItem"},{"count":1,"class":"io.quarkus.deployment.logging.LoggingSetupBuildItem"},{"count":1,"class":"io.quarkus.smallrye.faulttolerance.deployment.devui.FaultToleranceInfoBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ArcContainerBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.JsonRPCRuntimeMethodsBuildItem"},{"count":1,"class":"io.quarkus.smallrye.context.deployment.spi.ThreadContextProviderBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationClassNameBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.InitTaskCompletedBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.SecurityInformationBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.StreamingLogHandlerBuildItem"},{"count":1,"class":"io.quarkus.deployment.dev.DisableInstrumentationForIndexPredicateBuildItem"},{"count":1,"class":"io.quarkus.deployment.logging.LoggingDecorateBuildItem"},{"count":1,"class":"io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.CurrentContextFactoryBuildItem"},{"count":1,"class":"io.quarkus.rest.client.reactive.deployment.AnnotationToRegisterIntoClientContextBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ParameterContainersBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ConfigurationBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem"},{"count":1,"class":"io.quarkus.agroal.deployment.AggregatedDataSourceBuildTimeConfigBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.BodyHandlerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BuildExclusionsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.LogCategoryMinLevelDefaultsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.IOThreadDetectorBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.InvokerFactoryBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.SslNativeConfigBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.CustomScopeBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem"},{"count":1,"class":"io.quarkus.agroal.spi.JdbcDataSourceBuildItem"},{"count":1,"class":"io.quarkus.deployment.ide.IdeRunningProcessBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanContainerListenerBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.TransformedClassesBuildItem"},{"count":1,"class":"io.quarkus.netty.deployment.EventLoopGroupBuildItem"},{"count":1,"class":"io.quarkus.hibernate.orm.deployment.JpaModelIndexBuildItem"},{"count":1,"class":"io.quarkus.security.deployment.SecurityProcessor$MethodSecurityChecks"},{"count":1,"class":"io.quarkus.arc.deployment.devui.ArcBeanInfoBuildItem"},{"count":1,"class":"io.quarkus.jaxrs.client.reactive.deployment.RestClientDefaultProducesBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.RunTimeConfigurationProxyBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ResourceInterceptorsBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BuildCompatibleExtensionsBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.ThemeVarsBuildItem"},{"count":1,"class":"io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem"},{"count":1,"class":"io.quarkus.smallrye.context.deployment.ContextPropagationInitializedBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ExceptionMappersBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.InterceptorResolverBuildItem"},{"count":1,"class":"io.quarkus.panache.common.deployment.HibernateEnhancersRegisteredBuildItem"},{"count":1,"class":"io.quarkus.hibernate.validator.deployment.HibernateValidatorProcessor$AdditionalConstrainedClassesIndexBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanArchiveIndexBuildItem"},{"count":1,"class":"io.quarkus.jackson.spi.JacksonModuleBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ConsoleFormatterBannerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.SuppressConditionGeneratorBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BuildTimeEnabledStereotypesBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationArchivesBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ContextHandlerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.TransformedAnnotationsBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveResourceMethodEntriesBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.GeneratedFileSystemResourceHandledBuildItem"},{"count":1,"class":"io.quarkus.hibernate.orm.deployment.PersistenceProviderSetUpBuildItem"},{"count":1,"class":"io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem"},{"count":1,"class":"io.quarkus.micrometer.deployment.RootMeterRegistryBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.PreBeanContainerBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem"},{"count":1,"class":"io.quarkus.hibernate.orm.deployment.JpaModelPersistenceUnitContributionBuildItem"},{"count":1,"class":"io.quarkus.netty.deployment.MinNettyAllocatorMaxOrderBuildItem"},{"count":1,"class":"io.quarkus.smallrye.openapi.deployment.OpenApiFilteredIndexViewBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.VertxWebRouterBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.CombinedIndexBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProviderDefinedBuildItem"},{"count":1,"class":"io.quarkus.deployment.Capabilities"},{"count":1,"class":"io.quarkus.devui.deployment.ExtensionsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ExecutorBuildItem"},{"count":1,"class":"io.quarkus.deployment.dev.testing.TestListenerBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.SetupEndpointsResultBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveDeploymentInfoBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem"},{"count":1,"class":"io.quarkus.jaxrs.client.reactive.deployment.RestClientDefaultConsumesBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ResourceScanningResultBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ServerSerialisersBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.QualifierRegistrarBuildItem"},{"count":1,"class":"io.quarkus.hibernate.orm.deployment.JpaModelBuildItem"},{"count":1,"class":"io.quarkus.vertx.deployment.VertxBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveDeploymentBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanContainerBuildItem"},{"count":1,"class":"io.quarkus.deployment.ide.EffectiveIdeBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.JaxRsResourceIndexBuildItem"},{"count":1,"class":"io.quarkus.micrometer.deployment.export.RegistryBuildItem"},{"count":1,"class":"io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.HttpRootPathBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.DeploymentMethodBuildItem"},{"count":1,"class":"io.quarkus.deployment.steps.CapabilityAggregationStep$CapabilitiesConfiguredInDescriptorsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationStartBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.RelocationImportMapBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor$HttpAuthenticationHandlerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem"},{"count":1,"class":"io.quarkus.flyway.deployment.FlywayProcessor$MigrationStateBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem$ContextConfiguratorBuildItem"},{"count":1,"class":"io.quarkus.agroal.spi.JdbcDataSourceSchemaReadyBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.BuiltInReaderOverrideBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem"},{"count":1,"class":"io.quarkus.vertx.core.deployment.VertxOptionsConsumerBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationInfoBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.spi.ContainerRequestFilterBuildItem"},{"count":1,"class":"io.quarkus.deployment.ide.IdeFileBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.MainClassBuildItem"}],"itemsCount":10444,"buildTarget":"lions-user-manager-server-impl-quarkus-1.0.0"}
\ No newline at end of file
diff --git a/target/classes/application-dev.properties b/target/classes/application-dev.properties
index 836e8d5..ad9475f 100644
--- a/target/classes/application-dev.properties
+++ b/target/classes/application-dev.properties
@@ -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
diff --git a/target/classes/application.properties b/target/classes/application.properties
index 0cfcd12..88055f9 100644
--- a/target/classes/application.properties
+++ b/target/classes/application.properties
@@ -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
diff --git a/target/classes/dev/lions/user/manager/client/KeycloakAdminClientImpl.class b/target/classes/dev/lions/user/manager/client/KeycloakAdminClientImpl.class
index 7defcdd..8ce3fc6 100644
Binary files a/target/classes/dev/lions/user/manager/client/KeycloakAdminClientImpl.class and b/target/classes/dev/lions/user/manager/client/KeycloakAdminClientImpl.class differ
diff --git a/target/classes/dev/lions/user/manager/config/JacksonConfig.class b/target/classes/dev/lions/user/manager/config/JacksonConfig.class
new file mode 100644
index 0000000..70bc247
Binary files /dev/null and b/target/classes/dev/lions/user/manager/config/JacksonConfig.class differ
diff --git a/target/classes/dev/lions/user/manager/config/KeycloakTestUserConfig.class b/target/classes/dev/lions/user/manager/config/KeycloakTestUserConfig.class
new file mode 100644
index 0000000..457b21a
Binary files /dev/null and b/target/classes/dev/lions/user/manager/config/KeycloakTestUserConfig.class differ
diff --git a/target/classes/dev/lions/user/manager/mapper/RoleMapper.class b/target/classes/dev/lions/user/manager/mapper/RoleMapper.class
index 4f334fe..6a212b2 100644
Binary files a/target/classes/dev/lions/user/manager/mapper/RoleMapper.class and b/target/classes/dev/lions/user/manager/mapper/RoleMapper.class differ
diff --git a/target/classes/dev/lions/user/manager/mapper/UserMapper.class b/target/classes/dev/lions/user/manager/mapper/UserMapper.class
index 17b6ac8..fac9911 100644
Binary files a/target/classes/dev/lions/user/manager/mapper/UserMapper.class and b/target/classes/dev/lions/user/manager/mapper/UserMapper.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/AuditResource$CountResponse.class b/target/classes/dev/lions/user/manager/resource/AuditResource$CountResponse.class
new file mode 100644
index 0000000..42dbc5c
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/AuditResource$CountResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/AuditResource$ErrorResponse.class b/target/classes/dev/lions/user/manager/resource/AuditResource$ErrorResponse.class
new file mode 100644
index 0000000..455e3b9
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/AuditResource$ErrorResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/AuditResource.class b/target/classes/dev/lions/user/manager/resource/AuditResource.class
new file mode 100644
index 0000000..b4aa658
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/AuditResource.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/HealthResourceEndpoint.class b/target/classes/dev/lions/user/manager/resource/HealthResourceEndpoint.class
index 8cc2df4..f40ecce 100644
Binary files a/target/classes/dev/lions/user/manager/resource/HealthResourceEndpoint.class and b/target/classes/dev/lions/user/manager/resource/HealthResourceEndpoint.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/KeycloakHealthCheck.class b/target/classes/dev/lions/user/manager/resource/KeycloakHealthCheck.class
index 80557ca..342958e 100644
Binary files a/target/classes/dev/lions/user/manager/resource/KeycloakHealthCheck.class and b/target/classes/dev/lions/user/manager/resource/KeycloakHealthCheck.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/RoleResource$ErrorResponse.class b/target/classes/dev/lions/user/manager/resource/RoleResource$ErrorResponse.class
index a9907ea..0a740ef 100644
Binary files a/target/classes/dev/lions/user/manager/resource/RoleResource$ErrorResponse.class and b/target/classes/dev/lions/user/manager/resource/RoleResource$ErrorResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/RoleResource$RoleAssignmentRequest.class b/target/classes/dev/lions/user/manager/resource/RoleResource$RoleAssignmentRequest.class
index c2473d8..7111af6 100644
Binary files a/target/classes/dev/lions/user/manager/resource/RoleResource$RoleAssignmentRequest.class and b/target/classes/dev/lions/user/manager/resource/RoleResource$RoleAssignmentRequest.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/RoleResource.class b/target/classes/dev/lions/user/manager/resource/RoleResource.class
index 95fdf3e..48bb3f7 100644
Binary files a/target/classes/dev/lions/user/manager/resource/RoleResource.class and b/target/classes/dev/lions/user/manager/resource/RoleResource.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/SyncResource$ErrorResponse.class b/target/classes/dev/lions/user/manager/resource/SyncResource$ErrorResponse.class
new file mode 100644
index 0000000..77972b2
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/SyncResource$ErrorResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/SyncResource$ExistsCheckResponse.class b/target/classes/dev/lions/user/manager/resource/SyncResource$ExistsCheckResponse.class
new file mode 100644
index 0000000..42e202a
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/SyncResource$ExistsCheckResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/SyncResource$HealthCheckResponse.class b/target/classes/dev/lions/user/manager/resource/SyncResource$HealthCheckResponse.class
new file mode 100644
index 0000000..9747562
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/SyncResource$HealthCheckResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/SyncResource$SyncRolesResponse.class b/target/classes/dev/lions/user/manager/resource/SyncResource$SyncRolesResponse.class
new file mode 100644
index 0000000..e845a7f
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/SyncResource$SyncRolesResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/SyncResource$SyncUsersResponse.class b/target/classes/dev/lions/user/manager/resource/SyncResource$SyncUsersResponse.class
new file mode 100644
index 0000000..3211e1a
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/SyncResource$SyncUsersResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/SyncResource.class b/target/classes/dev/lions/user/manager/resource/SyncResource.class
new file mode 100644
index 0000000..5310496
Binary files /dev/null and b/target/classes/dev/lions/user/manager/resource/SyncResource.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/UserResource$ErrorResponse.class b/target/classes/dev/lions/user/manager/resource/UserResource$ErrorResponse.class
index c5bf094..9ab0f78 100644
Binary files a/target/classes/dev/lions/user/manager/resource/UserResource$ErrorResponse.class and b/target/classes/dev/lions/user/manager/resource/UserResource$ErrorResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/UserResource$PasswordResetRequest.class b/target/classes/dev/lions/user/manager/resource/UserResource$PasswordResetRequest.class
index 2a0fd43..358f968 100644
Binary files a/target/classes/dev/lions/user/manager/resource/UserResource$PasswordResetRequest.class and b/target/classes/dev/lions/user/manager/resource/UserResource$PasswordResetRequest.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/UserResource$SessionsRevokedResponse.class b/target/classes/dev/lions/user/manager/resource/UserResource$SessionsRevokedResponse.class
index 1598153..4997811 100644
Binary files a/target/classes/dev/lions/user/manager/resource/UserResource$SessionsRevokedResponse.class and b/target/classes/dev/lions/user/manager/resource/UserResource$SessionsRevokedResponse.class differ
diff --git a/target/classes/dev/lions/user/manager/resource/UserResource.class b/target/classes/dev/lions/user/manager/resource/UserResource.class
index 4f03dea..9ff14bc 100644
Binary files a/target/classes/dev/lions/user/manager/resource/UserResource.class and b/target/classes/dev/lions/user/manager/resource/UserResource.class differ
diff --git a/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer$DevSecurityContext$1.class b/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer$DevSecurityContext$1.class
new file mode 100644
index 0000000..763a45e
Binary files /dev/null and b/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer$DevSecurityContext$1.class differ
diff --git a/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer$DevSecurityContext.class b/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer$DevSecurityContext.class
new file mode 100644
index 0000000..dc63b2a
Binary files /dev/null and b/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer$DevSecurityContext.class differ
diff --git a/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer.class b/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer.class
new file mode 100644
index 0000000..b7d8d46
Binary files /dev/null and b/target/classes/dev/lions/user/manager/security/DevSecurityContextProducer.class differ
diff --git a/target/classes/dev/lions/user/manager/service/impl/AuditServiceImpl.class b/target/classes/dev/lions/user/manager/service/impl/AuditServiceImpl.class
index 221c8a6..2ea98d3 100644
Binary files a/target/classes/dev/lions/user/manager/service/impl/AuditServiceImpl.class and b/target/classes/dev/lions/user/manager/service/impl/AuditServiceImpl.class differ
diff --git a/target/classes/dev/lions/user/manager/service/impl/RoleServiceImpl.class b/target/classes/dev/lions/user/manager/service/impl/RoleServiceImpl.class
index 7243847..e75203e 100644
Binary files a/target/classes/dev/lions/user/manager/service/impl/RoleServiceImpl.class and b/target/classes/dev/lions/user/manager/service/impl/RoleServiceImpl.class differ
diff --git a/target/classes/dev/lions/user/manager/service/impl/SyncServiceImpl.class b/target/classes/dev/lions/user/manager/service/impl/SyncServiceImpl.class
new file mode 100644
index 0000000..41508cc
Binary files /dev/null and b/target/classes/dev/lions/user/manager/service/impl/SyncServiceImpl.class differ
diff --git a/target/classes/dev/lions/user/manager/service/impl/UserServiceImpl.class b/target/classes/dev/lions/user/manager/service/impl/UserServiceImpl.class
index 4d64aff..7042f2a 100644
Binary files a/target/classes/dev/lions/user/manager/service/impl/UserServiceImpl.class and b/target/classes/dev/lions/user/manager/service/impl/UserServiceImpl.class differ
diff --git a/target/lions-user-manager-server-impl-quarkus-1.0.0.jar b/target/lions-user-manager-server-impl-quarkus-1.0.0.jar
deleted file mode 100644
index 34fc61b..0000000
Binary files a/target/lions-user-manager-server-impl-quarkus-1.0.0.jar and /dev/null differ
diff --git a/target/lions-user-manager-server-impl-quarkus-dev.jar b/target/lions-user-manager-server-impl-quarkus-dev.jar
new file mode 100644
index 0000000..0be3578
Binary files /dev/null and b/target/lions-user-manager-server-impl-quarkus-dev.jar differ
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
deleted file mode 100644
index 280c289..0000000
--- a/target/maven-archiver/pom.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-artifactId=lions-user-manager-server-impl-quarkus
-groupId=dev.lions.user.manager
-version=1.0.0
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
index ee7f2e9..0e0a071 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -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
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
index 1517ef1..bcd585f 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -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
diff --git a/target/quarkus/bootstrap/dev-app-model.dat b/target/quarkus/bootstrap/dev-app-model.dat
new file mode 100644
index 0000000..6154e76
Binary files /dev/null and b/target/quarkus/bootstrap/dev-app-model.dat differ