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 Psr\Log\LoggerInterface; |
15
|
|
|
use Symfony\Component\Routing\Route; |
16
|
|
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage; |
17
|
|
|
use Symfony\Component\ExpressionLanguage\Expression; |
18
|
|
|
use Symfony\Component\ExpressionLanguage\SyntaxError; |
19
|
|
|
use Yarhon\RouteGuardBundle\Security\Http\RequestConstraint; |
20
|
|
|
use Yarhon\RouteGuardBundle\Security\Http\RouteMatcher; |
21
|
|
|
use Yarhon\RouteGuardBundle\Security\Test\TestBag; |
22
|
|
|
use Yarhon\RouteGuardBundle\Security\Test\SymfonyAccessControlTest; |
23
|
|
|
use Yarhon\RouteGuardBundle\Security\Http\RequestDependentTestBag; |
24
|
|
|
use Yarhon\RouteGuardBundle\Security\Authorization\SymfonySecurityExpressionVoter; |
25
|
|
|
use Yarhon\RouteGuardBundle\Security\TestProvider\SymfonyAccessControlProvider; |
26
|
|
|
use Yarhon\RouteGuardBundle\Exception\LogicException; |
27
|
|
|
use Yarhon\RouteGuardBundle\Exception\InvalidArgumentException; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @author Yaroslav Honcharuk <[email protected]> |
31
|
|
|
*/ |
32
|
|
|
class SymfonyAccessControlProviderTest extends TestCase |
33
|
|
|
{ |
34
|
|
|
private $expressionLanguage; |
35
|
|
|
|
36
|
|
|
private $routeMatcher; |
37
|
|
|
|
38
|
|
|
private $provider; |
39
|
|
|
|
40
|
|
|
private $route; |
41
|
|
|
|
42
|
|
|
public function setUp() |
43
|
|
|
{ |
44
|
|
|
$this->expressionLanguage = $this->createMock(ExpressionLanguage::class); |
45
|
|
|
|
46
|
|
|
$this->routeMatcher = $this->createMock(RouteMatcher::class); |
47
|
|
|
|
48
|
|
|
$this->provider = new SymfonyAccessControlProvider($this->routeMatcher); |
49
|
|
|
|
50
|
|
|
$this->route = new Route('/'); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @dataProvider getTestsDataProvider |
55
|
|
|
*/ |
56
|
|
|
public function testGetTests($tests, $routeMatcherResults, $expected) |
57
|
|
|
{ |
58
|
|
|
foreach ($tests as $test) { |
59
|
|
|
$this->provider->addRule(new RequestConstraint(), $test); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
$this->routeMatcher->method('matches') |
63
|
|
|
->willReturnOnConsecutiveCalls(...$routeMatcherResults); |
64
|
|
|
|
65
|
|
|
$testBag = $this->provider->getTests('index', $this->route); |
66
|
|
|
|
67
|
|
|
$this->assertEquals($expected, $testBag); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
public function getTestsDataProvider() |
71
|
|
|
{ |
72
|
|
|
return [ |
73
|
|
|
[ |
74
|
|
|
[new SymfonyAccessControlTest(['ROLE_ADMIN']), new SymfonyAccessControlTest(['ROLE_USER'])], |
75
|
|
|
[false, true], |
76
|
|
|
new TestBag([new SymfonyAccessControlTest(['ROLE_USER'])]), |
77
|
|
|
], |
78
|
|
|
[ |
79
|
|
|
[new SymfonyAccessControlTest(['ROLE_ADMIN']), new SymfonyAccessControlTest(['ROLE_USER'])], |
80
|
|
|
[new RequestConstraint('/admin'), true], |
81
|
|
|
new RequestDependentTestBag([ |
82
|
|
|
[[new SymfonyAccessControlTest(['ROLE_ADMIN'])], new RequestConstraint('/admin')], |
83
|
|
|
[[new SymfonyAccessControlTest(['ROLE_USER'])], null], |
84
|
|
|
]), |
85
|
|
|
], |
86
|
|
|
]; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
public function testLogRuntimeMatching() |
90
|
|
|
{ |
91
|
|
|
$logger = $this->createMock(LoggerInterface::class); |
92
|
|
|
$this->provider->setLogger($logger); |
93
|
|
|
|
94
|
|
|
$logger->expects($this->once()) |
95
|
|
|
->method('warning') |
96
|
|
|
->with('Route "index" (path "/") requires runtime matching to access_control rule(s) #0, #1 (zero-based), this would reduce performance.'); |
97
|
|
|
|
98
|
|
|
$this->provider->addRule(new RequestConstraint(), new SymfonyAccessControlTest(['ROLE_ADMIN'])); |
99
|
|
|
$this->provider->addRule(new RequestConstraint(), new SymfonyAccessControlTest(['ROLE_USER'])); |
100
|
|
|
|
101
|
|
|
$requestConstraintForMap = new RequestConstraint(); |
102
|
|
|
|
103
|
|
|
$this->routeMatcher->method('matches') |
104
|
|
|
->willReturnOnConsecutiveCalls($requestConstraintForMap, true); |
105
|
|
|
|
106
|
|
|
$this->provider->getTests('index', $this->route); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
public function testGetTestsWithoutMatches() |
110
|
|
|
{ |
111
|
|
|
$testBag = $this->provider->getTests('index', $this->route); |
112
|
|
|
|
113
|
|
|
$this->assertNull($testBag); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
public function testImportRules() |
117
|
|
|
{ |
118
|
|
|
$rule = $this->createRuleArray(); |
119
|
|
|
$rule['allow_if'] = null; |
120
|
|
|
|
121
|
|
|
$expectedConstraint = new RequestConstraint($rule['path'], $rule['host'], $rule['methods'], $rule['ips']); |
122
|
|
|
$expectedTest = new SymfonyAccessControlTest($rule['roles']); |
123
|
|
|
|
124
|
|
|
$this->provider->importRules([$rule]); |
125
|
|
|
|
126
|
|
|
$expectedRules = [[$expectedConstraint, $expectedTest]]; |
127
|
|
|
$this->assertAttributeEquals($expectedRules, 'rules', $this->provider); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
public function testImportRulesWithExpression() |
131
|
|
|
{ |
132
|
|
|
$rule = $this->createRuleArray(['allow_if' => 'request.isSecure']); |
133
|
|
|
|
134
|
|
|
$names = SymfonySecurityExpressionVoter::getVariableNames(); |
135
|
|
|
|
136
|
|
|
$this->provider->setExpressionLanguage($this->expressionLanguage); |
137
|
|
|
|
138
|
|
|
$this->expressionLanguage->expects($this->once()) |
139
|
|
|
->method('parse') |
140
|
|
|
->with($rule['allow_if'], $names) |
141
|
|
|
->willReturnCallback(function ($expressionString) { |
142
|
|
|
return new Expression($expressionString); |
143
|
|
|
}); |
144
|
|
|
|
145
|
|
|
$expectedConstraint = new RequestConstraint($rule['path'], $rule['host'], $rule['methods'], $rule['ips']); |
146
|
|
|
$expectedTest = new SymfonyAccessControlTest(array_merge($rule['roles'], [new Expression('request.isSecure')])); |
147
|
|
|
|
148
|
|
|
$this->provider->importRules([$rule]); |
149
|
|
|
|
150
|
|
|
$expectedRules = [[$expectedConstraint, $expectedTest]]; |
151
|
|
|
$this->assertAttributeEquals($expectedRules, 'rules', $this->provider); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
public function testImportRulesWithInvalidExpressionException() |
155
|
|
|
{ |
156
|
|
|
$rule = $this->createRuleArray(['allow_if' => 'request.isSecure']); |
157
|
|
|
|
158
|
|
|
$this->expressionLanguage->method('parse') |
159
|
|
|
->willThrowException(new SyntaxError('syntax')); |
160
|
|
|
|
161
|
|
|
$this->provider->setExpressionLanguage($this->expressionLanguage); |
162
|
|
|
|
163
|
|
|
$this->expectException(InvalidArgumentException::class); |
164
|
|
|
$this->expectExceptionMessage('Cannot parse expression "request.isSecure" with following variables: "token", "user", "object", "subject", "roles", "trust_resolver", "request".'); |
165
|
|
|
|
166
|
|
|
$this->provider->importRules([$rule]); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
public function testImportRulesWithExpressionWithoutExpressionLanguage() |
170
|
|
|
{ |
171
|
|
|
$rule = $this->createRuleArray(['allow_if' => 'request.isSecure']); |
172
|
|
|
|
173
|
|
|
$this->expectException(LogicException::class); |
174
|
|
|
$this->expectExceptionMessage('Cannot create expression because ExpressionLanguage is not provided.'); |
175
|
|
|
|
176
|
|
|
$this->provider->importRules([$rule]); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* @dataProvider sameInstancesOfEqualTestsDataProvider |
181
|
|
|
*/ |
182
|
|
|
public function testSameInstancesOfEqualTests($ruleOne, $ruleTwo, $expected) |
183
|
|
|
{ |
184
|
|
|
$ruleOne = $this->createRuleArray($ruleOne); |
185
|
|
|
$ruleTwo = $this->createRuleArray($ruleTwo); |
186
|
|
|
|
187
|
|
|
$this->provider->setExpressionLanguage($this->expressionLanguage); |
188
|
|
|
|
189
|
|
|
$this->expressionLanguage->method('parse') |
190
|
|
|
->willReturnCallback(function ($expressionString) { |
191
|
|
|
return new Expression($expressionString); |
192
|
|
|
}); |
193
|
|
|
|
194
|
|
|
$this->provider->importRules([$ruleOne, $ruleTwo]); |
195
|
|
|
|
196
|
|
|
$this->routeMatcher->method('matches') |
197
|
|
|
->willReturnOnConsecutiveCalls(true, false, true); |
198
|
|
|
|
199
|
|
|
$testBag = $this->provider->getTests('index', $this->route); |
200
|
|
|
$testOne = $testBag->getTests()[0]; |
201
|
|
|
|
202
|
|
|
$testBag = $this->provider->getTests('index', $this->route); |
203
|
|
|
$testTwo = $testBag->getTests()[0]; |
204
|
|
|
|
205
|
|
|
if ($expected) { |
206
|
|
|
$this->assertSame($testOne, $testTwo); |
207
|
|
|
} else { |
208
|
|
|
$this->assertNotSame($testOne, $testTwo); |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
public function sameInstancesOfEqualTestsDataProvider() |
213
|
|
|
{ |
214
|
|
|
return [ |
215
|
|
|
[ |
216
|
|
|
['roles' => ['ROLE_ADMIN']], |
217
|
|
|
['roles' => ['ROLE_ADMIN']], |
218
|
|
|
true, |
219
|
|
|
], |
220
|
|
|
[ |
221
|
|
|
['roles' => ['ROLE_ADMIN', 'ROLE_USER']], |
222
|
|
|
['roles' => ['ROLE_USER', 'ROLE_ADMIN']], |
223
|
|
|
true, |
224
|
|
|
], |
225
|
|
|
[ |
226
|
|
|
['roles' => ['ROLE_ADMIN', 'ROLE_USER']], |
227
|
|
|
['roles' => ['ROLE_USER']], |
228
|
|
|
false, |
229
|
|
|
], |
230
|
|
|
[ |
231
|
|
|
['roles' => ['ROLE_ADMIN'], 'allow_if' => 'request.isSecure'], |
232
|
|
|
['roles' => ['ROLE_ADMIN']], |
233
|
|
|
false, |
234
|
|
|
], |
235
|
|
|
[ |
236
|
|
|
['roles' => ['ROLE_ADMIN'], 'allow_if' => 'request.isSecure'], |
237
|
|
|
['roles' => ['ROLE_ADMIN'], 'allow_if' => 'request.isSecure'], |
238
|
|
|
true, |
239
|
|
|
], |
240
|
|
|
[ |
241
|
|
|
['roles' => ['ROLE_ADMIN'], 'allow_if' => 'request.isSecure'], |
242
|
|
|
['roles' => ['ROLE_ADMIN'], 'allow_if' => 'not request.isSecure'], |
243
|
|
|
false, |
244
|
|
|
], |
245
|
|
|
]; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
private function createRuleArray(array $values = []) |
249
|
|
|
{ |
250
|
|
|
$defaults = [ |
251
|
|
|
'path' => null, |
252
|
|
|
'host' => null, |
253
|
|
|
'methods' => [], |
254
|
|
|
'ips' => [], |
255
|
|
|
'allow_if' => null, |
256
|
|
|
'roles' => [], |
257
|
|
|
]; |
258
|
|
|
|
259
|
|
|
return array_merge($defaults, $values); |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
|