Skip to content

Commit 6b012e6

Browse files
committed
Improving exception message in case of expression language syntax error
1 parent a5ba026 commit 6b012e6

7 files changed

+106
-6
lines changed

src/FieldsBuilder.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ private function getFieldsByAnnotations(?object $controller, string $annotationN
234234
}
235235

236236
$fieldDescriptor = new QueryFieldDescriptor();
237+
$fieldDescriptor->setRefMethod($refMethod);
237238

238239
$docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod);
239240
$docBlockComment = $docBlockObj->getSummary() . "\n" . $docBlockObj->getDescription()->render();
@@ -390,6 +391,7 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
390391
}
391392

392393
$fieldDescriptor = new QueryFieldDescriptor();
394+
$fieldDescriptor->setRefMethod($refMethod);
393395
$fieldDescriptor->setName($sourceField->getName());
394396

395397
$methodName = $refMethod->getName();
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TheCodingMachine\GraphQLite\Middlewares;
6+
7+
use Exception;
8+
use TheCodingMachine\GraphQLite\QueryFieldDescriptor;
9+
use Throwable;
10+
11+
/**
12+
* Exception wrapping exceptions occurring when the Security annotation is evaluated
13+
*/
14+
class BadExpressionInSecurityException extends Exception
15+
{
16+
public static function wrapException(Throwable $e, QueryFieldDescriptor $fieldDescriptor): self
17+
{
18+
$refMethod = $fieldDescriptor->getRefMethod();
19+
$message = 'An error occurred while evaluating expression in @Security annotation of method "' . $refMethod->getDeclaringClass()->getName() . '::' . $refMethod->getName() . '": ' . $e->getMessage();
20+
21+
return new self($message, $e->getCode(), $e);
22+
}
23+
}

src/Middlewares/SecurityFieldMiddleware.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use TheCodingMachine\GraphQLite\QueryFieldDescriptor;
1818
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
1919
use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface;
20+
use Throwable;
2021
use Webmozart\Assert\Assert;
2122
use function array_combine;
2223
use function array_keys;
@@ -88,11 +89,17 @@ public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandler
8889

8990
$parameters = $queryFieldDescriptor->getParameters();
9091

91-
$queryFieldDescriptor->setCallable(function (...$args) use ($securityAnnotations, $callable, $failWith, $parameters) {
92+
$queryFieldDescriptor->setCallable(function (...$args) use ($securityAnnotations, $callable, $failWith, $parameters, $queryFieldDescriptor) {
9293
$variables = $this->getVariables($args, $parameters, $callable);
9394

9495
foreach ($securityAnnotations as $annotation) {
95-
if (! $this->language->evaluate($annotation->getExpression(), $variables)) {
96+
try {
97+
$authorized = $this->language->evaluate($annotation->getExpression(), $variables);
98+
} catch (Throwable $e) {
99+
throw BadExpressionInSecurityException::wrapException($e, $queryFieldDescriptor);
100+
}
101+
102+
if (! $authorized) {
96103
if ($annotation->isFailWithSet()) {
97104
return $annotation->getFailWith();
98105
}

src/QueryFieldDescriptor.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use GraphQL\Type\Definition\OutputType;
88
use GraphQL\Type\Definition\Type;
9+
use ReflectionMethod;
910
use TheCodingMachine\GraphQLite\Annotations\MiddlewareAnnotations;
1011
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
1112

@@ -40,6 +41,8 @@ class QueryFieldDescriptor
4041
private $comment;
4142
/** @var MiddlewareAnnotations */
4243
private $middlewareAnnotations;
44+
/** @var ReflectionMethod */
45+
private $refMethod;
4346

4447
public function getName(): string
4548
{
@@ -160,4 +163,14 @@ public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnota
160163
{
161164
$this->middlewareAnnotations = $middlewareAnnotations;
162165
}
166+
167+
public function getRefMethod(): ReflectionMethod
168+
{
169+
return $this->refMethod;
170+
}
171+
172+
public function setRefMethod(ReflectionMethod $refMethod): void
173+
{
174+
$this->refMethod = $refMethod;
175+
}
163176
}

tests/AbstractQueryProviderTest.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use Mouf\Picotainer\Picotainer;
1616
use PHPUnit\Framework\TestCase;
1717
use Psr\Container\ContainerInterface;
18+
use Symfony\Component\Cache\Adapter\Psr16Adapter;
1819
use Symfony\Component\Cache\Simple\ArrayCache;
20+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
1921
use Symfony\Component\Lock\Factory as LockFactory;
2022
use Symfony\Component\Lock\Store\FlockStore;
2123
use Symfony\Component\Lock\Store\SemaphoreStore;
@@ -37,7 +39,10 @@
3739
use TheCodingMachine\GraphQLite\Containers\EmptyContainer;
3840
use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer;
3941
use TheCodingMachine\GraphQLite\Middlewares\AuthorizationFieldMiddleware;
42+
use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewarePipe;
43+
use TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware;
4044
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
45+
use TheCodingMachine\GraphQLite\Security\SecurityExpressionLanguageProvider;
4146
use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService;
4247
use TheCodingMachine\GraphQLite\Security\VoidAuthorizationService;
4348
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
@@ -260,6 +265,14 @@ protected function getAnnotationReader(): AnnotationReader
260265

261266
protected function buildFieldsBuilder(): FieldsBuilder
262267
{
268+
$fieldMiddlewarePipe = new FieldMiddlewarePipe();
269+
$fieldMiddlewarePipe->pipe(new AuthorizationFieldMiddleware(
270+
new VoidAuthenticationService(),
271+
new VoidAuthorizationService()
272+
));
273+
$expressionLanguage = new ExpressionLanguage(new Psr16Adapter(new ArrayCache()), [new SecurityExpressionLanguageProvider()]);
274+
$fieldMiddlewarePipe->pipe(new SecurityFieldMiddleware($expressionLanguage, new VoidAuthenticationService(), new VoidAuthorizationService()));
275+
263276
return new FieldsBuilder(
264277
$this->getAnnotationReader(),
265278
$this->getTypeMapper(),
@@ -274,10 +287,7 @@ protected function buildFieldsBuilder(): FieldsBuilder
274287
new CompositeParameterMapper([
275288
new ResolveInfoParameterMapper()
276289
]),
277-
new AuthorizationFieldMiddleware(
278-
new VoidAuthenticationService(),
279-
new VoidAuthorizationService()
280-
)
290+
$fieldMiddlewarePipe
281291
);
282292
}
283293

tests/FieldsBuilderTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use TheCodingMachine\GraphQLite\Fixtures\TestControllerNoReturnType;
2424
use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithArrayParam;
2525
use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithArrayReturnType;
26+
use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithBadSecurity;
2627
use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithFailWith;
2728
use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithInputType;
2829
use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithInvalidInputType;
@@ -57,6 +58,7 @@
5758
use TheCodingMachine\GraphQLite\Mappers\Root\BaseTypeMapper;
5859
use TheCodingMachine\GraphQLite\Mappers\Root\CompositeRootTypeMapper;
5960
use TheCodingMachine\GraphQLite\Middlewares\AuthorizationFieldMiddleware;
61+
use TheCodingMachine\GraphQLite\Middlewares\BadExpressionInSecurityException;
6062
use TheCodingMachine\GraphQLite\Parameters\MissingArgumentException;
6163
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
6264
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
@@ -665,4 +667,23 @@ public function testPrefetchMethod(): void
665667
$this->assertSame('string', $testField->args[0]->name);
666668
$this->assertSame('int', $testField->args[1]->name);
667669
}
670+
671+
public function testSecurityBadQuery(): void
672+
{
673+
$controller = new TestControllerWithBadSecurity();
674+
675+
$queryProvider = $this->buildFieldsBuilder();
676+
677+
$queries = $queryProvider->getQueries($controller);
678+
679+
$this->assertCount(1, $queries);
680+
$query = $queries[0];
681+
$this->assertSame('testBadSecurity', $query->name);
682+
683+
$resolve = $query->resolveFn;
684+
685+
$this->expectException(BadExpressionInSecurityException::class);
686+
$this->expectExceptionMessage('An error occurred while evaluating expression in @Security annotation of method "TheCodingMachine\GraphQLite\Fixtures\TestControllerWithBadSecurity::testBadSecurity": Unexpected token "name" of value "is" around position 6 for expression `this is not valid expression language`.');
687+
$result = $resolve(new stdClass(), [], null, $this->createMock(ResolveInfo::class));
688+
}
668689
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
4+
namespace TheCodingMachine\GraphQLite\Fixtures;
5+
6+
use ArrayObject;
7+
use TheCodingMachine\GraphQLite\Annotations\FailWith;
8+
use TheCodingMachine\GraphQLite\Annotations\Logged;
9+
use TheCodingMachine\GraphQLite\Annotations\Mutation;
10+
use TheCodingMachine\GraphQLite\Annotations\Query;
11+
use TheCodingMachine\GraphQLite\Annotations\Right;
12+
use TheCodingMachine\GraphQLite\Annotations\Security;
13+
14+
class TestControllerWithBadSecurity
15+
{
16+
/**
17+
* @Query
18+
* @Security("this is not valid expression language")
19+
*/
20+
public function testBadSecurity(): TestObject
21+
{
22+
return new TestObject('foo');
23+
}
24+
}

0 commit comments

Comments
 (0)