-
Notifications
You must be signed in to change notification settings - Fork 100
/
Copy pathAuthorizationFieldMiddleware.php
102 lines (82 loc) · 4.21 KB
/
AuthorizationFieldMiddleware.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php
declare(strict_types=1);
namespace TheCodingMachine\GraphQLite\Middlewares;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\OutputType;
use TheCodingMachine\GraphQLite\Annotations\Exceptions\IncompatibleAnnotationsException;
use TheCodingMachine\GraphQLite\Annotations\FailWith;
use TheCodingMachine\GraphQLite\Annotations\HideIfUnauthorized;
use TheCodingMachine\GraphQLite\Annotations\Logged;
use TheCodingMachine\GraphQLite\Annotations\Right;
use TheCodingMachine\GraphQLite\QueryFieldDescriptor;
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface;
use function assert;
/**
* Middleware in charge of managing "Logged" and "Right" annotations.
*/
class AuthorizationFieldMiddleware implements FieldMiddlewareInterface
{
public function __construct(
private AuthenticationServiceInterface $authenticationService,
private AuthorizationServiceInterface $authorizationService,
) {
}
public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): FieldDefinition|null
{
$annotations = $queryFieldDescriptor->getMiddlewareAnnotations();
$loggedAnnotation = $annotations->getAnnotationByType(Logged::class);
assert($loggedAnnotation === null || $loggedAnnotation instanceof Logged);
$rightAnnotation = $annotations->getAnnotationByType(Right::class);
assert($rightAnnotation === null || $rightAnnotation instanceof Right);
// Avoid wrapping resolver callback when no annotations are specified.
if (! $loggedAnnotation && ! $rightAnnotation) {
return $fieldHandler->handle($queryFieldDescriptor);
}
$failWith = $annotations->getAnnotationByType(FailWith::class);
assert($failWith === null || $failWith instanceof FailWith);
$hideIfUnauthorized = $annotations->getAnnotationByType(HideIfUnauthorized::class);
assert($hideIfUnauthorized instanceof HideIfUnauthorized || $hideIfUnauthorized === null);
if ($failWith !== null && $hideIfUnauthorized !== null) {
throw IncompatibleAnnotationsException::cannotUseFailWithAndHide();
}
// If the failWith value is null and the return type is non-nullable, we must set it to nullable.
$type = $queryFieldDescriptor->getType();
if ($failWith !== null && $type instanceof NonNull && $failWith->getValue() === null) {
$type = $type->getWrappedType();
assert($type instanceof OutputType);
$queryFieldDescriptor->setType($type);
}
// When using the same Schema instance for multiple subsequent requests, this middleware will only
// get called once, meaning #[HideIfUnauthorized] only works when Schema is used for a single request
// and then discarded. This check is to keep the latter case working.
if ($hideIfUnauthorized !== null && ! $this->isAuthorized($loggedAnnotation, $rightAnnotation)) {
return null;
}
$resolver = $queryFieldDescriptor->getResolver();
$queryFieldDescriptor->setResolver(function (...$args) use ($rightAnnotation, $loggedAnnotation, $failWith, $resolver) {
if ($this->isAuthorized($loggedAnnotation, $rightAnnotation)) {
return $resolver(...$args);
}
if ($failWith !== null) {
return $failWith->getValue();
}
if ($loggedAnnotation !== null && ! $this->authenticationService->isLogged()) {
throw MissingAuthorizationException::unauthorized();
}
throw MissingAuthorizationException::forbidden();
});
return $fieldHandler->handle($queryFieldDescriptor);
}
/**
* Checks the @Logged and @Right annotations.
*/
private function isAuthorized(Logged|null $loggedAnnotation, Right|null $rightAnnotation): bool
{
if ($loggedAnnotation !== null && ! $this->authenticationService->isLogged()) {
return false;
}
return $rightAnnotation === null || $this->authorizationService->isAllowed($rightAnnotation->getName());
}
}