Passed
Pull Request — master (#20)
by
unknown
02:23
created

Authorize::unauthorized()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 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
        if (isset($config['show_user_attribute']) && is_string($config['show_user_attribute'])) {
119
            $this->show_user_attribute = $config['show_user_attribute'];
0 ignored issues
show
Bug Best Practice introduced by
The property show_user_attribute does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
120
            unset($config['show_user_attribute']);
121
        }
122
123
        foreach ($config as $attribute => $values) {
124
            if (is_string($values)) {
125
                $arrayUtils = new Utils\Arrays();
126
                $values = $arrayUtils->arrayize($values);
127
            } elseif (!is_array($values)) {
128
                throw new Exception(sprintf(
129
                    'Filter Authorize: Attribute values is neither string nor array: %s',
130
                    var_export($attribute, true),
131
                ));
132
            }
133
134
            foreach ($values as $value) {
135
                if (!is_string($value)) {
136
                    throw new Exception(sprintf(
137
                        'Filter Authorize: Each value should be a string for attribute: %s value: %s config: %s',
138
                        var_export($attribute, true),
139
                        var_export($value, true),
140
                        var_export($config, true),
141
                    ));
142
                }
143
            }
144
            $this->valid_attribute_values[$attribute] = $values;
145
        }
146
    }
147
148
149
    /**
150
     * Apply filter to validate attributes.
151
     *
152
     * @param array &$state  The current request
153
     */
154
    public function process(array &$state): void
155
    {
156
        Assert::keyExists($state, 'Attributes');
157
158
        $authorize = $this->deny;
159
        $attributes = &$state['Attributes'];
160
        $ctx = [];
161
162
        // Store the rejection message array in the $state
163
        if (!empty($this->reject_msg)) {
164
            $state['authprocAuthorize_reject_msg'] = $this->reject_msg;
165
        }
166
        $state['authprocAuthorize_errorURL'] = $this->errorURL;
167
        $state['authprocAuthorize_allow_reauthentication'] = $this->allow_reauthentication;
168
        $arrayUtils = new Utils\Arrays();
169
        foreach ($this->valid_attribute_values as $name => $patterns) {
170
            if (array_key_exists($name, $attributes)) {
171
                foreach ($patterns as $pattern) {
172
                    $values = $arrayUtils->arrayize($attributes[$name]);
173
                    foreach ($values as $value) {
174
                        if ($this->regex) {
175
                            $matched = preg_match($pattern, $value);
176
                        } else {
177
                            $matched = ($value === $pattern);
178
                        }
179
180
                        if ($matched) {
181
                            $authorize = ($this->deny ? false : true);
182
                            array_push($ctx, $name);
183
                            break 3;
184
                        }
185
                    }
186
                }
187
            }
188
        }
189
190
        if (!$authorize) {
191
            if ($this->show_user_attribute !== null && array_key_exists($this->show_user_attribute, $attributes)) {
192
                $userAttribute =  $attributes[$this->show_user_attribute][0] ?? null;
193
                if ($userAttribute !== null) {
194
                    $state['authprocAuthorize_user_attribute'] = $userAttribute;
195
                }
196
            }
197
198
            // Try to hint at which attributes may have failed as context for errorURL processing
199
            if ($this->deny) {
200
                $state['authprocAuthorize_ctx'] = implode(' ', $ctx);
201
            } else {
202
                $state['authprocAuthorize_ctx'] = implode(
203
                    ' ',
204
                    array_diff(array_keys($this->valid_attribute_values), $ctx),
205
                );
206
            }
207
            $this->unauthorized($state);
208
        }
209
    }
210
211
212
    /**
213
     * When the process logic determines that the user is not
214
     * authorized for this service, then forward the user to
215
     * an 403 unauthorized page.
216
     *
217
     * Separated this code into its own method so that child
218
     * classes can override it and change the action. Forward
219
     * thinking in case a "chained" ACL is needed, more complex
220
     * permission logic.
221
     *
222
     * @param array $state
223
     */
224
    protected function unauthorized(array &$state): void
225
    {
226
        // Save state and redirect to 403 page
227
        $id = Auth\State::saveState($state, 'authorize:Authorize');
228
        $url = Module::getModuleURL('authorize/error/forbidden');
229
        $httpUtils = new Utils\HTTP();
230
        $httpUtils->redirectTrustedURL($url, ['StateId' => $id]);
231
    }
232
}
233