diff --git a/src/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteCollectionScopedExtension.php b/src/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteCollectionScopedExtension.php index 8339159..0486a6f 100644 --- a/src/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteCollectionScopedExtension.php +++ b/src/Module/Sites/Infrastructure/ApiPlatform/Extension/SiteCollectionScopedExtension.php @@ -30,6 +30,8 @@ use function sprintf; * - resource != Site::class → no-op (les autres resources sont * gerees par SiteScopedQueryExtension) ; * - is_granted('sites.bypass_scope') → pas de filtre (admin / bypass) ; + * - is_granted('sites.read_ref') → pas de filtre (lecture-referentiel + * transverse complet, ERP-102) ; * - user non authentifie → no-op (API Platform renvoie 401 avant) ; * - user sans aucun site → WHERE 1 = 0 (aucun acces) ; * - cas normal → WHERE site.id IN (:allowedSites). @@ -84,6 +86,16 @@ final class SiteCollectionScopedExtension implements QueryCollectionExtensionInt return; } + // 2bis) Lecture-referentiel transverse (ERP-102) : `sites.read_ref` donne + // acces a la LISTE COMPLETE des sites (selects d'adresse des modules Tiers). + // Sans ce bypass, le cloisonnement par site rattache reduirait le select + // aux seuls sites de l'utilisateur (voire a rien s'il n'en a aucun) et le + // referentiel ne serait plus "transverse". `read_ref` est une lecture seule : + // il ouvre la visibilite sans permettre la moindre ecriture. + if ($this->security->isGranted('sites.read_ref')) { + return; + } + // 3) Pas d'user authentifie -> no-op (API Platform gere le 401 en amont). $user = $this->security->getUser(); if (!$user instanceof User) { diff --git a/tests/Module/Commercial/Api/ClientRBACMatrixTest.php b/tests/Module/Commercial/Api/ClientRBACMatrixTest.php index 32c66d4..e2afbf8 100644 --- a/tests/Module/Commercial/Api/ClientRBACMatrixTest.php +++ b/tests/Module/Commercial/Api/ClientRBACMatrixTest.php @@ -6,6 +6,7 @@ namespace App\Tests\Module\Commercial\Api; use ApiPlatform\Symfony\Bundle\Test\Client; use App\Module\Core\Infrastructure\DataFixtures\RbacDemoFixtures; +use App\Module\Sites\Domain\Entity\Site; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; @@ -281,14 +282,32 @@ final class ClientRBACMatrixTest extends AbstractCommercialApiTestCase // / sites.read_ref) attachee par la matrice § 2.7 — sans pour autant porter // la permission d'administration `.view`. Usine, sans aucune permission, // reste interdit. + // Le referentiel /sites est TRANSVERSE et COMPLET : le cloisonnement par + // site rattache (SiteCollectionScopedExtension) est neutralise par + // `sites.read_ref` (ERP-102). Les comptes demo ne sont rattaches qu'a un + // seul site (Chatellerault) alors que la base en compte plusieurs : on + // verifie donc que le role voit la TOTALITE du referentiel, pas son seul + // site rattache. Sans le bypass de scope, totalItems vaudrait 1. + $totalSites = $this->getEm()->getRepository(Site::class)->count([]); + self::assertGreaterThan( + 1, + $totalSites, + 'Pre-requis du test : la base doit contenir plusieurs sites pour distinguer scope et bypass.', + ); + foreach (['bureau', 'compta', 'commerciale'] as $role) { $client = $this->authAs($role); $client->request('GET', '/api/categories', ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(200, sprintf('Le role %s doit pouvoir lister /categories', $role)); - $client->request('GET', '/api/sites', ['headers' => ['Accept' => self::LD]]); + $response = $client->request('GET', '/api/sites', ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(200, sprintf('Le role %s doit pouvoir lister /sites', $role)); + self::assertSame( + $totalSites, + $response->toArray()['totalItems'] ?? null, + sprintf('Le role %s doit voir tout le referentiel sites (%d), pas seulement son site rattache', $role, $totalSites), + ); } // Usine : aucune permission -> reste a 403 sur les referentiels.