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:
@@ -0,0 +1,157 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service de gestion des modules actifs par organisation.
|
||||
*
|
||||
* <p>Architecture Option C — les modules actifs sont déterminés par le TYPE
|
||||
* d'organisation, pas par le plan tarifaire. Le plan impacte uniquement la
|
||||
* profondeur fonctionnelle (reporting, API, fédération).
|
||||
*
|
||||
* <p>Les modules sont regroupés en deux catégories :
|
||||
* <ul>
|
||||
* <li>MODULES_COMMUNS — accessibles à tous les types d'org</li>
|
||||
* <li>Modules métier — spécifiques au type d'org (TONTINE, CREDIT, etc.)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class OrganisationModuleService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(OrganisationModuleService.class);
|
||||
|
||||
/** Modules présents sur toutes les organisations quelle que soit leur nature. */
|
||||
public static final Set<String> MODULES_COMMUNS = Set.of(
|
||||
"MEMBRES",
|
||||
"COTISATIONS",
|
||||
"EVENEMENTS",
|
||||
"COMMUNICATION",
|
||||
"DOCUMENTS",
|
||||
"NOTIFICATION",
|
||||
"AIDE"
|
||||
);
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
/**
|
||||
* Retourne l'ensemble des modules actifs pour une organisation donnée.
|
||||
* Combine les modules communs avec les modules métier du type d'org.
|
||||
*/
|
||||
public Set<String> getModulesActifs(UUID organisationId) {
|
||||
Optional<Organisation> opt = organisationRepository.findByIdOptional(organisationId);
|
||||
if (opt.isEmpty()) {
|
||||
LOG.warnf("Organisation introuvable : %s", organisationId);
|
||||
return MODULES_COMMUNS;
|
||||
}
|
||||
return getModulesActifs(opt.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'ensemble des modules actifs pour une organisation.
|
||||
*/
|
||||
public Set<String> getModulesActifs(Organisation organisation) {
|
||||
Set<String> modules = new LinkedHashSet<>(MODULES_COMMUNS);
|
||||
|
||||
// 1. Modules issus du champ modulesActifs persisté (calculé depuis types_reference en V18)
|
||||
String modulesActifsCsv = organisation.getModulesActifs();
|
||||
if (modulesActifsCsv != null && !modulesActifsCsv.isBlank()) {
|
||||
Arrays.stream(modulesActifsCsv.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.forEach(modules::add);
|
||||
return modules;
|
||||
}
|
||||
|
||||
// 2. Fallback : déduction depuis le typeOrganisation si modulesActifs non renseigné
|
||||
modules.addAll(getModulesParType(organisation.getTypeOrganisation()));
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un module spécifique est actif pour une organisation.
|
||||
*/
|
||||
public boolean isModuleActif(UUID organisationId, String module) {
|
||||
return getModulesActifs(organisationId).contains(module.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne les modules métier associés à un type d'organisation.
|
||||
* Utilisé en fallback si la colonne modules_actifs n'est pas peuplée.
|
||||
*/
|
||||
public Set<String> getModulesParType(String typeOrganisation) {
|
||||
if (typeOrganisation == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> modules = new LinkedHashSet<>();
|
||||
switch (typeOrganisation.toUpperCase()) {
|
||||
case "TONTINE" -> {
|
||||
modules.add("TONTINE");
|
||||
modules.add("FINANCE");
|
||||
}
|
||||
case "MUTUELLE_EPARGNE" -> {
|
||||
modules.add("EPARGNE");
|
||||
modules.add("FINANCE");
|
||||
modules.add("LCB_FT");
|
||||
}
|
||||
case "MUTUELLE_CREDIT" -> {
|
||||
modules.add("EPARGNE");
|
||||
modules.add("CREDIT");
|
||||
modules.add("FINANCE");
|
||||
modules.add("LCB_FT");
|
||||
}
|
||||
case "COOPERATIVE" -> {
|
||||
modules.add("AGRICULTURE");
|
||||
modules.add("FINANCE");
|
||||
}
|
||||
case "ONG", "FONDATION" -> {
|
||||
modules.add("PROJETS_ONG");
|
||||
modules.add("COLLECTE_FONDS");
|
||||
modules.add("FINANCE");
|
||||
}
|
||||
case "EGLISE", "GROUPE_PRIERE" -> {
|
||||
modules.add("CULTE_DONS");
|
||||
}
|
||||
case "SYNDICAT", "ORDRE_PROFESSIONNEL", "FEDERATION" -> {
|
||||
modules.add("VOTES");
|
||||
modules.add("REGISTRE_AGREMENT");
|
||||
}
|
||||
case "GIE" -> {
|
||||
modules.add("FINANCE");
|
||||
}
|
||||
case "ASSOCIATION", "CLUB_SERVICE", "CLUB_SPORTIF", "CLUB_CULTUREL" -> {
|
||||
modules.add("VOTES");
|
||||
}
|
||||
default -> LOG.debugf("Type d''organisation non reconnu pour module mapping : %s", typeOrganisation);
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la liste des modules actifs sous forme de tableau JSON-friendly.
|
||||
* Utilisé par l'endpoint /api/organisations/{id}/modules-actifs
|
||||
*/
|
||||
public ModulesActifsResponse getModulesActifsResponse(UUID organisationId) {
|
||||
Optional<Organisation> opt = organisationRepository.findByIdOptional(organisationId);
|
||||
if (opt.isEmpty()) {
|
||||
return new ModulesActifsResponse(organisationId, Collections.emptySet(), "UNKNOWN");
|
||||
}
|
||||
Organisation org = opt.get();
|
||||
Set<String> modules = getModulesActifs(org);
|
||||
return new ModulesActifsResponse(organisationId, modules, org.getTypeOrganisation());
|
||||
}
|
||||
|
||||
/** DTO de réponse pour l'endpoint modules-actifs. */
|
||||
public record ModulesActifsResponse(UUID organisationId, Set<String> modules, String typeOrganisation) {}
|
||||
}
|
||||
Reference in New Issue
Block a user