1 | <?php |
||||
2 | /** |
||||
3 | * This file is part of the O2System Framework package. |
||||
4 | * |
||||
5 | * For the full copyright and license information, please view the LICENSE |
||||
6 | * file that was distributed with this source code. |
||||
7 | * |
||||
8 | * @author Steeve Andrian Salim |
||||
9 | * @copyright Copyright (c) Steeve Andrian Salim |
||||
10 | */ |
||||
11 | |||||
12 | // ------------------------------------------------------------------------ |
||||
13 | |||||
14 | namespace O2System\Security\Authentication; |
||||
15 | |||||
16 | // ------------------------------------------------------------------------ |
||||
17 | |||||
18 | /** |
||||
19 | * Class HttpAuthentication |
||||
20 | * |
||||
21 | * @package O2System\Security\Authentication |
||||
22 | */ |
||||
23 | class Http |
||||
24 | { |
||||
25 | /** |
||||
26 | * HttpAuthentication::AUTH_BASIC |
||||
27 | * |
||||
28 | * Basic Realm HTTP Authentication. |
||||
29 | * |
||||
30 | * @var int |
||||
31 | */ |
||||
32 | const AUTH_BASIC = 1; |
||||
33 | |||||
34 | /** |
||||
35 | * HttpAuthentication::AUTH_DIGEST |
||||
36 | * |
||||
37 | * Digest HTTP Authentication. |
||||
38 | * |
||||
39 | * @var int |
||||
40 | */ |
||||
41 | const AUTH_DIGEST = 2; |
||||
42 | |||||
43 | /** |
||||
44 | * HttpAuthentication::$type |
||||
45 | * |
||||
46 | * HTTP authentication type. |
||||
47 | * |
||||
48 | * @var int |
||||
49 | */ |
||||
50 | private $type; |
||||
51 | |||||
52 | /** |
||||
53 | * HttpAuthentication::$realm |
||||
54 | * |
||||
55 | * HTTP authentication realm. |
||||
56 | * |
||||
57 | * @var string |
||||
58 | */ |
||||
59 | private $realm; |
||||
60 | |||||
61 | /** |
||||
62 | * HttpAuthentication::$authenticate |
||||
63 | * |
||||
64 | * HTTP authentication validation. |
||||
65 | * |
||||
66 | * @var \Closure |
||||
67 | */ |
||||
68 | private $validation; |
||||
69 | |||||
70 | /** |
||||
71 | * HttpAuthentication::$users |
||||
72 | * |
||||
73 | * List of users access. |
||||
74 | * |
||||
75 | * @var array |
||||
76 | */ |
||||
77 | private $users = []; |
||||
78 | |||||
79 | // ------------------------------------------------------------------------ |
||||
80 | |||||
81 | /** |
||||
82 | * HttpAuthentication::__construct |
||||
83 | */ |
||||
84 | public function __construct($realm, $type = HttpAuthentication::AUTH_BASIC) |
||||
0 ignored issues
–
show
|
|||||
85 | { |
||||
86 | $this->setRealm($realm) |
||||
87 | ->setType($type); |
||||
88 | |||||
89 | if (class_exists('\O2System\Framework', false) or class_exists('\O2System\Reactor', false)) { |
||||
90 | if ($security = config()->getItem('security')) { |
||||
0 ignored issues
–
show
The function
config was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
91 | if ($security->offsetExists('httpAuthentication')) { |
||||
92 | $this->users = $security->offsetGet('httpAuthentication'); |
||||
93 | } |
||||
94 | } elseif (false !== ($users = config()->loadFile('HttpAuthentication'))) { |
||||
95 | $this->users = $users; |
||||
96 | } |
||||
97 | } |
||||
98 | } |
||||
99 | |||||
100 | // ------------------------------------------------------------------------ |
||||
101 | |||||
102 | /** |
||||
103 | * HttpAuthentication::setType |
||||
104 | * |
||||
105 | * Sets WWW-Authenticate Type |
||||
106 | * |
||||
107 | * @param int $type WWW-Authenticate Type. |
||||
108 | * |
||||
109 | * @return static |
||||
110 | */ |
||||
111 | public function setType($type) |
||||
112 | { |
||||
113 | if (in_array($type, [self::AUTH_BASIC, self::AUTH_DIGEST])) { |
||||
114 | $this->type = $type; |
||||
115 | } |
||||
116 | |||||
117 | return $this; |
||||
118 | } |
||||
119 | |||||
120 | // ------------------------------------------------------------------------ |
||||
121 | |||||
122 | /** |
||||
123 | * HttpAuthentication::setRealm |
||||
124 | * |
||||
125 | * Sets WWW-Authenticate Realm |
||||
126 | * |
||||
127 | * @param string $realm WWW-Authenticate Realm. |
||||
128 | * |
||||
129 | * @return static |
||||
130 | */ |
||||
131 | public function setRealm($realm) |
||||
132 | { |
||||
133 | $this->realm = trim($realm); |
||||
134 | |||||
135 | return $this; |
||||
136 | } |
||||
137 | |||||
138 | // ------------------------------------------------------------------------ |
||||
139 | |||||
140 | /** |
||||
141 | * HttpAuthentication::setUsers |
||||
142 | * |
||||
143 | * Sets WWW-Authenticate Users. |
||||
144 | * |
||||
145 | * @param array $users WWW-Authenticate Users. |
||||
146 | * |
||||
147 | * @return static |
||||
148 | */ |
||||
149 | public function setUsers(array $users) |
||||
150 | { |
||||
151 | $this->users = $users; |
||||
152 | |||||
153 | return $this; |
||||
154 | } |
||||
155 | |||||
156 | // ------------------------------------------------------------------------ |
||||
157 | |||||
158 | /** |
||||
159 | * HttpAuthentication::setUsersValidation |
||||
160 | * |
||||
161 | * Sets WWW-Authenticate Validation. |
||||
162 | * |
||||
163 | * @param \Closure $closure WWW-Authenticate Validation Callback. |
||||
164 | * |
||||
165 | * @return static |
||||
166 | */ |
||||
167 | public function setUsersValidation(\Closure $closure) |
||||
168 | { |
||||
169 | $this->validation = $closure; |
||||
170 | |||||
171 | return $this; |
||||
172 | } |
||||
173 | |||||
174 | // ------------------------------------------------------------------------ |
||||
175 | |||||
176 | /** |
||||
177 | * HttpAuthentication::verify |
||||
178 | * |
||||
179 | * Verify client access based on request headers. |
||||
180 | * |
||||
181 | * @return bool |
||||
182 | */ |
||||
183 | public function verify() |
||||
184 | { |
||||
185 | switch ($this->type) { |
||||
186 | default: |
||||
187 | case self::AUTH_BASIC: |
||||
188 | |||||
189 | if ($authorization = input()->server('HTTP_AUTHORIZATION')) { |
||||
190 | $authentication = unserialize(base64_decode($authorization)); |
||||
191 | if ($this->login($authentication[ 'username' ], $authentication[ 'password' ])) { |
||||
192 | return true; |
||||
193 | } |
||||
194 | } else { |
||||
195 | $authentication = $this->parseBasic(); |
||||
196 | } |
||||
197 | |||||
198 | if ($this->login($authentication[ 'username' ], $authentication[ 'password' ])) { |
||||
199 | |||||
200 | header('Authorization: Basic ' . base64_encode(serialize($authentication))); |
||||
201 | |||||
202 | return true; |
||||
203 | } else { |
||||
204 | unset($_SERVER[ 'PHP_AUTH_USER' ], $_SERVER[ 'PHP_AUTH_PW' ]); |
||||
205 | $this->protect(); |
||||
206 | } |
||||
207 | |||||
208 | break; |
||||
209 | case self::AUTH_DIGEST: |
||||
210 | if ($authorization = input()->server('HTTP_AUTHORIZATION')) { |
||||
211 | $authentication = $this->parseDigest($authorization); |
||||
212 | } elseif ($authorization = input()->server('PHP_AUTH_DIGEST')) { |
||||
213 | $authentication = $this->parseDigest($authorization); |
||||
214 | } |
||||
215 | |||||
216 | if (isset($authentication) AND |
||||
217 | false !== ($password = $this->login($authentication[ 'username' ])) |
||||
218 | ) { |
||||
219 | $A1 = md5($authentication[ 'username' ] . ':' . $this->realm . ':' . $password); |
||||
0 ignored issues
–
show
Are you sure
$password of type string|true can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
220 | $A2 = md5($_SERVER[ 'REQUEST_METHOD' ] . ':' . $authentication[ 'uri' ]); |
||||
221 | $response = md5( |
||||
222 | $A1 |
||||
223 | . ':' |
||||
224 | . $authentication[ 'nonce' ] |
||||
225 | . ':' |
||||
226 | . $authentication[ 'nc' ] |
||||
227 | . ':' |
||||
228 | . $authentication[ 'cnonce' ] |
||||
229 | . ':' |
||||
230 | . $authentication[ 'qop' ] |
||||
231 | . ':' |
||||
232 | . $A2 |
||||
233 | ); |
||||
234 | |||||
235 | if ($authentication[ 'response' ] === $response) { |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
236 | header( |
||||
237 | sprintf( |
||||
238 | 'Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", qop=%s, nc=%s, cnonce="%s", response="%s", opaque="%s"', |
||||
239 | $authentication[ 'username' ], |
||||
240 | $this->realm, |
||||
241 | $authentication[ 'nonce' ], |
||||
242 | $authentication[ 'uri' ], |
||||
243 | $authentication[ 'qop' ], |
||||
244 | $authentication[ 'nc' ], |
||||
245 | $authentication[ 'cnonce' ], |
||||
246 | $response, |
||||
247 | $authentication[ 'opaque' ] |
||||
248 | ) |
||||
249 | ); |
||||
250 | |||||
251 | return true; |
||||
252 | } |
||||
253 | } |
||||
254 | |||||
255 | unset($_SERVER[ 'PHP_AUTH_DIGEST' ], $_SERVER[ 'HTTP_AUTHORIZATION' ]); |
||||
256 | $this->protect(); |
||||
257 | |||||
258 | break; |
||||
259 | } |
||||
260 | |||||
261 | return false; |
||||
262 | } |
||||
263 | |||||
264 | // ------------------------------------------------------------------------ |
||||
265 | |||||
266 | /** |
||||
267 | * HttpAuthentication::login |
||||
268 | * |
||||
269 | * Perform WWW-Authenticate Login. |
||||
270 | * |
||||
271 | * @param string $username Authentication username. |
||||
272 | * @param string $password Authentication password. |
||||
273 | * |
||||
274 | * @return bool|string |
||||
275 | */ |
||||
276 | public function login($username, $password = null) |
||||
277 | { |
||||
278 | switch ($this->type) { |
||||
279 | default: |
||||
280 | case self::AUTH_BASIC: |
||||
281 | if (isset($username) AND isset($password)) { |
||||
282 | if ($this->validation instanceof \Closure) { |
||||
0 ignored issues
–
show
|
|||||
283 | return call_user_func_array($this->validation, func_get_args()); |
||||
284 | } else { |
||||
285 | if (array_key_exists($username, $this->users)) { |
||||
286 | if ($this->users[ $username ] === $password) { |
||||
287 | return true; |
||||
288 | } |
||||
289 | } |
||||
290 | } |
||||
291 | } |
||||
292 | break; |
||||
293 | case self::AUTH_DIGEST: |
||||
294 | if (isset($username)) { |
||||
295 | if ($this->validation instanceof \Closure) { |
||||
0 ignored issues
–
show
|
|||||
296 | return call_user_func_array($this->validation, func_get_args()); |
||||
297 | } else { |
||||
298 | if (array_key_exists($username, $this->users)) { |
||||
299 | return $this->users[ $username ]; |
||||
300 | } |
||||
301 | } |
||||
302 | } |
||||
303 | break; |
||||
304 | } |
||||
305 | |||||
306 | return false; |
||||
307 | } |
||||
308 | |||||
309 | // ------------------------------------------------------------------------ |
||||
310 | |||||
311 | /** |
||||
312 | * HttpAuthentication::parseBasic |
||||
313 | * |
||||
314 | * Parse Basic Realm HTTP Authentication data. |
||||
315 | * |
||||
316 | * @return array Basic Realm HTTP Authentication data. |
||||
317 | */ |
||||
318 | protected function parseBasic() |
||||
319 | { |
||||
320 | return [ |
||||
321 | 'username' => input()->server('PHP_AUTH_USER'), |
||||
322 | 'password' => input()->server('PHP_AUTH_PW'), |
||||
323 | ]; |
||||
324 | } |
||||
325 | |||||
326 | // ------------------------------------------------------------------------ |
||||
327 | |||||
328 | /** |
||||
329 | * HttpAuthentication::protect |
||||
330 | * |
||||
331 | * Protect requested page with HTTP Authorization form dialog. |
||||
332 | * |
||||
333 | * @return void |
||||
334 | */ |
||||
335 | protected function protect() |
||||
336 | { |
||||
337 | header('HTTP/1.1 401 Unauthorized'); |
||||
338 | |||||
339 | switch ($this->type) { |
||||
340 | default: |
||||
341 | case self::AUTH_BASIC: |
||||
342 | header('WWW-Authenticate: Basic realm="' . $this->realm . '"'); |
||||
343 | break; |
||||
344 | case self::AUTH_DIGEST: |
||||
345 | header( |
||||
346 | 'WWW-Authenticate: Digest realm="' . $this->realm . |
||||
347 | '", qop="auth", nonce="' . md5(uniqid()) . '", opaque="' . md5(uniqid()) . '"' |
||||
348 | ); |
||||
349 | break; |
||||
350 | } |
||||
351 | } |
||||
352 | |||||
353 | // ------------------------------------------------------------------------ |
||||
354 | |||||
355 | /** |
||||
356 | * HttpAuthentication::parseBasic |
||||
357 | * |
||||
358 | * Parse Digest HTTP Authentication data. |
||||
359 | * |
||||
360 | * @param string $digest Authentication Digest. |
||||
361 | * |
||||
362 | * @return array Digest HTTP Authentication data. |
||||
363 | */ |
||||
364 | protected function parseDigest($digest) |
||||
365 | { |
||||
366 | $digest = str_replace('Digest ', '', $digest); |
||||
367 | $digest = trim($digest); |
||||
368 | |||||
369 | $parts = explode(',', $digest); |
||||
370 | $parts = array_map('trim', $parts); |
||||
371 | |||||
372 | $data = []; |
||||
373 | foreach ($parts as $part) { |
||||
374 | $elements = explode('=', $part); |
||||
375 | $elements = array_map( |
||||
376 | function ($element) { |
||||
377 | return trim(str_replace('"', '', $element)); |
||||
378 | |||||
379 | }, |
||||
380 | $elements |
||||
381 | ); |
||||
382 | |||||
383 | $data[ $elements[ 0 ] ] = $elements[ 1 ]; |
||||
384 | } |
||||
385 | |||||
386 | return empty($data) |
||||
0 ignored issues
–
show
The expression
return empty($data) ? false : $data could also return false which is incompatible with the documented return type array . Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled. ![]() |
|||||
387 | ? false |
||||
388 | : $data; |
||||
389 | } |
||||
390 | } |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths