Skip to content

Commit

Permalink
Allow checking root model in @can directive
Browse files Browse the repository at this point in the history
  • Loading branch information
awarrenlove authored Oct 6, 2023
1 parent 2d59623 commit 362b136
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 7 deletions.
13 changes: 10 additions & 3 deletions docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ directive @can(
Check the policy against the model instances returned by the field resolver.
Only use this if the field does not mutate data, it is run before checking.
Mutually exclusive with `query` and `find`.
Mutually exclusive with `query`, `find`, and `root`.
"""
resolved: Boolean! = false

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

Expand All @@ -663,14 +663,21 @@ directive @can(
You may pass the string in dot notation to use nested inputs.
Mutually exclusive with `resolved` and `query`.
Mutually exclusive with `resolved`, `query`, and `root`.
"""
find: String

"""
Should the query fail when the models of `find` were not found?
"""
findOrFail: Boolean! = true

"""
If your policy should check against the root value.
Mutually exclusive with `resolved`, `query`, and `find`.
"""
root: Boolean! = false
) repeatable on FIELD_DEFINITION

"""
Expand Down
19 changes: 15 additions & 4 deletions src/Auth/CanDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static function definition(): string
Check the policy against the model instances returned by the field resolver.
Only use this if the field does not mutate data, it is run before checking.
Mutually exclusive with `query` and `find`.
Mutually exclusive with `query`, `find`, and `root`.
"""
resolved: Boolean! = false
Expand All @@ -80,7 +80,7 @@ public static function definition(): string
Query for specific model instances to check the policy against, using arguments
with directives that add constraints to the query builder, such as `@eq`.
Mutually exclusive with `resolved` and `find`.
Mutually exclusive with `resolved`, `find`, and `root`.
"""
query: Boolean! = false
Expand All @@ -95,14 +95,21 @@ public static function definition(): string
You may pass the string in dot notation to use nested inputs.
Mutually exclusive with `resolved` and `query`.
Mutually exclusive with `resolved`, `query`, and `root`.
"""
find: String
"""
Should the query fail when the models of `find` were not found?
"""
findOrFail: Boolean! = true
"""
If your policy should check against the root value.
Mutually exclusive with `resolved`, `query`, and `find`.
"""
root: Boolean! = false
) repeatable on FIELD_DEFINITION
"""
Expand Down Expand Up @@ -167,6 +174,10 @@ protected function modelsToCheck(mixed $root, array $args, GraphQLContext $conte
->get();
}

if ($this->directiveArgValue('root')) {
return [$root];
}

if ($find = $this->directiveArgValue('find')) {
$findValue = Arr::get($args, $find)
?? throw self::missingKeyToFindModel($find);
Expand Down Expand Up @@ -276,7 +287,7 @@ protected function buildCheckArguments(array $args): array

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

if ($this->directiveHasArgument('resolved') && $parentType->name->value === RootType::MUTATION) {
throw self::resolvedIsUnsafeInMutations($fieldDefinition->name->value);
Expand Down
37 changes: 37 additions & 0 deletions tests/Unit/Auth/CanDirectiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,42 @@ public function testInjectArgsPassesClientArgumentToPolicy(): void
]);
}

public function testChecksAgainstRootModel(): void
{
$this->be(new User());

$this->mockResolver(fn (): User => $this->resolveUser());

$this->schema = /** @lang GraphQL */ '
type Query {
user(foo: String): User! @mock
}
type User {
name: String @can(ability: "view", root: true)
email: String @can(ability: "superAdminOnly", root: true)
}
';

$this->graphQL(/** @lang GraphQL */ '
{
user(foo: "bar") {
name
email
}
}
')->assertJson([
'data' => [
'user' => [
'name' => 'foo',
'email' => null,
],
],
])->assertJsonFragment([
'message' => 'Only super admins allowed',
]);
}

public function testInjectedArgsAndStaticArgs(): void
{
$this->be(new User());
Expand Down Expand Up @@ -322,6 +358,7 @@ public static function resolveUser(): User
{
$user = new User();
$user->name = 'foo';
$user->email = '[email protected]';

return $user;
}
Expand Down

0 comments on commit 362b136

Please sign in to comment.