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

AlgorithmsTest::testPoliciesOrSetsPermitUnlessDeny()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 1
eloc 13
nc 1
nop 0
1
<?php namespace Limoncello\Tests\Auth\Authorization\PolicyDecision;
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\Advice;
20
use Limoncello\Auth\Authorization\PolicyAdministration\AllOf;
21
use Limoncello\Auth\Authorization\PolicyAdministration\AnyOf;
22
use Limoncello\Auth\Authorization\PolicyAdministration\Logical;
23
use Limoncello\Auth\Authorization\PolicyAdministration\Policy;
24
use Limoncello\Auth\Authorization\PolicyAdministration\PolicySet;
25
use Limoncello\Auth\Authorization\PolicyAdministration\Rule;
26
use Limoncello\Auth\Authorization\PolicyAdministration\Target;
27
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\PoliciesOrSetsDenyOverrides;
28
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\PoliciesOrSetsDenyUnlessPermit;
29
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\PoliciesOrSetsFirstApplicable;
30
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\PoliciesOrSetsPermitOverrides;
31
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\PoliciesOrSetsPermitUnlessDeny;
32
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\RulesDenyOverrides;
33
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\RulesFirstApplicable;
34
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\RulesPermitOverrides;
35
use Limoncello\Auth\Authorization\PolicyDecision\Algorithms\RulesPermitUnlessDeny;
36
use Limoncello\Auth\Authorization\PolicyEnforcement\Request;
37
use Limoncello\Auth\Authorization\PolicyInformation\Context;
38
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\EvaluationEnum;
39
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\TargetInterface;
40
use PHPUnit\Framework\TestCase;
41
use RuntimeException;
42
43
/**
44
 * @package Limoncello\Tests\Auth
45
 */
46
class AlgorithmsTest extends TestCase
47
{
48
    const CALLBACK_11 = [self::class, 'callback11'];
49
    const CALLBACK_12 = [self::class, 'callback12'];
50
    const CALLBACK_21 = [self::class, 'callback21'];
51
    const CALLBACK_22 = [self::class, 'callback22'];
52
53
    /**
54
     * @var int
55
     */
56
    private static $callback11Counter;
57
58
    /**
59
     * @var int
60
     */
61
    private static $callback12Counter;
62
63
    /**
64
     * @var int
65
     */
66
    private static $callback21Counter;
67
68
    /**
69
     * @var int
70
     */
71
    private static $callback22Counter;
72
73
    /**
74
     * @inheritdoc
75
     */
76
    protected function setUp()
77
    {
78
        parent::setUp();
79
80
        static::$callback11Counter = 0;
81
        static::$callback12Counter = 0;
82
        static::$callback21Counter = 0;
83
        static::$callback22Counter = 0;
84
    }
85
86
    /**
87
     * Test first applicable algorithm.
88
     */
89
    public function testRuleFirstApplicable()
90
    {
91
        $algorithm    = new RulesFirstApplicable();
92
        $advice11     = new Advice(EvaluationEnum::PERMIT, self::CALLBACK_11);
93
        $advice12     = new Advice(EvaluationEnum::DENY, self::CALLBACK_12);
94
        $advice21     = new Advice(EvaluationEnum::PERMIT, self::CALLBACK_21);
95
        $advice22     = new Advice(EvaluationEnum::DENY, self::CALLBACK_22);
96
        $logicalFalse = new Logical([static::class, 'logicalFalse']);
97
98
        $rulesData = $algorithm->optimize([
99
            // permit
100
            (new Rule())
101
                ->setTarget($this->target('key1', 'value1'))
102
                ->setAdvice([$advice11, $advice12]),
103
            // deny
104
            (new Rule())
105
                ->setTarget($this->target('key2', 'value2'))
106
                ->setAdvice([$advice21, $advice22])
107
                ->setEffect($logicalFalse),
108
        ]);
109
110
        $result = $algorithm->callRuleAlgorithm(new Context(new Request(['key1' => 'value1'])), $rulesData);
111
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
112
        $value  = $result[RulesFirstApplicable::EVALUATION_VALUE];
113
        $advice = $result[RulesFirstApplicable::EVALUATION_ADVICE];
114
115
        $this->assertEquals(EvaluationEnum::PERMIT, $value);
116
        $this->assertCount(1, $advice);
117
        $this->assertEquals(self::CALLBACK_11, $advice[0]);
118
119
        $result = $algorithm->callRuleAlgorithm(new Context(new Request(['key2' => 'value2'])), $rulesData);
120
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
121
        $value  = $result[RulesFirstApplicable::EVALUATION_VALUE];
122
        $advice = $result[RulesFirstApplicable::EVALUATION_ADVICE];
123
124
        $this->assertEquals(EvaluationEnum::DENY, $value);
125
        $this->assertCount(1, $advice);
126
        $this->assertEquals(self::CALLBACK_22, $advice[0]);
127
128
        $result = $algorithm->callRuleAlgorithm(new Context(new Request(['key3' => 'non-existing'])), $rulesData);
129
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
130
        $value = $result[RulesFirstApplicable::EVALUATION_VALUE];
131
        $this->assertEquals(EvaluationEnum::NOT_APPLICABLE, $value);
132
    }
133
134
    /**
135
     * Test permit unless deny algorithm.
136
     */
137
    public function testRulePermitUnlessDeny()
138
    {
139
        $algorithm    = new RulesPermitUnlessDeny();
140
        $advice11     = new Advice(EvaluationEnum::PERMIT, self::CALLBACK_11);
141
        $advice12     = new Advice(EvaluationEnum::DENY, self::CALLBACK_12);
142
        $advice21     = new Advice(EvaluationEnum::PERMIT, self::CALLBACK_21);
143
        $advice22     = new Advice(EvaluationEnum::DENY, self::CALLBACK_22);
144
        $logicalFalse = new Logical([static::class, 'logicalFalse']);
145
146
        $rulesData = $algorithm->optimize([
147
            // permit
148
            (new Rule())
149
                ->setTarget($this->target('key1', 'value1'))
150
                ->setAdvice([$advice11, $advice12]),
151
            // deny
152
            (new Rule())
153
                ->setTarget($this->target('key2', 'value2'))
154
                ->setAdvice([$advice21, $advice22])
155
                ->setEffect($logicalFalse),
156
        ]);
157
158
        $result = $algorithm->callRuleAlgorithm(new Context(new Request(['key1' => 'value1'])), $rulesData);
159
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
160
        $value  = $result[RulesPermitUnlessDeny::EVALUATION_VALUE];
161
        $advice = $result[RulesPermitUnlessDeny::EVALUATION_ADVICE];
162
163
        $this->assertEquals(EvaluationEnum::PERMIT, $value);
164
        $this->assertEmpty($advice);
165
166
        $result = $algorithm->callRuleAlgorithm(new Context(new Request(['key2' => 'value2'])), $rulesData);
167
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
168
        $value  = $result[RulesPermitUnlessDeny::EVALUATION_VALUE];
169
        $advice = $result[RulesPermitUnlessDeny::EVALUATION_ADVICE];
170
171
        $this->assertEquals(EvaluationEnum::DENY, $value);
172
        $this->assertCount(1, $advice);
173
        $this->assertEquals(self::CALLBACK_22, $advice[0]);
174
175
        $result = $algorithm->callRuleAlgorithm(new Context(new Request(['key3' => 'non-existing'])), $rulesData);
176
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
177
        $value = $result[RulesPermitUnlessDeny::EVALUATION_VALUE];
178
        $this->assertEquals(EvaluationEnum::PERMIT, $value);
179
    }
180
181
    /**
182
     * Test deny unless permit for policies.
183
     */
184
    public function testPoliciesOrSetsDenyUnlessPermit()
185
    {
186
        $algorithm = new PoliciesOrSetsDenyUnlessPermit();
187
188
        // permit rule
189
        $rule         = new Rule();
190
        $policiesData = $algorithm->optimize([
191
            (new Policy([$rule], new RulesFirstApplicable()))->setTarget($this->target('key1', 'value1')),
192
        ]);
193
194
        $logger = null;
195
196
        $result = $algorithm->callPolicyAlgorithm(
197
            new Context(new Request(['key1' => 'value1'])),
198
            $policiesData,
199
            $logger
200
        );
201
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
202
        $value = $result[PoliciesOrSetsDenyUnlessPermit::EVALUATION_VALUE];
203
        $this->assertEquals(EvaluationEnum::PERMIT, $value);
204
205
        $result = $algorithm->callPolicyAlgorithm(
206
            new Context(new Request(['key1' => 'non-existing'])),
207
            $policiesData,
208
            $logger
209
        );
210
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
211
        $value = $result[PoliciesOrSetsDenyUnlessPermit::EVALUATION_VALUE];
212
        $this->assertEquals(EvaluationEnum::DENY, $value);
213
    }
214
215
    /**
216
     * Test permit unless deny for policies.
217
     */
218
    public function testPoliciesOrSetsPermitUnlessDeny()
219
    {
220
        $algorithm = new PoliciesOrSetsPermitUnlessDeny();
221
222
        $logicalFalse = new Logical([static::class, 'logicalFalse']);
223
        // deny rule
224
        $rule         = (new Rule())->setEffect($logicalFalse);
225
        $policiesData = $algorithm->optimize([
226
            (new Policy([$rule], new RulesFirstApplicable()))
227
                ->setTarget($this->target('key1', 'value1')),
228
        ]);
229
230
        $logger = null;
231
232
        $result = $algorithm->callPolicyAlgorithm(
233
            new Context(new Request(['key1' => 'value1'])),
234
            $policiesData,
235
            $logger
236
        );
237
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
238
        $value = $result[PoliciesOrSetsPermitUnlessDeny::EVALUATION_VALUE];
239
        $this->assertEquals(EvaluationEnum::DENY, $value);
240
241
        $result = $algorithm->callPolicyAlgorithm(
242
            new Context(new Request(['key1' => 'non-existing'])),
243
            $policiesData,
244
            $logger
245
        );
246
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
247
        $value = $result[PoliciesOrSetsPermitUnlessDeny::EVALUATION_VALUE];
248
        $this->assertEquals(EvaluationEnum::PERMIT, $value);
249
    }
250
251
    /**
252
     * Test first applicable policies.
253
     */
254
    public function testPoliciesOrSetsFirstApplicable()
255
    {
256
        $algorithm = new PoliciesOrSetsFirstApplicable();
257
258
        // deny rule
259
        $logicalFalse = new Logical([static::class, 'logicalFalse']);
260
        $rule1        = (new Rule())->setEffect($logicalFalse);
261
        // permit rule
262
        $rule2        = new Rule();
263
        $policiesData = $algorithm->optimize([
264
            (new Policy([$rule1], new RulesFirstApplicable()))->setTarget($this->target('key1', 'value1')),
265
            (new Policy([$rule2], new RulesFirstApplicable()))->setTarget($this->target('key2', 'value2')),
266
        ]);
267
268
        $logger = null;
269
270
        $result = $algorithm->callPolicyAlgorithm(
271
            new Context(new Request(['key1' => 'value1'])),
272
            $policiesData,
273
            $logger
274
        );
275
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
276
        $this->assertEquals(EvaluationEnum::DENY, $result[PoliciesOrSetsFirstApplicable::EVALUATION_VALUE]);
277
278
        $result = $algorithm->callPolicyAlgorithm(
279
            new Context(new Request(['key2' => 'value2'])),
280
            $policiesData,
281
            $logger
282
        );
283
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
284
        $this->assertEquals(EvaluationEnum::PERMIT, $result[PoliciesOrSetsFirstApplicable::EVALUATION_VALUE]);
285
    }
286
287
    /**
288
     * Test permit overrides for policies.
289
     */
290
    public function testPoliciesOrSetsPermitOverrides()
291
    {
292
        $algorithm = new PoliciesOrSetsPermitOverrides();
293
294
        // deny rule
295
        $logicalFalse = new Logical([static::class, 'logicalFalse']);
296
        $rule1        = (new Rule())->setEffect($logicalFalse);
297
        // permit rule
298
        $rule2        = new Rule();
299
        $policiesData = $algorithm->optimize([
300
            (new Policy([$rule1], new RulesFirstApplicable()))->setTarget($this->target('key1', 'value1')),
301
            (new Policy([$rule2], new RulesFirstApplicable()))->setTarget($this->target('key1', 'value1')),
302
        ]);
303
304
        $logger = null;
305
306
        $result = $algorithm->callPolicyAlgorithm(
307
            new Context(new Request(['key1' => 'value1'])),
308
            $policiesData,
309
            $logger
310
        );
311
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
312
        $value = $result[PoliciesOrSetsPermitOverrides::EVALUATION_VALUE];
313
        $this->assertEquals(EvaluationEnum::PERMIT, $value);
314
    }
315
316
    /**
317
     * Test permit overrides with intermediate permit and intermediate deny.
318
     *
319
     * Sorry this test is very much about specifics of the algorithm and difficult to understand.
320
     */
321
    public function testPermitOverridesWithIntermediates()
322
    {
323
        $algorithm = new PoliciesOrSetsPermitOverrides();
324
325
        // intermediate deny rule
326
        $logicalEx    = new Logical([static::class, 'throwsException']);
327
        $logicalFalse = new Logical([static::class, 'logicalFalse']);
328
        $rule1        = (new Rule())->setCondition($logicalEx)->setEffect($logicalFalse);
329
        // intermediate permit rule
330
        $rule2   = (new Rule())->setCondition($logicalEx);
331
        $policy1 = (new Policy([$rule1], new RulesPermitOverrides()))->setTarget($this->target('key1', 'value1'));
332
        $policy2 = (new Policy([$rule2], new RulesPermitOverrides()))->setTarget($this->target('key1', 'value1'));
333
334
        $logger = null;
335
336
        $policiesData = $algorithm->optimize([$policy1, $policy2]);
337
        $result       = $algorithm->callPolicyAlgorithm(
338
            new Context(new Request(['key1' => 'value1'])),
339
            $policiesData,
340
            $logger
341
        );
342
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
343
        $value = $result[PoliciesOrSetsPermitOverrides::EVALUATION_VALUE];
344
        $this->assertEquals(EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT, $value);
345
346
        // now we'll try to cover those policies in set
347
        $set          = new PolicySet([$policy1, $policy2], $algorithm);
348
        $policiesData = $algorithm->optimize([$set]);
349
        $result       = $algorithm->callPolicyAlgorithm(
350
            new Context(new Request(['key1' => 'value1'])),
351
            $policiesData,
352
            $logger
353
        );
354
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
355
        $value = $result[PoliciesOrSetsPermitOverrides::EVALUATION_VALUE];
356
        $this->assertEquals(EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT, $value);
357
    }
358
359
    /**
360
     * Test deny overrides with intermediate permit and intermediate deny.
361
     *
362
     * Sorry this test is very much about specifics of the algorithm and difficult to understand.
363
     */
364
    public function testDenyOverridesWithIntermediates()
365
    {
366
        $algorithm = new PoliciesOrSetsDenyOverrides();
367
368
        // intermediate deny rule
369
        $logicalEx    = new Logical([static::class, 'throwsException']);
370
        $logicalFalse = new Logical([static::class, 'logicalFalse']);
371
        $rule1        = (new Rule())->setCondition($logicalEx)->setEffect($logicalFalse);
372
        // intermediate permit rule
373
        $rule2   = (new Rule())->setCondition($logicalEx);
374
        $policy1 = (new Policy([$rule1], new RulesDenyOverrides()))->setTarget($this->target('key1', 'value1'));
375
        $policy2 = (new Policy([$rule2], new RulesDenyOverrides()))->setTarget($this->target('key1', 'value1'));
376
377
        $logger = null;
378
379
        $policiesData = $algorithm->optimize([$policy1, $policy2]);
380
        $result       = $algorithm->callPolicyAlgorithm(
381
            new Context(new Request(['key1' => 'value1'])),
382
            $policiesData,
383
            $logger
384
        );
385
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
386
        $value = $result[PoliciesOrSetsDenyOverrides::EVALUATION_VALUE];
387
        $this->assertEquals(EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT, $value);
388
389
        // now we'll try to cover those policies in set
390
        $set          = new PolicySet([$policy1, $policy2], $algorithm);
391
        $policiesData = $algorithm->optimize([$set]);
392
        $result       = $algorithm->callPolicyAlgorithm(
393
            new Context(new Request(['key1' => 'value1'])),
394
            $policiesData,
395
            $logger
396
        );
397
        // here we rely on knowledge if internal structure of the result (it's not intended for direct usage)
398
        $value = $result[PoliciesOrSetsDenyOverrides::EVALUATION_VALUE];
399
        $this->assertEquals(EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT, $value);
400
    }
401
402
    /**
403
     * @return void
404
     */
405
    public static function callback11()
406
    {
407
        static::$callback11Counter++;
408
    }
409
410
    /**
411
     * @return void
412
     */
413
    public static function callback12()
414
    {
415
        static::$callback12Counter++;
416
    }
417
418
    /**
419
     * @return void
420
     */
421
    public static function callback21()
422
    {
423
        static::$callback21Counter++;
424
    }
425
426
    /**
427
     * @return void
428
     */
429
    public static function callback22()
430
    {
431
        static::$callback22Counter++;
432
    }
433
434
    /**
435
     * @return bool
436
     */
437
    public static function logicalFalse()
438
    {
439
        return false;
440
    }
441
442
    /**
443
     * @throws RuntimeException
444
     */
445
    public static function throwsException()
446
    {
447
        throw new RuntimeException();
448
    }
449
450
    /**
451
     * @param string $key
452
     * @param string $value
453
     *
454
     * @return TargetInterface
455
     */
456
    private function target($key, $value)
457
    {
458
        return new Target(new AnyOf([new AllOf([$key => $value])]));
459
    }
460
}
461