From 161375f4bc8dbfbe6934a2694b686649a715607c Mon Sep 17 00:00:00 2001 From: borNfreee Date: Tue, 1 Oct 2019 10:39:13 +0300 Subject: [PATCH 01/17] Do not append `body` parameter if it already exists --- .../Serializer/DocumentationNormalizer.php | 23 ++- .../DocumentationNormalizerV2Test.php | 195 ++++++++++++++++++ 2 files changed, 213 insertions(+), 5 deletions(-) diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index 475a9750169..5cf2c806fc5 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -543,15 +543,28 @@ private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObj return $pathOperation; } - $pathOperation['parameters'][] = [ - 'name' => lcfirst($resourceShortName), - 'in' => 'body', - 'description' => $description, - ] + $message; + if (!$this->isBodyParameterAlreadyExists($pathOperation['parameters'] ?? [])) { + $pathOperation['parameters'][] = [ + 'name' => lcfirst($resourceShortName), + 'in' => 'body', + 'description' => $description, + ] + $message; + } return $pathOperation; } + private function isBodyParameterAlreadyExists(array $parameters): bool + { + foreach ($parameters as $parameter) { + if (array_key_exists('in', $parameter) && $parameter['in'] === 'body') { + return true; + } + } + + return false; + } + private function updateDeleteOperation(bool $v3, \ArrayObject $pathOperation, string $resourceShortName, string $operationType, string $operationName, ResourceMetadata $resourceMetadata): \ArrayObject { $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Removes the %s resource.', $resourceShortName); diff --git a/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php b/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php index 3b7f806dff6..eaf1f99fdad 100644 --- a/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php +++ b/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php @@ -803,6 +803,201 @@ public function testNormalizeWithOnlyNormalizationGroups(): void $this->assertEquals($expected, $normalizer->normalize($documentation)); } + public function testNormalizeNotAddExtraBodyParameters(): void + { + $title = 'Test API'; + $description = 'This is a test API.'; + $version = '1.2.3'; + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); + $groups = ['dummy', 'foo', 'bar']; + + $ref = 'Dummy-'.implode('_', $groups); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, ['serializer_groups' => $groups])->shouldBeCalledTimes(1)->willReturn(new PropertyNameCollection(['gerard'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name'])); + + $dummyMetadata = new ResourceMetadata( + 'Dummy', + 'This is a dummy.', + 'http://schema.example.com/Dummy', + [ + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, + ], + [ + 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, + 'post' => [ + 'method' => 'POST', + 'swagger_context' => [ + 'parameters' => [ + [ + 'name' => 'dummy', + 'in' => 'body', + 'description' => 'The new custom Dummy resource', + 'schema' => ['$ref' => '#/definitions/Dummy'], + ] + ] + ] + ] + self::OPERATION_FORMATS, + ] + ); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); + + $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); + + $normalizer = new DocumentationNormalizer( + $resourceMetadataFactoryProphecy->reveal(), + $propertyNameCollectionFactoryProphecy->reveal(), + $propertyMetadataFactoryProphecy->reveal(), + null, + null, + $operationPathResolver + ); + + $expected = [ + 'swagger' => '2.0', + 'basePath' => '/', + 'info' => [ + 'title' => 'Test API', + 'description' => 'This is a test API.', + 'version' => '1.2.3', + ], + 'paths' => new \ArrayObject([ + '/dummies' => [ + 'get' => new \ArrayObject([ + 'tags' => [ + 'Dummy', + ], + 'operationId' => 'getDummyCollection', + 'produces' => ['application/ld+json'], + 'summary' => 'Retrieves the collection of Dummy resources.', + 'parameters' => [ + [ + 'name' => 'page', + 'in' => 'query', + 'required' => false, + 'type' => 'integer', + 'description' => 'The collection page number', + ], + ], + 'responses' => [ + 200 => [ + 'description' => 'Dummy collection response', + 'schema' => [ + 'type' => 'array', + 'items' => ['$ref' => '#/definitions/Dummy'], + ], + ], + ], + ]), + 'post' => new \ArrayObject([ + 'tags' => ['Dummy'], + 'operationId' => 'postDummyCollection', + 'consumes' => ['application/ld+json'], + 'produces' => ['application/ld+json'], + 'summary' => 'Creates a Dummy resource.', + 'parameters' => [[ + 'name' => 'dummy', + 'in' => 'body', + 'description' => 'The new custom Dummy resource', + 'schema' => ['$ref' => '#/definitions/Dummy'], + ]], + 'responses' => [ + 201 => [ + 'description' => 'Dummy resource created', + 'schema' => ['$ref' => '#/definitions/Dummy'], + ], + 400 => ['description' => 'Invalid input'], + 404 => ['description' => 'Resource not found'], + ], + ]), + ], + '/dummies/{id}' => [ + 'get' => new \ArrayObject([ + 'tags' => ['Dummy'], + 'operationId' => 'getDummyItem', + 'produces' => ['application/ld+json'], + 'summary' => 'Retrieves a Dummy resource.', + 'parameters' => [[ + 'name' => 'id', + 'in' => 'path', + 'type' => 'string', + 'required' => true, + ]], + 'responses' => [ + 200 => [ + 'description' => 'Dummy resource response', + 'schema' => ['$ref' => '#/definitions/Dummy'], + ], + 404 => ['description' => 'Resource not found'], + ], + ]), + 'put' => new \ArrayObject([ + 'tags' => ['Dummy'], + 'operationId' => 'putDummyItem', + 'consumes' => ['application/ld+json'], + 'produces' => ['application/ld+json'], + 'summary' => 'Replaces the Dummy resource.', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'path', + 'type' => 'string', + 'required' => true, + ], + [ + 'name' => 'dummy', + 'in' => 'body', + 'description' => 'The updated Dummy resource', + 'schema' => ['$ref' => '#/definitions/Dummy'], + ], + ], + 'responses' => [ + 200 => [ + 'description' => 'Dummy resource updated', + 'schema' => ['$ref' => '#/definitions/'.$ref], + ], + 400 => ['description' => 'Invalid input'], + 404 => ['description' => 'Resource not found'], + ], + ]), + ], + ]), + 'definitions' => new \ArrayObject([ + 'Dummy' => new \ArrayObject([ + 'type' => 'object', + 'description' => 'This is a dummy.', + 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], + 'properties' => [ + 'name' => new \ArrayObject([ + 'type' => 'string', + 'description' => 'This is a name.', + ]), + ], + ]), + $ref => new \ArrayObject([ + 'type' => 'object', + 'description' => 'This is a dummy.', + 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], + 'properties' => [ + 'gerard' => new \ArrayObject([ + 'type' => 'string', + 'description' => 'This is a gerard.', + ]), + ], + ]), + ]), + ]; + + $this->assertEquals($expected, $normalizer->normalize($documentation)); + } + public function testNormalizeWithSwaggerDefinitionName(): void { $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); From 4c67031b1c7ea0e1b94295d839a06c7f417dcca3 Mon Sep 17 00:00:00 2001 From: borNfreee Date: Tue, 1 Oct 2019 10:54:44 +0300 Subject: [PATCH 02/17] Run php-cs-fixer --- .../Serializer/DocumentationNormalizer.php | 2 +- .../DocumentationNormalizerV2Test.php | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index 5cf2c806fc5..fcb656a664b 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -557,7 +557,7 @@ private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObj private function isBodyParameterAlreadyExists(array $parameters): bool { foreach ($parameters as $parameter) { - if (array_key_exists('in', $parameter) && $parameter['in'] === 'body') { + if (\array_key_exists('in', $parameter) && 'body' === $parameter['in']) { return true; } } diff --git a/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php b/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php index eaf1f99fdad..9d07d3045f3 100644 --- a/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php +++ b/tests/Swagger/Serializer/DocumentationNormalizerV2Test.php @@ -828,18 +828,18 @@ public function testNormalizeNotAddExtraBodyParameters(): void [ 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, 'post' => [ - 'method' => 'POST', - 'swagger_context' => [ - 'parameters' => [ - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new custom Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ] - ] - ] - ] + self::OPERATION_FORMATS, + 'method' => 'POST', + 'swagger_context' => [ + 'parameters' => [ + [ + 'name' => 'dummy', + 'in' => 'body', + 'description' => 'The new custom Dummy resource', + 'schema' => ['$ref' => '#/definitions/Dummy'], + ], + ], + ], + ] + self::OPERATION_FORMATS, ] ); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); From 2f80d1ea44acd43124c649939b059ef3199fa3a6 Mon Sep 17 00:00:00 2001 From: Beno!t POLASZEK Date: Tue, 1 Oct 2019 09:58:06 +0200 Subject: [PATCH 03/17] Push resources as fetch - Fix #3107 --- features/push_relations/push.feature | 4 ++-- src/EventListener/SerializeListener.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/push_relations/push.feature b/features/push_relations/push.feature index 6c14442a0a9..fe4a7d1de75 100644 --- a/features/push_relations/push.feature +++ b/features/push_relations/push.feature @@ -9,9 +9,9 @@ Feature: Push relations using HTTP/2 Given there are 2 dummy objects with relatedDummy When I add "Content-Type" header equal to "application/ld+json" And I send a "GET" request to "/dummies" - Then the header "Link" should be equal to '; rel="preload",; rel="preload",; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"' + Then the header "Link" should be equal to '; rel="preload"; as="fetch",; rel="preload"; as="fetch",; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"' Scenario: Push the relations of an item When I add "Content-Type" header equal to "application/ld+json" And I send a "GET" request to "/dummies/1" - Then the header "Link" should be equal to '; rel="preload",; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"' + Then the header "Link" should be equal to '; rel="preload"; as="fetch",; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"' diff --git a/src/EventListener/SerializeListener.php b/src/EventListener/SerializeListener.php index 54731fc678f..c0669484056 100644 --- a/src/EventListener/SerializeListener.php +++ b/src/EventListener/SerializeListener.php @@ -101,7 +101,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void $linkProvider = $request->attributes->get('_links', new GenericLinkProvider()); foreach ($resourcesToPush as $resourceToPush) { - $linkProvider = $linkProvider->withLink(new Link('preload', $resourceToPush)); + $linkProvider = $linkProvider->withLink((new Link('preload', $resourceToPush))->withAttribute('as', 'fetch')); } $request->attributes->set('_links', $linkProvider); } From fbf69f031c6a60308e0411af97e24f6fb92f06b3 Mon Sep 17 00:00:00 2001 From: Robin C Date: Tue, 1 Oct 2019 21:49:38 +0200 Subject: [PATCH 04/17] Update thrown exception and clarify error message. As discussed in issue #3045, the given error message was misleading. I also updated the thrown exception according to the Exception mentioned in the same issue. Of course I updated the related PHPUnit test too. --- .../RamseyUuid/Identifier/Normalizer/UuidNormalizer.php | 8 +++++--- tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php b/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php index 8bac4e27f52..20478c33a68 100644 --- a/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php +++ b/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php @@ -13,10 +13,10 @@ namespace ApiPlatform\Core\Bridge\RamseyUuid\Identifier\Normalizer; -use ApiPlatform\Core\Exception\InvalidIdentifierException; use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; /** @@ -28,20 +28,22 @@ final class UuidNormalizer implements DenormalizerInterface { /** * {@inheritdoc} + * + * @throws NotNormalizableValueException Occurs when the identifier format is invalid */ public function denormalize($data, $class, $format = null, array $context = []) { try { return Uuid::fromString($data); } catch (InvalidUuidStringException $e) { - throw new InvalidIdentifierException($e->getMessage(), $e->getCode(), $e); + throw new NotNormalizableValueException('Not found, because of an invalid identifier format', $e->getCode(), $e); } } /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null) + public function supportsDenormalization($data, $type, $format = null): bool { return is_a($type, UuidInterface::class, true); } diff --git a/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php b/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php index eaf83bbf406..2656dd7a2c5 100644 --- a/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php +++ b/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php @@ -16,6 +16,7 @@ use ApiPlatform\Core\Bridge\RamseyUuid\Identifier\Normalizer\UuidNormalizer; use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; class UuidNormalizerTest extends TestCase { @@ -36,7 +37,7 @@ public function testNoSupportDenormalizeUuid() public function testFailDenormalizeUuid() { - $this->expectException(\ApiPlatform\Core\Exception\InvalidIdentifierException::class); + $this->expectException(NotNormalizableValueException::class); $uuid = 'notanuuid'; $normalizer = new UuidNormalizer(); From d38d96c4ddbf78802aefa0d99f30e4a2f179a8fa Mon Sep 17 00:00:00 2001 From: WhiteRabbitDE Date: Wed, 2 Oct 2019 10:31:13 +0200 Subject: [PATCH 05/17] add missing oauth2-redirect configuration --- .../Resources/public/init-swagger-ui.js | 1 + .../public/swagger-ui/oauth2-redirect.html | 67 +++++++++++++++++++ .../Resources/views/SwaggerUi/index.html.twig | 3 +- update-js.sh | 1 + 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html diff --git a/src/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js b/src/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js index 1600643d778..95f2b63ed5e 100644 --- a/src/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js +++ b/src/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js @@ -45,6 +45,7 @@ window.onload = function() { spec: data.spec, dom_id: '#swagger-ui', validatorUrl: null, + oauth2RedirectUrl: data.oauth.redirectUrl, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset, diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html b/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html new file mode 100644 index 00000000000..fb68399d264 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html @@ -0,0 +1,67 @@ + + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig b/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig index 8e2e6925f77..afaaa4f1cb2 100644 --- a/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig +++ b/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig @@ -10,8 +10,9 @@ {% endblock %} + {% set oauthData = {'oauth': swagger_data.oauth|merge({'redirectUrl' : absolute_url(asset('bundles/apiplatform/swagger-ui/oauth2-redirect.html')) })} %} {# json_encode(65) is for JSON_UNESCAPED_SLASHES|JSON_HEX_TAG to avoid JS XSS #} - + diff --git a/update-js.sh b/update-js.sh index d1efaa52df2..0d425d01540 100755 --- a/update-js.sh +++ b/update-js.sh @@ -15,6 +15,7 @@ cp node_modules/swagger-ui-dist/swagger-ui-standalone-preset.js "$dest" cp node_modules/swagger-ui-dist/swagger-ui-standalone-preset.js.map "$dest" cp node_modules/swagger-ui-dist/swagger-ui.css "$dest" cp node_modules/swagger-ui-dist/swagger-ui.css.map "$dest" +cp node_modules/swagger-ui-dist/oauth2-redirect.html "$dest" dest=src/Bridge/Symfony/Bundle/Resources/public/react/ if [[ -d "$dest" ]]; then From 717596b026899dd873a3bdc2bc3643a2f2faf60e Mon Sep 17 00:00:00 2001 From: WhiteRabbitDE Date: Wed, 2 Oct 2019 11:23:37 +0200 Subject: [PATCH 06/17] fix cs --- .../Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig b/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig index afaaa4f1cb2..c516c4df42c 100644 --- a/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig +++ b/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig @@ -10,9 +10,9 @@ {% endblock %} - {% set oauthData = {'oauth': swagger_data.oauth|merge({'redirectUrl' : absolute_url(asset('bundles/apiplatform/swagger-ui/oauth2-redirect.html')) })} %} + {% set oauth_data = {'oauth': swagger_data.oauth|merge({'redirectUrl' : absolute_url(asset('bundles/apiplatform/swagger-ui/oauth2-redirect.html')) })} %} {# json_encode(65) is for JSON_UNESCAPED_SLASHES|JSON_HEX_TAG to avoid JS XSS #} - + From 435d49391cb1eda9c8f0a6c28c30af5ce9c4c391 Mon Sep 17 00:00:00 2001 From: Mark Hillebert Date: Wed, 2 Oct 2019 06:30:04 -0500 Subject: [PATCH 07/17] =?UTF-8?q?Bugfix:=20Notice:=20Undefined=20index:=20?= =?UTF-8?q?method=20thrown=20in=20SerializerContextBu=E2=80=A6=20(#3128)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bugfix: Notice: Undefined index: method thrown in SerializerContextBuilder (since version 2.5.0) - switch the order of the checks in the if statement to prevent checking for non-existent 'method' key in $operation check. * Update src/Serializer/SerializerContextBuilder.php Co-Authored-By: Kévin Dunglas --- src/Serializer/SerializerContextBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php index 73ed3bef63e..a38a69eaccd 100644 --- a/src/Serializer/SerializerContextBuilder.php +++ b/src/Serializer/SerializerContextBuilder.php @@ -94,7 +94,7 @@ public function createFromRequest(Request $request, bool $normalization, array $ } foreach ($resourceMetadata->getItemOperations() as $operation) { - if ('PATCH' === $operation['method'] && \in_array('application/merge-patch+json', $operation['input_formats']['json'] ?? [], true)) { + if ('PATCH' === ($operation['method'] ?? '') && \in_array('application/merge-patch+json', $operation['input_formats']['json'] ?? [], true)) { $context['skip_null_values'] = true; break; From 6ea80303c7d7fb589458138e459e64a812756216 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Thu, 3 Oct 2019 16:16:08 +0200 Subject: [PATCH 08/17] MongoDB ODM 2.0 has been released (#3143) --- composer.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 3db9bfbf012..3362e7bb3f1 100644 --- a/composer.json +++ b/composer.json @@ -36,8 +36,8 @@ "doctrine/data-fixtures": "^1.2.2", "doctrine/doctrine-bundle": "^1.8", "doctrine/doctrine-cache-bundle": "^1.3.5", - "doctrine/mongodb-odm": "^2.0@rc", - "doctrine/mongodb-odm-bundle": "^4.0@rc", + "doctrine/mongodb-odm": "^2.0", + "doctrine/mongodb-odm-bundle": "^4.0", "doctrine/orm": "^2.6.3", "elasticsearch/elasticsearch": "^6.0", "friendsofsymfony/user-bundle": "^2.2@dev", @@ -89,8 +89,7 @@ }, "conflict": { "doctrine/common": "<2.7", - "doctrine/mongodb-odm": "<2.0", - "doctrine/mongodb-odm-bundle": "4.0.0-RC2" + "doctrine/mongodb-odm": "<2.0" }, "suggest": { "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", From b501e3939153d2d05aa66bfe8ac21e835df6379a Mon Sep 17 00:00:00 2001 From: Teoh Han Hui Date: Thu, 3 Oct 2019 19:45:19 +0200 Subject: [PATCH 09/17] Add test for updating embedded relation using plain identifier --- features/json/relation.feature | 228 ++++++++++++++++++ features/main/relation.feature | 130 ---------- .../Bundle/Resources/config/metadata/xml.xml | 2 +- src/Serializer/AbstractItemNormalizer.php | 3 +- .../TestBundle/Document/RelatedDummy.php | 4 +- .../TestBundle/Entity/RelatedDummy.php | 4 +- 6 files changed, 236 insertions(+), 135 deletions(-) create mode 100644 features/json/relation.feature diff --git a/features/json/relation.feature b/features/json/relation.feature new file mode 100644 index 00000000000..7c39dc75f71 --- /dev/null +++ b/features/json/relation.feature @@ -0,0 +1,228 @@ +Feature: JSON relations support + In order to use a hypermedia API + As a client software developer + I need to be able to update relations between resources + + @createSchema + Scenario: Create a third level + When I add "Content-Type" header equal to "application/json" + And I send a "POST" request to "/third_levels" with body: + """ + { + "level": 3 + } + """ + Then the response status code should be 201 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/ThirdLevel", + "@id": "/third_levels/1", + "@type": "ThirdLevel", + "fourthLevel": null, + "badFourthLevel": null, + "id": 1, + "level": 3, + "test": true + } + """ + + Scenario: Create a new relation + When I add "Content-Type" header equal to "application/json" + And I send a "POST" request to "/relation_embedders" with body: + """ + { + "anotherRelated": { + "symfony": "laravel" + } + } + """ + Then the response status code should be 201 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/RelationEmbedder", + "@id": "/relation_embedders/1", + "@type": "RelationEmbedder", + "krondstadt": "Krondstadt", + "anotherRelated": { + "@id": "/related_dummies/1", + "@type": "https://schema.org/Product", + "symfony": "laravel", + "thirdLevel": null + }, + "related": null + } + """ + + Scenario: Update the relation with a new one + When I add "Content-Type" header equal to "application/json" + And I send a "PUT" request to "/relation_embedders/1" with body: + """ + { + "anotherRelated": { + "symfony": "laravel2" + } + } + """ + Then the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/RelationEmbedder", + "@id": "/relation_embedders/1", + "@type": "RelationEmbedder", + "krondstadt": "Krondstadt", + "anotherRelated": { + "@id": "/related_dummies/2", + "@type": "https://schema.org/Product", + "symfony": "laravel2", + "thirdLevel": null + }, + "related": null + } + """ + + Scenario: Update an embedded relation using an IRI + When I add "Content-Type" header equal to "application/json" + And I send a "PUT" request to "/relation_embedders/1" with body: + """ + { + "anotherRelated": { + "id": "/related_dummies/1", + "symfony": "API Platform" + } + } + """ + Then the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/RelationEmbedder", + "@id": "/relation_embedders/1", + "@type": "RelationEmbedder", + "krondstadt": "Krondstadt", + "anotherRelated": { + "@id": "/related_dummies/1", + "@type": "https://schema.org/Product", + "symfony": "API Platform", + "thirdLevel": null + }, + "related": null + } + """ + + Scenario: Update an embedded relation using plain identifiers + When I add "Content-Type" header equal to "application/json" + And I send a "PUT" request to "/relation_embedders/1" with body: + """ + { + "anotherRelated": { + "id": 1, + "symfony": "API Platform 2" + } + } + """ + Then the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/RelationEmbedder", + "@id": "/relation_embedders/1", + "@type": "RelationEmbedder", + "krondstadt": "Krondstadt", + "anotherRelated": { + "@id": "/related_dummies/1", + "@type": "https://schema.org/Product", + "symfony": "API Platform 2", + "thirdLevel": null + }, + "related": null + } + """ + + Scenario: Create a related dummy with a relation + When I add "Content-Type" header equal to "application/json" + And I send a "POST" request to "/related_dummies" with body: + """ + { + "thirdLevel": "1" + } + """ + Then the response status code should be 201 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/RelatedDummy", + "@id": "/related_dummies/3", + "@type": "https://schema.org/Product", + "id": 3, + "name": null, + "symfony": "symfony", + "dummyDate": null, + "thirdLevel": { + "@id": "/third_levels/1", + "@type": "ThirdLevel", + "fourthLevel": null + }, + "relatedToDummyFriend": [], + "dummyBoolean": null, + "embeddedDummy": [], + "age": null + } + """ + + Scenario: Passing a (valid) plain identifier on a relation + When I add "Content-Type" header equal to "application/json" + And I send a "POST" request to "/dummies" with body: + """ + { + "relatedDummy": "1", + "relatedDummies": [ + "1" + ], + "name": "Dummy with plain relations" + } + """ + Then the response status code should be 201 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/Dummy", + "@id": "/dummies/1", + "@type": "Dummy", + "description": null, + "dummy": null, + "dummyBoolean": null, + "dummyDate": null, + "dummyFloat": null, + "dummyPrice": null, + "relatedDummy": "/related_dummies/1", + "relatedDummies": [ + "/related_dummies/1" + ], + "jsonData": [], + "arrayData": [], + "name_converted": null, + "relatedOwnedDummy": null, + "relatedOwningDummy": null, + "id": 1, + "name": "Dummy with plain relations", + "alias": null, + "foo": null + } + """ diff --git a/features/main/relation.feature b/features/main/relation.feature index 8ad186cf050..fa3fe30c50a 100644 --- a/features/main/relation.feature +++ b/features/main/relation.feature @@ -365,66 +365,6 @@ Feature: Relations support And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - Scenario: Create a new relation (json) - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "anotherRelated": { - "symfony": "laravel" - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/3", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": { - "@id": "/related_dummies/4", - "@type": "https://schema.org/Product", - "symfony": "laravel", - "thirdLevel": null - }, - "related": null - } - """ - - Scenario: Update the relation with a new one (json) - When I add "Content-Type" header equal to "application/json" - And I send a "PUT" request to "/relation_embedders/3" with body: - """ - { - "anotherRelated": { - "symfony": "laravel2" - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/3", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": { - "@id": "/related_dummies/5", - "@type": "https://schema.org/Product", - "symfony": "laravel2", - "thirdLevel": null - }, - "related": null - } - """ - Scenario: Update an embedded relation When I add "Content-Type" header equal to "application/ld+json" And I send a "PUT" request to "/relation_embedders/2" with body: @@ -456,37 +396,6 @@ Feature: Relations support } """ - Scenario: Create a related dummy with a relation (json) - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/related_dummies" with body: - """ - {"thirdLevel": "1"} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedDummy", - "@id": "/related_dummies/6", - "@type": "https://schema.org/Product", - "id": 6, - "name": null, - "symfony": "symfony", - "dummyDate": null, - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": null - }, - "relatedToDummyFriend": [], - "dummyBoolean": null, - "embeddedDummy": [], - "age": null - } - """ - Scenario: Issue #1222 Given there are people having pets When I add "Content-Type" header equal to "application/ld+json" @@ -519,45 +428,6 @@ Feature: Relations support } """ - Scenario: Passing a (valid) plain identifier on a relation - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/dummies" with body: - """ - { - "relatedDummy": "1", - "relatedDummies": ["1"], - "name": "Dummy with plain relations" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context":"/contexts/Dummy", - "@id":"/dummies/2", - "@type":"Dummy", - "description":null, - "dummy":null, - "dummyBoolean":null, - "dummyDate":null, - "dummyFloat":null, - "dummyPrice":null, - "relatedDummy":"/related_dummies/1", - "relatedDummies":["/related_dummies/1"], - "jsonData":[], - "arrayData":[], - "name_converted":null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id":2, - "name":"Dummy with plain relations", - "alias":null, - "foo":null - } - """ - Scenario: Eager load relations should not be duplicated Given there is an order with same customer and recipient When I add "Content-Type" header equal to "application/ld+json" diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml index 9836ed19d6a..157a764d913 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml @@ -26,7 +26,7 @@ - + diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 6b7faacc1e1..e8be0fe6691 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -472,14 +472,13 @@ protected function denormalizeRelation(string $attributeName, PropertyMetadata $ } /** - * Gets a valid context for property name collection / property metadata factories. + * Gets the options for the property name collection / property metadata factories. */ protected function getFactoryOptions(array $context): array { $options = []; if (isset($context[self::GROUPS])) { - /* @see https://github.com/symfony/symfony/blob/v4.2.6/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php */ $options['serializer_groups'] = $context[self::GROUPS]; } diff --git a/tests/Fixtures/TestBundle/Document/RelatedDummy.php b/tests/Fixtures/TestBundle/Document/RelatedDummy.php index 04f0871ea2f..dcf01f6e5de 100644 --- a/tests/Fixtures/TestBundle/Document/RelatedDummy.php +++ b/tests/Fixtures/TestBundle/Document/RelatedDummy.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document; +use ApiPlatform\Core\Annotation\ApiProperty; use ApiPlatform\Core\Annotation\ApiResource; use ApiPlatform\Core\Annotation\ApiSubresource; use Doctrine\Common\Collections\ArrayCollection; @@ -32,9 +33,10 @@ class RelatedDummy extends ParentDummy { /** + * @ApiProperty(writable=false) * @ApiSubresource * @ODM\Id(strategy="INCREMENT", type="integer") - * @Groups({"friends"}) + * @Groups({"chicago", "friends"}) */ private $id; diff --git a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php index 8802cdfc054..02f13cf929e 100644 --- a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php +++ b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity; +use ApiPlatform\Core\Annotation\ApiProperty; use ApiPlatform\Core\Annotation\ApiResource; use ApiPlatform\Core\Annotation\ApiSubresource; use Doctrine\Common\Collections\ArrayCollection; @@ -31,11 +32,12 @@ class RelatedDummy extends ParentDummy { /** + * @ApiProperty(writable=false) * @ApiSubresource * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") - * @Groups({"friends"}) + * @Groups({"chicago", "friends"}) */ private $id; From b77e7b38fecf530651c02dbccd5e5516c3331e41 Mon Sep 17 00:00:00 2001 From: Balint Zoltan Date: Thu, 3 Oct 2019 00:00:02 +0300 Subject: [PATCH 10/17] Allow multiple elasticsearch queries to be set Fixes #3139 --- .../Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php b/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php index e12ee385d76..fe6f2bc185e 100644 --- a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php +++ b/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php @@ -83,7 +83,7 @@ public function apply(array $clauseBody, string $resourceClass, ?string $operati return $clauseBody; } - return array_merge($clauseBody, [ + return array_merge_recursive($clauseBody, [ 'bool' => [ 'must' => $searches, ], From c5e9168d31c1cd4e8eda46004c907b42d4195a55 Mon Sep 17 00:00:00 2001 From: Maks Rafalko Date: Fri, 4 Oct 2019 12:57:04 +0300 Subject: [PATCH 11/17] Update src/Swagger/Serializer/DocumentationNormalizer.php Co-Authored-By: Antoine Bluchet --- src/Swagger/Serializer/DocumentationNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index fcb656a664b..a041d1989cd 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -543,7 +543,7 @@ private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObj return $pathOperation; } - if (!$this->isBodyParameterAlreadyExists($pathOperation['parameters'] ?? [])) { + if (!$this->hasBodyParameter($pathOperation['parameters'] ?? [])) { $pathOperation['parameters'][] = [ 'name' => lcfirst($resourceShortName), 'in' => 'body', From ddfec0973d2d6cb1c71ecb709d881953fb093734 Mon Sep 17 00:00:00 2001 From: Maks Rafalko Date: Fri, 4 Oct 2019 12:57:34 +0300 Subject: [PATCH 12/17] Update src/Swagger/Serializer/DocumentationNormalizer.php Co-Authored-By: Antoine Bluchet --- src/Swagger/Serializer/DocumentationNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index a041d1989cd..7fdc800b180 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -554,7 +554,7 @@ private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObj return $pathOperation; } - private function isBodyParameterAlreadyExists(array $parameters): bool + private function hasBodyParameter(array $parameters): bool { foreach ($parameters as $parameter) { if (\array_key_exists('in', $parameter) && 'body' === $parameter['in']) { From 536893059d83925df5b7bbeb887742e9db73a8e7 Mon Sep 17 00:00:00 2001 From: Robin C Date: Tue, 1 Oct 2019 22:22:57 +0200 Subject: [PATCH 13/17] Update thrown error code to 404. I overlooked the behat test which fails since I didn't update the error code which is thrown by the exception. --- .../RamseyUuid/Identifier/Normalizer/UuidNormalizer.php | 8 +++----- src/EventListener/ReadListener.php | 2 +- tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php b/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php index 20478c33a68..8bac4e27f52 100644 --- a/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php +++ b/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php @@ -13,10 +13,10 @@ namespace ApiPlatform\Core\Bridge\RamseyUuid\Identifier\Normalizer; +use ApiPlatform\Core\Exception\InvalidIdentifierException; use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; -use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; /** @@ -28,22 +28,20 @@ final class UuidNormalizer implements DenormalizerInterface { /** * {@inheritdoc} - * - * @throws NotNormalizableValueException Occurs when the identifier format is invalid */ public function denormalize($data, $class, $format = null, array $context = []) { try { return Uuid::fromString($data); } catch (InvalidUuidStringException $e) { - throw new NotNormalizableValueException('Not found, because of an invalid identifier format', $e->getCode(), $e); + throw new InvalidIdentifierException($e->getMessage(), $e->getCode(), $e); } } /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization($data, $type, $format = null) { return is_a($type, UuidInterface::class, true); } diff --git a/src/EventListener/ReadListener.php b/src/EventListener/ReadListener.php index 48698755754..6d97441197f 100644 --- a/src/EventListener/ReadListener.php +++ b/src/EventListener/ReadListener.php @@ -109,7 +109,7 @@ public function onKernelRequest(GetResponseEvent $event): void $data = $this->getSubresourceData($identifiers, $attributes, $context); } } catch (InvalidIdentifierException $e) { - throw new NotFoundHttpException('Not found, because of an invalid identifier configuration', $e); + throw new NotFoundHttpException('Invalid identifier value or configuration.', $e); } if (null === $data) { diff --git a/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php b/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php index 2656dd7a2c5..3a74e6cfb55 100644 --- a/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php +++ b/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php @@ -14,9 +14,9 @@ namespace ApiPlatform\Core\Tests\Bridge\RamseyUuid\Normalizer; use ApiPlatform\Core\Bridge\RamseyUuid\Identifier\Normalizer\UuidNormalizer; +use ApiPlatform\Core\Exception\InvalidIdentifierException; use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; -use Symfony\Component\Serializer\Exception\NotNormalizableValueException; class UuidNormalizerTest extends TestCase { @@ -37,7 +37,7 @@ public function testNoSupportDenormalizeUuid() public function testFailDenormalizeUuid() { - $this->expectException(NotNormalizableValueException::class); + $this->expectException(InvalidIdentifierException::class); $uuid = 'notanuuid'; $normalizer = new UuidNormalizer(); From a807e84f21066a84589d9497326cd61a6eb8f0af Mon Sep 17 00:00:00 2001 From: Maxime Helias Date: Sat, 5 Oct 2019 15:22:14 +0200 Subject: [PATCH 14/17] non-int value in the execute command will be deprecated --- src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php b/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php index 2a44b68c106..71722613f13 100644 --- a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php +++ b/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php @@ -100,5 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } else { $output->writeln($content); } + + return 0; } } From 8011f35aae8e5ab2c16682fd46adae3d760c5f29 Mon Sep 17 00:00:00 2001 From: Anto Date: Sun, 6 Oct 2019 09:19:31 +0200 Subject: [PATCH 15/17] Fix typos in ApiTestCase --- src/Bridge/Symfony/Bundle/Test/ApiTestCase.php | 2 +- src/Bridge/Symfony/Bundle/Test/Response.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php b/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php index e1269ddee57..caef5608d2c 100644 --- a/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php +++ b/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php @@ -71,7 +71,7 @@ protected static function createClient(array $kernelOptions = [], array $default } /** - * Finds the IRI of a resource item macthing the resource class and the specified criteria. + * Finds the IRI of a resource item matching the resource class and the specified criteria. */ protected function findIriBy(string $resourceClass, array $criteria): ?string { diff --git a/src/Bridge/Symfony/Bundle/Test/Response.php b/src/Bridge/Symfony/Bundle/Test/Response.php index 36d1dcbec1e..663bfbcc833 100644 --- a/src/Bridge/Symfony/Bundle/Test/Response.php +++ b/src/Bridge/Symfony/Bundle/Test/Response.php @@ -170,7 +170,7 @@ public function getKernelResponse(): HttpFoundationResponse } /** - * Returns the internal BrowserKit reponse. + * Returns the internal BrowserKit response. */ public function getBrowserKitResponse(): BrowserKitResponse { From e25cfde2df312881fd8fff18cc3d12a682bd6c1e Mon Sep 17 00:00:00 2001 From: ReynierPM Date: Mon, 7 Oct 2019 07:59:16 -0400 Subject: [PATCH 16/17] Fixed dependency issue when SecurityBundle is not installed (#3149) * Fixed dependency issue when SecurityBundle is not installed * Fix tests * Move condition in SecurityPostDenormalizeStage --- .../Bundle/Resources/config/graphql.xml | 4 +-- .../Stage/SecurityPostDenormalizeStage.php | 7 ++++- src/GraphQl/Resolver/Stage/SecurityStage.php | 7 ++++- .../SecurityPostDenormalizeStageTest.php | 30 +++++++++++++++++++ .../Resolver/Stage/SecurityStageTest.php | 30 +++++++++++++++++++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml b/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml index 701a7ac9878..07617218bf5 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml @@ -53,12 +53,12 @@ - + - + diff --git a/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php b/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php index 18a1e2ad18a..d6ab052d085 100644 --- a/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php +++ b/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php @@ -30,7 +30,7 @@ final class SecurityPostDenormalizeStage implements SecurityPostDenormalizeStage private $resourceMetadataFactory; private $resourceAccessChecker; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourceAccessCheckerInterface $resourceAccessChecker) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ?ResourceAccessCheckerInterface $resourceAccessChecker) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->resourceAccessChecker = $resourceAccessChecker; @@ -44,6 +44,7 @@ public function __invoke(string $resourceClass, string $operationName, array $co $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); $isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'security_post_denormalize', null, true); + if (null === $isGranted) { // Backward compatibility $isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'access_control', null, true); @@ -52,6 +53,10 @@ public function __invoke(string $resourceClass, string $operationName, array $co } } + if (null !== $isGranted && null === $this->resourceAccessChecker) { + throw new \LogicException('Cannot check security expression when SecurityBundle is not installed. Try running "composer require symfony/security-bundle".'); + } + if (null === $isGranted || $this->resourceAccessChecker->isGranted($resourceClass, (string) $isGranted, $context['extra_variables'])) { return; } diff --git a/src/GraphQl/Resolver/Stage/SecurityStage.php b/src/GraphQl/Resolver/Stage/SecurityStage.php index 36fa2e37619..b3afa035618 100644 --- a/src/GraphQl/Resolver/Stage/SecurityStage.php +++ b/src/GraphQl/Resolver/Stage/SecurityStage.php @@ -30,7 +30,7 @@ final class SecurityStage implements SecurityStageInterface private $resourceMetadataFactory; private $resourceAccessChecker; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourceAccessCheckerInterface $resourceAccessChecker) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ?ResourceAccessCheckerInterface $resourceAccessChecker) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->resourceAccessChecker = $resourceAccessChecker; @@ -44,6 +44,11 @@ public function __invoke(string $resourceClass, string $operationName, array $co $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); $isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'security', null, true); + + if (null !== $isGranted && null === $this->resourceAccessChecker) { + throw new \LogicException('Cannot check security expression when SecurityBundle is not installed. Try running "composer require symfony/security-bundle".'); + } + if (null === $isGranted || $this->resourceAccessChecker->isGranted($resourceClass, (string) $isGranted, $context['extra_variables'])) { return; } diff --git a/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php b/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php index 21f11dda5ce..301963c836a 100644 --- a/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php +++ b/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php @@ -115,4 +115,34 @@ public function testNotGranted(): void 'extra_variables' => $extraVariables, ]); } + + public function testNoSecurityBundleInstalled(): void + { + $this->securityPostDenormalizeStage = new SecurityPostDenormalizeStage($this->resourceMetadataFactoryProphecy->reveal(), null); + + $operationName = 'item_query'; + $resourceClass = 'myResource'; + $isGranted = 'not_granted'; + $resourceMetadata = (new ResourceMetadata())->withGraphql([ + $operationName => ['security_post_denormalize' => $isGranted], + ]); + $this->resourceMetadataFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + + $this->expectException(\LogicException::class); + + ($this->securityPostDenormalizeStage)($resourceClass, 'item_query', []); + } + + public function testNoSecurityBundleInstalledNoExpression(): void + { + $this->securityPostDenormalizeStage = new SecurityPostDenormalizeStage($this->resourceMetadataFactoryProphecy->reveal(), null); + + $resourceClass = 'myResource'; + $resourceMetadata = new ResourceMetadata(); + $this->resourceMetadataFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + + $this->resourceAccessCheckerProphecy->isGranted(Argument::any())->shouldNotBeCalled(); + + ($this->securityPostDenormalizeStage)($resourceClass, 'item_query', []); + } } diff --git a/tests/GraphQl/Resolver/Stage/SecurityStageTest.php b/tests/GraphQl/Resolver/Stage/SecurityStageTest.php index 4b18a7345fd..a49207679a2 100644 --- a/tests/GraphQl/Resolver/Stage/SecurityStageTest.php +++ b/tests/GraphQl/Resolver/Stage/SecurityStageTest.php @@ -96,4 +96,34 @@ public function testNotGranted(): void 'extra_variables' => $extraVariables, ]); } + + public function testNoSecurityBundleInstalled(): void + { + $this->securityStage = new SecurityStage($this->resourceMetadataFactoryProphecy->reveal(), null); + + $operationName = 'item_query'; + $resourceClass = 'myResource'; + $isGranted = 'not_granted'; + $resourceMetadata = (new ResourceMetadata())->withGraphql([ + $operationName => ['security' => $isGranted], + ]); + $this->resourceMetadataFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + + $this->expectException(\LogicException::class); + + ($this->securityStage)($resourceClass, 'item_query', []); + } + + public function testNoSecurityBundleInstalledNoExpression(): void + { + $this->securityStage = new SecurityStage($this->resourceMetadataFactoryProphecy->reveal(), null); + + $resourceClass = 'myResource'; + $resourceMetadata = new ResourceMetadata(); + $this->resourceMetadataFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + + $this->resourceAccessCheckerProphecy->isGranted(Argument::any())->shouldNotBeCalled(); + + ($this->securityStage)($resourceClass, 'item_query', []); + } } From c5b46f6fa848684713336a4cefad77150920a3a2 Mon Sep 17 00:00:00 2001 From: SinithH <45849343+SinithH@users.noreply.github.com> Date: Fri, 11 Oct 2019 13:39:13 +0530 Subject: [PATCH 17/17] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1fa17ab58..0558a18b0cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ * Add basic infrastructure for cursor-based pagination (#2532) * Change ExistsFilter syntax to `exists[property]`, old syntax still supported see #2243, fixes it's behavior on GraphQL (also related #2640). * Pagination with subresources (#2698) -* Improve search filter id's managment (#1844) +* Improve search filter id's management (#1844) * Add support of name converter in filters (#2751, #2897), filter signature in abstract methods has changed see b42dfd198b1644904fd6a684ab2cedaf530254e3 * Ability to change the Vary header via `cacheHeaders` attributes of a resource (#2758) * Ability to use the Query object in a paginator (#2493) @@ -149,7 +149,7 @@ Please read #2825 if you have issues with the behavior of Readable/Writable Link ## 2.4.2 -* Fix a dependency injection injection problem in `FilterEagerLoadingExtension` +* Fix a dependency injection problem in `FilterEagerLoadingExtension` * Improve performance by adding a `NoOpScalarNormalizer` handling scalar values ## 2.4.1