From a2dfae9a0bdc04a0bc8f08cf6d6a9963417cc4e2 Mon Sep 17 00:00:00 2001
From: dahoud <41957584+DahoudG@users.noreply.github.com>
Date: Tue, 7 Apr 2026 20:52:26 +0000
Subject: [PATCH] =?UTF-8?q?fix(security):=20audit=20RBAC=20complet=20v3.0?=
=?UTF-8?q?=20=E2=80=94=20r=C3=B4les=20normalis=C3=A9s,=20lifecycle,=20cha?=
=?UTF-8?q?ngement=20mdp=20mobile?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
---
.../server/auth/AuthCallbackResource.java | 2 +
.../server/entity/BaremeCotisationRole.java | 62 ++
.../ParametresCotisationOrganisation.java | 9 +
.../BaremeCotisationRoleRepository.java | 20 +
.../repository/CotisationRepository.java | 18 +
.../server/repository/DocumentRepository.java | 10 +
...etresCotisationOrganisationRepository.java | 22 +
.../server/resource/AnalyticsResource.java | 16 +-
.../server/resource/ApprovalResource.java | 34 +-
.../server/resource/BackupResource.java | 6 +-
.../server/resource/BudgetResource.java | 32 +-
.../server/resource/ComptabiliteResource.java | 42 +-
.../resource/CompteAdherentResource.java | 51 ++
.../server/resource/CotisationResource.java | 32 +-
.../server/resource/DocumentResource.java | 53 +-
.../server/resource/EvenementResource.java | 5 +
.../server/resource/HealthResource.java | 2 +
.../resource/LogsMonitoringResource.java | 10 +-
.../server/resource/MembreResource.java | 66 ++
.../server/resource/NotificationResource.java | 44 +-
.../server/resource/PaiementResource.java | 18 +
.../resource/PropositionAideResource.java | 2 +
.../server/resource/RoleResource.java | 2 +
.../server/resource/SouscriptionResource.java | 31 +
.../server/resource/SystemResource.java | 6 +-
.../server/resource/WaveRedirectResource.java | 97 ++-
.../server/resource/WaveResource.java | 44 +-
.../scheduler/MemberLifecycleScheduler.java | 63 ++
.../server/security/ModuleAccessFilter.java | 106 +++
.../security/OrganisationContextFilter.java | 141 ++++
.../security/OrganisationContextHolder.java | 61 ++
.../server/security/RequiresModule.java | 42 +
.../server/security/RoleConstant.java | 42 +
.../CotisationAutoGenerationService.java | 154 ++++
.../server/service/CotisationService.java | 33 +-
.../server/service/DemandeAideService.java | 16 +-
.../server/service/DocumentService.java | 13 +
.../service/MemberLifecycleService.java | 319 ++++++++
.../service/MembreKeycloakSyncService.java | 94 ++-
.../server/service/MembreService.java | 10 +-
.../service/OrganisationModuleService.java | 157 ++++
.../server/service/PaiementService.java | 113 ++-
.../server/service/SouscriptionService.java | 35 +
.../server/service/SystemMetricsService.java | 29 +-
.../server/service/WaveCheckoutService.java | 81 ++
src/main/resources/application-dev.properties | 4 +-
...d_Role_Categories_And_Functional_Roles.sql | 144 ++++
...Categorie_Type_And_Seed_Official_Types.sql | 135 ++++
...uleAbonnement_Option_C_And_Org_Statuts.sql | 180 +++++
...atutMembre_And_Seed_Atomic_Permissions.sql | 149 ++++
.../V21__Add_Member_Lifecycle_Columns.sql | 26 +
...__Bareme_Cotisation_Et_Auto_Generation.sql | 31 +
...ix_SystemAlerts_Legacy_NotNull_Columns.sql | 13 +
.../entity/SouscriptionQuotaOptionCTest.java | 331 ++++++++
.../resource/CompteAdherentResourceTest.java | 58 ++
.../resource/CotisationResourceMockTest.java | 31 +-
.../DashboardWebSocketEndpointTest.java | 31 +
.../resource/DemandeAideMockResourceTest.java | 54 ++
.../server/resource/ExportResourceTest.java | 41 +
.../resource/FinanceWorkflowResourceTest.java | 48 ++
.../server/resource/HealthResourceTest.java | 34 +
.../LogsMonitoringResourceCoverageTest.java | 42 +
.../MembreDashboardMockResourceTest.java | 39 +
.../MembreResourceLifecycleRbacTest.java | 371 +++++++++
.../OrganisationResourceLambdaFilterTest.java | 43 ++
...ganisationResourceMissingBranchesTest.java | 46 ++
.../resource/PreferencesResourceTest.java | 42 +
.../PropositionAideMockResourceTest.java | 60 ++
.../SouscriptionResourceMockTest.java | 720 ++++++++++++++++++
...eRedirectResourceMockDisabledUnitTest.java | 48 ++
.../credit/DemandeCreditMockResourceTest.java | 57 ++
.../TransactionEpargneResourceMockTest.java | 65 ++
...OrganisationContextFilterMultiOrgTest.java | 217 ++++++
.../OrganisationContextHolderTest.java | 85 +++
.../CotisationServiceAdminBranchesTest.java | 42 +-
.../CotisationServiceFinalBranchesTest.java | 5 +-
.../server/service/CotisationServiceTest.java | 18 +-
.../service/MemberLifecycleServiceTest.java | 453 +++++++++++
78 files changed, 5637 insertions(+), 271 deletions(-)
create mode 100644 src/main/java/dev/lions/unionflow/server/entity/BaremeCotisationRole.java
create mode 100644 src/main/java/dev/lions/unionflow/server/repository/BaremeCotisationRoleRepository.java
create mode 100644 src/main/java/dev/lions/unionflow/server/repository/ParametresCotisationOrganisationRepository.java
create mode 100644 src/main/java/dev/lions/unionflow/server/scheduler/MemberLifecycleScheduler.java
create mode 100644 src/main/java/dev/lions/unionflow/server/security/ModuleAccessFilter.java
create mode 100644 src/main/java/dev/lions/unionflow/server/security/OrganisationContextFilter.java
create mode 100644 src/main/java/dev/lions/unionflow/server/security/OrganisationContextHolder.java
create mode 100644 src/main/java/dev/lions/unionflow/server/security/RequiresModule.java
create mode 100644 src/main/java/dev/lions/unionflow/server/security/RoleConstant.java
create mode 100644 src/main/java/dev/lions/unionflow/server/service/CotisationAutoGenerationService.java
create mode 100644 src/main/java/dev/lions/unionflow/server/service/MemberLifecycleService.java
create mode 100644 src/main/java/dev/lions/unionflow/server/service/OrganisationModuleService.java
create mode 100644 src/main/resources/db/migration/V17__Add_Role_Categories_And_Functional_Roles.sql
create mode 100644 src/main/resources/db/migration/V18__Add_Organisation_Categorie_Type_And_Seed_Official_Types.sql
create mode 100644 src/main/resources/db/migration/V19__FormuleAbonnement_Option_C_And_Org_Statuts.sql
create mode 100644 src/main/resources/db/migration/V20__Enrich_StatutMembre_And_Seed_Atomic_Permissions.sql
create mode 100644 src/main/resources/db/migration/V21__Add_Member_Lifecycle_Columns.sql
create mode 100644 src/main/resources/db/migration/V22__Bareme_Cotisation_Et_Auto_Generation.sql
create mode 100644 src/main/resources/db/migration/V23__Fix_SystemAlerts_Legacy_NotNull_Columns.sql
create mode 100644 src/test/java/dev/lions/unionflow/server/entity/SouscriptionQuotaOptionCTest.java
create mode 100644 src/test/java/dev/lions/unionflow/server/resource/MembreResourceLifecycleRbacTest.java
create mode 100644 src/test/java/dev/lions/unionflow/server/resource/SouscriptionResourceMockTest.java
create mode 100644 src/test/java/dev/lions/unionflow/server/security/OrganisationContextFilterMultiOrgTest.java
create mode 100644 src/test/java/dev/lions/unionflow/server/security/OrganisationContextHolderTest.java
create mode 100644 src/test/java/dev/lions/unionflow/server/service/MemberLifecycleServiceTest.java
diff --git a/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
index 08e310e..f21a671 100644
--- a/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
+++ b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java
@@ -1,5 +1,6 @@
package de.lions.unionflow.server.auth;
+import io.quarkus.security.PermitAll;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@@ -11,6 +12,7 @@ import org.jboss.logging.Logger;
* mobile.
*/
@Path("/auth")
+@PermitAll
public class AuthCallbackResource {
private static final Logger log = Logger.getLogger(AuthCallbackResource.class);
diff --git a/src/main/java/dev/lions/unionflow/server/entity/BaremeCotisationRole.java b/src/main/java/dev/lions/unionflow/server/entity/BaremeCotisationRole.java
new file mode 100644
index 0000000..0af03c1
--- /dev/null
+++ b/src/main/java/dev/lions/unionflow/server/entity/BaremeCotisationRole.java
@@ -0,0 +1,62 @@
+package dev.lions.unionflow.server.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import lombok.*;
+
+/**
+ * Barème de cotisation par rôle fonctionnel au sein d'une organisation.
+ *
+ *
Permet de définir des montants différenciés selon le rôle du membre
+ * (PRESIDENT, TRESORIER, MEMBRE_ORDINAIRE, etc.).
+ *
+ *
Si aucun barème n'est défini pour un rôle donné, le système utilise
+ * le montant par défaut de {@link ParametresCotisationOrganisation}.
+ *
+ *
Table : {@code bareme_cotisation_role}
+ */
+@Entity
+@Table(
+ name = "bareme_cotisation_role",
+ uniqueConstraints = @UniqueConstraint(
+ name = "uq_bareme_cot_org_role",
+ columnNames = {"organisation_id", "role_org"}
+ )
+)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode(callSuper = true)
+public class BaremeCotisationRole extends BaseEntity {
+
+ @NotNull
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "organisation_id", nullable = false)
+ private Organisation organisation;
+
+ /**
+ * Rôle fonctionnel dans l'organisation (ex: PRESIDENT, TRESORIER, SECRETAIRE, MEMBRE_ORDINAIRE).
+ * Correspond à {@link dev.lions.unionflow.server.entity.MembreOrganisation#getRoleOrg()}.
+ */
+ @NotBlank
+ @Column(name = "role_org", nullable = false, length = 50)
+ private String roleOrg;
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_mensuel", nullable = false, precision = 12, scale = 2)
+ private BigDecimal montantMensuel = BigDecimal.ZERO;
+
+ @Builder.Default
+ @DecimalMin("0.00")
+ @Digits(integer = 10, fraction = 2)
+ @Column(name = "montant_annuel", nullable = false, precision = 12, scale = 2)
+ private BigDecimal montantAnnuel = BigDecimal.ZERO;
+
+ /** Description optionnelle du barème (ex: "Taux réduit bureau exécutif"). */
+ @Column(name = "description", length = 255)
+ private String description;
+}
diff --git a/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java b/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
index d58f375..bd314eb 100644
--- a/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
+++ b/src/main/java/dev/lions/unionflow/server/entity/ParametresCotisationOrganisation.java
@@ -73,6 +73,15 @@ public class ParametresCotisationOrganisation extends BaseEntity {
@Column(name = "cotisation_obligatoire", nullable = false)
private Boolean cotisationObligatoire = true;
+ /**
+ * Active la génération automatique mensuelle des cotisations pour cette organisation.
+ * Quand {@code true}, un job planifié crée automatiquement une cotisation par membre actif
+ * le 1er de chaque mois, en utilisant les barèmes par rôle ou le montant par défaut.
+ */
+ @Builder.Default
+ @Column(name = "generation_automatique_activee", nullable = false)
+ private Boolean generationAutomatiqueActivee = false;
+
// ── Méthodes métier ────────────────────────────────────────────────────────
/**
diff --git a/src/main/java/dev/lions/unionflow/server/repository/BaremeCotisationRoleRepository.java b/src/main/java/dev/lions/unionflow/server/repository/BaremeCotisationRoleRepository.java
new file mode 100644
index 0000000..03e6111
--- /dev/null
+++ b/src/main/java/dev/lions/unionflow/server/repository/BaremeCotisationRoleRepository.java
@@ -0,0 +1,20 @@
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.BaremeCotisationRole;
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+@ApplicationScoped
+public class BaremeCotisationRoleRepository implements PanacheRepositoryBase {
+
+ public Optional findByOrganisationIdAndRoleOrg(UUID organisationId, String roleOrg) {
+ return find("organisation.id = ?1 AND roleOrg = ?2", organisationId, roleOrg).firstResultOptional();
+ }
+
+ public List findByOrganisationId(UUID organisationId) {
+ return find("organisation.id = ?1", organisationId).list();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java
index 197f34c..ad37ea1 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java
@@ -601,4 +601,22 @@ public class CotisationRepository extends BaseRepository {
}
return orderBy.toString();
}
+
+ /**
+ * Vérifie si une cotisation mensuelle existe déjà pour ce membre, cette organisation,
+ * cette année et ce mois. Utilisé pour éviter les doublons lors de la génération automatique.
+ */
+ public boolean existsByMembreOrganisationAnneeAndMois(UUID membreId, UUID organisationId, int annee, int mois) {
+ Long count = entityManager.createQuery(
+ "SELECT COUNT(c) FROM Cotisation c "
+ + "WHERE c.membre.id = :membreId AND c.organisation.id = :orgId "
+ + "AND c.annee = :annee AND c.mois = :mois",
+ Long.class)
+ .setParameter("membreId", membreId)
+ .setParameter("orgId", organisationId)
+ .setParameter("annee", annee)
+ .setParameter("mois", mois)
+ .getSingleResult();
+ return count != null && count > 0;
+ }
}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java b/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java
index ba830a9..1b16a4f 100644
--- a/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java
+++ b/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java
@@ -66,6 +66,16 @@ public class DocumentRepository implements PanacheRepositoryBase
public List findAllActifs() {
return find("actif = true ORDER BY dateCreation DESC").list();
}
+
+ /**
+ * Trouve les documents créés par un utilisateur (par email)
+ *
+ * @param email Email de l'utilisateur (creePar)
+ * @return Liste des documents
+ */
+ public List findByCreePar(String email) {
+ return find("creePar = ?1 AND actif = true ORDER BY dateCreation DESC", email).list();
+ }
}
diff --git a/src/main/java/dev/lions/unionflow/server/repository/ParametresCotisationOrganisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/ParametresCotisationOrganisationRepository.java
new file mode 100644
index 0000000..5c60dd1
--- /dev/null
+++ b/src/main/java/dev/lions/unionflow/server/repository/ParametresCotisationOrganisationRepository.java
@@ -0,0 +1,22 @@
+package dev.lions.unionflow.server.repository;
+
+import dev.lions.unionflow.server.entity.ParametresCotisationOrganisation;
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+@ApplicationScoped
+public class ParametresCotisationOrganisationRepository
+ implements PanacheRepositoryBase {
+
+ public Optional findByOrganisationId(UUID organisationId) {
+ return find("organisation.id = ?1", organisationId).firstResultOptional();
+ }
+
+ /** Retourne toutes les organisations ayant activé la génération automatique. */
+ public List findAvecGenerationAutomatiqueActivee() {
+ return find("generationAutomatiqueActivee = true AND actif = true").list();
+ }
+}
diff --git a/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java b/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java
index dda8cc4..f8f7d04 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java
@@ -50,7 +50,7 @@ public class AnalyticsResource {
/** Calcule une métrique analytics pour une période donnée */
@GET
@Path("/metriques/{typeMetrique}")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer une métrique analytics",
description = "Calcule une métrique spécifique pour une période et organisation données")
@@ -88,7 +88,7 @@ public class AnalyticsResource {
/** Calcule les tendances d'un KPI sur une période */
@GET
@Path("/tendances/{typeMetrique}")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer la tendance d'un KPI",
description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée")
@@ -127,7 +127,7 @@ public class AnalyticsResource {
/** Obtient tous les KPI pour une organisation */
@GET
@Path("/kpis")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir tous les KPI",
description = "Récupère tous les KPI calculés pour une organisation et période données")
@@ -163,7 +163,7 @@ public class AnalyticsResource {
/** Calcule le KPI de performance globale */
@GET
@Path("/performance-globale")
- @RolesAllowed({"ADMIN", "MANAGER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(
summary = "Calculer la performance globale",
description = "Calcule le score de performance globale de l'organisation")
@@ -208,7 +208,7 @@ public class AnalyticsResource {
/** Obtient les évolutions des KPI par rapport à la période précédente */
@GET
@Path("/evolutions")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les évolutions des KPI",
description = "Récupère les évolutions des KPI par rapport à la période précédente")
@@ -248,7 +248,7 @@ public class AnalyticsResource {
/** Obtient les widgets du tableau de bord pour un utilisateur */
@GET
@Path("/dashboard/widgets")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les widgets du tableau de bord",
description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur")
@@ -285,7 +285,7 @@ public class AnalyticsResource {
/** Obtient les types de métriques disponibles */
@GET
@Path("/types-metriques")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les types de métriques disponibles",
description = "Récupère la liste de tous les types de métriques disponibles")
@@ -300,7 +300,7 @@ public class AnalyticsResource {
/** Obtient les périodes d'analyse disponibles */
@GET
@Path("/periodes-analyse")
- @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
+ @RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les périodes d'analyse disponibles",
description = "Récupère la liste de toutes les périodes d'analyse disponibles")
diff --git a/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java b/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java
index df2bb76..1b21d57 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/ApprovalResource.java
@@ -1,6 +1,7 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.ApprovalService;
+import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
import dev.lions.unionflow.server.api.dto.finance_workflow.request.ApproveTransactionRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.request.RejectTransactionRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.response.TransactionApprovalResponse;
@@ -53,7 +54,7 @@ public class ApprovalResource {
if (transactionId == null || transactionType == null || amount == null) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("transactionId, transactionType et amount sont requis"))
+ .entity(ErrorResponse.of("transactionId, transactionType et amount sont requis"))
.build();
}
@@ -64,12 +65,12 @@ public class ApprovalResource {
return Response.status(Response.Status.CREATED).entity(approval).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la création de la demande d'approbation", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -88,7 +89,7 @@ public class ApprovalResource {
} catch (Exception e) {
LOG.error("Erreur lors de la récupération des approbations en attente", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -106,12 +107,12 @@ public class ApprovalResource {
return Response.ok(approval).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération de l'approbation", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -131,16 +132,16 @@ public class ApprovalResource {
return Response.ok(approval).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (ForbiddenException e) {
return Response.status(Response.Status.FORBIDDEN)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de l'approbation de la transaction", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -160,16 +161,16 @@ public class ApprovalResource {
return Response.ok(approval).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (ForbiddenException e) {
return Response.status(Response.Status.FORBIDDEN)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors du rejet de la transaction", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -197,12 +198,12 @@ public class ApprovalResource {
return Response.ok(approvals).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération de l'historique", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -221,12 +222,11 @@ public class ApprovalResource {
} catch (Exception e) {
LOG.error("Erreur lors du comptage des approbations", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
- // Classes internes pour les réponses
- record ErrorResponse(String message) {}
+ // Classe interne pour le comptage
record CountResponse(long count) {}
}
diff --git a/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java b/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java
index faa65e8..5b7adac 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/BackupResource.java
@@ -36,7 +36,7 @@ public class BackupResource {
* Lister toutes les sauvegardes
*/
@GET
- @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
+ @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Lister toutes les sauvegardes disponibles")
public List getAllBackups() {
log.info("GET /api/backups");
@@ -48,7 +48,7 @@ public class BackupResource {
*/
@GET
@Path("/{id}")
- @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
+ @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer une sauvegarde par ID")
public BackupResponse getBackupById(@PathParam("id") UUID id) {
log.info("GET /api/backups/{}", id);
@@ -98,7 +98,7 @@ public class BackupResource {
*/
@GET
@Path("/config")
- @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
+ @RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration des sauvegardes automatiques")
public BackupConfigResponse getBackupConfig() {
log.info("GET /api/backups/config");
diff --git a/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java b/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java
index da8f7f3..a109c14 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/BudgetResource.java
@@ -1,6 +1,7 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.BudgetService;
+import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
import dev.lions.unionflow.server.api.dto.finance_workflow.request.CreateBudgetRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.response.BudgetResponse;
import jakarta.annotation.security.RolesAllowed;
@@ -51,12 +52,12 @@ public class BudgetResource {
return Response.ok(budgets).build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération des budgets", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -74,12 +75,12 @@ public class BudgetResource {
return Response.ok(budget).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -98,16 +99,16 @@ public class BudgetResource {
.build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la création du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -125,12 +126,12 @@ public class BudgetResource {
return Response.ok(tracking).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la récupération du suivi budgétaire", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -150,16 +151,16 @@ public class BudgetResource {
return Response.ok(budget).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la mise à jour du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
@@ -177,16 +178,15 @@ public class BudgetResource {
return Response.noContent().build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
} catch (Exception e) {
LOG.error("Erreur lors de la suppression du budget", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.of(e.getMessage()))
.build();
}
}
- // Classe interne pour les réponses d'erreur
- record ErrorResponse(String message) {}
}
+
diff --git a/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java
index 57782a0..d2c08de 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java
@@ -2,6 +2,7 @@ package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.comptabilite.request.*;
import dev.lions.unionflow.server.api.dto.comptabilite.response.*;
+import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
import dev.lions.unionflow.server.service.ComptabiliteService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
@@ -52,12 +53,12 @@ public class ComptabiliteResource {
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.ofError(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du compte comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la création du compte comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la création du compte comptable: " + e.getMessage()))
.build();
}
}
@@ -76,12 +77,12 @@ public class ComptabiliteResource {
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse("Compte comptable non trouvé"))
+ .entity(ErrorResponse.ofError("Compte comptable non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du compte comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
.build();
}
}
@@ -100,7 +101,7 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des comptes comptables");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
.build();
}
}
@@ -124,12 +125,12 @@ public class ComptabiliteResource {
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.ofError(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création du journal comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la création du journal comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la création du journal comptable: " + e.getMessage()))
.build();
}
}
@@ -148,12 +149,12 @@ public class ComptabiliteResource {
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse("Journal comptable non trouvé"))
+ .entity(ErrorResponse.ofError("Journal comptable non trouvé"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche du journal comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
.build();
}
}
@@ -172,7 +173,7 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des journaux comptables");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
.build();
}
}
@@ -196,12 +197,12 @@ public class ComptabiliteResource {
return Response.status(Response.Status.CREATED).entity(result).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse(e.getMessage()))
+ .entity(ErrorResponse.ofError(e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la création de l'écriture comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
.build();
}
}
@@ -220,12 +221,12 @@ public class ComptabiliteResource {
return Response.ok(result).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
- .entity(new ErrorResponse("Écriture comptable non trouvée"))
+ .entity(ErrorResponse.ofError("Écriture comptable non trouvée"))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la recherche de l'écriture comptable");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
.build();
}
}
@@ -245,7 +246,7 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des écritures");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des écritures: " + e.getMessage()))
.build();
}
}
@@ -265,17 +266,10 @@ public class ComptabiliteResource {
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la liste des écritures");
return Response.status(Response.Status.BAD_REQUEST)
- .entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
+ .entity(ErrorResponse.ofError("Erreur lors de la liste des écritures: " + e.getMessage()))
.build();
}
}
- /** Classe interne pour les réponses d'erreur */
- public static class ErrorResponse {
- public String error;
-
- public ErrorResponse(String error) {
- this.error = error;
- }
- }
}
+
diff --git a/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java b/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java
index ce7638a..42a5dc4 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/CompteAdherentResource.java
@@ -247,4 +247,55 @@ public class CompteAdherentResource {
membreKeycloakSyncService.changerMotDePassePremierLogin(membreOpt.get().getId(), nouveauMotDePasse);
return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
}
+
+ /**
+ * Endpoint mobile : changement de mot de passe depuis l'app Flutter.
+ * Bypass lions-user-manager — appel direct à l'API Admin Keycloak.
+ *
+ * Body attendu : {@code { "userId": "...", "oldPassword": "...", "newPassword": "..." }}
+ */
+ @POST
+ @Path("/auth/change-password")
+ @Authenticated
+ @Operation(
+ summary = "Changer le mot de passe (mobile)",
+ description = "Endpoint dédié à l'application mobile. Bypass lions-user-manager via API Admin Keycloak directe."
+ )
+ public Response changerMotDePasseMobile(Map body) {
+ String email = securiteHelper.resolveEmail();
+ if (email == null || email.isBlank()) {
+ return Response.status(Response.Status.UNAUTHORIZED).build();
+ }
+
+ String newPassword = body == null ? null : body.get("newPassword");
+ if (newPassword == null || newPassword.isBlank()) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(Map.of("message", "Le champ 'newPassword' est requis."))
+ .build();
+ }
+ if (newPassword.length() < 8) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(Map.of("message", "Le mot de passe doit contenir au moins 8 caractères."))
+ .build();
+ }
+
+ Optional membreOpt = membreRepository.findByEmail(email.trim())
+ .or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));
+
+ if (membreOpt.isEmpty()) {
+ return Response.status(Response.Status.NOT_FOUND)
+ .entity(Map.of("message", "Aucun membre trouvé pour ce compte."))
+ .build();
+ }
+
+ try {
+ membreKeycloakSyncService.changerMotDePasseDirectKeycloak(membreOpt.get().getId(), newPassword);
+ return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
+ } catch (Exception e) {
+ LOG.errorf(e, "Erreur changement mot de passe pour %s", email);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("message", "Erreur lors du changement de mot de passe: " + e.getMessage()))
+ .build();
+ }
+ }
}
diff --git a/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java b/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java
index e7bcb8f..22ee1f8 100644
--- a/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java
+++ b/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java
@@ -60,19 +60,19 @@ public class CotisationResource {
try {
log.info("GET /api/cotisations/public - page: {}, size: {}", page, size);
- List cotisations = cotisationService.getAllCotisations(page, size);
+ List cotisations = cotisationService.getAllCotisations(page, size);
List