Rôles & permissions (RBAC)¶
Ce que fait ce système¶
Jigi contrôle l'accès à chaque fonctionnalité via un modèle de rôles dynamiques et configurables, pas via une poignée de rôles fixes codés en dur. Un administrateur de société peut créer ses propres rôles et choisir exactement quelles permissions leur attribuer, en plus des rôles système fournis par défaut.
Le modèle¶
Trois tables forment le socle du contrôle d'accès :
| Table | Rôle |
|---|---|
roles |
Un rôle, propre à un tenant (tenant_id renseigné) ou global/système (tenant_id IS NULL, system_role = true). |
permissions |
Un code de permission unique, au format <module>.<action> (ex. leave.approve, accounting.expense.own). |
role_permissions |
La table d'association qui relie un rôle à l'ensemble de ses permissions. |
Un utilisateur se voit attribuer un ou plusieurs rôles ; ses permissions effectives sont l'union des permissions de tous ses rôles.
Rôles système globaux (fournis par défaut)¶
Ces rôles sont créés une fois pour la plateforme entière et disponibles à chaque nouvelle société :
| Rôle | Portée | Permissions clés |
|---|---|---|
ORG_ADMIN |
Tenant | Toutes les permissions du tenant |
HR_MANAGER |
Tenant | leave.manage, leave.approve, leave.create, users.manage, config.manage, audit.read, employee.read |
MANAGER |
Tenant | leave.approve, leave.create, employee.read |
RECRUITER |
Tenant | recruitment.requisition.create/update/delete/submit/read |
JIGI_OPERATOR |
Plateforme (cross-tenant) | platform.tenants.read, platform.payments.manage, platform.analytics.read |
JIGI_ADMIN |
Plateforme (cross-tenant) | Toutes les permissions platform.*, plus users.manage, roles.manage, audit.read |
JIGI_OPERATOR vs JIGI_ADMIN
JIGI_OPERATOR a un accès opérationnel restreint (lecture des tenants, validation des paiements, lecture des analytics). JIGI_ADMIN a un accès complet à la plateforme, y compris la gestion des comptes opérateurs eux-mêmes. Voir Guide Administrateur plateforme Jigi.
Rôles personnalisés propres à un tenant¶
Une société peut créer ses propres rôles, avec exactement les permissions dont elle a besoin. Exemple réel du module comptabilité, où une société a défini ACCOUNTANT et CFO en plus des rôles standards :
| Rôle | Permissions |
|---|---|
EMPLOYEE |
accounting.expense.own |
MANAGER |
accounting.expense.own, accounting.expense.manage |
ACCOUNTANT |
accounting.read, accounting.write, accounting.validate, accounting.expense.own, accounting.expense.manage, accounting.reconciliation.read, accounting.reconciliation.manage, accounting.declaration.read, accounting.declaration.manage, accounting.report.read, accounting.dashboard.read, accounting.budget.read, accounting.budget.manage, accounting.invoicing.read, accounting.config |
CFO |
accounting.read, accounting.declaration.read, accounting.report.read, accounting.dashboard.read, accounting.budget.read, accounting.invoicing.read |
ADMIN |
Toutes les permissions accounting.* |
Ce type de rôle métier fin (une société peut vouloir un rôle « Contrôleur de gestion » avec un accès en lecture seule à certains rapports, par exemple) se crée via Utilisateurs, rôles & permissions.
Comment une permission est vérifiée¶
Chaque endpoint sensible du backend est protégé par une annotation Spring Security :
@PreAuthorize("hasAuthority('leave.approve')")
Si l'utilisateur connecté ne possède, via aucun de ses rôles, la permission leave.approve, la requête est rejetée avec une erreur 403 Forbidden avant même d'atteindre la logique métier.
La règle absolue : une permission référencée doit être seedée¶
Une permission citée dans un @PreAuthorize(...) (ou dans la définition d'un type d'entité auditable) doit obligatoirement exister dans la table permissions, insérée par une migration Flyway. Si ce n'est pas le cas, tous les utilisateurs sont bloqués — y compris les administrateurs — car le service de permissions ne peut retourner que des codes qui existent réellement en base. Ce n'est pas un bug qui affecte seulement les utilisateurs à faibles droits : c'est un verrouillage total, immédiat, et qui compile et passe les tests de type sans erreur.
Cette classe de bug s'est déjà produite deux fois sur Jigi (tickets internes RH-297/RH-298 et RH-305), sur les permissions leave.view/booking.view puis supplies.view/accounting.view/accounting.viewPayroll. Un test automatisé (PermissionSeedConsistencyIT) scanne désormais tout le code pour détecter ce cas avant qu'il n'atteigne la production.
Détails techniques
- Table
roles: colonnesid,tenant_id(nullable),name,description,system_role(boolean). - Un index unique partiel
uq_roles_global_name ON roles (name) WHERE tenant_id IS NULLempêche les doublons parmi les rôles globaux (PostgreSQL ne traite pasNULL = NULLdans une contrainteUNIQUE(tenant_id, name)classique). - Table
permissions:code(unique, format<module>.<action>),description. - Table
role_permissions: clé composite(role_id, permission_id). - Classes clés :
app.jigi.admin.role.domain.Role,RolePermission,RoleService,RoleAdminController,UserRoleAdminController. - Migrations de référence :
V001(schéma initial),V110(ORG_ADMIN/HR_MANAGER/MANAGER),V121(JIGI_OPERATOR/JIGI_ADMIN+subscription.operator_audit_log),V203(RECRUITER),V185(ACCOUNTANT/CFO+ correction de deux permissions trop larges accordées àMANAGER). - Test de garde-fou :
backend/src/test/java/app/jigi/auth/PermissionSeedConsistencyIT.java— exécuter avec./mvnw test -Dtest=PermissionSeedConsistencyIT -Djacoco.skip=true(nécessite Docker pour Testcontainers) après toute modification touchant des permissions. - Limite connue : le test ne détecte que les chaînes littérales
hasAuthority('code')— une permission construite depuis une constante Java n'est pas détectée automatiquement et doit être vérifiée manuellement.
FAQ¶
Un administrateur de société peut-il créer un rôle qui a accès à un module désactivé pour son abonnement ? Non — l'accès aux modules dépend aussi du plan d'abonnement souscrit, indépendamment des rôles internes du tenant.
Que se passe-t-il si un utilisateur a plusieurs rôles avec des permissions qui se chevauchent ? Ses permissions effectives sont simplement l'union de toutes les permissions de tous ses rôles — il n'y a pas de conflit possible, une permission est soit accordée, soit non.
Comment savoir quelles permissions couvre un rôle donné ? Depuis l'écran Utilisateurs, rôles & permissions, chaque rôle affiche la liste complète de ses permissions cochées.
Pourquoi certains rôles comme RECRUITER sont-ils globaux et pas propres à un tenant ?
Ce sont des rôles « métier standard » utiles à toutes les sociétés dès leur création — ils évitent à chaque tenant de devoir recréer manuellement les mêmes rôles courants. Un tenant reste libre de créer ses propres rôles en complément.