diff --git a/features/graphql/query.feature b/features/graphql/query.feature index cbf2bdfb892..69fcdf40326 100644 --- a/features/graphql/query.feature +++ b/features/graphql/query.feature @@ -664,3 +664,16 @@ Feature: GraphQL query support And the header "Content-Type" should be equal to "application/json" And the JSON node "data.dummyDifferentGraphQlSerializationGroup.name" should be equal to "Name #1" And the JSON node "data.dummyDifferentGraphQlSerializationGroup.title" should be equal to "Title #1" + + Scenario: Call security after resolver + When I send the following GraphQL request: + """ + { + getSecurityAfterResolver(id: "/security_after_resolvers/1") { + name + } + } + """ + Then the response status code should be 200 + And the header "Content-Type" should be equal to "application/json" + And the JSON node "data.getSecurityAfterResolver.name" should be equal to "test" diff --git a/src/Metadata/GraphQl/Operation.php b/src/Metadata/GraphQl/Operation.php index ee78af138ba..6f556b37a69 100644 --- a/src/Metadata/GraphQl/Operation.php +++ b/src/Metadata/GraphQl/Operation.php @@ -41,6 +41,8 @@ public function __construct( protected ?array $extraArgs = null, protected ?array $links = null, protected ?bool $validateAfterResolver = null, + protected ?string $securityAfterResolver = null, + protected ?string $securityMessageAfterResolver = null, ?string $shortName = null, ?string $class = null, @@ -209,4 +211,30 @@ public function withValidateAfterResolver(bool $validateAfterResolver = true): s return $self; } + + public function getSecurityAfterResolver(): ?string + { + return $this->securityAfterResolver; + } + + public function withSecurityAfterResolver(string $securityAfterResolver): self + { + $self = clone $this; + $self->securityAfterResolver = $securityAfterResolver; + + return $self; + } + + public function getSecurityMessageAfterResolver(): ?string + { + return $this->securityMessageAfterResolver; + } + + public function withSecurityMessageAfterResolver(string $securityMessageAfterResolver): self + { + $self = clone $this; + $self->securityMessageAfterResolver = $securityMessageAfterResolver; + + return $self; + } } diff --git a/src/Metadata/GraphQl/Query.php b/src/Metadata/GraphQl/Query.php index 16f9ee66b39..e426f98acfa 100644 --- a/src/Metadata/GraphQl/Query.php +++ b/src/Metadata/GraphQl/Query.php @@ -24,6 +24,8 @@ public function __construct( ?array $args = null, ?array $extraArgs = null, ?array $links = null, + ?string $securityAfterResolver = null, + ?string $securityMessageAfterResolver = null, ?string $shortName = null, ?string $class = null, @@ -79,6 +81,8 @@ public function __construct( args: $args, extraArgs: $extraArgs, links: $links, + securityAfterResolver: $securityAfterResolver, + securityMessageAfterResolver: $securityMessageAfterResolver, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, diff --git a/src/Metadata/GraphQl/QueryCollection.php b/src/Metadata/GraphQl/QueryCollection.php index a78c0ed7e41..23d3d7a1029 100644 --- a/src/Metadata/GraphQl/QueryCollection.php +++ b/src/Metadata/GraphQl/QueryCollection.php @@ -25,6 +25,8 @@ public function __construct( ?array $args = null, ?array $extraArgs = null, ?array $links = null, + ?string $securityAfterResolver = null, + ?string $securityMessageAfterResolver = null, ?string $shortName = null, ?string $class = null, @@ -80,6 +82,8 @@ public function __construct( args: $args, extraArgs: $extraArgs, links: $links, + securityAfterResolver: $securityAfterResolver, + securityMessageAfterResolver: $securityMessageAfterResolver, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, diff --git a/src/Metadata/GraphQl/Subscription.php b/src/Metadata/GraphQl/Subscription.php index 9ae13d28511..8f3020f7c60 100644 --- a/src/Metadata/GraphQl/Subscription.php +++ b/src/Metadata/GraphQl/Subscription.php @@ -24,6 +24,8 @@ public function __construct( ?array $args = null, ?array $extraArgs = null, ?array $links = null, + ?string $securityAfterResolver = null, + ?string $securityMessageAfterResolver = null, ?string $shortName = null, ?string $class = null, @@ -77,6 +79,8 @@ public function __construct( args: $args, extraArgs: $extraArgs, links: $links, + securityAfterResolver: $securityAfterResolver, + securityMessageAfterResolver: $securityMessageAfterResolver, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, diff --git a/src/Symfony/Bundle/Resources/config/graphql/security.xml b/src/Symfony/Bundle/Resources/config/graphql/security.xml index 05ecdb0c009..bd88721e6ef 100644 --- a/src/Symfony/Bundle/Resources/config/graphql/security.xml +++ b/src/Symfony/Bundle/Resources/config/graphql/security.xml @@ -19,5 +19,11 @@ post_validate + + + + + after_resolver + diff --git a/src/Symfony/Security/State/AccessCheckerProvider.php b/src/Symfony/Security/State/AccessCheckerProvider.php index 69f7953dafa..85c42b002ac 100644 --- a/src/Symfony/Security/State/AccessCheckerProvider.php +++ b/src/Symfony/Security/State/AccessCheckerProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Symfony\Security\State; +use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\HttpOperation; @@ -45,6 +46,14 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $isGranted = $operation->getSecurityPostValidation(); $message = $operation->getSecurityPostValidationMessage(); break; + case 'after_resolver': + if (!$operation instanceof GraphQlOperation) { + throw new RuntimeException('Not a graphql operation'); + } + + $isGranted = $operation->getSecurityAfterResolver(); + $message = $operation->getSecurityMessageAfterResolver(); + // no break default: $isGranted = $operation->getSecurity(); $message = $operation->getSecurityMessage(); diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue6427/SecurityAfterResolver.php b/tests/Fixtures/TestBundle/ApiResource/Issue6427/SecurityAfterResolver.php new file mode 100644 index 00000000000..c94cce04c91 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue6427/SecurityAfterResolver.php @@ -0,0 +1,39 @@ + + * + * 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\Tests\Fixtures\TestBundle\ApiResource\Issue6427; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GraphQl\Query; + +#[ApiResource( + provider: [self::class, 'provide'], + graphQlOperations: [ + new Query( + resolver: 'app.graphql.query_resolver.security_after_resolver', + securityAfterResolver: "object.name == 'test'", + name: 'get' + ), + ] +)] +class SecurityAfterResolver +{ + public function __construct(public ?string $id, public ?string $name) + { + } + + public static function provide() + { + return new self('1', '1'); + } +} diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue6427/SecurityAfterResolverResolver.php b/tests/Fixtures/TestBundle/ApiResource/Issue6427/SecurityAfterResolverResolver.php new file mode 100644 index 00000000000..8b015ad8248 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue6427/SecurityAfterResolverResolver.php @@ -0,0 +1,28 @@ + + * + * 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\Tests\Fixtures\TestBundle\ApiResource\Issue6427; + +use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface; + +final class SecurityAfterResolverResolver implements QueryItemResolverInterface +{ + /** + * @param object|null $item + * @param mixed[] $context + */ + public function __invoke($item, array $context): SecurityAfterResolver + { + return new SecurityAfterResolver('1', 'test'); + } +} diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml index c0d92672909..79e50ab65c3 100644 --- a/tests/Fixtures/app/config/config_common.yml +++ b/tests/Fixtures/app/config/config_common.yml @@ -464,3 +464,8 @@ services: class: 'ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6354\CreateActivityLogResolver' tags: - name: 'api_platform.graphql.resolver' + + app.graphql.query_resolver.security_after_resolver: + class: 'ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6427\SecurityAfterResolverResolver' + tags: + - name: 'api_platform.graphql.resolver'