BaseRuleAlgorithm::evaluateRule()   B
last analyzed

Complexity

Conditions 11
Paths 40

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 50
ccs 27
cts 27
cp 1
rs 7.3166
c 0
b 0
f 0
cc 11
nc 40
nop 4
crap 11

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\RuleCombiningAlgorithmInterface;
23
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\RuleInterface;
24
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\TargetMatchEnum;
25
use Limoncello\Auth\Contracts\Authorization\PolicyInformation\ContextInterface;
26
use Psr\Log\LoggerInterface;
27
use RuntimeException;
28
use function assert;
29
use function count;
30
use function is_array;
31
use function is_callable;
32
use function is_string;
33
34
/**
35
 * @package Limoncello\Auth
36
 */
37
abstract class BaseRuleAlgorithm extends BaseAlgorithm implements RuleCombiningAlgorithmInterface
38
{
39
    /**
40
     * @param array $targets
41
     *
42
     * @return array
43
     */
44
    abstract protected function optimizeTargets(array $targets): array;
45
46
    /**
47
     * @param ContextInterface     $context
48 21
     * @param array                $rulesData
49
     * @param LoggerInterface|null $logger
50
     *
51
     * @return array
52
     *
53 21
     * @SuppressWarnings(PHPMD.StaticAccess)
54 21
     */
55 21
    public static function callRuleAlgorithm(
56 21
        ContextInterface $context,
57 21
        array $rulesData,
58 21
        LoggerInterface $logger = null
59
    ): array {
60
        return static::callAlgorithm(
61
            static::getCallable($rulesData),
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...
62
            $context,
63
            static::getTargets($rulesData),
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...
64
            static::getRules($rulesData),
0 ignored issues
show
Bug introduced by
Since getRules() 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 getRules() 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
            $logger
66
        );
67 36
    }
68
69 36
    /**
70
     * @inheritdoc
71 36
     *
72 36
     * @SuppressWarnings(PHPMD.StaticAccess)
73 36
     */
74 36
    public function optimize(array $rules): array
75
    {
76 36
        assert(empty($rules) === false);
77 36
78 36
        $ruleId       = 0;
79
        $rawTargets   = [];
80
        $encodedRules = [];
81 36
        foreach ($rules as $rule) {
82 36
            /** @var RuleInterface $rule */
83
            $rawTargets[$ruleId]   = $rule->getTarget();
84
            $encodedRules[$ruleId] = Encoder::encodeRule($rule);
85 36
            $ruleId++;
86 36
        }
87 36
88
        $callable         = static::METHOD;
89
        $optimizedTargets = $this->optimizeTargets($rawTargets);
90 36
91 36
        /** @var callable|array $callable */
92 36
        assert($callable !== null && is_array($callable) === true &&
93
            is_callable($callable) === true && count($callable) === 2 &&
94
            is_string($callable[0]) === true && is_string($callable[1]) === true);
95
96
        return [
97
            static::INDEX_TARGETS  => $optimizedTargets,
98
            static::INDEX_RULES    => $encodedRules,
99
            static::INDEX_CALLABLE => $callable,
100
        ];
101 24
    }
102
103
    /**
104
     * @inheritdoc
105
     *
106
     * @SuppressWarnings(PHPMD.StaticAccess)
107 24
     */
108
    public static function evaluateItem(
109
        ContextInterface $context,
110
        int $match,
111
        array $encodedItem,
112
        ?LoggerInterface $logger
113
    ): array {
114
        return static::evaluateRule($context, $match, $encodedItem, $logger);
0 ignored issues
show
Bug introduced by
Since evaluateRule() 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 evaluateRule() 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...
115
    }
116
117
    /**
118
     * @param ContextInterface     $context
119
     * @param int                  $match
120
     * @param array                $encodedRule
121
     * @param LoggerInterface|null $logger
122 24
     *
123
     * @return array
124
     *
125
     * @SuppressWarnings(PHPMD.StaticAccess)
126
     * @SuppressWarnings(PHPMD.ElseExpression)
127
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
128 24
     */
129
    private static function evaluateRule(
130 24
        ContextInterface $context,
131 24
        int $match,
132 7
        array $encodedRule,
133 7
        LoggerInterface $logger = null
134 7
    ): array {
135
        assert(Encoder::isRule($encodedRule));
136
137
        $ruleName = null;
138
        if ($logger !== null) {
139 24
            $ruleName  = Encoder::ruleName($encodedRule);
140 1
            $matchName = TargetMatchEnum::toString($match);
141 1
            $logger->debug("Rule '$ruleName' evaluation started for match '$matchName'.");
142 1
        }
143 24
144 20
        /** @see http://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html #7.11 (table 4) */
145
146 20
        if ($match === TargetMatchEnum::INDETERMINATE) {
147 20
            $isPermit   = static::evaluateIsPermit($context, $encodedRule);
0 ignored issues
show
Bug introduced by
Since evaluateIsPermit() 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 evaluateIsPermit() 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...
148 2
            $evaluation = $isPermit === true ?
149
                EvaluationEnum::INDETERMINATE_PERMIT : EvaluationEnum::INDETERMINATE_DENY;
150 17
        } elseif ($match === TargetMatchEnum::NO_TARGET || $match === TargetMatchEnum::MATCH) {
151
            $isPermit = static::evaluateIsPermit($context, $encodedRule);
0 ignored issues
show
Bug introduced by
Since evaluateIsPermit() 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 evaluateIsPermit() 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...
152 3
            try {
153 3
                $condition = Encoder::ruleCondition($encodedRule);
154 20
                if (static::evaluateLogical($context, $condition) === false) {
0 ignored issues
show
Bug introduced by
It seems like $condition defined by \Limoncello\Auth\Authori...Condition($encodedRule) on line 153 can also be of type callable; however, Limoncello\Auth\Authoriz...ithm::evaluateLogical() does only seem to accept null|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
155
                    $evaluation = EvaluationEnum::NOT_APPLICABLE;
156
                } else {
157 12
                    $evaluation = $isPermit === true ? EvaluationEnum::PERMIT : EvaluationEnum::DENY;
158 12
                }
159
            } catch (RuntimeException $exception) {
160
                $evaluation = $isPermit === true ?
161 24
                    EvaluationEnum::INDETERMINATE_PERMIT : EvaluationEnum::INDETERMINATE_DENY;
162 7
            }
163 7
        } else {
164
            assert($match === TargetMatchEnum::NOT_MATCH);
165
            $evaluation = EvaluationEnum::NOT_APPLICABLE;
166 24
        }
167 24
168 24
        if ($logger !== null) {
169 24
            $evaluationName = EvaluationEnum::toString($evaluation);
170
            $logger->info("Rule '$ruleName' evaluated as '$evaluationName'.");
171
        }
172
173
        return static::packEvaluationResult(
174
            $evaluation,
175
            Encoder::getFulfillObligations($evaluation, Encoder::ruleObligations($encodedRule)),
176
            Encoder::getAppliedAdvice($evaluation, Encoder::ruleAdvice($encodedRule))
177
        );
178
    }
179
180
    /**
181 21
     * @param ContextInterface $context
182
     * @param array            $encodedRule
183 21
     *
184
     * @return bool
185
     *
186 21
     * @SuppressWarnings(PHPMD.StaticAccess)
187 1
     */
188 1
    private static function evaluateIsPermit(ContextInterface $context, array $encodedRule): bool
189
    {
190
        $ruleEffect = Encoder::ruleEffect($encodedRule);
191 21
192
        try {
193
            $isPermit = static::evaluateLogical($context, $ruleEffect);
194
        } catch (RuntimeException $exception) {
195
            $isPermit = false;
196
        }
197
198
        return $isPermit;
199 21
    }
200
201 21
    /**
202
     * @param array $rulesData
203
     *
204
     * @return array
205
     */
206
    private static function getTargets(array $rulesData): array
207
    {
208
        return $rulesData[self::INDEX_TARGETS];
209 21
    }
210
211 21
    /**
212
     * @param array $rulesData
213
     *
214
     * @return array
215
     */
216
    private static function getRules(array $rulesData): array
217
    {
218
        return $rulesData[self::INDEX_RULES];
219 21
    }
220
221 21
    /**
222
     * @param array $rulesData
223
     *
224
     * @return callable
225
     */
226
    private static function getCallable(array $rulesData)
227
    {
228
        return $rulesData[self::INDEX_CALLABLE];
229
    }
230
}
231