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

PolicyDecisionTest::createRules()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 49
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 49
rs 9.2258
c 0
b 0
f 0
cc 3
eloc 38
nc 4
nop 2
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\Obligation;
24
use Limoncello\Auth\Authorization\PolicyAdministration\Rule;
25
use Limoncello\Auth\Authorization\PolicyAdministration\Target;
26
use Limoncello\Auth\Authorization\PolicyDecision\RuleAlgorithm;
27
use Limoncello\Auth\Authorization\PolicyEnforcement\Request;
28
use Limoncello\Auth\Authorization\PolicyInformation\Context;
29
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\EvaluationEnum;
30
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\RuleCombiningAlgorithmInterface;
31
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\RuleInterface;
32
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\TargetInterface;
33
use Limoncello\Auth\Contracts\Authorization\PolicyInformation\ContextInterface;
34
use PHPUnit\Framework\TestCase;
35
use RuntimeException;
36
37
/**
38
 * @package Limoncello\Tests\Auth
39
 */
40
class PolicyDecisionTest extends TestCase
41
{
42
    /**
43
     * @var bool
44
     */
45
    private static $wasConditionCalled = false;
46
47
    /**
48
     * @var bool
49
     */
50
    private static $wasObligationCalled = false;
51
52
    /**
53
     * @var bool
54
     */
55
    private static $wasAdviceCalled = false;
56
57
    /**
58
     * @inheritdoc
59
     */
60
    protected function setUp()
61
    {
62
        parent::setUp();
63
64
        static::$wasConditionCalled  = false;
65
        static::$wasObligationCalled = false;
66
        static::$wasAdviceCalled     = false;
67
    }
68
69
    /**
70
     * @return array
71
     */
72
    public function testOptimize()
73
    {
74
        $algorithm = RuleAlgorithm::denyUnlessPermit();
75
76
        list($callable, $targets, $rules) = $this->optimizedRules($algorithm, $this->createRules());
77
78
        $this->assertTrue(is_callable($callable));
79
        $this->assertTrue(is_array($callable));
80
        $this->assertCount(4, $targets);
81
        $this->assertCount(4, $rules);
82
83
        return [$callable, $targets, $rules];
84
    }
85
86
    /**
87
     * Test 'Deny' result.
88
     */
89
    public function testDeny()
90
    {
91
        $algorithm = RuleAlgorithm::denyUnlessPermit();
92
        list($callable, $targets, $rules) = $this->optimizedRules($algorithm, $this->createRules(false));
93
94
        $logger  = null;
95
        $context = new Context(new Request([]), []);
96
        $this->assertEquals(
97
            [EvaluationEnum::DENY, [], []],
98
            call_user_func($callable, $context, $targets, $rules, $logger)
99
        );
100
    }
101
102
    /**
103
     * Test 'Permit' for 'no-target'.
104
     */
105
    public function testNoTargetPermit()
106
    {
107
        list($callable, $targets, $rules) = $this->testOptimize();
108
109
        $logger  = null;
110
        $context = new Context(new Request([]), []);
111
        $this->assertEquals(
112
            [EvaluationEnum::PERMIT, [], []],
113
            call_user_func($callable, $context, $targets, $rules, $logger)
114
        );
115
    }
116
117
    /**
118
     * Test 'Permit'.
119
     */
120
    public function testPermit11()
121
    {
122
        list($callable, $targets, $rules) = $this->testOptimize();
123
124
        $logger  = null;
125
        $context = new Context(new Request([
126
            'key11_1' => 'value11_1',
127
            'key11_2' => 'value11_2',
128
            'key11_3' => 'value11_3',
129
        ]), []);
130
        $this->assertEquals(
131
            [EvaluationEnum::PERMIT, [[self::class, 'ruleObligation1']], [[self::class, 'ruleAdvice1']]],
132
            call_user_func($callable, $context, $targets, $rules, $logger)
133
        );
134
    }
135
136
    /**
137
     * Test 'Permit'.
138
     */
139
    public function testPermit12()
140
    {
141
        list($callable, $targets, $rules) = $this->testOptimize();
142
143
        // value pairs could be split (should be no difference)
144
        $logger  = null;
145
        $context = new Context(new Request([
146
            'key12_1' => 'value12_1',
147
        ]), [
148
            'key12_2' => 'value12_2',
149
        ]);
150
        $this->assertEquals(
151
            [EvaluationEnum::PERMIT, [[self::class, 'ruleObligation1']], [[self::class, 'ruleAdvice1']]],
152
            call_user_func($callable, $context, $targets, $rules, $logger)
153
        );
154
    }
155
156
    /**
157
     * Test 'Deny Overrides'.
158
     */
159
    public function testDenyOverrides()
160
    {
161
        $algorithm = RuleAlgorithm::denyOverrides();
162
        list($callable, $targets, $rules) = $this->optimizedRules($algorithm, $this->createRules());
163
164
        $logger  = null;
165
        $context = new Context(new Request([
166
            'key21_1' => 'value21_1',
167
            'key21_2' => 'value21_2',
168
            'key21_3' => 'value21_3',
169
        ]), []);
170
        $this->assertEquals(
171
            [EvaluationEnum::DENY, [[self::class, 'ruleObligation2']], [[self::class, 'ruleAdvice2']]],
172
            call_user_func($callable, $context, $targets, $rules, $logger)
173
        );
174
    }
175
176
    /**
177
     * Test exception in condition.
178
     */
179
    public function testExceptionInCondition()
180
    {
181
        $algorithm = RuleAlgorithm::denyUnlessPermit();
182
        list($callable, $targets, $rules) = $this->optimizedRules($algorithm, $this->createRules(false, true));
183
184
        $logger  = null;
185
        $context = new Context(new Request([
186
            'key11_1' => 'value11_1',
187
            'key11_2' => 'value11_2',
188
            'key11_3' => 'value11_3',
189
        ]), []);
190
        $this->assertEquals(
191
            [EvaluationEnum::DENY, [], []],
192
            call_user_func($callable, $context, $targets, $rules, $logger)
193
        );
194
    }
195
196
    /**
197
     * Test exception in target.
198
     */
199
    public function testExceptionInTarget()
200
    {
201
        $algorithm = RuleAlgorithm::denyUnlessPermit();
202
        list($callable, $targets, $rules) = $this->optimizedRules($algorithm, $this->createRules(false));
203
204
        $logger          = null;
205
        $exceptionThrown = false;
206
        $context         = new Context(new Request([]), [
207
            'key11_1' => function () use (&$exceptionThrown) {
208
                $exceptionThrown = true;
209
                throw new RuntimeException();
210
            },
211
        ]);
212
        $this->assertEquals(
213
            [EvaluationEnum::DENY, [], []],
214
            call_user_func($callable, $context, $targets, $rules, $logger)
215
        );
216
        $this->assertTrue($exceptionThrown);
217
    }
218
219
    /**
220
     * Test 'false' in condition.
221
     */
222
    public function testFalseInCondition()
223
    {
224
        $rule = (new Rule())
225
            ->setTarget($this->target('key', 'value'))
226
            ->setCondition(new Logical([self::class, 'ruleConditionFalse']));
227
        list($callable, $targets, $rules) = $this->optimizedRules(RuleAlgorithm::denyUnlessPermit(), [$rule]);
228
229
        $logger  = null;
230
        $context = new Context(new Request([]), ['key' => 'value']);
231
        $this->assertEquals(
232
            [EvaluationEnum::DENY, [], []],
233
            call_user_func($callable, $context, $targets, $rules, $logger)
234
        );
235
        $this->assertTrue(static::$wasConditionCalled);
236
    }
237
238
    /**
239
     * Test effect fails.
240
     */
241
    public function testEffectFails()
242
    {
243
        $rule = (new Rule())
244
            ->setTarget($this->target('key', 'value'))
245
            ->setEffect(new Logical([self::class, 'logicalThrowsException']));
246
        list($callable, $targets, $rules) = $this->optimizedRules(RuleAlgorithm::permitOverrides(), [$rule]);
247
248
        $logger  = null;
249
        $context = new Context(new Request([]), ['key' => 'value']);
250
        $this->assertEquals(
251
            [EvaluationEnum::DENY, [], []],
252
            call_user_func($callable, $context, $targets, $rules, $logger)
253
        );
254
        $this->assertTrue(static::$wasConditionCalled);
255
    }
256
257
    /**
258
     * @param ContextInterface $context
259
     *
260
     * @return bool
261
     */
262
    public static function ruleCondition(ContextInterface $context)
263
    {
264
        static::assertNotNull($context);
265
266
        static::$wasConditionCalled = true;
267
268
        return true;
269
    }
270
271
    /**
272
     * @param ContextInterface $context
273
     *
274
     * @return bool
275
     */
276
    public static function ruleConditionFalse(ContextInterface $context)
277
    {
278
        static::assertNotNull($context);
279
280
        static::$wasConditionCalled = true;
281
282
        return false;
283
    }
284
285
    /**
286
     * @param ContextInterface $context
287
     *
288
     * @throws RuntimeException
289
     */
290
    public static function logicalThrowsException(ContextInterface $context)
291
    {
292
        static::assertNotNull($context);
293
294
        static::$wasConditionCalled = true;
295
296
        throw new RuntimeException();
297
    }
298
299
    /**
300
     * @param ContextInterface $context
301
     */
302
    public static function ruleObligation1(ContextInterface $context)
303
    {
304
        static::assertNotNull($context);
305
        static::$wasObligationCalled = true;
306
    }
307
308
    /**
309
     * @param ContextInterface $context
310
     */
311
    public static function ruleAdvice1(ContextInterface $context)
312
    {
313
        static::assertNotNull($context);
314
        static::$wasAdviceCalled = true;
315
    }
316
317
    /**
318
     * @param ContextInterface $context
319
     */
320
    public static function ruleObligation2(ContextInterface $context)
321
    {
322
        static::assertNotNull($context);
323
        static::$wasObligationCalled = true;
324
    }
325
326
    /**
327
     * @param ContextInterface $context
328
     */
329
    public static function ruleAdvice2(ContextInterface $context)
330
    {
331
        static::assertNotNull($context);
332
        static::$wasAdviceCalled = true;
333
    }
334
335
    /**
336
     * @param ContextInterface $context
337
     *
338
     * @return bool
339
     */
340
    public static function effectDeny(ContextInterface $context)
341
    {
342
        static::assertNotNull($context);
343
344
        return false;
345
    }
346
347
    /**
348
     * @param RuleCombiningAlgorithmInterface $algorithm
349
     * @param RuleInterface[]                 $rules
350
     *
351
     * @return array
352
     */
353
    private function optimizedRules(RuleCombiningAlgorithmInterface $algorithm, array $rules)
354
    {
355
        $this->assertNotEmpty($optimized = $algorithm->optimize($rules));
356
        $this->assertNotEmpty($targets = $optimized[RuleCombiningAlgorithmInterface::INDEX_TARGETS]);
357
        $this->assertNotEmpty($rules = $optimized[RuleCombiningAlgorithmInterface::INDEX_RULES]);
358
        $this->assertNotEmpty($callable = $optimized[RuleCombiningAlgorithmInterface::INDEX_CALLABLE]);
359
360
        return [$callable, $targets, $rules];
361
    }
362
363
    /**
364
     * @param bool $addTargetAll
365
     * @param bool $exInCondition
366
     *
367
     * @return RuleInterface[]
368
     */
369
    private function createRules($addTargetAll = true, $exInCondition = false)
370
    {
371
        $allOf11 = new AllOf([
372
            'key11_1' => 'value11_1',
373
            'key11_2' => 'value11_2',
374
            'key11_3' => 'value11_3',
375
        ]);
376
        $allOf12 = new AllOf([
377
            'key12_1' => 'value12_1',
378
            'key12_2' => 'value12_2',
379
        ]);
380
        $allOf21 = new AllOf([
381
            'key21_1' => 'value21_1',
382
            'key21_2' => 'value21_2',
383
            'key21_3' => 'value21_3',
384
        ]);
385
        $allOf22 = new AllOf([
386
            'key22_1' => 'value22_1',
387
            'key22_2' => 'value22_2',
388
        ]);
389
        $allOf3  = new AllOf([
390
            'key31' => 'value31',
391
        ]);
392
393
        $anyOf1 = new AnyOf([$allOf11, $allOf12]);
394
        $anyOf2 = new AnyOf([$allOf21, $allOf22]);
395
        $anyOf3 = new AnyOf([$allOf3]);
396
397
        $target1 = new Target($anyOf1);
398
        $target2 = new Target($anyOf2);
399
        $target3 = new Target($anyOf3);
400
401
        $methodName = $exInCondition === true ? 'logicalThrowsException' : 'ruleCondition';
402
        $rule1      = (new Rule())
403
            ->setTarget($target1)
404
            ->setCondition(new Logical([self::class, $methodName]))
405
            ->setObligations([new Obligation(EvaluationEnum::PERMIT, [self::class, 'ruleObligation1'])])
406
            ->setAdvice([new Advice(EvaluationEnum::PERMIT, [self::class, 'ruleAdvice1'])]);
407
        $rule2      = (new Rule())
408
            ->setEffect(new Logical([self::class, 'effectDeny']))
409
            ->setTarget($target2)
410
            ->setCondition(new Logical([self::class, $methodName]))
411
            ->setObligations([new Obligation(EvaluationEnum::DENY, [self::class, 'ruleObligation2'])])
412
            ->setAdvice([new Advice(EvaluationEnum::DENY, [self::class, 'ruleAdvice2'])]);
413
        $rule3      = (new Rule())->setTarget($target3);
414
        $rule4      = new Rule();
415
416
        return $addTargetAll === true ? [$rule1, $rule2, $rule3, $rule4] : [$rule1, $rule2, $rule3];
417
    }
418
419
    /**
420
     * @param string $key
421
     * @param string $value
422
     *
423
     * @return TargetInterface
424
     */
425
    private function target($key, $value)
426
    {
427
        return new Target(new AnyOf([new AllOf([$key => $value])]));
428
    }
429
}
430