Issues (62)

Security/TestProvider/SensioExtraProviderTest.php (1 issue)

Labels
Severity
1
<?php
2
3
/*
4
 *
5
 * (c) Yaroslav Honcharuk <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Yarhon\RouteGuardBundle\Tests\Security\TestProvider;
12
13
use PHPUnit\Framework\TestCase;
14
use Symfony\Component\Routing\Route;
15
use Symfony\Component\ExpressionLanguage\Expression;
16
use Symfony\Component\ExpressionLanguage\ParsedExpression;
17
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
18
use Symfony\Component\ExpressionLanguage\SyntaxError;
19
use Symfony\Component\ExpressionLanguage\Node\Node;
20
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
21
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security as SecurityAnnotation;
22
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted as IsGrantedAnnotation;
23
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter as ParamConverterAnnotation;
24
use Yarhon\RouteGuardBundle\Annotations\ClassMethodAnnotationReaderInterface;
25
use Yarhon\RouteGuardBundle\ExpressionLanguage\ExpressionDecorator;
26
use Yarhon\RouteGuardBundle\ExpressionLanguage\ExpressionAnalyzer;
27
use Yarhon\RouteGuardBundle\Controller\ControllerMetadata;
28
use Yarhon\RouteGuardBundle\Routing\RequestAttributesFactory;
29
use Yarhon\RouteGuardBundle\Routing\RouteMetadataFactory;
30
use Yarhon\RouteGuardBundle\Routing\RouteMetadata;
31
use Yarhon\RouteGuardBundle\Security\Test\SensioExtraTest;
32
use Yarhon\RouteGuardBundle\Security\Test\TestBag;
33
use Yarhon\RouteGuardBundle\Security\Authorization\SensioSecurityExpressionVoter;
34
use Yarhon\RouteGuardBundle\Security\TestProvider\SensioExtraProvider;
35
use Yarhon\RouteGuardBundle\Exception\LogicException;
36
use Yarhon\RouteGuardBundle\Exception\InvalidArgumentException;
37
38
/**
39
 * @author Yaroslav Honcharuk <[email protected]>
40
 */
41
class SensioExtraProviderTest extends TestCase
42
{
43
    private $annotationReader;
44
45
    private $requestAttributesFactory;
46
47
    private $expressionLanguage;
48
49
    private $expressionAnalyzer;
50
51
    private $provider;
52
53
    private $route;
54
55
    public function setUp()
56
    {
57
        $this->annotationReader = $this->createMock(ClassMethodAnnotationReaderInterface::class);
58
59
        $this->requestAttributesFactory = $this->createMock(RequestAttributesFactory::class);
60
61
        $routeMetadataFactory = $this->createMock(RouteMetadataFactory::class);
62
        $routeMetadataFactory->method('createMetadata')
0 ignored issues
show
The method method() does not exist on PHPUnit\Framework\MockObject\MockObject. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

62
        $routeMetadataFactory->/** @scrutinizer ignore-call */ 
63
                               method('createMetadata')

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
63
            ->willReturn(new RouteMetadata([], []));
64
65
        $this->expressionLanguage = $this->createMock(ExpressionLanguage::class);
66
67
        $this->expressionAnalyzer = $this->createMock(ExpressionAnalyzer::class);
68
69
        $this->provider = new SensioExtraProvider($this->annotationReader, $this->requestAttributesFactory, $routeMetadataFactory);
70
71
        $this->route = new Route('/');
72
    }
73
74
    /**
75
     * @dataProvider securityAnnotationDataProvider
76
     */
77
    public function testSecurityAnnotation($annotation, $controllerArguments, $requestAttributes, $parseOnFirstCall, $expected)
78
    {
79
        $allowedVariables = array_unique(array_merge($controllerArguments, $requestAttributes));
80
81
        $this->provider->setExpressionLanguage($this->expressionLanguage);
82
83
        $this->annotationReader->method('read')
84
            ->willReturn([$annotation]);
85
86
        $this->requestAttributesFactory->method('getAttributeNames')
87
            ->willReturn($requestAttributes);
88
89
        $namesToParse = SensioSecurityExpressionVoter::getVariableNames();
90
91
        if ($parseOnFirstCall) {
92
            $this->expressionLanguage->expects($this->at(0))
93
                ->method('parse')
94
                ->with($annotation->getExpression(), $namesToParse)
95
                ->willReturnCallback(function ($expressionString) {
96
                    return new Expression($expressionString);
97
                });
98
        } else {
99
            $this->expressionLanguage->expects($this->at(0))
100
                ->method('parse')
101
                ->with($annotation->getExpression(), $namesToParse)
102
                ->willThrowException(new SyntaxError('syntax'));
103
104
            $namesToParse = array_merge($namesToParse, $allowedVariables);
105
106
            $this->expressionLanguage->expects($this->at(1))
107
                ->method('parse')
108
                ->with($annotation->getExpression(), $namesToParse)
109
                ->willReturnCallback(function ($expressionString) {
110
                    return new Expression($expressionString);
111
                });
112
        }
113
114
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments);
115
116
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
117
118
        $this->assertInstanceOf(TestBag::class, $testBag);
119
        $test = $testBag->getTests()[0];
120
121
        $this->assertEquals($expected, $test);
122
    }
123
124
    public function securityAnnotationDataProvider()
125
    {
126
        return [
127
            [
128
                new SecurityAnnotation(['expression' => 'request.isSecure']),
129
                [],
130
                [],
131
                false,
132
                new SensioExtraTest([new ExpressionDecorator(new Expression('request.isSecure'), [])]),
133
            ],
134
            [
135
                new SecurityAnnotation(['expression' => 'request.isSecure']),
136
                ['foo'],
137
                ['foo'],
138
                false,
139
                new SensioExtraTest([new ExpressionDecorator(new Expression('request.isSecure'), ['foo'])]),
140
            ],
141
            [
142
                new SecurityAnnotation(['expression' => 'request.isSecure']),
143
                ['foo', 'bar'],
144
                ['baz'],
145
                false,
146
                (new SensioExtraTest([new ExpressionDecorator(new Expression('request.isSecure'), ['foo', 'bar', 'baz'])]))->setMetadata('request_attributes', ['baz']),
147
            ],
148
        ];
149
    }
150
151
    /**
152
     * @dataProvider securityAnnotationWithExpressionAnalyzerDataProvider
153
     */
154
    public function testSecurityAnnotationWithExpressionAnalyzer($annotation, $allowedVariables, $usedVariables, $expected)
155
    {
156
        $this->provider->setExpressionLanguage($this->expressionLanguage);
157
        $this->provider->setExpressionAnalyzer($this->expressionAnalyzer);
158
159
        $this->annotationReader->method('read')
160
            ->willReturn([$annotation]);
161
162
        $this->requestAttributesFactory->method('getAttributeNames')
163
            ->willReturn([]);
164
165
        $namesToParse = SensioSecurityExpressionVoter::getVariableNames();
166
        $namesToParse = array_merge($namesToParse, $allowedVariables);
167
168
        $this->expressionLanguage->expects($this->once())
169
            ->method('parse')
170
            ->with($annotation->getExpression(), $namesToParse)
171
            ->willReturnCallback(function ($expressionString) {
172
                return $this->createParsedExpression($expressionString);
173
            });
174
175
        $this->expressionAnalyzer->method('getUsedVariables')
176
            ->willReturn($usedVariables);
177
178
        $controllerMetadata = $this->createControllerMetadata('class::method', $allowedVariables);
179
180
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
181
182
        $this->assertInstanceOf(TestBag::class, $testBag);
183
        $test = $testBag->getTests()[0];
184
185
        $this->assertEquals($expected, $test);
186
    }
187
188
    public function securityAnnotationWithExpressionAnalyzerDataProvider()
189
    {
190
        return [
191
            [
192
                new SecurityAnnotation(['expression' => 'request.isSecure']),
193
                [],
194
                [],
195
                new SensioExtraTest([new ExpressionDecorator($this->createParsedExpression('request.isSecure'), [])]),
196
            ],
197
            [
198
                new SecurityAnnotation(['expression' => 'request.isSecure']),
199
                ['foo'],
200
                ['request', 'foo'],
201
                new SensioExtraTest([new ExpressionDecorator($this->createParsedExpression('request.isSecure'), ['foo'])]),
202
            ],
203
        ];
204
    }
205
206
    public function testSecurityAnnotationWithoutExpressionLanguageException()
207
    {
208
        $annotation = new SecurityAnnotation([]);
209
210
        $this->annotationReader->method('read')
211
            ->willReturn([$annotation]);
212
213
        $this->requestAttributesFactory->method('getAttributeNames')
214
            ->willReturn([]);
215
216
        $controllerMetadata = $this->createControllerMetadata('class::method', []);
217
218
        $this->expectException(LogicException::class);
219
        $this->expectExceptionMessage('Cannot create expression because ExpressionLanguage is not provided.');
220
221
        $this->provider->getTests('index', $this->route, $controllerMetadata);
222
    }
223
224
    public function testSecurityAnnotationExpressionException()
225
    {
226
        $this->provider->setExpressionLanguage($this->expressionLanguage);
227
228
        $annotation = new SecurityAnnotation(['expression' => 'request.isSecure']);
229
230
        $this->annotationReader->method('read')
231
            ->willReturn([$annotation]);
232
233
        $this->requestAttributesFactory->method('getAttributeNames')
234
            ->willReturn(['bar', 'baz']);
235
236
        $this->expressionLanguage->method('parse')
237
            ->willThrowException(new SyntaxError('syntax'));
238
239
        $controllerMetadata = $this->createControllerMetadata('class::method', ['foo', 'bar']);
240
241
        $this->expectException(InvalidArgumentException::class);
242
        $this->expectExceptionMessage('Cannot parse expression "request.isSecure" with following variables: "token", "user", "object", "subject", "roles", "trust_resolver", "auth_checker", "request", "foo", "bar", "baz".');
243
244
        $this->provider->getTests('index', $this->route, $controllerMetadata);
245
    }
246
247
    /**
248
     * @dataProvider isGrantedAnnotationDataProvider
249
     */
250
    public function testIsGrantedAnnotation($annotation, $controllerArguments, $requestAttributes, $expected)
251
    {
252
        $this->annotationReader->method('read')
253
            ->willReturn([$annotation]);
254
255
        $this->requestAttributesFactory->method('getAttributeNames')
256
            ->willReturn($requestAttributes);
257
258
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments);
259
260
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
261
262
        $this->assertInstanceOf(TestBag::class, $testBag);
263
        $test = $testBag->getTests()[0];
264
265
        $this->assertEquals($expected, $test);
266
    }
267
268
    public function isGrantedAnnotationDataProvider()
269
    {
270
        return [
271
            [
272
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']),
273
                ['foo', 'bar'],
274
                ['bar', 'baz'],
275
                new SensioExtraTest(['ROLE_ADMIN']),
276
            ],
277
            [
278
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'foo']),
279
                ['foo', 'bar'],
280
                ['bar', 'baz'],
281
                new SensioExtraTest(['ROLE_ADMIN'], 'foo'),
282
            ],
283
            [
284
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'bar']),
285
                ['foo', 'bar'],
286
                ['bar', 'baz'],
287
                new SensioExtraTest(['ROLE_ADMIN'], 'bar'),
288
            ],
289
            [
290
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'baz']),
291
                ['foo', 'bar'],
292
                ['bar', 'baz'],
293
                (new SensioExtraTest(['ROLE_ADMIN'], 'baz'))->setMetadata('request_attributes', ['baz']),
294
            ],
295
        ];
296
    }
297
298
    public function testIsGrantedAnnotationSubjectException()
299
    {
300
        $annotation = new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'foo']);
301
302
        $this->annotationReader->method('read')
303
            ->willReturn([$annotation]);
304
305
        $this->requestAttributesFactory->method('getAttributeNames')
306
            ->willReturn(['baz']);
307
308
        $controllerMetadata = $this->createControllerMetadata('class::method', ['bar']);
309
310
        $this->expectException(InvalidArgumentException::class);
311
        $this->expectExceptionMessage('Unknown subject variable "foo". Allowed variables: "bar", "baz');
312
313
        $this->provider->getTests('index', $this->route, $controllerMetadata);
314
    }
315
316
    public function testNoAnnotations()
317
    {
318
        $this->annotationReader->method('read')
319
            ->willReturn([]);
320
321
        $controllerMetadata = $this->createControllerMetadata('class::method', []);
322
323
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
324
325
        $this->assertNull($testBag);
326
    }
327
328
    public function testNoControllerMetadata()
329
    {
330
        $testBag = $this->provider->getTests('index', $this->route, null);
331
332
        $this->assertNull($testBag);
333
    }
334
335
    /**
336
     * @dataProvider sameInstancesOfEqualTestsDataProvider
337
     */
338
    public function testSameInstancesOfEqualTests($callOne, $callTwo, $expected)
339
    {
340
        $annotations = [$callOne[0], $callTwo[0]];
341
        $controllerArguments = [$callOne[1], $callTwo[1]];
342
        $requestAttributes = [$callOne[2], $callTwo[2]];
343
344
        $this->provider->setExpressionLanguage($this->expressionLanguage);
345
346
        $this->annotationReader->method('read')
347
            ->willReturnOnConsecutiveCalls([$annotations[0]], [$annotations[1]]);
348
349
        $this->requestAttributesFactory->method('getAttributeNames')
350
            ->willReturnOnConsecutiveCalls($requestAttributes[0], $requestAttributes[1]);
351
352
        $this->expressionLanguage->method('parse')
353
            ->willReturnCallback(function ($expressionString) {
354
                return new Expression($expressionString);
355
            });
356
357
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments[0]);
358
359
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
360
        $testOne = $testBag->getTests()[0];
361
362
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments[1]);
363
364
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
365
        $testTwo = $testBag->getTests()[0];
366
367
        if ($expected) {
368
            $this->assertSame($testOne, $testTwo);
369
        } else {
370
            $this->assertNotSame($testOne, $testTwo);
371
        }
372
    }
373
374
    public function sameInstancesOfEqualTestsDataProvider()
375
    {
376
        return [
377
            [
378
                [new SecurityAnnotation(['expression' => 'request.isSecure']), [], []],
379
                [new SecurityAnnotation(['expression' => 'not request.isSecure']), [], []],
380
                false,
381
            ],
382
            [
383
                [new SecurityAnnotation(['expression' => 'request.isSecure']), [], []],
384
                [new SecurityAnnotation(['expression' => 'request.isSecure']), [], []],
385
                true,
386
            ],
387
            [
388
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
389
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
390
                true,
391
            ],
392
            [
393
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
394
                [new IsGrantedAnnotation(['attributes' => 'ROLE_USER']), ['arg1'], ['attr1']],
395
                false,
396
            ],
397
            [
398
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'arg1']), ['arg1'], ['attr1']],
399
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
400
                false,
401
            ],
402
        ];
403
    }
404
405
    private function createControllerMetadata($controllerName, $argumentNames)
406
    {
407
        $arguments = [];
408
409
        foreach ($argumentNames as $name) {
410
            $arguments[] = new ArgumentMetadata($name, 'int', false, false, null);
411
        }
412
413
        return new ControllerMetadata($controllerName, 'class', 'method', $arguments);
414
    }
415
416
    private function createParsedExpression($expression)
417
    {
418
        return new ParsedExpression($expression, $this->createMock(Node::class));
419
    }
420
}
421