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.java — findByRoleIds(Set<UUID>) → List<Permission>
- RoleRepository.java — findByTenantIdAndCode(UUID, String) → Optional<Role>
- UserTenantMembershipRepository.java — findByUserIdAndTenantId(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 Eventsdocs/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