fix(security): audit RBAC complet v3.0 — rôles normalisés, lifecycle, changement mdp mobile

RBAC:
- HealthResource: @PermitAll
- RoleResource: @RolesAllowed ADMIN/SUPER_ADMIN/ADMIN_ORGANISATION class-level
- PropositionAideResource: @RolesAllowed MEMBRE/USER class-level
- AuthCallbackResource: @PermitAll
- EvenementResource: @PermitAll /publics et /test, count restreint
- BackupResource/LogsMonitoringResource/SystemResource: MODERATOR → MODERATEUR
- AnalyticsResource: MANAGER/MEMBER → ADMIN_ORGANISATION/MEMBRE
- RoleConstant.java: constantes de rôles centralisées

Cycle de vie membres:
- MemberLifecycleService: ajouterMembre()/retirerMembre() sur activation/radiation/archivage
- MembreResource: endpoint GET /numero/{numeroMembre}
- MembreService: méthode trouverParNumeroMembre()

Changement mot de passe:
- CompteAdherentResource: endpoint POST /auth/change-password (mobile)
- MembreKeycloakSyncService: changerMotDePasseDirectKeycloak() via API Admin Keycloak directe
- Fallback automatique si lions-user-manager indisponible

Workflow:
- Flyway V17-V23: rôles, types org, formules Option C, lifecycle columns, bareme cotisation
- Nouvelles classes: MemberLifecycleService, OrganisationModuleService, scheduler
- Security: OrganisationContextFilter, OrganisationContextHolder, ModuleAccessFilter
This commit is contained in:
dahoud
2026-04-07 20:52:26 +00:00
parent c74ae25ad6
commit a2dfae9a0b
78 changed files with 5637 additions and 271 deletions

View File

@@ -421,6 +421,55 @@ public class MembreResource {
}
}
/**
* Liste tous les membres actifs (statut compte = ACTIF).
* Utilisé notamment pour la création de campagnes de cotisations.
*/
@GET
@Path("/actifs")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TRESORIER" })
@Operation(summary = "Membres actifs", description = "Liste tous les membres dont le compte est actif")
@APIResponse(responseCode = "200", description = "Liste des membres actifs")
public Response getMembresActifs() {
try {
LOG.info("GET /api/membres/actifs");
List<Membre> membres = membreService.listerMembresActifs();
List<MembreResponse> membresDTO = membreService.convertToResponseList(membres);
return Response.ok(membresDTO).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur récupération membres actifs");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", e.getMessage())).build();
}
}
/**
* Liste les membres d'une organisation spécifique (statut ACTIF dans l'organisation).
* Utilisé pour la création de campagnes ciblées.
*/
@GET
@Path("/organisation/{organisationId}")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TRESORIER" })
@Operation(summary = "Membres d'une organisation", description = "Liste les membres actifs d'une organisation")
@APIResponse(responseCode = "200", description = "Liste des membres")
public Response getMembresParOrganisation(
@Parameter(description = "UUID de l'organisation") @PathParam("organisationId") UUID organisationId) {
try {
LOG.infof("GET /api/membres/organisation/%s", organisationId);
List<dev.lions.unionflow.server.entity.MembreOrganisation> liens =
membreOrgRepository.findMembresActifsParOrganisation(organisationId);
List<MembreResponse> membresDTO = liens.stream()
.filter(mo -> mo.getMembre() != null)
.map(mo -> membreService.convertToResponse(mo.getMembre()))
.collect(java.util.stream.Collectors.toList());
return Response.ok(membresDTO).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur récupération membres organisation %s", organisationId);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", e.getMessage())).build();
}
}
@GET
@Path("/recherche")
@Operation(summary = "Rechercher des membres par nom ou prénom")
@@ -1248,6 +1297,23 @@ public class MembreResource {
return Response.ok(Map.of("statut", updated.getStatutMembre())).build();
}
/**
* Trouve un membre par son numéro de membre (ex: MBR-0001).
* Utilisé notamment pour la recherche de parrain lors de l'inscription.
*/
@GET
@Path("/numero/{numeroMembre}")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "MEMBRE", "USER" })
@Operation(summary = "Trouver un membre par son numéro")
@APIResponse(responseCode = "200", description = "Membre trouvé")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
public Response obtenirParNumero(@PathParam("numeroMembre") String numeroMembre) {
LOG.infof("GET /api/membres/numero/%s", numeroMembre);
Membre membre = membreService.trouverParNumeroMembre(numeroMembre)
.orElseThrow(() -> new NotFoundException("Membre non trouvé avec le numéro: " + numeroMembre));
return Response.ok(membreService.convertToResponse(membre)).build();
}
/** Résout l'UUID de l'admin connecté depuis le JWT subject. */
private UUID resolveCurrentAdminId() {
try {