Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php namespace Limoncello\Tests\Auth\Authorization\PolicyEnforcement; |
||
47 | class PolicyEnforcementTest extends TestCase |
||
48 | { |
||
49 | /** |
||
50 | * @var bool |
||
51 | */ |
||
52 | public static $obligationCalled = false; |
||
53 | |||
54 | /** |
||
55 | * @var bool |
||
56 | */ |
||
57 | public static $adviceCalled = false; |
||
58 | |||
59 | /** |
||
60 | * @var resource |
||
61 | */ |
||
62 | private $logStream; |
||
63 | |||
64 | /** |
||
65 | * @var LoggerInterface |
||
66 | */ |
||
67 | private $logger; |
||
68 | |||
69 | /** |
||
70 | * @var int|null |
||
71 | */ |
||
72 | private $currentUserId; |
||
73 | |||
74 | /** |
||
75 | * @var string|null |
||
76 | */ |
||
77 | private $currentUserRole; |
||
78 | |||
79 | /** |
||
80 | * @var bool |
||
81 | */ |
||
82 | private $isWorkTime; |
||
83 | |||
84 | /** |
||
85 | * Test authorization of operation without any additional parameters (e.g. 'send message'). |
||
86 | */ |
||
87 | public function testAuthorizeOperationSuccess() |
||
88 | { |
||
89 | // set up environment variables (current user is not signed in and currently is work time) |
||
90 | $this->currentUserId = null; |
||
91 | $this->currentUserRole = null; |
||
92 | $this->isWorkTime = true; |
||
93 | $policyEnforcement = $this->createPolicyEnforcementPoint(); |
||
94 | |||
95 | // send authorization request |
||
96 | $authRequest = new Request([ |
||
97 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
98 | ]); |
||
99 | $this->assertTrue($policyEnforcement->authorize($authRequest)); |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * @return LoggerInterface |
||
104 | */ |
||
105 | protected function getLogger() |
||
106 | { |
||
107 | return $this->logger; |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Test authorization of operation without any additional parameters (e.g. 'send message'). |
||
112 | */ |
||
113 | public function testAuthorizeOperationFail() |
||
114 | { |
||
115 | // set up environment variables (current user is not signed in and currently is not work time) |
||
116 | $this->currentUserId = null; |
||
117 | $this->currentUserRole = null; |
||
118 | $this->isWorkTime = false; |
||
119 | $policyEnforcement = $this->createPolicyEnforcementPoint(); |
||
120 | |||
121 | // send authorization request |
||
122 | $authRequest = new Request([ |
||
123 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
124 | ]); |
||
125 | $this->assertFalse($policyEnforcement->authorize($authRequest)); |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Test authorization of operation on resource type (e.g. 'create comment'). |
||
130 | */ |
||
131 | public function testAuthorizeOperationOnResourceTypeSuccess() |
||
132 | { |
||
133 | // set up environment variables (current user is signed as member) |
||
134 | $this->currentUserRole = 'member'; |
||
135 | $this->currentUserId = 123; |
||
136 | $policyEnforcement = $this->createPolicyEnforcementPoint(); |
||
137 | |||
138 | // send authorization request |
||
139 | $authRequest = new Request([ |
||
140 | RequestProperties::PARAM_OPERATION => Comments::OPERATION_CREATE, |
||
141 | RequestProperties::PARAM_RESOURCE_TYPE => Comments::RESOURCE_TYPE, |
||
142 | ]); |
||
143 | $this->assertTrue($policyEnforcement->authorize($authRequest)); |
||
144 | $this->assertTrue(static::$obligationCalled); |
||
145 | $this->assertTrue(static::$adviceCalled); |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Test authorization of operation on resource type (e.g. 'create comment'). |
||
150 | */ |
||
151 | public function testAuthorizeOperationOnResourceTypeFail() |
||
152 | { |
||
153 | // set up environment variables (current user is not signed in) |
||
154 | // check that non signed in user cannot create comments |
||
155 | $this->currentUserRole = null; |
||
156 | $this->currentUserId = null; |
||
157 | $policyEnforcement = $this->createPolicyEnforcementPoint(); |
||
158 | |||
159 | // send authorization request |
||
160 | $authRequest = new Request([ |
||
161 | RequestProperties::PARAM_OPERATION => Comments::OPERATION_CREATE, |
||
162 | RequestProperties::PARAM_RESOURCE_TYPE => Comments::RESOURCE_TYPE, |
||
163 | ]); |
||
164 | $this->assertFalse($policyEnforcement->authorize($authRequest)); |
||
165 | } |
||
166 | |||
167 | /** |
||
168 | * Test authorization of operation on specific resource (e.g. 'update comment with ID 123'). |
||
169 | */ |
||
170 | public function testAuthorizeOperationOnSpecificResourceAsMember() |
||
171 | { |
||
172 | // set up environment variables (current user is signed as member) |
||
173 | $this->currentUserRole = 'member'; |
||
174 | $this->currentUserId = 456; |
||
175 | $policyEnforcement = $this->createPolicyEnforcementPoint(); |
||
176 | |||
177 | // send authorization request |
||
178 | $authRequest = new Request([ |
||
179 | RequestProperties::PARAM_OPERATION => Comments::OPERATION_UPDATE, |
||
180 | RequestProperties::PARAM_RESOURCE_TYPE => Comments::RESOURCE_TYPE, |
||
181 | RequestProperties::PARAM_RESOURCE_IDENTITY => 123, // <- ID 123 is owned |
||
182 | ]); |
||
183 | $this->assertTrue($policyEnforcement->authorize($authRequest)); |
||
184 | |||
185 | // send authorization request |
||
186 | $authRequest = new Request([ |
||
187 | RequestProperties::PARAM_OPERATION => Comments::OPERATION_UPDATE, |
||
188 | RequestProperties::PARAM_RESOURCE_TYPE => Comments::RESOURCE_TYPE, |
||
189 | RequestProperties::PARAM_RESOURCE_IDENTITY => 1234, // <- ID 1234 is not owned |
||
190 | ]); |
||
191 | $this->assertFalse($policyEnforcement->authorize($authRequest)); |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Test authorization of operation on specific resource (e.g. 'update comment with ID 123'). |
||
196 | */ |
||
197 | public function testAuthorizeOperationOnSpecificResourceAsAdmin() |
||
198 | { |
||
199 | // admin can edit any resource |
||
200 | $this->currentUserRole = 'admin'; |
||
201 | $this->currentUserId = 789; |
||
202 | $policyEnforcement = $this->createPolicyEnforcementPoint(); |
||
203 | $authRequest = new Request([ |
||
204 | RequestProperties::PARAM_OPERATION => Comments::OPERATION_UPDATE, |
||
205 | RequestProperties::PARAM_RESOURCE_TYPE => Comments::RESOURCE_TYPE, |
||
206 | RequestProperties::PARAM_RESOURCE_IDENTITY => 1234, // <- ID 1234 is not owned |
||
207 | ]); |
||
208 | $this->assertTrue($policyEnforcement->authorize($authRequest)); |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * Test evaluate policy. |
||
213 | */ |
||
214 | public function testEvaluatePolicyWithIntermediateAndPermit() |
||
215 | { |
||
216 | TestRuleAlgorithm::$result = EvaluationEnum::PERMIT; |
||
217 | $policy = new Policy([(new Rule())], new TestRuleAlgorithm()); |
||
218 | $encodedPolicy = Encoder::encodePolicy($policy); |
||
219 | $context = new Context(new Request([ |
||
220 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
221 | ]), $this->getContextDefinitions()); |
||
222 | |||
223 | $result = BasePolicyOrSetAlgorithm::evaluatePolicy( |
||
224 | $context, |
||
225 | TargetMatchEnum::INDETERMINATE, |
||
226 | $encodedPolicy, |
||
227 | $this->logger |
||
228 | ); |
||
229 | $this->assertEquals([ |
||
230 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::INDETERMINATE_PERMIT, |
||
231 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
232 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
233 | ], $result); |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * Test evaluate policy. |
||
238 | */ |
||
239 | public function testEvaluatePolicyWithIntermediateAndDeny() |
||
240 | { |
||
241 | TestRuleAlgorithm::$result = EvaluationEnum::DENY; |
||
242 | $policy = new Policy([(new Rule())], new TestRuleAlgorithm()); |
||
243 | $encodedPolicy = Encoder::encodePolicy($policy); |
||
244 | $context = new Context(new Request([ |
||
245 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
246 | ]), $this->getContextDefinitions()); |
||
247 | |||
248 | $result = BasePolicyOrSetAlgorithm::evaluatePolicy( |
||
249 | $context, |
||
250 | TargetMatchEnum::INDETERMINATE, |
||
251 | $encodedPolicy, |
||
252 | $this->logger |
||
253 | ); |
||
254 | $this->assertEquals([ |
||
255 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::INDETERMINATE_DENY, |
||
256 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
257 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
258 | ], $result); |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Test evaluate policy. |
||
263 | */ |
||
264 | public function testEvaluatePolicyWithIntermediateAndIntermediate() |
||
265 | { |
||
266 | TestRuleAlgorithm::$result = EvaluationEnum::INDETERMINATE; |
||
267 | $policy = new Policy([(new Rule())], new TestRuleAlgorithm()); |
||
268 | $encodedPolicy = Encoder::encodePolicy($policy); |
||
269 | $context = new Context(new Request([ |
||
270 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
271 | ]), $this->getContextDefinitions()); |
||
272 | |||
273 | $result = BasePolicyOrSetAlgorithm::evaluatePolicy( |
||
274 | $context, |
||
275 | TargetMatchEnum::INDETERMINATE, |
||
276 | $encodedPolicy, |
||
277 | $this->logger |
||
278 | ); |
||
279 | $this->assertEquals([ |
||
280 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT, |
||
281 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
282 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
283 | ], $result); |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Test evaluate policy. |
||
288 | */ |
||
289 | public function testEvaluatePolicyWithIntermediateAndIntermediateDenyOrPermit() |
||
290 | { |
||
291 | TestRuleAlgorithm::$result = EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT; |
||
292 | $policy = new Policy([(new Rule())], new TestRuleAlgorithm()); |
||
293 | $encodedPolicy = Encoder::encodePolicy($policy); |
||
294 | $context = new Context(new Request([ |
||
295 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
296 | ]), $this->getContextDefinitions()); |
||
297 | |||
298 | $result = BasePolicyOrSetAlgorithm::evaluatePolicy( |
||
299 | $context, |
||
300 | TargetMatchEnum::INDETERMINATE, |
||
301 | $encodedPolicy, |
||
302 | $this->logger |
||
303 | ); |
||
304 | $this->assertEquals([ |
||
305 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT, |
||
306 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
307 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
308 | ], $result); |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Test evaluate policy. |
||
313 | */ |
||
314 | public function testEvaluatePolicyWithIntermediateAndIntermediatePermit() |
||
315 | { |
||
316 | TestRuleAlgorithm::$result = EvaluationEnum::INDETERMINATE_PERMIT; |
||
317 | $policy = new Policy([(new Rule())], new TestRuleAlgorithm()); |
||
318 | $encodedPolicy = Encoder::encodePolicy($policy); |
||
319 | $context = new Context(new Request([ |
||
320 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
321 | ]), $this->getContextDefinitions()); |
||
322 | |||
323 | $result = BasePolicyOrSetAlgorithm::evaluatePolicy( |
||
324 | $context, |
||
325 | TargetMatchEnum::INDETERMINATE, |
||
326 | $encodedPolicy, |
||
327 | $this->logger |
||
328 | ); |
||
329 | $this->assertEquals([ |
||
330 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::INDETERMINATE_PERMIT, |
||
331 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
332 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
333 | ], $result); |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Test evaluate policy. |
||
338 | */ |
||
339 | public function testEvaluatePolicyWithIntermediateAndIntermediateDeny() |
||
340 | { |
||
341 | TestRuleAlgorithm::$result = EvaluationEnum::INDETERMINATE_DENY; |
||
342 | $policy = new Policy([(new Rule())], new TestRuleAlgorithm()); |
||
343 | $encodedPolicy = Encoder::encodePolicy($policy); |
||
344 | $context = new Context(new Request([ |
||
345 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
346 | ]), $this->getContextDefinitions()); |
||
347 | |||
348 | $result = BasePolicyOrSetAlgorithm::evaluatePolicy( |
||
349 | $context, |
||
350 | TargetMatchEnum::INDETERMINATE, |
||
351 | $encodedPolicy, |
||
352 | $this->logger |
||
353 | ); |
||
354 | $this->assertEquals([ |
||
355 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::INDETERMINATE_DENY, |
||
356 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
357 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
358 | ], $result); |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Test evaluate policy set. |
||
363 | */ |
||
364 | public function testEvaluatePolicySetWithIntermediate() |
||
365 | { |
||
366 | $set = Application::getApplicationPolicy(); |
||
367 | $encodedPolicy = Encoder::encodePolicySet($set); |
||
368 | $context = new Context(new Request([ |
||
369 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
370 | ]), $this->getContextDefinitions()); |
||
371 | |||
372 | $logger = null; |
||
373 | |||
374 | $result = BasePolicyOrSetAlgorithm::evaluatePolicySet( |
||
375 | $context, |
||
376 | TargetMatchEnum::INDETERMINATE, |
||
377 | $encodedPolicy, |
||
378 | $logger |
||
379 | ); |
||
380 | $this->assertEquals([ |
||
381 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::NOT_APPLICABLE, |
||
382 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
383 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
384 | ], $result); |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * Test evaluate policy set. |
||
389 | */ |
||
390 | public function testEvaluatePolicySetWithNotMatch() |
||
391 | { |
||
392 | $set = Application::getApplicationPolicy(); |
||
393 | $encodedPolicy = Encoder::encodePolicySet($set); |
||
394 | $context = new Context(new Request([ |
||
395 | RequestProperties::PARAM_OPERATION => Messaging::OPERATION_SEND, |
||
396 | ]), $this->getContextDefinitions()); |
||
397 | |||
398 | $logger = null; |
||
399 | |||
400 | $result = BasePolicyOrSetAlgorithm::evaluatePolicySet( |
||
401 | $context, |
||
402 | TargetMatchEnum::NOT_MATCH, |
||
403 | $encodedPolicy, |
||
404 | $logger |
||
405 | ); |
||
406 | $this->assertEquals([ |
||
407 | BasePolicyOrSetAlgorithm::EVALUATION_VALUE => EvaluationEnum::NOT_APPLICABLE, |
||
408 | BasePolicyOrSetAlgorithm::EVALUATION_OBLIGATIONS => [], |
||
409 | BasePolicyOrSetAlgorithm::EVALUATION_ADVICE => [], |
||
410 | ], $result); |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * @param ContextInterface $context |
||
415 | * |
||
416 | * @return void |
||
417 | */ |
||
418 | public static function markObligationAsCalled(ContextInterface $context) |
||
419 | { |
||
420 | // cant be null but to avoid 'not used variable' warning |
||
421 | static::assertNotNull($context); |
||
422 | |||
423 | static::$obligationCalled = true; |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * @param ContextInterface $context |
||
428 | * |
||
429 | * @return void |
||
430 | */ |
||
431 | public static function markAdviceAsCalled(ContextInterface $context) |
||
432 | { |
||
433 | // cant be null but to avoid 'not used variable' warning |
||
434 | static::assertNotNull($context); |
||
435 | |||
436 | static::$adviceCalled = true; |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * @inheritdoc |
||
441 | */ |
||
442 | protected function setUp() |
||
443 | { |
||
444 | parent::setUp(); |
||
445 | |||
446 | $this->logStream = fopen('php://memory', 'rw'); |
||
447 | $this->logger = new Logger('auth', [new StreamHandler($this->getLogStream())]); |
||
448 | |||
449 | $this->currentUserId = null; |
||
450 | $this->currentUserRole = null; |
||
451 | $this->isWorkTime = false; |
||
452 | |||
453 | static::$obligationCalled = false; |
||
454 | static::$adviceCalled = false; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * @return resource |
||
459 | */ |
||
460 | protected function getLogStream() |
||
461 | { |
||
462 | return $this->logStream; |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * @inheritdoc |
||
467 | */ |
||
468 | protected function tearDown() |
||
469 | { |
||
470 | parent::tearDown(); |
||
471 | |||
472 | fclose($this->getLogStream()); |
||
473 | } |
||
474 | |||
475 | /** |
||
476 | * @return string |
||
477 | */ |
||
478 | protected function getLogs() |
||
479 | { |
||
480 | rewind($this->getLogStream()); |
||
481 | $logs = stream_get_contents($this->getLogStream()); |
||
482 | |||
483 | return $logs; |
||
484 | } |
||
485 | |||
486 | /** |
||
487 | * @return PolicyEnforcementPointInterface |
||
488 | */ |
||
489 | private function createPolicyEnforcementPoint() |
||
490 | { |
||
491 | $pip = $this->createPolicyInformationPoint(); |
||
492 | |||
493 | $pdp = new PolicyDecisionPoint(Application::getApplicationPolicy()); |
||
494 | $pdp->setLogger($this->getLogger()); |
||
495 | |||
496 | $pep = new PolicyEnforcementPoint($pip, $pdp); |
||
497 | $pep->setLogger($this->getLogger()); |
||
498 | |||
499 | $this->assertTrue($pep->isExecuteAdvice()); |
||
500 | $pep->disableExecuteAdvice(); |
||
501 | $this->assertFalse($pep->isExecuteAdvice()); |
||
502 | $pep->enableExecuteAdvice(); |
||
503 | $this->assertTrue($pep->isExecuteAdvice()); |
||
504 | |||
505 | return $pep; |
||
506 | } |
||
507 | |||
508 | /** |
||
509 | * @return PolicyInformationPointInterface |
||
510 | */ |
||
511 | private function createPolicyInformationPoint() |
||
512 | { |
||
513 | // Typically values are taken from application container, system environment and |
||
514 | // external systems but not from constructor parameters. However it's fine for testing purposes. |
||
515 | // Both scalar values and methods/closures are supported. |
||
516 | $pip = new PolicyInformationPoint($this->getContextDefinitions()); |
||
517 | |||
518 | return $pip; |
||
519 | } |
||
520 | |||
521 | /** |
||
522 | * @return array |
||
523 | */ |
||
524 | private function getContextDefinitions() |
||
538 |