Skip to content

Commit 362b136

Browse files
authored
Allow checking root model in @can directive
1 parent 2d59623 commit 362b136

File tree

3 files changed

+62
-7
lines changed

3 files changed

+62
-7
lines changed

docs/master/api-reference/directives.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ directive @can(
621621
Check the policy against the model instances returned by the field resolver.
622622
Only use this if the field does not mutate data, it is run before checking.
623623
624-
Mutually exclusive with `query` and `find`.
624+
Mutually exclusive with `query`, `find`, and `root`.
625625
"""
626626
resolved: Boolean! = false
627627

@@ -648,7 +648,7 @@ directive @can(
648648
Query for specific model instances to check the policy against, using arguments
649649
with directives that add constraints to the query builder, such as `@eq`.
650650
651-
Mutually exclusive with `resolved` and `find`.
651+
Mutually exclusive with `resolved`, `find`, and `root`.
652652
"""
653653
query: Boolean! = false
654654

@@ -663,14 +663,21 @@ directive @can(
663663
664664
You may pass the string in dot notation to use nested inputs.
665665
666-
Mutually exclusive with `resolved` and `query`.
666+
Mutually exclusive with `resolved`, `query`, and `root`.
667667
"""
668668
find: String
669669

670670
"""
671671
Should the query fail when the models of `find` were not found?
672672
"""
673673
findOrFail: Boolean! = true
674+
675+
"""
676+
If your policy should check against the root value.
677+
678+
Mutually exclusive with `resolved`, `query`, and `find`.
679+
"""
680+
root: Boolean! = false
674681
) repeatable on FIELD_DEFINITION
675682

676683
"""

src/Auth/CanDirective.php

+15-4
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static function definition(): string
5353
Check the policy against the model instances returned by the field resolver.
5454
Only use this if the field does not mutate data, it is run before checking.
5555
56-
Mutually exclusive with `query` and `find`.
56+
Mutually exclusive with `query`, `find`, and `root`.
5757
"""
5858
resolved: Boolean! = false
5959
@@ -80,7 +80,7 @@ public static function definition(): string
8080
Query for specific model instances to check the policy against, using arguments
8181
with directives that add constraints to the query builder, such as `@eq`.
8282
83-
Mutually exclusive with `resolved` and `find`.
83+
Mutually exclusive with `resolved`, `find`, and `root`.
8484
"""
8585
query: Boolean! = false
8686
@@ -95,14 +95,21 @@ public static function definition(): string
9595
9696
You may pass the string in dot notation to use nested inputs.
9797
98-
Mutually exclusive with `resolved` and `query`.
98+
Mutually exclusive with `resolved`, `query`, and `root`.
9999
"""
100100
find: String
101101
102102
"""
103103
Should the query fail when the models of `find` were not found?
104104
"""
105105
findOrFail: Boolean! = true
106+
107+
"""
108+
If your policy should check against the root value.
109+
110+
Mutually exclusive with `resolved`, `query`, and `find`.
111+
"""
112+
root: Boolean! = false
106113
) repeatable on FIELD_DEFINITION
107114
108115
"""
@@ -167,6 +174,10 @@ protected function modelsToCheck(mixed $root, array $args, GraphQLContext $conte
167174
->get();
168175
}
169176

177+
if ($this->directiveArgValue('root')) {
178+
return [$root];
179+
}
180+
170181
if ($find = $this->directiveArgValue('find')) {
171182
$findValue = Arr::get($args, $find)
172183
?? throw self::missingKeyToFindModel($find);
@@ -276,7 +287,7 @@ protected function buildCheckArguments(array $args): array
276287

277288
public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefinitionNode &$fieldDefinition, ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode &$parentType): void
278289
{
279-
$this->validateMutuallyExclusiveArguments(['resolved', 'query', 'find']);
290+
$this->validateMutuallyExclusiveArguments(['resolved', 'query', 'find', 'root']);
280291

281292
if ($this->directiveHasArgument('resolved') && $parentType->name->value === RootType::MUTATION) {
282293
throw self::resolvedIsUnsafeInMutations($fieldDefinition->name->value);

tests/Unit/Auth/CanDirectiveTest.php

+37
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,42 @@ public function testInjectArgsPassesClientArgumentToPolicy(): void
281281
]);
282282
}
283283

284+
public function testChecksAgainstRootModel(): void
285+
{
286+
$this->be(new User());
287+
288+
$this->mockResolver(fn (): User => $this->resolveUser());
289+
290+
$this->schema = /** @lang GraphQL */ '
291+
type Query {
292+
user(foo: String): User! @mock
293+
}
294+
295+
type User {
296+
name: String @can(ability: "view", root: true)
297+
email: String @can(ability: "superAdminOnly", root: true)
298+
}
299+
';
300+
301+
$this->graphQL(/** @lang GraphQL */ '
302+
{
303+
user(foo: "bar") {
304+
name
305+
email
306+
}
307+
}
308+
')->assertJson([
309+
'data' => [
310+
'user' => [
311+
'name' => 'foo',
312+
'email' => null,
313+
],
314+
],
315+
])->assertJsonFragment([
316+
'message' => 'Only super admins allowed',
317+
]);
318+
}
319+
284320
public function testInjectedArgsAndStaticArgs(): void
285321
{
286322
$this->be(new User());
@@ -322,6 +358,7 @@ public static function resolveUser(): User
322358
{
323359
$user = new User();
324360
$user->name = 'foo';
361+
$user->email = '[email protected]';
325362

326363
return $user;
327364
}

0 commit comments

Comments
 (0)