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

@@ -149,6 +149,62 @@ public class WaveCheckoutService {
return HexFormat.of().formatHex(hash);
}
/**
* Interroge l'état d'une session Wave Checkout (spec : GET /v1/checkout/sessions/:id).
* Utilisé par le polling web pour détecter automatiquement la complétion du paiement.
*
* @param sessionId ID de session Wave (cos-xxx)
* @return statut de la session (checkout_status, payment_status, transaction_id)
*/
public WaveSessionStatusResponse getSession(String sessionId) throws WaveCheckoutException {
boolean useMock = mockEnabled || apiKey == null || apiKey.trim().isBlank();
if (useMock) {
// En mock, on ne peut pas vraiment vérifier — retourner EN_COURS (polling s'arrête via /web-success)
LOG.warnf("Wave getSession en mode MOCK — session %s", sessionId);
return new WaveSessionStatusResponse(sessionId, "open", "processing", null);
}
String base = (baseUrl == null || baseUrl.endsWith("/")) ? baseUrl.replaceAll("/+$", "") : baseUrl;
if (!base.endsWith("/v1")) base = base + "/v1";
String url = base + "/checkout/sessions/" + sessionId;
try {
long timestamp = System.currentTimeMillis() / 1000;
java.net.http.HttpRequest.Builder requestBuilder = java.net.http.HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(15))
.GET();
if (signingSecret != null && !signingSecret.trim().isBlank()) {
String sig = computeWaveSignature(timestamp, "");
requestBuilder.header("Wave-Signature", "t=" + timestamp + ",v1=" + sig);
}
java.net.http.HttpClient client = java.net.http.HttpClient.newBuilder().build();
java.net.http.HttpResponse<String> response = client.send(
requestBuilder.build(),
java.net.http.HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() >= 400) {
throw new WaveCheckoutException("Wave API: " + response.statusCode() + " " + response.body());
}
JsonNode root = objectMapper.readTree(response.body());
String checkoutStatus = root.has("checkout_status") ? root.get("checkout_status").asText() : null;
String paymentStatus = root.has("payment_status") ? root.get("payment_status").asText() : null;
String transactionId = root.has("transaction_id") ? root.get("transaction_id").asText() : null;
return new WaveSessionStatusResponse(sessionId, checkoutStatus, paymentStatus, transactionId);
} catch (WaveCheckoutException e) {
throw e;
} catch (Exception e) {
LOG.error(e.getMessage(), e);
throw new WaveCheckoutException("Erreur vérification session Wave: " + e.getMessage(), e);
}
}
public String getRedirectBaseUrl() {
return (redirectBaseUrl == null || redirectBaseUrl.trim().isBlank()) ? "http://localhost:8080" : redirectBaseUrl.trim();
}
@@ -159,6 +215,31 @@ public class WaveCheckoutService {
return new WaveCheckoutSessionResponse(mockId, successUrl);
}
public static final class WaveSessionStatusResponse {
public final String sessionId;
/** "open" | "complete" | "expired" */
public final String checkoutStatus;
/** "processing" | "cancelled" | "succeeded" */
public final String paymentStatus;
/** ID transaction Wave (TCN...) — non-null si succeeded */
public final String transactionId;
public WaveSessionStatusResponse(String sessionId, String checkoutStatus, String paymentStatus, String transactionId) {
this.sessionId = sessionId;
this.checkoutStatus = checkoutStatus;
this.paymentStatus = paymentStatus;
this.transactionId = transactionId;
}
public boolean isSucceeded() {
return "succeeded".equals(paymentStatus) && "complete".equals(checkoutStatus);
}
public boolean isExpired() {
return "expired".equals(checkoutStatus);
}
}
public static final class WaveCheckoutSessionResponse {
public final String id;
public final String waveLaunchUrl;