BasePolicyOrSetAlgorithm   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 6
dl 0
loc 327
ccs 120
cts 120
cp 1
rs 9.44
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
optimizeTargets() 0 1 ?
A callPolicyAlgorithm() 0 13 1
B optimize() 0 29 8
B evaluatePolicy() 0 56 6
B evaluatePolicySet() 0 58 7
A evaluateItem() 0 12 2
B correctEvaluationOnIntermediateTarget() 0 37 8
A getTargets() 0 4 1
A getPoliciesAndSets() 0 4 1
A getCallable() 0 4 1
A mergeFulfilledObligations() 0 4 1
A mergeAppliedAdvice() 0 4 1
1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Auth\Authorization\PolicyDecision\Algorithms;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\EvaluationEnum;
22
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\PolicyCombiningAlgorithmInterface;
23
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\PolicyInterface;
24
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\PolicySetInterface;
25
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\TargetMatchEnum;
26
use Limoncello\Auth\Contracts\Authorization\PolicyInformation\ContextInterface;
27
use Psr\Log\LoggerInterface;
28
use function assert;
29
use function array_merge;
30
use function count;
31
use function is_array;
32
use function is_callable;
33
use function is_string;
34
35
/**
36
 * @package Limoncello\Auth
37
 */
38
abstract class BasePolicyOrSetAlgorithm extends BaseAlgorithm implements PolicyCombiningAlgorithmInterface
39
{
40
    /**
41
     * @param array $targets
42
     *
43
     * @return array
44
     */
45
    abstract protected function optimizeTargets(array $targets): array;
46
47
    /**
48 15
     * @param ContextInterface     $context
49
     * @param array                $policiesAndSetsData
50
     * @param LoggerInterface|null $logger
51
     *
52
     * @return array
53 15
     *
54 15
     * @SuppressWarnings(PHPMD.StaticAccess)
55 15
     */
56 15
    public static function callPolicyAlgorithm(
57 15
        ContextInterface $context,
58 15
        array $policiesAndSetsData,
59
        ?LoggerInterface $logger
60
    ): array {
61
        return static::callAlgorithm(
62
            static::getCallable($policiesAndSetsData),
0 ignored issues
show
Bug introduced by
Since getCallable() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getCallable() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
63
            $context,
64
            static::getTargets($policiesAndSetsData),
0 ignored issues
show
Bug introduced by
Since getTargets() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getTargets() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
65
            static::getPoliciesAndSets($policiesAndSetsData),
0 ignored issues
show
Bug introduced by
Since getPoliciesAndSets() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getPoliciesAndSets() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
66
            $logger
67 17
        );
68
    }
69 17
70
    /**
71 17
     * @inheritdoc
72 17
     *
73 17
     * @SuppressWarnings(PHPMD.StaticAccess)
74 17
     */
75
    public function optimize(array $policiesAndSets): array
76 17
    {
77 17
        assert(empty($policiesAndSets) === false);
78 17
79 17
        $index              = 0;
80
        $rawTargets         = [];
81
        $serializedPolicies = [];
82 17
        foreach ($policiesAndSets as $policyOrSet) {
83 17
            /** @var PolicyInterface|PolicySetInterface $policyOrSet */
84
            $rawTargets[$index]         = $policyOrSet->getTarget();
85
            $serializedPolicies[$index] = $policyOrSet instanceof PolicyInterface ?
86 17
                Encoder::encodePolicy($policyOrSet) : Encoder::encodePolicySet($policyOrSet);
87 17
            $index++;
88 17
        }
89
90
        $callable         = static::METHOD;
91 17
        $optimizedTargets = $this->optimizeTargets($rawTargets);
92 17
93 17
        /** @var callable|array $callable */
94
        assert($callable !== null && is_array($callable) === true &&
95
            is_callable($callable) === true && count($callable) === 2 &&
96
            is_string($callable[0]) === true && is_string($callable[1]) === true);
97
98
        return [
99
            static::INDEX_TARGETS           => $optimizedTargets,
100
            static::INDEX_POLICIES_AND_SETS => $serializedPolicies,
101
            static::INDEX_CALLABLE          => $callable,
102
        ];
103
    }
104
105
    /**
106
     * @param ContextInterface     $context
107
     * @param int                  $match
108 21
     * @param array                $encodedPolicy
109
     * @param LoggerInterface|null $logger
110
     *
111
     * @return array
112
     *
113
     * @SuppressWarnings(PHPMD.StaticAccess)
114 21
     * @SuppressWarnings(PHPMD.ElseExpression)
115 21
     */
116
    public static function evaluatePolicy(
117
        ContextInterface $context,
118
        int $match,
119
        array $encodedPolicy,
120
        ?LoggerInterface $logger
121
    ): array {
122
        assert(Encoder::isPolicy($encodedPolicy));
123
        assert($match !== TargetMatchEnum::NO_TARGET);
124
125
        /** @see http://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html #7.12 (table 5) */
126
127
        // Reminder on obligations and advice (from 7.18)
128 21
        //---------------------------------------------------------------------
129 21
        // no obligations or advice SHALL be returned to the PEP if the rule,
130 14
        // policies, or policy sets from which they are drawn are not evaluated,
131 14
        // or if their evaluated result is "Indeterminate" or "NotApplicable",
132 14
        // or if the decision resulting from evaluating the rule, policy,
133
        // or policy set does not match the decision resulting from evaluating
134
        // an enclosing policy set.
135 21
136 19
        $policyName = null;
137 19
        if ($logger !== null) {
138 19
            $policyName = Encoder::policyName($encodedPolicy);
139 19
            $matchName  = TargetMatchEnum::toString($match);
140
            $logger->debug("Policy '$policyName' evaluation started for match '$matchName'.");
141
        }
142 19
143
        if ($match === TargetMatchEnum::MATCH || $match === TargetMatchEnum::INDETERMINATE) {
144 6
            list ($evaluation, $obligations, $advice) = BaseRuleAlgorithm::callRuleAlgorithm(
145 6
                $context,
146
                Encoder::rulesData(Encoder::policyRules($encodedPolicy)),
147 13
                $logger
148 13
            );
149 13
150 13
            if ($match === TargetMatchEnum::INDETERMINATE) {
151 13
                // evaluate final result in accordance to table 7
152 19
                $correctedEvaluation = static::correctEvaluationOnIntermediateTarget($evaluation, $logger);
0 ignored issues
show
Bug introduced by
Since correctEvaluationOnIntermediateTarget() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of correctEvaluationOnIntermediateTarget() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
153
                $result              = static::packEvaluationResult($correctedEvaluation);
154
            } else {
155
                $obligationsMap = Encoder::policyObligations($encodedPolicy);
156 11
                $adviceMap      = Encoder::policyAdvice($encodedPolicy);
157 11
                $result         = static::packEvaluationResult(
158
                    $evaluation,
159
                    static::mergeFulfilledObligations($obligations, $evaluation, $obligationsMap),
0 ignored issues
show
Bug introduced by
Since mergeFulfilledObligations() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeFulfilledObligations() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
160 21
                    static::mergeAppliedAdvice($advice, $evaluation, $adviceMap)
0 ignored issues
show
Bug introduced by
Since mergeAppliedAdvice() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeAppliedAdvice() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
161
                );
162 21
            }
163
        } else {
164
            assert($match === TargetMatchEnum::NOT_MATCH);
165
            $result = static::packEvaluationResult(EvaluationEnum::NOT_APPLICABLE);
166
        }
167
168
        $logger === null ?: $logger->debug("Policy '$policyName' evaluation ended.");
169
170
        return $result;
171
    }
172
173
    /**
174
     * @param ContextInterface     $context
175
     * @param int                  $match
176 12
     * @param array                $encodedPolicySet
177
     * @param LoggerInterface|null $logger
178
     *
179
     * @return array
180
     *
181
     * @SuppressWarnings(PHPMD.StaticAccess)
182 12
     * @SuppressWarnings(PHPMD.ElseExpression)
183
     */
184
    public static function evaluatePolicySet(
185
        ContextInterface $context,
186
        int $match,
187
        array $encodedPolicySet,
188
        ?LoggerInterface $logger
189
    ): array {
190
        assert(Encoder::isPolicySet($encodedPolicySet));
191
192
        /** @see http://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html #7.13 (table 6) */
193
194
        // Reminder on obligations and advice (from 7.18)
195 12
        //---------------------------------------------------------------------
196 12
        // no obligations or advice SHALL be returned to the PEP if the rule,
197 8
        // policies, or policy sets from which they are drawn are not evaluated,
198 8
        // or if their evaluated result is "Indeterminate" or "NotApplicable",
199 8
        // or if the decision resulting from evaluating the rule, policy,
200
        // or policy set does not match the decision resulting from evaluating
201
        // an enclosing policy set.
202 12
203 12
        $policySetName = null;
204 12
        if ($logger !== null) {
205
            $policySetName = Encoder::policySetName($encodedPolicySet);
206 11
            $matchName     = TargetMatchEnum::toString($match);
207 11
            $logger->debug("Policy set '$policySetName' evaluation started for match '$matchName'.");
208 11
        }
209 11
210
        if ($match === TargetMatchEnum::MATCH ||
211
            $match === TargetMatchEnum::NO_TARGET ||
212 11
            $match === TargetMatchEnum::INDETERMINATE
213
        ) {
214 1
            list ($evaluation, $obligations, $advice) = static::callPolicyAlgorithm(
215 1
                $context,
216
                Encoder::policiesAndSetsData(Encoder::policySetChildren($encodedPolicySet)),
217 10
                $logger
218 10
            );
219 10
220 10
            if ($match === TargetMatchEnum::INDETERMINATE) {
221 10
                // evaluate final result in accordance to table 7
222 11
                $correctedEvaluation = static::correctEvaluationOnIntermediateTarget($evaluation, $logger);
0 ignored issues
show
Bug introduced by
Since correctEvaluationOnIntermediateTarget() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of correctEvaluationOnIntermediateTarget() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
223
                $result = static::packEvaluationResult($correctedEvaluation);
224
            } else {
225
                $obligationsMap = Encoder::policySetObligations($encodedPolicySet);
226 1
                $adviceMap      = Encoder::policySetAdvice($encodedPolicySet);
227 1
                $result         = static::packEvaluationResult(
228
                    $evaluation,
229
                    static::mergeFulfilledObligations($obligations, $evaluation, $obligationsMap),
0 ignored issues
show
Bug introduced by
Since mergeFulfilledObligations() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeFulfilledObligations() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
230 12
                    static::mergeAppliedAdvice($advice, $evaluation, $adviceMap)
0 ignored issues
show
Bug introduced by
Since mergeAppliedAdvice() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeAppliedAdvice() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
231
                );
232 12
            }
233
        } else {
234
            assert($match === TargetMatchEnum::NOT_MATCH);
235
            $result = static::packEvaluationResult(EvaluationEnum::NOT_APPLICABLE);
236
        }
237
238
        $logger === null ?: $logger->debug("Policy set '$policySetName' evaluation ended.");
239
240 15
        return $result;
241
    }
242
243
    /**
244
     * @inheritdoc
245
     *
246 15
     * @SuppressWarnings(PHPMD.StaticAccess)
247
     */
248 15
    public static function evaluateItem(
249 10
        ContextInterface $context,
250 15
        int $match,
251
        array $encodedItem,
252
        ?LoggerInterface $logger
253
    ): array {
254
        $isSet = Encoder::isPolicySet($encodedItem);
255
256
        return $isSet === true ?
257
            static::evaluatePolicySet($context, $match, $encodedItem, $logger) :
258
            static::evaluatePolicy($context, $match, $encodedItem, $logger);
259
    }
260
261 7
    /**
262
     * @param int                  $evaluation
263
     * @param LoggerInterface|null $logger
264
     *
265
     * @return int
266 7
     *
267 1
     * @SuppressWarnings(PHPMD.StaticAccess)
268 1
     */
269 6
    private static function correctEvaluationOnIntermediateTarget(int $evaluation, ?LoggerInterface $logger): int
270 1
    {
271 1
        /** @see http://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html #7.14 (table 7) */
272 5
273 1
        switch ($evaluation) {
274 1
            case EvaluationEnum::NOT_APPLICABLE:
275 4
                $result = EvaluationEnum::NOT_APPLICABLE;
276 1
                break;
277 1
            case EvaluationEnum::PERMIT:
278 3
                $result = EvaluationEnum::INDETERMINATE_PERMIT;
279 1
                break;
280 1
            case EvaluationEnum::DENY:
281 2
                $result = EvaluationEnum::INDETERMINATE_DENY;
282 1
                break;
283 1
            case EvaluationEnum::INDETERMINATE:
284
                $result = EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT;
285 1
                break;
286 1
            case EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT:
287 1
                $result = EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT;
288
                break;
289
            case EvaluationEnum::INDETERMINATE_PERMIT:
290 7
                $result = EvaluationEnum::INDETERMINATE_PERMIT;
291 6
                break;
292 6
            default:
293 6
                assert($evaluation === EvaluationEnum::INDETERMINATE_DENY);
294
                $result = EvaluationEnum::INDETERMINATE_DENY;
295
                break;
296 7
        }
297
298
        if ($logger !== null) {
299
            $fromEval = EvaluationEnum::toString($evaluation);
300
            $toEval   = EvaluationEnum::toString($result);
301
            $logger->info("Due to error while checking target the evaluation changed from '$fromEval' to '$toEval'.");
302
        }
303
304 15
        return $result;
305
    }
306 15
307
    /**
308
     * @param array $policiesAndSetsData
309
     *
310
     * @return array
311
     */
312
    private static function getTargets(array $policiesAndSetsData): array
313
    {
314 15
        return $policiesAndSetsData[self::INDEX_TARGETS];
315
    }
316 15
317
    /**
318
     * @param array $policiesAndSetsData
319
     *
320
     * @return array
321
     */
322
    private static function getPoliciesAndSets(array $policiesAndSetsData): array
323
    {
324 15
        return $policiesAndSetsData[self::INDEX_POLICIES_AND_SETS];
325
    }
326 15
327
    /**
328
     * @param array $policiesAndSetsData
329
     *
330
     * @return callable
331
     */
332
    private static function getCallable(array $policiesAndSetsData): callable
333
    {
334
        return $policiesAndSetsData[self::INDEX_CALLABLE];
335
    }
336
337
    /**
338 14
     * @param array $obligations
339
     * @param int   $evaluation
340 14
     * @param array $obligationsMap
341
     *
342
     * @return array
343
     *
344
     * @SuppressWarnings(PHPMD.StaticAccess)
345
     */
346
    private static function mergeFulfilledObligations(array $obligations, $evaluation, array $obligationsMap): array
347
    {
348
        return array_merge($obligations, Encoder::getFulfillObligations($evaluation, $obligationsMap));
349
    }
350
351
    /**
352 14
     * @param array $advice
353
     * @param int   $evaluation
354 14
     * @param array $adviceMap
355
     *
356
     * @return array
357
     *
358
     * @SuppressWarnings(PHPMD.StaticAccess)
359
     */
360
    private static function mergeAppliedAdvice(array $advice, $evaluation, array $adviceMap): array
361
    {
362
        return array_merge($advice, Encoder::getAppliedAdvice($evaluation, $adviceMap));
363
    }
364
}
365