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'