403 partout. * * On prouve aussi que l'acces n'est pas « admin only » par hasard mais bien * porte par les permissions : un non-admin avec `view` lit (200) mais ne peut * pas creer (403, refus au niveau securite avant denormalisation). * * Note : on ne teste pas « un non-admin avec `manage` cree un produit » — ce role * n'existe dans aucun persona (catalogue admin-only) et un tel user ne pourrait * de toute facon pas resoudre les IRI sites / categories / storage_types lors de * la denormalisation (ces ressources portent leur propre controle d'acces). La * creation par un porteur de `manage` est couverte par l'Admin (qui bypass). * * @internal */ final class ProductRBACMatrixTest extends AbstractProductApiTestCase { /** Personas metier sans permission produit (§ 5.2). */ private const array PERSONAS = ['Bureau', 'Compta', 'Commerciale', 'Usine']; public function testAdminHasFullAccess(): void { $client = $this->createAdminClient(); $client->request('GET', '/api/products', ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(200); $client->request('POST', '/api/products', [ 'headers' => ['Content-Type' => self::LD], 'json' => $this->validProductPayload(), ]); self::assertResponseStatusCodeSame(201); } public function testBusinessPersonasAreForbiddenEverywhere(): void { // Produit existant cible des operations item (seede par l'admin via l'EM). $product = $this->seedProductEntity(); $id = (int) $product->getId(); foreach (self::PERSONAS as $persona) { $client = $this->createPersonaClient($persona); $client->request('GET', '/api/products', ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(403, $persona.' ne doit pas lister les produits.'); $client->request('GET', '/api/products/'.$id, ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(403, $persona.' ne doit pas consulter un produit.'); $client->request('POST', '/api/products', [ 'headers' => ['Content-Type' => self::LD], 'json' => $this->validProductPayload(), ]); self::assertResponseStatusCodeSame(403, $persona.' ne doit pas creer de produit.'); $client->request('PATCH', '/api/products/'.$id, [ 'headers' => ['Content-Type' => self::MERGE], 'json' => ['name' => 'Renomme par '.$persona], ]); self::assertResponseStatusCodeSame(403, $persona.' ne doit pas modifier un produit.'); } } public function testViewPermissionReadsButCannotManage(): void { $product = $this->seedProductEntity(); $client = $this->authView(); $client->request('GET', '/api/products', ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(200); $client->request('GET', '/api/products/'.$product->getId(), ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(200); // view sans manage : creation refusee au niveau securite (403 avant que la // denormalisation ne tente de resoudre les IRI -> pas de 400 parasite). $client->request('POST', '/api/products', [ 'headers' => ['Content-Type' => self::LD], 'json' => $this->validProductPayload(), ]); self::assertResponseStatusCodeSame(403); } }