From c6a756cfd845e7dcb7f815f7e48ab41482321951 Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Tue, 24 Dec 2024 16:30:02 +0100 Subject: [PATCH] fix(openapi): not forbidden response on openAPI doc --- src/OpenApi/Factory/OpenApiFactory.php | 4 +++ .../Tests/Factory/OpenApiFactoryTest.php | 29 ++++++++++++++++++- .../Tests/Fixtures/Issue6872/Diamond.php | 19 ++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/OpenApi/Tests/Fixtures/Issue6872/Diamond.php diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index 84a779ccc91..1a51fbb5423 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -408,6 +408,10 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection } } + if (true === $overrideResponses && !isset($existingResponses[403]) && $operation->getSecurity()) { + $openapiOperation = $openapiOperation->withResponse(403, new Response('Forbidden')); + } + if (true === $overrideResponses && !$operation instanceof CollectionOperationInterface && 'POST' !== $operation->getMethod()) { if (!isset($existingResponses[404])) { $openapiOperation = $openapiOperation->withResponse(404, new Response('Resource not found')); diff --git a/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php b/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php index 1d3511df8e6..0acf7dba882 100644 --- a/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php +++ b/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php @@ -59,6 +59,7 @@ use ApiPlatform\OpenApi\Tests\Fixtures\Dummy; use ApiPlatform\OpenApi\Tests\Fixtures\DummyErrorResource; use ApiPlatform\OpenApi\Tests\Fixtures\DummyFilter; +use ApiPlatform\OpenApi\Tests\Fixtures\Issue6872\Diamond; use ApiPlatform\OpenApi\Tests\Fixtures\OutputDto; use ApiPlatform\State\Pagination\PaginationOptions; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\WithParameter; @@ -85,6 +86,7 @@ public function testInvoke(): void $baseOperation = (new HttpOperation())->withTypes(['http://schema.example.com/Dummy'])->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats'])->withClass(Dummy::class)->withOutput([ 'class' => OutputDto::class, ])->withPaginationClientItemsPerPage(true)->withShortName('Dummy')->withDescription('This is a dummy'); + $dummyResourceWebhook = (new ApiResource())->withOperations(new Operations([ 'dummy webhook' => (new Get())->withUriTemplate('/dummy/{id}')->withShortName('short')->withOpenapi(new Webhook('first webhook')), 'an other dummy webhook' => (new Post())->withUriTemplate('/dummies')->withShortName('short something')->withOpenapi(new Webhook('happy webhook', new Model\PathItem(post: new Operation( @@ -272,13 +274,23 @@ public function testInvoke(): void ]))->withOperation($baseOperation), ])); + $diamondResource = (new ApiResource()) + ->withOperations(new Operations([ + 'getDiamondCollection' => (new GetCollection(uriTemplate: '/diamonds')) + ->withSecurity("is_granted('ROLE_USER')") + ->withOperation($baseOperation), + 'putDiamond' => (new Put(uriTemplate: '/diamond/{id}')) + ->withOperation($baseOperation), + ])); + $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class, WithParameter::class])); + $resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class, WithParameter::class, Diamond::class])); $resourceCollectionMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $resourceCollectionMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new ResourceMetadataCollection(Dummy::class, [$dummyResource, $dummyResourceWebhook])); $resourceCollectionMetadataFactoryProphecy->create(DummyErrorResource::class)->shouldBeCalled()->willReturn(new ResourceMetadataCollection(DummyErrorResource::class, [new ApiResource(operations: [new ErrorOperation(name: 'err', description: 'nice one!')])])); $resourceCollectionMetadataFactoryProphecy->create(WithParameter::class)->shouldBeCalled()->willReturn(new ResourceMetadataCollection(WithParameter::class, [$parameterResource])); + $resourceCollectionMetadataFactoryProphecy->create(Diamond::class)->shouldBeCalled()->willReturn(new ResourceMetadataCollection(Diamond::class, [$diamondResource])); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate', 'enum'])); @@ -1171,5 +1183,20 @@ public function testInvoke(): void ], deprecated: false ), $paths->getPath('/erroredDummies')->getGet()); + + $diamondsGetPath = $paths->getPath('/diamonds'); + $diamondGetOperation = $diamondsGetPath->getGet(); + $diamondGetResponses = $diamondGetOperation->getResponses(); + + $this->assertNotNull($diamondGetOperation); + $this->assertArrayHasKey('403', $diamondGetResponses); + $this->assertSame('Forbidden', $diamondGetResponses['403']->getDescription()); + + $diamondsPutPath = $paths->getPath('/diamond/{id}'); + $diamondPutOperation = $diamondsPutPath->getPut(); + $diamondPutResponses = $diamondPutOperation->getResponses(); + + $this->assertNotNull($diamondPutOperation); + $this->assertArrayNotHasKey('403', $diamondPutResponses); } } diff --git a/src/OpenApi/Tests/Fixtures/Issue6872/Diamond.php b/src/OpenApi/Tests/Fixtures/Issue6872/Diamond.php new file mode 100644 index 00000000000..e2cf520f62c --- /dev/null +++ b/src/OpenApi/Tests/Fixtures/Issue6872/Diamond.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\OpenApi\Tests\Fixtures\Issue6872; + +class Diamond +{ + public float $weight; +}