Completed
Push — master ( d411c4...f436ec )
by Tim
10:33 queued 10:30
created

Authorize::process()   F

Complexity

Conditions 19
Paths 546

Size

Total Lines 72
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 44
c 1
b 0
f 0
dl 0
loc 72
rs 0.9805
cc 19
nc 546
nop 1

How to fix   Long Method    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
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\authorize\Auth\Process;
6
7
use SimpleSAML\Auth;
8
use SimpleSAML\Error;
9
use SimpleSAML\Module;
10
use SimpleSAML\SAML2\Assert\Assert;
11
use SimpleSAML\Utils;
12
13
use function array_diff;
14
use function array_key_exists;
15
use function array_keys;
16
use function array_push;
17
use function implode;
18
use function is_array;
19
use function is_bool;
20
use function is_string;
21
use function preg_match;
22
use function var_export;
23
24
/**
25
 * Filter to authorize only certain users.
26
 * See docs directory.
27
 *
28
 * @package SimpleSAMLphp
29
 */
30
31
class Authorize extends Auth\ProcessingFilter
32
{
33
    /**
34
     * Flag to deny/unauthorize the user a attribute filter IS found
35
     *
36
     * @var bool
37
     */
38
    protected bool $deny = false;
39
40
    /**
41
     * Flag to turn the REGEX pattern matching on or off
42
     *
43
     * @var bool
44
     */
45
    protected bool $regex = true;
46
47
    /**
48
     * Array of localised rejection messages
49
     *
50
     * @var string[]
51
     */
52
    protected array $reject_msg = [];
53
54
    /**
55
     * Flag to toggle generation of errorURL
56
     *
57
     * @var bool
58
     */
59
    protected bool $errorURL = true;
60
61
    /**
62
     * Array of valid users. Each element is a regular expression. You should
63
     * use \ to escape special chars, like '.' etc.
64
     * can also contain 'spEntityIDs' arrays to restrict rules to specific SPs.
65
     *
66
     * @var array<mixed>
67
     */
68
    protected array $valid_attribute_values = [];
69
70
    /**
71
     * Flag to allow re-authentication when user is not authorized
72
     * @var bool
73
     */
74
    protected bool $allow_reauthentication = false;
75
76
    /**
77
     * The attribute to show in the error page
78
     * @var string|null
79
     */
80
    protected ?string $show_user_attribute = null;
81
82
    /**
83
     * Initialize this filter.
84
     * Validate configuration parameters.
85
     *
86
     * @param array<mixed> $config  Configuration information about this filter.
87
     * @param mixed $reserved  For future use.
88
     */
89
    public function __construct(array $config, $reserved)
90
    {
91
        parent::__construct($config, $reserved);
92
93
        // Check for the deny option
94
        // Must be bool specifically, if not, it might be for an attrib filter below
95
        if (isset($config['deny']) && is_bool($config['deny'])) {
96
            $this->deny = $config['deny'];
97
            unset($config['deny']);
98
        }
99
100
        // Check for the regex option
101
        // Must be bool specifically, if not, it might be for an attrib filter below
102
        if (isset($config['regex']) && is_bool($config['regex'])) {
103
            $this->regex = $config['regex'];
104
            unset($config['regex']);
105
        }
106
107
        // Check for the reject_msg option; Must be array of languages
108
        if (isset($config['reject_msg']) && is_array($config['reject_msg'])) {
109
            $this->reject_msg = $config['reject_msg'];
110
            unset($config['reject_msg']);
111
        }
112
113
        // Check for the errorURL option
114
        // Must be bool specifically, if not, it might be for an attrib filter below
115
        if (isset($config['errorURL']) && is_bool($config['errorURL'])) {
116
            $this->errorURL = $config['errorURL'];
117
            unset($config['errorURL']);
118
        }
119
120
        if (isset($config['allow_reauthentication']) && is_bool($config['allow_reauthentication'])) {
121
            $this->allow_reauthentication = $config['allow_reauthentication'];
122
            unset($config['allow_reauthentication']);
123
        }
124
125
        if (isset($config['show_user_attribute']) && is_string($config['show_user_attribute'])) {
126
            $this->show_user_attribute = $config['show_user_attribute'];
127
            unset($config['show_user_attribute']);
128
        }
129
130
        foreach ($config as $attribute => $values) {
131
            if (is_string($values)) {
132
                $arrayUtils = new Utils\Arrays();
133
                $values = $arrayUtils->arrayize($values);
134
            } elseif (!is_array($values)) {
135
                throw new Error\Exception(sprintf(
136
                    'Filter Authorize: Attribute values is neither string nor array: %s',
137
                    var_export($attribute, true),
138
                ));
139
            }
140
141
            // Extract spEntityIDs if present
142
            $spEntityIDs = null;
143
            if (isset($values['spEntityIDs'])) {
144
                Assert::isArray(
145
                    $values['spEntityIDs'],
146
                    sprintf(
147
                        'Filter Authorize: spEntityIDs must be an array for attribute: %s',
148
                        var_export($attribute, true),
149
                    ),
150
                    Error\Exception::class,
151
                );
152
                Assert::allValidEntityID($values['spEntityIDs']);
153
154
                $spEntityIDs = $values['spEntityIDs'];
155
                unset($values['spEntityIDs']);
156
            }
157
158
            foreach ($values as $value) {
159
                if (!is_string($value)) {
160
                    throw new Error\Exception(sprintf(
161
                        'Filter Authorize: Each value should be a string for attribute: %s value: %s config: %s',
162
                        var_export($attribute, true),
163
                        var_export($value, true),
164
                        var_export($config, true),
165
                    ));
166
                }
167
            }
168
169
            $this->valid_attribute_values[$attribute] = [
170
                'values' => $values,
171
                'spEntityIDs' => $spEntityIDs,
172
            ];
173
        }
174
    }
175
176
177
    /**
178
     * Apply filter to validate attributes.
179
     *
180
     * @param array<mixed> &$state  The current request
181
     */
182
    public function process(array &$state): void
183
    {
184
        Assert::keyExists($state, 'Attributes');
185
186
        $authorize = $this->deny;
187
        $attributes = &$state['Attributes'];
188
        $ctx = [];
189
190
        // Store the rejection message array in the $state
191
        if (!empty($this->reject_msg)) {
192
            $state['authprocAuthorize_reject_msg'] = $this->reject_msg;
193
        }
194
        $state['authprocAuthorize_errorURL'] = $this->errorURL;
195
        $state['authprocAuthorize_allow_reauthentication'] = $this->allow_reauthentication;
196
        // Get current SP EntityID from state
197
        $currentSpEntityId = null;
198
        if (isset($state['saml:sp:State']['core:SP'])) {
199
            $currentSpEntityId = $state['saml:sp:State']['core:SP'];
200
        } elseif (isset($state['Destination']['entityid'])) {
201
            $currentSpEntityId = $state['Destination']['entityid'];
202
        }
203
204
        $arrayUtils = new Utils\Arrays();
205
        foreach ($this->valid_attribute_values as $name => $ruleConfig) {
206
            if (array_key_exists($name, $attributes)) {
207
                $patterns = $ruleConfig['values'];
208
                $spEntityIDs = $ruleConfig['spEntityIDs'];
209
210
                // If spEntityIDs is specified, check if current SP is in the list
211
                if ($spEntityIDs !== null) {
212
                    if ($currentSpEntityId === null || !in_array($currentSpEntityId, $spEntityIDs, true)) {
213
                        continue; // Skip this rule if SP is not specified or not in allowed list
214
                    }
215
                }
216
217
                foreach ($patterns as $pattern) {
218
                    $values = $arrayUtils->arrayize($attributes[$name]);
219
                    foreach ($values as $value) {
220
                        if ($this->regex) {
221
                            $matched = preg_match($pattern, $value);
222
                        } else {
223
                            $matched = ($value === $pattern);
224
                        }
225
226
                        if ($matched) {
227
                            $authorize = ($this->deny ? false : true);
228
                            array_push($ctx, $name);
229
                            break 3;
230
                        }
231
                    }
232
                }
233
            }
234
        }
235
236
        if (!$authorize) {
237
            if ($this->show_user_attribute !== null && array_key_exists($this->show_user_attribute, $attributes)) {
238
                $userAttribute =  $attributes[$this->show_user_attribute][0] ?? null;
239
                if ($userAttribute !== null) {
240
                    $state['authprocAuthorize_user_attribute'] = $userAttribute;
241
                }
242
            }
243
244
            // Try to hint at which attributes may have failed as context for errorURL processing
245
            if ($this->deny) {
246
                $state['authprocAuthorize_ctx'] = implode(' ', $ctx);
247
            } else {
248
                $state['authprocAuthorize_ctx'] = implode(
249
                    ' ',
250
                    array_diff(array_keys($this->valid_attribute_values), $ctx),
251
                );
252
            }
253
            $this->unauthorized($state);
254
        }
255
    }
256
257
258
    /**
259
     * When the process logic determines that the user is not
260
     * authorized for this service, then forward the user to
261
     * an 403 unauthorized page.
262
     *
263
     * Separated this code into its own method so that child
264
     * classes can override it and change the action. Forward
265
     * thinking in case a "chained" ACL is needed, more complex
266
     * permission logic.
267
     *
268
     * @param array<mixed> $state
269
     */
270
    protected function unauthorized(array &$state): void
271
    {
272
        // Save state and redirect to 403 page
273
        $id = Auth\State::saveState($state, 'authorize:Authorize');
274
        $url = Module::getModuleURL('authorize/error/forbidden');
275
        $httpUtils = new Utils\HTTP();
276
        $httpUtils->redirectTrustedURL($url, ['StateId' => $id]);
277
    }
278
}
279