- Ajoute #[Groups(['me:read'])] sur getEffectivePermissions() dans User.php
- Fixe la serialisation de isAdmin : le prefixe "is" etait strip par Symfony,
expose desormais via le getter avec #[SerializedName('isAdmin')] + groups lecture,
la propriete conserve uniquement le groupe d'ecriture user:rbac:write
- Cree MeApiTest avec 4 tests fonctionnels (isAdmin admin, permissions vides user,
401 sans auth, effectivePermissions avec role portant une permission)
Introduit AdminHeadcountGuardInterface pour permettre le mock en tests
unitaires, puis cree UserProcessor qui protege DELETE /api/users/{id}
contre la suppression du dernier administrateur via la garde domaine.
Ajoute une operation Patch dediee `PATCH /api/users/{id}/rbac` (nom
`user_rbac_patch`) qui accepte exclusivement les champs RBAC isAdmin,
roles et directPermissions via le groupe user:rbac:write. L'endpoint est
separe volontairement du Patch profil existant pour isoler la modification
des droits de celle des donnees profil (decision 0fc4e16).
UserRbacProcessor delegue au PersistProcessor Doctrine decore et applique
une garde auto-suicide : un admin ne peut pas retirer ses propres droits
administrateur (compare l'etat entrant a l'etat UnitOfWork). La garde
'dernier admin' globale est reportee au ticket #345.
La propriete Doctrine $roles est renommee $rbacRoles pour eviter la
collision avec UserInterface::getRoles() (qui renvoie list<string>) lors
de la normalization API Platform. La cle JSON reste `roles` grace a
SerializedName, le contrat API est inchange.
Tests : 6 unitaires (UserRbacProcessorTest) + 8 fonctionnels
(UserRbacApiTest) couvrant promotion admin, remplacement des collections
roles/directPermissions, 401/403, filtrage du groupe denormalization
(`username` ignore), preservation de isAdmin sur le Patch profil, et
garde auto-suicide.
Extrait l'helper authenticatedClient(), $alwaysBootKernel et getEm() dans
une classe de base commune aux tests fonctionnels API Platform du module
Core. Supprime la duplication entre PermissionApiTest et RoleApiTest
(flaggee en code review de la Task 2). Prepare le terrain pour le nouveau
UserRbacApiTest introduit avec la Task 4.
- Expose l'entite Permission via ApiResource (GetCollection + Get uniquement)
- Serialisation limitee au groupe permission:read (id, code, label, module, orphan)
- Securite temporaire is_granted('ROLE_ADMIN'), a remplacer par
is_granted('core.permissions.view') au ticket #345
- Filtres : SearchFilter exact sur module, BooleanFilter sur orphan
- Configure api_platform.mapping.paths pour que le compile pass AP decouvre
les ApiResource/ApiFilter declares dans src/Module/Core/Domain/Entity
- Ajoute symfony/browser-kit et symfony/http-client en dev pour les tests
fonctionnels API Platform, plus KERNEL_CLASS dans phpunit.dist.xml
- Tests fonctionnels PermissionApiTest : collection, get item, filtres
module et orphan, 405 sur POST, 401 non authentifie, 403 non-admin
isAdmin, roles et directPermissions ne doivent pas etre modifiables via
PATCH /api/users/{id}. L exposition en ecriture sera traitee par un
processor dedie dans le ticket #344 (spec section 2 OUT).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documente le bug Doctrine Migrations 3.x (tri par FQCN au lieu de
version timestamp avec plusieurs migrations_paths) et la regle
provisoire : migrations d'init au namespace racine, namespace
modulaire reserve aux migrations applicatives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug decouvert a l'execution de 'make db-reset' sur base vide : Doctrine
Migrations 3.x avec plusieurs 'migrations_paths' execute les migrations
dans l'ordre (namespace, version) et non (version, namespace). Le
Version20260414150034 sous 'App\Module\Core\...' passait donc avant
Version20260407095546 sous 'DoctrineMigrations', provoquant un
"relation user does not exist".
Deplacement du fichier vers 'migrations/' (namespace DoctrineMigrations).
Le chemin modulaire reste configure pour les futurs modules, mais
la migration RBAC d'initialisation vit a la racine pour que
'make db-reset' fonctionne en one-shot.
Smoke test end-to-end valide :
- db-reset + fixtures : admin (is_admin=t, role admin), alice/bob
(is_admin=f, role user)
- app:sync-permissions : 4 permissions Core ajoutees, idempotent au 2e run
- User::getRoles() : ['ROLE_USER', 'ROLE_ADMIN'] pour admin, ['ROLE_USER']
pour alice/bob
- User::getEffectivePermissions() : union triee des permissions via roles
Ticket #343 - 7/7 : smoke test end-to-end OK.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ensureSystemRole() recopie desormais la description depuis la migration
RBAC pour que les chemins prod (migration) et dev (fixtures) produisent
un etat identique.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AppFixtures : rattachement des users aux entites Role via
RoleRepositoryInterface. Re-seed idempotent des roles systeme dans
ensureSystemRole() pour compenser le purger Doctrine qui vide la table
role avant load(), afin que "make db-reset && make fixtures" reste un
workflow one-shot.
- CreateUserCommand : flag --admin attache au role systeme admin + is_admin,
sinon au role user. Gestion d'erreur explicite si les roles systeme sont
absents (FAILURE + message pointant vers la migration).
- CreateUserCommand devient final, descriptions traduites en francais.
Ticket #343 - 6/7 : fixtures et command alignes sur le RBAC relationnel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Nouvelles tables permission, role, role_permission, user_role, user_permission
- Ajout user.is_admin (BOOLEAN, default false)
- Seed des roles systeme admin et user via SQL brut (autonome, pas besoin
de fixtures pour cette etape)
- Migration des donnees : is_admin reflete ROLE_ADMIN du JSON roles, puis
rattachement user_role selon admin/user
- Drop user.roles en dernier (apres la migration de donnees)
- down() recree la colonne roles et la rehydrate depuis is_admin
Ticket #343 - 5/7 : persistance + migration donnees safe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CoreModule declare 4 permissions initiales (users.view/manage, roles.manage,
permissions.view)
- Nouvelle commande app:sync-permissions :
* scan des *Module::permissions() via config/modules.php
* validation stricte : cles [code, label], prefixe module, non-vides
* upsert transactionnel non-destructif
* revival des permissions orphelines qui reapparaissent
* marquage orphan pour les permissions disparues du code
* un seul flush() final (evite le flush-par-save de la repo save())
Ticket #343 - 4/7 : scanner et synchroniseur de permissions RBAC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Suppression de la colonne JSON roles (persiste jusqu'a la migration Task 5)
- Ajout is_admin bool (seul levier de bypass RBAC via getRoles())
- Ajout ManyToMany User-Role (EAGER, table user_role)
- Ajout ManyToMany User-Permission directes (EAGER, table user_permission)
- getEffectivePermissions() : union dedupliquee triee, utilisee par le
futur PermissionVoter (#345)
- getRbacRoles() pour ne pas shadow getRoles() de UserInterface Symfony
- Tests unitaires couvrant derivation getRoles, union, deduplication, tri
Ticket #343 - 3/7 : migration du User vers le modele RBAC relationnel.
Fetch EAGER documente : evite le lazy-load au refresh JWT.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PermissionRepositoryInterface avec findByCode et findAllCodes (pour le sync
command et le futur PermissionVoter)
- RoleRepositoryInterface avec findByCode
- Implementations Doctrine alignees sur DoctrineUserRepository
- Alias DI dans config/services.yaml
- Rebranchement de repositoryClass sur les entites Permission et Role
Ticket #343 - 2/7 : couche persistence RBAC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Permission : guards constructeur (code/label/module non vides, code avec point)
- Permission::revive() reutilise updateMetadata() pour eviter la duplication
- Suppression de SystemRolesTest (tautologique, ne capture aucun comportement)
- Role::permissions : commentaire explicite sur la raison du fetch EAGER
- Alignement des types de retour sur static (style User.php)
- Nouveau test Role::addPermission avec permissions distinctes
Ticket #343 - Task 1 polish (revue qualite).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Entite Permission avec methodes markOrphan/revive/updateMetadata
- Entite Role avec addPermission/removePermission/ensureDeletable
- Constantes SystemRoles (codes admin/user partages)
- Exception SystemRoleDeletionException pour la garde de suppression
- Tests unitaires couvrant le comportement domaine (pas de BDD)
Ticket #343 - 1/7 : fondations RBAC (domaine pur, sans persistence).
Les entites ne portent pas encore repositoryClass (ajoute en Task 2).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Spec detaillee des fondations RBAC backend (entites Role/Permission, sync
command, migration, fixtures, tests) dans docs/rbac/ticket-343-spec.md
- Ajout CLAUDE.md des regles projet : commentaires francais (PHP + TS/Vue)
et convention de nommage des permissions module.resource[.sub].action
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Persist var/log/ via named volume coltura_logs so logs survive
container restarts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ESLint with @nuxt/eslint-config enforcing 4-space indentation.
Add make nuxt-lint and nuxt-lint-fix targets.
Add ESLint check to pre-commit hook (lint only, no auto-fix).
Fix auth.vue indentation from 2 to 4 spaces.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>