feat(lum): KeycloakRealmSetupService + rôles RBAC UnionFlow + Jacoco 100%

- Ajoute KeycloakRealmSetupService : auto-initialisation des rôles realm
  (admin, user_manager, user_viewer, role_manager...) et assignation du rôle
  user_manager au service account unionflow-server au démarrage (idempotent,
  retries, thread séparé pour ne pas bloquer le démarrage)
  → Corrige le 403 sur resetPassword / changement de mot de passe premier login

- UserResource : étend les @RolesAllowed avec ADMIN/SUPER_ADMIN/USER pour
  permettre aux appels inter-services unionflow-server d'accéder aux endpoints
  sans être bloqués par le RBAC LUM ; corrige sendVerificationEmail (retourne Response)

- application-dev.properties : service-accounts.user-manager-clients=unionflow-server
- application-prod.properties : client-id, credentials.secret, token.audience, auto-setup
- application-test.properties : H2 in-memory (plus besoin de Docker pour les tests)
- pom.xml : H2 scope test, Jacoco 100% enforcement (exclusions MapStruct/repos/setup),
  annotation processors MapStruct+Lombok explicites
- .gitignore + .env ajouté (.env exclu du commit)
- script/docker/.env.example : variables KEYCLOAK_ADMIN_USERNAME/PASSWORD documentées
This commit is contained in:
dahoud
2026-04-12 15:04:23 +00:00
parent 2ed890803c
commit 8ab1513bf5
35 changed files with 5594 additions and 19 deletions

View File

@@ -6,6 +6,7 @@ import jakarta.ws.rs.core.UriInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -84,5 +85,94 @@ class DevSecurityContextProducerTest {
verify(requestContext, times(1)).setSecurityContext(any(SecurityContext.class));
}
@Test
void testDevSecurityContext_GetUserPrincipal() throws Exception {
setField("profile", "dev");
setField("oidcEnabled", false);
when(requestContext.getUriInfo()).thenReturn(uriInfo);
when(uriInfo.getPath()).thenReturn("/api/test");
when(requestContext.getSecurityContext()).thenReturn(originalSecurityContext);
ArgumentCaptor<SecurityContext> captor = ArgumentCaptor.forClass(SecurityContext.class);
producer.filter(requestContext);
verify(requestContext).setSecurityContext(captor.capture());
SecurityContext devCtx = captor.getValue();
assertNotNull(devCtx.getUserPrincipal());
assertEquals("dev-user", devCtx.getUserPrincipal().getName());
}
@Test
void testDevSecurityContext_IsUserInRole() throws Exception {
setField("profile", "dev");
setField("oidcEnabled", false);
when(requestContext.getUriInfo()).thenReturn(uriInfo);
when(uriInfo.getPath()).thenReturn("/api/test");
when(requestContext.getSecurityContext()).thenReturn(originalSecurityContext);
ArgumentCaptor<SecurityContext> captor = ArgumentCaptor.forClass(SecurityContext.class);
producer.filter(requestContext);
verify(requestContext).setSecurityContext(captor.capture());
SecurityContext devCtx = captor.getValue();
assertTrue(devCtx.isUserInRole("admin"));
assertTrue(devCtx.isUserInRole("user_manager"));
assertTrue(devCtx.isUserInRole("any_role"));
}
@Test
void testDevSecurityContext_IsSecure_WithOriginal() throws Exception {
setField("profile", "dev");
setField("oidcEnabled", false);
when(requestContext.getUriInfo()).thenReturn(uriInfo);
when(uriInfo.getPath()).thenReturn("/api/test");
when(requestContext.getSecurityContext()).thenReturn(originalSecurityContext);
when(originalSecurityContext.isSecure()).thenReturn(true);
ArgumentCaptor<SecurityContext> captor = ArgumentCaptor.forClass(SecurityContext.class);
producer.filter(requestContext);
verify(requestContext).setSecurityContext(captor.capture());
SecurityContext devCtx = captor.getValue();
assertTrue(devCtx.isSecure()); // delegates to original which returns true
}
@Test
void testDevSecurityContext_IsSecure_WithNullOriginal() throws Exception {
setField("profile", "dev");
setField("oidcEnabled", false);
when(requestContext.getUriInfo()).thenReturn(uriInfo);
when(uriInfo.getPath()).thenReturn("/api/test");
when(requestContext.getSecurityContext()).thenReturn(null); // null original
ArgumentCaptor<SecurityContext> captor = ArgumentCaptor.forClass(SecurityContext.class);
producer.filter(requestContext);
verify(requestContext).setSecurityContext(captor.capture());
SecurityContext devCtx = captor.getValue();
assertFalse(devCtx.isSecure()); // original is null → returns false
}
@Test
void testDevSecurityContext_GetAuthenticationScheme() throws Exception {
setField("profile", "dev");
setField("oidcEnabled", false);
when(requestContext.getUriInfo()).thenReturn(uriInfo);
when(uriInfo.getPath()).thenReturn("/api/test");
when(requestContext.getSecurityContext()).thenReturn(originalSecurityContext);
ArgumentCaptor<SecurityContext> captor = ArgumentCaptor.forClass(SecurityContext.class);
producer.filter(requestContext);
verify(requestContext).setSecurityContext(captor.capture());
SecurityContext devCtx = captor.getValue();
assertEquals("DEV", devCtx.getAuthenticationScheme());
}
}