Skip to content

Commit 106e305

Browse files
committed
Merge branch 'master' of github.com:thecodingmachine/graphql-controllers
2 parents 7c3d350 + 5aeb745 commit 106e305

19 files changed

+251
-30
lines changed

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,55 @@ This library allows you to write your GraphQL queries in simple to write control
1818

1919
```php
2020
use TheCodingMachine\GraphQL\Controllers\Annotations\Query;
21+
use TheCodingMachine\GraphQL\Controllers\Annotations\Mutation;
2122

2223
class UserController
2324
{
2425
/**
25-
* @Query()
26+
* @Query
2627
* @return User[]
2728
*/
2829
public function users(int $limit, int $offset): array
2930
{
3031
// Some code that returns an array of "users".
3132
// This completely replaces the "resolve" method.
3233
}
34+
35+
/**
36+
* @Mutation
37+
* @return User
38+
*/
39+
public function saveUser(UserInput $user): User
40+
{
41+
// Some code that saves a user.
42+
// This completely replaces the "resolve" method.
43+
}
44+
}
45+
```
46+
47+
There is an additional support for authentication and authorization:
48+
49+
```php
50+
use TheCodingMachine\GraphQL\Controllers\Annotations\Query;
51+
use TheCodingMachine\GraphQL\Controllers\Annotations\Mutation;
52+
53+
class UserController
54+
{
55+
/**
56+
* @Query
57+
* @Logged
58+
* @Right("CAN_VIEW_USER_LIST")
59+
* @return User[]
60+
*/
61+
public function users(int $limit, int $offset): array
62+
{
63+
//
64+
}
3365
}
3466
```
3567

3668

69+
3770
Troubleshooting
3871
---------------
3972

src/AggregateControllerQueryProvider.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Doctrine\Common\Annotations\Reader;
1515
use phpDocumentor\Reflection\Types\Integer;
1616
use TheCodingMachine\GraphQL\Controllers\Annotations\Query;
17+
use TheCodingMachine\GraphQL\Controllers\Security\AuthenticationServiceInterface;
18+
use TheCodingMachine\GraphQL\Controllers\Security\AuthorizationServiceInterface;
1719
use Youshido\GraphQL\Field\Field;
1820
use Youshido\GraphQL\Type\ListType\ListType;
1921
use Youshido\GraphQL\Type\NonNullType;
@@ -47,17 +49,27 @@ class AggregateControllerQueryProvider implements QueryProviderInterface
4749
* @var ContainerInterface
4850
*/
4951
private $container;
52+
/**
53+
* @var AuthenticationServiceInterface
54+
*/
55+
private $authenticationService;
56+
/**
57+
* @var AuthorizationServiceInterface
58+
*/
59+
private $authorizationService;
5060

5161
/**
5262
* @param string[] $controllers A list of controllers name in the container.
5363
*/
54-
public function __construct(array $controllers, ContainerInterface $container, Reader $annotationReader, TypeMapperInterface $typeMapper, HydratorInterface $hydrator)
64+
public function __construct(array $controllers, ContainerInterface $container, Reader $annotationReader, TypeMapperInterface $typeMapper, HydratorInterface $hydrator, AuthenticationServiceInterface $authenticationService, AuthorizationServiceInterface $authorizationService)
5565
{
5666
$this->controllers = $controllers;
5767
$this->container = $container;
5868
$this->annotationReader = $annotationReader;
5969
$this->typeMapper = $typeMapper;
6070
$this->hydrator = $hydrator;
71+
$this->authenticationService = $authenticationService;
72+
$this->authorizationService = $authorizationService;
6173
}
6274

6375
/**
@@ -69,7 +81,7 @@ public function getQueries(): array
6981

7082
foreach ($this->controllers as $controllerName) {
7183
$controller = $this->container->get($controllerName);
72-
$queryProvider = new ControllerQueryProvider($controller, $this->annotationReader, $this->typeMapper, $this->hydrator);
84+
$queryProvider = new ControllerQueryProvider($controller, $this->annotationReader, $this->typeMapper, $this->hydrator, $this->authenticationService, $this->authorizationService);
7385
$queryList = array_merge($queryList, $queryProvider->getQueries());
7486
}
7587

src/Annotations/Logged.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
4+
namespace TheCodingMachine\GraphQL\Controllers\Annotations;
5+
6+
/**
7+
* @Annotation
8+
* @Target({"METHOD"})
9+
*/
10+
class Logged
11+
{
12+
}

src/Annotations/Mutation.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@
88
*/
99
class Mutation
1010
{
11-
1211
}

src/Annotations/Query.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@
88
*/
99
class Query
1010
{
11-
1211
}

src/Annotations/Right.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace TheCodingMachine\GraphQL\Controllers\Annotations;
5+
6+
/**
7+
* @Annotation
8+
* @Target({"METHOD"})
9+
* @Attributes({
10+
* @Attribute("name", type = "string"),
11+
* })
12+
*/
13+
class Right
14+
{
15+
/**
16+
* @var string
17+
*/
18+
private $name;
19+
20+
/**
21+
* @param array $values
22+
*
23+
* @throws \BadMethodCallException
24+
*/
25+
public function __construct(array $values)
26+
{
27+
if (!isset($values['value']) && !isset($values['name'])) {
28+
throw new \BadMethodCallException('The @Right annotation must be passed a right name. For instance: "@Right(\'my_right\')"');
29+
}
30+
$this->name = $values['value'] ?? $values['name'];
31+
}
32+
33+
/**
34+
* @return string
35+
*/
36+
public function getName(): string
37+
{
38+
return $this->name;
39+
}
40+
}

src/ControllerQueryProvider.php

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
use Roave\BetterReflection\Reflection\ReflectionMethod;
1313
use Doctrine\Common\Annotations\Reader;
1414
use phpDocumentor\Reflection\Types\Integer;
15+
use TheCodingMachine\GraphQL\Controllers\Annotations\Logged;
1516
use TheCodingMachine\GraphQL\Controllers\Annotations\Mutation;
1617
use TheCodingMachine\GraphQL\Controllers\Annotations\Query;
18+
use TheCodingMachine\GraphQL\Controllers\Annotations\Right;
19+
use TheCodingMachine\GraphQL\Controllers\Security\AuthenticationServiceInterface;
20+
use TheCodingMachine\GraphQL\Controllers\Security\AuthorizationServiceInterface;
1721
use Youshido\GraphQL\Field\Field;
1822
use Youshido\GraphQL\Type\ListType\ListType;
1923
use Youshido\GraphQL\Type\NonNullType;
@@ -43,16 +47,26 @@ class ControllerQueryProvider implements QueryProviderInterface
4347
* @var HydratorInterface
4448
*/
4549
private $hydrator;
50+
/**
51+
* @var AuthenticationServiceInterface
52+
*/
53+
private $authenticationService;
54+
/**
55+
* @var AuthorizationServiceInterface
56+
*/
57+
private $authorizationService;
4658

4759
/**
4860
* @param object $controller
4961
*/
50-
public function __construct($controller, Reader $annotationReader, TypeMapperInterface $typeMapper, HydratorInterface $hydrator)
62+
public function __construct($controller, Reader $annotationReader, TypeMapperInterface $typeMapper, HydratorInterface $hydrator, AuthenticationServiceInterface $authenticationService, AuthorizationServiceInterface $authorizationService)
5163
{
5264
$this->controller = $controller;
5365
$this->annotationReader = $annotationReader;
5466
$this->typeMapper = $typeMapper;
5567
$this->hydrator = $hydrator;
68+
$this->authenticationService = $authenticationService;
69+
$this->authorizationService = $authorizationService;
5670
}
5771

5872
/**
@@ -84,7 +98,12 @@ private function getFieldsByAnnotations(string $annotationName): array
8498
$standardPhpMethod = new \ReflectionMethod(get_class($this->controller), $refMethod->getName());
8599
// First, let's check the "Query" annotation
86100
$queryAnnotation = $this->annotationReader->getMethodAnnotation($standardPhpMethod, $annotationName);
101+
87102
if ($queryAnnotation !== null) {
103+
if (!$this->isAuthorized($standardPhpMethod)) {
104+
continue;
105+
}
106+
88107
$methodName = $refMethod->getName();
89108

90109
$args = $this->mapParameters($refMethod, $standardPhpMethod);
@@ -98,6 +117,30 @@ private function getFieldsByAnnotations(string $annotationName): array
98117
return $queryList;
99118
}
100119

120+
/**
121+
* Checks the @Logged and @Right annotations.
122+
*
123+
* @param \ReflectionMethod $reflectionMethod
124+
* @return bool
125+
*/
126+
private function isAuthorized(\ReflectionMethod $reflectionMethod) : bool
127+
{
128+
$loggedAnnotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, Logged::class);
129+
130+
if ($loggedAnnotation !== null && !$this->authenticationService->isLogged()) {
131+
return false;
132+
}
133+
134+
$rightAnnotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, Right::class);
135+
/** @var $rightAnnotation Right */
136+
137+
if ($rightAnnotation !== null && !$this->authorizationService->isAllowed($rightAnnotation->getName())) {
138+
return false;
139+
}
140+
141+
return true;
142+
}
143+
101144
/**
102145
* Note: there is a bug in $refMethod->allowsNull that forces us to use $standardRefMethod->allowsNull instead.
103146
*
@@ -183,7 +226,7 @@ private function toGraphQlType(Type $type): TypeInterface
183226
*/
184227
private function typesWithoutNullable(array $docBlockTypeHints): array
185228
{
186-
return array_filter($docBlockTypeHints, function($item) {
229+
return array_filter($docBlockTypeHints, function ($item) {
187230
return !$item instanceof Null_;
188231
});
189232
}

src/GraphQLMiddleware.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function process(\Psr\Http\Message\ServerRequestInterface $request, \Inte
6161
return $delegate->process($request);
6262
}
6363

64-
if (!in_array($request->getMethod(), $this->allowed_methods, true)){
64+
if (!in_array($request->getMethod(), $this->allowed_methods, true)) {
6565
return new JsonResponse([
6666
"Method not allowed. Allowed methods are " . implode(", ", $this->allowed_methods)
6767
], 405);
@@ -96,12 +96,12 @@ private function hasGraphQLHeader(ServerRequestInterface $request)
9696
return false;
9797
}
9898

99-
$request_headers = array_map(function($header){
99+
$request_headers = array_map(function ($header) {
100100
return trim($header);
101101
}, explode(",", $request->getHeaderLine("content-type")));
102102

103103
foreach ($this->graphql_headers as $allowed_header) {
104-
if (in_array($allowed_header, $request_headers)){
104+
if (in_array($allowed_header, $request_headers)) {
105105
return true;
106106
}
107107
}
@@ -134,7 +134,6 @@ private function fromGet(ServerRequestInterface $request)
134134
$variables = is_string($variables) ? json_decode($variables, true) ?: [] : [];
135135

136136
return [$query, $variables];
137-
138137
}
139138

140139
private function fromPost(ServerRequestInterface $request)
@@ -167,6 +166,5 @@ private function fromPost(ServerRequestInterface $request)
167166
}
168167
}
169168
return [$query, $variables];
170-
171169
}
172-
}
170+
}

src/HydratorInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
namespace TheCodingMachine\GraphQL\Controllers;
5+
56
use Youshido\GraphQL\Type\TypeInterface;
67

78
/**

src/QueryField.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function __construct(string $name, TypeInterface $type, array $arguments,
4646
$type = $this->stripNonNullType($type);
4747
if ($type instanceof ListType) {
4848
$subtype = $this->stripNonNullType($type->getItemType());
49-
$val = array_map(function($item) use ($subtype) {
49+
$val = array_map(function ($item) use ($subtype) {
5050
if ($subtype->getKind() === TypeMap::KIND_OBJECT) {
5151
return $this->hydrator->hydrate($item, $subtype);
5252
};

0 commit comments

Comments
 (0)