Completed
Branch master (91621e)
by Neomerx
01:35
created

PolicyEnforcementTest::testEvaluatePolicyWithIntermediateAndPermit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 17

Duplication

Lines 21
Ratio 100 %

Importance

Changes 0
Metric Value
dl 21
loc 21
rs 9.3142
c 0
b 0
f 0
cc 1
eloc 17
nc 1
nop 0
1
<?php namespace Limoncello\Tests\Auth\Authorization\PolicyEnforcement;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Limoncello\Auth\Authorization\PolicyAdministration\Policy;
20
use Limoncello\Auth\Authorization\PolicyAdministration\Rule;
21
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\BasePolicyOrSetAlgorithm;
22
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\Encoder;
23
use Limoncello\Auth\Authorization\PolicyDecision\PolicyDecisionPoint;
24
use Limoncello\Auth\Authorization\PolicyEnforcement\PolicyEnforcementPoint;
25
use Limoncello\Auth\Authorization\PolicyEnforcement\Request;
26
use Limoncello\Auth\Authorization\PolicyInformation\Context;
27
use Limoncello\Auth\Authorization\PolicyInformation\PolicyInformationPoint;
28
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\EvaluationEnum;
29
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\TargetMatchEnum;
30
use Limoncello\Auth\Contracts\Authorization\PolicyEnforcement\PolicyEnforcementPointInterface;
31
use Limoncello\Auth\Contracts\Authorization\PolicyInformation\ContextInterface;
32
use Limoncello\Auth\Contracts\Authorization\PolicyInformation\PolicyInformationPointInterface;
33
use Limoncello\Tests\Auth\Authorization\PolicyEnforcement\Data\ContextProperties;
34
use Limoncello\Tests\Auth\Authorization\PolicyEnforcement\Data\Policies\Application;
35
use Limoncello\Tests\Auth\Authorization\PolicyEnforcement\Data\Policies\Comments;
36
use Limoncello\Tests\Auth\Authorization\PolicyEnforcement\Data\Policies\Messaging;
37
use Limoncello\Tests\Auth\Authorization\PolicyEnforcement\Data\RequestProperties;
38
use Limoncello\Tests\Auth\Authorization\PolicyEnforcement\Data\TestRuleAlgorithm;
39
use Monolog\Handler\StreamHandler;
40
use Monolog\Logger;
41
use PHPUnit\Framework\TestCase;
42
use Psr\Log\LoggerInterface;
43
44
/**
45
 * @package Limoncello\Tests\Auth
46
 */
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()
525
    {
526
        return [
527
            LoggerInterface::class                     => $this->getLogger(),
528
            ContextProperties::PARAM_CURRENT_USER_ID   => $this->currentUserId,
529
            ContextProperties::PARAM_CURRENT_USER_ROLE => $this->currentUserRole,
530
            ContextProperties::PARAM_USER_IS_SIGNED_IN => $this->currentUserId !== null &&
531
                $this->currentUserRole !== null,
532
            ContextProperties::PARAM_IS_WORK_TIME      => function () {
533
                return $this->isWorkTime;
534
            },
535
        ];
536
    }
537
}
538