fix(rbac) : référentiels /categories et /sites lisibles par les rôles métier (ERP-102) #53

Merged
malio merged 2 commits from fix/ERP-102-rbac-referentiels-transverses into develop 2026-06-03 12:56:48 +00:00
Owner

Contexte

ERP-102 — Découvert pendant ERP-64. Connecté avec un rôle métier (bureau / compta / commerciale), GET /api/categories et GET /api/sites renvoient 403, alors que /tva_modes, /payment_delays, /payment_types, /banks renvoient 200.

Conséquences : page Création client inutilisable (le Promise.all rejetait → tous les selects vides) et filtres Catégories/Sites vides au répertoire.

Cause

La security des GetCollection/Get de Category et Site exigeait catalog.categories.view / sites.view — permissions d'administration du Catalogue / des Sites. Or ces référentiels sont transverses : tout rôle qui gère un tiers doit pouvoir les lire.

Correctif back — Option C (permission de lecture-référentiel dédiée)

Choix d'archi retenu parmi les 3 du ticket :

  • Pourquoi pas A (... or is_granted('commercial.clients.view')) : coupler Category/Site à une permission Commercial viole l'esprit de la règle ABSOLUE n°1 et ne scale pas (M2 Fournisseurs devrait rajouter un OR).
  • Pourquoi pas B (donner .view aux rôles métier) : .view = accès admin → items sidebar admin Catégories/Sites exposés à une commerciale.
  • C : nouvelle permission catalog.categories.read_ref / sites.read_ref, distincte de .view (pas d'item sidebar) et de .manage. Chaque permission appartient à son module → isolement inter-module préservé, réutilisable tel quel par M2 Fournisseurs. C'est la « permission référentiel lisible » que le ticket pointe lui-même.

Détail :

  • CatalogModule / SitesModule : déclaration des deux permissions read_ref.
  • Category / Site : security lecture (liste + item) = view OR read_ref.
  • RbacSeeder (matrice § 2.7) : read_ref attaché à bureau / compta / commerciale ; usine reste sans accès.

Durcissement front (résilience — requis dans tous les cas)

useClientReferentials.loadCommon : Promise.allPromise.allSettled avec affectation isolée par référentiel. L'échec d'un endpoint ne vide plus que son select, plus la totalité du formulaire.

Tests (TDD)

  • ClientRBACMatrixTest::testBusinessRolesCanReadCategoriesAndSitesReferentials — bureau/compta/commerciale listent /categories et /sites (200), usine reste 403.
  • SitesModuleTest — set de permissions porté à 4 codes.
  • useClientReferentials.spec (Vitest) — un référentiel en échec ne vide que son select.

Vérifications

  • make test (back) : 467/467
  • make nuxt-test (front) : 131/131
  • make php-cs-fixer : conforme ✓

Note miroirs RBAC

config/sidebar.php / personas.ts / SeedE2ECommand.php non touchés : read_ref n'ajoute aucun item sidebar, le persona E2E user-full lit déjà via .view, et aucun persona ne modélise un rôle métier seul. Pas de nouveau test E2E (règle n°7 : bug attrapé avant prod). La source de vérité de la matrice (RbacSeeder) est mise à jour et couverte par ClientRBACMatrixTest.

Closes ERP-102.

## Contexte ERP-102 — Découvert pendant ERP-64. Connecté avec un rôle **métier** (bureau / compta / commerciale), `GET /api/categories` et `GET /api/sites` renvoient **403**, alors que `/tva_modes`, `/payment_delays`, `/payment_types`, `/banks` renvoient 200. Conséquences : page **Création client** inutilisable (le `Promise.all` rejetait → **tous** les selects vides) et **filtres Catégories/Sites vides** au répertoire. ## Cause La `security` des `GetCollection`/`Get` de `Category` et `Site` exigeait `catalog.categories.view` / `sites.view` — permissions d'**administration** du Catalogue / des Sites. Or ces référentiels sont **transverses** : tout rôle qui gère un tiers doit pouvoir les lire. ## Correctif back — Option C (permission de lecture-référentiel dédiée) Choix d'archi retenu parmi les 3 du ticket : - **Pourquoi pas A** (`... or is_granted('commercial.clients.view')`) : coupler `Category`/`Site` à une permission **Commercial** viole l'esprit de la règle ABSOLUE n°1 et ne scale pas (M2 Fournisseurs devrait rajouter un OR). - **Pourquoi pas B** (donner `.view` aux rôles métier) : `.view` = accès admin → items sidebar admin Catégories/Sites exposés à une commerciale. - **C** : nouvelle permission `catalog.categories.read_ref` / `sites.read_ref`, distincte de `.view` (pas d'item sidebar) et de `.manage`. Chaque permission appartient à **son** module → isolement inter-module préservé, **réutilisable tel quel par M2 Fournisseurs**. C'est la « permission référentiel lisible » que le ticket pointe lui-même. Détail : - `CatalogModule` / `SitesModule` : déclaration des deux permissions `read_ref`. - `Category` / `Site` : security lecture (liste + item) = `view OR read_ref`. - `RbacSeeder` (matrice § 2.7) : `read_ref` attaché à bureau / compta / commerciale ; usine reste sans accès. ## Durcissement front (résilience — requis dans tous les cas) `useClientReferentials.loadCommon` : `Promise.all` → **`Promise.allSettled`** avec affectation isolée par référentiel. L'échec d'un endpoint ne vide plus que **son** select, plus la totalité du formulaire. ## Tests (TDD) - `ClientRBACMatrixTest::testBusinessRolesCanReadCategoriesAndSitesReferentials` — bureau/compta/commerciale listent `/categories` et `/sites` (200), usine reste 403. - `SitesModuleTest` — set de permissions porté à 4 codes. - `useClientReferentials.spec` (Vitest) — un référentiel en échec ne vide que son select. ## Vérifications - `make test` (back) : **467/467** ✓ - `make nuxt-test` (front) : **131/131** ✓ - `make php-cs-fixer` : conforme ✓ ## Note miroirs RBAC `config/sidebar.php` / `personas.ts` / `SeedE2ECommand.php` **non touchés** : `read_ref` n'ajoute aucun item sidebar, le persona E2E `user-full` lit déjà via `.view`, et aucun persona ne modélise un rôle métier seul. Pas de nouveau test E2E (règle n°7 : bug attrapé avant prod). La source de vérité de la matrice (`RbacSeeder`) est mise à jour et couverte par `ClientRBACMatrixTest`. Closes ERP-102.
matthieu added 1 commit 2026-06-03 12:24:27 +00:00
fix(rbac) : referentiels /categories et /sites lisibles par les roles metier (ERP-102)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m50s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m24s
54fe48993f
Les roles metier (bureau / compta / commerciale) prenaient un 403 sur
GET /api/categories et GET /api/sites : la security des GetCollection/Get
exigeait catalog.categories.view / sites.view, permissions reservees a
l'administration du Catalogue et des Sites. Or ces referentiels sont
transverses (selects de creation/filtre client) : creation de client
cassee et filtres vides pour ces roles.

Correctif back (Option C — permission de lecture-referentiel dediee) :
- Nouvelles permissions catalog.categories.read_ref et sites.read_ref,
  distinctes de .view (pas d'item sidebar admin) et de .manage. Chaque
  permission appartient a son propre module -> aucun couplage inter-module
  (regle ABSOLUE n°1) et reutilisable tel quel par M2 Fournisseurs.
- Security lecture (liste + item) elargie : view OR read_ref sur Category
  et Site.
- Matrice RBAC § 2.7 (RbacSeeder) : read_ref attache a bureau / compta /
  commerciale. Usine reste sans acces.

Durcissement front (resilience, requis dans tous les cas) :
- useClientReferentials.loadCommon passe de Promise.all a Promise.allSettled
  avec affectation isolee par referentiel : l'echec d'un endpoint ne vide
  que SON select, plus la totalite du formulaire.

Tests :
- ClientRBACMatrixTest : les roles metier listent /categories et /sites (200),
  usine reste a 403.
- SitesModuleTest : set de permissions porte a 4 codes.
- useClientReferentials.spec : resilience d'un referentiel en echec.

Miroirs E2E (personas.ts / SeedE2ECommand) non touches : read_ref n'ajoute
aucun lien sidebar, le persona user-full lit deja via .view, et aucun
persona ne modelise un role metier seul ; pas de nouveau test E2E (regle n°7).
matthieu force-pushed fix/ERP-102-rbac-referentiels-transverses from f9c881c771 to 54fe48993f 2026-06-03 12:24:27 +00:00 Compare
matthieu added the backM1-Clienttype/fix labels 2026-06-03 12:24:59 +00:00
matthieu added 1 commit 2026-06-03 12:53:21 +00:00
fix(sites) : referentiel /sites complet pour les roles read_ref (ERP-102)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m50s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m21s
efdbfa5673
Le cloisonnement par site rattache (SiteCollectionScopedExtension) tronquait
encore la collection /sites pour les roles metier :  ouvrait la
security mais le filtre IN sur les sites de l'utilisateur reduisait le select
d'adresse client a son seul site rattache (voire a rien sans site) — le bug
"selects vides" d'ERP-102 etait deplace du 403 vers une liste vide.

- SiteCollectionScopedExtension :  neutralise le scope (lecture
  du referentiel complet), au meme titre que . Lecture seule.
- ClientRBACMatrixTest : l'assertion ne se contente plus du 200, elle verifie
  que les roles metier voient la TOTALITE du referentiel sites (totalItems ==
  nombre de sites en base), pas seulement leur site rattache.
malio merged commit 1888b70623 into develop 2026-06-03 12:56:48 +00:00
malio deleted branch fix/ERP-102-rbac-referentiels-transverses 2026-06-03 12:56:49 +00:00
Sign in to join this conversation.