Completed
Push — master ( 0f0d0d...791e0e )
by Tim
15s queued 14s
created

Authorize   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 28
eloc 72
c 2
b 0
f 0
dl 0
loc 188
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
B process() 0 47 11
D __construct() 0 58 16
A unauthorized() 0 7 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\authorize\Auth\Process;
6
7
use Exception;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\Auth;
10
use SimpleSAML\Module;
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 array
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
     * user \ to escape special chars, like '.' etc.
64
     *
65
     * @param array
66
     */
67
    protected array $valid_attribute_values = [];
68
69
    /**
70
     * Flag to allow re-authentication when user is not authorized
71
     * @var bool
72
     */
73
    protected bool $allow_reauthentication = false;
74
75
    /**
76
     * Initialize this filter.
77
     * Validate configuration parameters.
78
     *
79
     * @param array $config  Configuration information about this filter.
80
     * @param mixed $reserved  For future use.
81
     */
82
    public function __construct(array $config, $reserved)
83
    {
84
        parent::__construct($config, $reserved);
85
86
        // Check for the deny option
87
        // Must be bool specifically, if not, it might be for an attrib filter below
88
        if (isset($config['deny']) && is_bool($config['deny'])) {
89
            $this->deny = $config['deny'];
90
            unset($config['deny']);
91
        }
92
93
        // Check for the regex option
94
        // Must be bool specifically, if not, it might be for an attrib filter below
95
        if (isset($config['regex']) && is_bool($config['regex'])) {
96
            $this->regex = $config['regex'];
97
            unset($config['regex']);
98
        }
99
100
        // Check for the reject_msg option; Must be array of languages
101
        if (isset($config['reject_msg']) && is_array($config['reject_msg'])) {
102
            $this->reject_msg = $config['reject_msg'];
103
            unset($config['reject_msg']);
104
        }
105
106
        // Check for the errorURL option
107
        // Must be bool specifically, if not, it might be for an attrib filter below
108
        if (isset($config['errorURL']) && is_bool($config['errorURL'])) {
109
            $this->errorURL = $config['errorURL'];
110
            unset($config['errorURL']);
111
        }
112
113
        if (isset($config['allow_reauthentication']) && is_bool($config['allow_reauthentication'])) {
114
            $this->allow_reauthentication = $config['allow_reauthentication'];
115
            unset($config['allow_reauthentication']);
116
        }
117
118
        foreach ($config as $attribute => $values) {
119
            if (is_string($values)) {
120
                $arrayUtils = new Utils\Arrays();
121
                $values = $arrayUtils->arrayize($values);
122
            } elseif (!is_array($values)) {
123
                throw new Exception(sprintf(
124
                    'Filter Authorize: Attribute values is neither string nor array: %s',
125
                    var_export($attribute, true),
126
                ));
127
            }
128
129
            foreach ($values as $value) {
130
                if (!is_string($value)) {
131
                    throw new Exception(sprintf(
132
                        'Filter Authorize: Each value should be a string for attribute: %s value: %s config: %s',
133
                        var_export($attribute, true),
134
                        var_export($value, true),
135
                        var_export($config, true),
136
                    ));
137
                }
138
            }
139
            $this->valid_attribute_values[$attribute] = $values;
140
        }
141
    }
142
143
144
    /**
145
     * Apply filter to validate attributes.
146
     *
147
     * @param array &$state  The current request
148
     */
149
    public function process(array &$state): void
150
    {
151
        Assert::keyExists($state, 'Attributes');
152
153
        $authorize = $this->deny;
154
        $attributes = &$state['Attributes'];
155
        $ctx = [];
156
157
        // Store the rejection message array in the $state
158
        if (!empty($this->reject_msg)) {
159
            $state['authprocAuthorize_reject_msg'] = $this->reject_msg;
160
        }
161
        $state['authprocAuthorize_errorURL'] = $this->errorURL;
162
        $state['authprocAuthorize_allow_reauthentication'] = $this->allow_reauthentication;
163
        $arrayUtils = new Utils\Arrays();
164
        foreach ($this->valid_attribute_values as $name => $patterns) {
165
            if (array_key_exists($name, $attributes)) {
166
                foreach ($patterns as $pattern) {
167
                    $values = $arrayUtils->arrayize($attributes[$name]);
168
                    foreach ($values as $value) {
169
                        if ($this->regex) {
170
                            $matched = preg_match($pattern, $value);
171
                        } else {
172
                            $matched = ($value === $pattern);
173
                        }
174
175
                        if ($matched) {
176
                            $authorize = ($this->deny ? false : true);
177
                            array_push($ctx, $name);
178
                            break 3;
179
                        }
180
                    }
181
                }
182
            }
183
        }
184
185
        if (!$authorize) {
186
            // Try to hint at which attributes may have failed as context for errorURL processing
187
            if ($this->deny) {
188
                $state['authprocAuthorize_ctx'] = implode(' ', $ctx);
189
            } else {
190
                $state['authprocAuthorize_ctx'] = implode(
191
                    ' ',
192
                    array_diff(array_keys($this->valid_attribute_values), $ctx),
193
                );
194
            }
195
            $this->unauthorized($state);
196
        }
197
    }
198
199
200
    /**
201
     * When the process logic determines that the user is not
202
     * authorized for this service, then forward the user to
203
     * an 403 unauthorized page.
204
     *
205
     * Separated this code into its own method so that child
206
     * classes can override it and change the action. Forward
207
     * thinking in case a "chained" ACL is needed, more complex
208
     * permission logic.
209
     *
210
     * @param array $state
211
     */
212
    protected function unauthorized(array &$state): void
213
    {
214
        // Save state and redirect to 403 page
215
        $id = Auth\State::saveState($state, 'authorize:Authorize');
216
        $url = Module::getModuleURL('authorize/error/forbidden');
217
        $httpUtils = new Utils\HTTP();
218
        $httpUtils->redirectTrustedURL($url, ['StateId' => $id]);
219
    }
220
}
221