Aller au contenu

Story CP-014 : Résolution des Permissions + Contrôle d'Accès aux Modules

Module : control-plane Slice : Slice 3 from architecture (split from CP-003) Brand context : [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority : 3 Depends on : CP-003 Estimated complexity : M


Objectif

Implémenter le moteur de résolution des permissions : résoudre les permissions effectives d'un utilisateur en croisant ses permissions basées sur les rôles avec les modules actifs du tenant, et en filtrant les permissions d'écriture pour les tenants suspendus. Il s'agit du "cerveau" backend uniquement qui contrôle chaque appel API. Fournit l'endpoint GET /api/v1/auth/my-permissions et le PermissionService + PermissionCacheService utilisés par tous les contrôles d'autorisation.


Périmètre Backend

Entités & Records

Cette story consomme les entités définies dans CP-003 (User, UserTenantMembership, MembershipRole, Role, Permission, RolePermission). Aucune nouvelle entité n'est créée.

Entité Permission (définie dans CP-003, référence) :

Champ Type Contraintes
id UUID v7 PK
code String NOT NULL, unique (ex. "cp.employee.create")
module_code String NOT NULL (ex. "CP", "ABSMGT")
description String NOT NULL

Format de permission : {module_code}.{entity}.{action} (ex. cp.employee.create, absence.leave_request.approve)

DTOs :

public record MyPermissionsResponse(List<String> permissions) {}
// e.g., ["cp.employee.create", "cp.employee.read", "absence.leave_request.create"]

Couche Repository

Utilise les repositories de CP-003 : - PermissionRepository.javafindByRoleIds(Set<UUID>) → List<Permission> - RoleRepository.javafindByTenantIdAndCode(UUID, String) → Optional<Role> - UserTenantMembershipRepository.javafindByUserIdAndTenantId(UUID, UUID) → Optional<UserTenantMembership>

Couche Service

PermissionService.java : - getPermissionsForUser(UUID userId, UUID tenantId) → List<String> — Résout les permissions : 1. Obtenir les rôles de l'utilisateur pour ce tenant (depuis l'adhésion) 2. Obtenir toutes les permissions pour ces rôles 3. Filtrer : inclure uniquement les permissions dont le module correspondant est ACTIVE pour le tenant (module gating — dépend de CP-007, stubé initialement) 4. Filtrer : si le tenant est SUSPENDED, supprimer toutes les permissions d'écriture

PermissionCacheService.java : - Cache Caffeine : clé = userId:tenantId, valeur = List<String> permissions - TTL : 5 minutes - Invalidé lors de : changement de rôle, activation/désactivation de module, changement de statut tenant - Invalidation du cache via Spring Application Events

Endpoints API

Méthode Chemin Corps de requête Réponse Auth
GET /api/v1/auth/my-permissions MyPermissionsResponse Tout utilisateur authentifié

Règles de validation

  • L'utilisateur doit avoir une adhésion active dans le tenant courant
  • Module gating : les permissions pour les modules inactifs sont silencieusement exclues (pas d'erreur, juste absentes)
  • Tenant SUSPENDED : permissions d'écriture (.create, .update, .delete) supprimées ; permissions de lecture (.read, *.list) conservées

Considérations multi-tenant

  • La résolution des permissions se produit toujours dans le contexte d'un tenant spécifique (depuis TenantContext)
  • Un utilisateur peut avoir des rôles différents (et donc des permissions différentes) dans des tenants différents
  • Le statut d'activation des modules est par tenant (depuis CP-007 — stubé avec tout-actif jusqu'à l'implémentation de CP-007)

Périmètre Frontend

Aucun — cette story est uniquement backend. Le frontend consomme my-permissions via le contexte auth établi dans CP-001.


Critères d'acceptation

AC-020: Get my permissions (module-gated)
Given I have role EMPLOYE and my tenant has modules ABSMGT=ACTIVE and PAYROL=INACTIVE
When I call GET /api/v1/auth/my-permissions
Then I receive permissions for ABSMGT (e.g., "absence.leave_request.create")
And I do NOT receive permissions for PAYROL

AC-021: Permission denied for suspended tenant writes
Given my tenant is SUSPENDED
When I call GET /api/v1/auth/my-permissions
Then all write permissions are removed
And read permissions remain

AC-022: Assign role with inactive module permissions
Given I assign role RH_MANAGER which includes permissions for PAYROL (inactive module)
When the role is saved
Then the PAYROL permissions are silently ignored (not shown in my-permissions, 403 on API calls)

Règles OHADA & Réglementaires

  • Loi 2013-450 (Art. 36) : Les données de permission déterminent l'accès aux données personnelles. Le moteur de résolution des permissions applique le principe du moindre privilège — les utilisateurs ne voient que les données des modules que leur tenant a activés.

Standards & Conventions

  • docs/standards/java-spring-guidelines.md — §Couche Service, §Cache, §Application Events
  • docs/standards/api-guidelines.md — §Endpoints d'authentification, §Enveloppe standard

Exigences de test

Tests unitaires

  • PermissionService : résolution rôle → permissions avec module gating
  • PermissionService : tenant SUSPENDED → permissions d'écriture supprimées, lecture conservée
  • PermissionService : rôles multiples → permissions fusionnées (union)
  • PermissionCacheService : succès de cache retourne la valeur en cache
  • PermissionCacheService : échec de cache déclenche la résolution
  • PermissionCacheService : invalidation lors d'un événement de changement de rôle
  • PermissionCacheService : invalidation lors d'un événement d'activation/désactivation de module
  • PermissionCacheService : invalidation lors d'un événement de changement de statut tenant

Tests d'intégration

  • GET /api/v1/auth/my-permissions — avec le rôle EMPLOYE et des états de modules mixtes
  • GET /api/v1/auth/my-permissions — avec un tenant SUSPENDED
  • GET /api/v1/auth/my-permissions — utilisateur sans adhésion → 403
  • Comportement du cache : second appel dans le TTL retourne le résultat en cache

Ce que le QA validera

  • L'endpoint de permissions retourne les permissions correctes pour diverses combinaisons rôle/module
  • Le tenant suspendu supprime correctement les permissions d'écriture
  • Les permissions de modules inactifs sont silencieusement exclues

Hors périmètre

  • Rôles personnalisés — V2. Le MVP n'a que 5 rôles par défaut fixes.
  • UI des permissions (visualisation/édition des permissions dans l'admin) — V2
  • Activation/désactivation de modules — CP-007 (stubé ici)
  • Changement de tenant — CP-011

Définition de Done

  • [ ] PermissionService résout les permissions correctement avec le module gating
  • [ ] PermissionCacheService met en cache et invalide correctement
  • [ ] L'endpoint GET /api/v1/auth/my-permissions retourne les données correctes
  • [ ] Tous les critères d'acceptation passent manuellement
  • [ ] Tests unitaires écrits et passants
  • [ ] Tests d'intégration écrits et passants
  • [ ] Module gating stubé (tous les modules ACTIVE) jusqu'à CP-007
  • [ ] Pas de types Java bruts — tous les génériques explicites
  • [ ] Les réponses API respectent le format d'enveloppe standard