1 | <?php |
||||||
2 | |||||||
3 | /** |
||||||
4 | * This file is concerned pretty entirely, as you see from its name, with |
||||||
5 | * logging in and out members, and the validation of that. |
||||||
6 | * |
||||||
7 | * @package ElkArte Forum |
||||||
8 | * @copyright ElkArte Forum contributors |
||||||
9 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||||||
10 | * |
||||||
11 | * This file contains code covered by: |
||||||
12 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||||||
13 | * |
||||||
14 | * @version 2.0 dev |
||||||
15 | * |
||||||
16 | */ |
||||||
17 | |||||||
18 | namespace ElkArte\Controller; |
||||||
19 | |||||||
20 | use ElkArte\AbstractController; |
||||||
21 | use ElkArte\Cache\Cache; |
||||||
22 | use ElkArte\Errors\Errors; |
||||||
0 ignored issues
–
show
|
|||||||
23 | use ElkArte\Exceptions\Exception; |
||||||
24 | use ElkArte\Helper\Util; |
||||||
25 | use ElkArte\Http\Headers; |
||||||
26 | use ElkArte\Languages\Txt; |
||||||
27 | use ElkArte\Request; |
||||||
28 | use ElkArte\User; |
||||||
29 | use ElkArte\UserSettingsLoader; |
||||||
30 | |||||||
31 | /** |
||||||
32 | * Deals with logging in and out members, and the validation of them |
||||||
33 | * |
||||||
34 | * @package Authorization |
||||||
35 | */ |
||||||
36 | class Auth extends AbstractController |
||||||
37 | { |
||||||
38 | /** |
||||||
39 | * {@inheritDoc} |
||||||
40 | */ |
||||||
41 | public function needSecurity($action = '') |
||||||
42 | { |
||||||
43 | return $action !== 'action_keepalive'; |
||||||
44 | } |
||||||
45 | |||||||
46 | /** |
||||||
47 | * Entry point in Auth controller |
||||||
48 | * |
||||||
49 | * - (well no, not really. We route directly to the rest.) |
||||||
50 | 2 | * |
|||||
51 | * @see AbstractController::action_index |
||||||
52 | */ |
||||||
53 | 2 | public function action_index() |
|||||
54 | 2 | { |
|||||
55 | // What can we do? login page! |
||||||
56 | $this->action_login(); |
||||||
57 | } |
||||||
58 | |||||||
59 | /** |
||||||
60 | * Ask them for their login information. |
||||||
61 | * |
||||||
62 | * What it does: |
||||||
63 | * - Shows a page for the user to type in their username and password. |
||||||
64 | * - It caches the referring URL in $_SESSION['login_url']. |
||||||
65 | * - It is accessed from ?action=login. |
||||||
66 | 2 | * |
|||||
67 | * @uses Login template and language file with the login sub-template. |
||||||
68 | 2 | */ |
|||||
69 | public function action_login() |
||||||
70 | { |
||||||
71 | 2 | global $txt, $context; |
|||||
72 | |||||||
73 | // You are already logged in, go take a tour of the boards |
||||||
74 | if (!empty($this->user->id)) |
||||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||||
75 | { |
||||||
76 | redirectexit(); |
||||||
77 | 2 | } |
|||||
78 | 2 | ||||||
79 | 2 | // Load the Login template/language file. |
|||||
80 | 2 | Txt::load('Login'); |
|||||
81 | |||||||
82 | // If API, clear out the header/footer and only return the form |
||||||
83 | 2 | if ($this->getApi()) |
|||||
84 | 2 | { |
|||||
85 | 2 | $template_layers = theme()->getLayers(); |
|||||
86 | 2 | $template_layers->removeAll(); |
|||||
87 | 2 | } |
|||||
88 | 2 | ||||||
89 | theme()->getTemplates()->load('Login'); |
||||||
90 | $context['sub_template'] = 'login'; |
||||||
91 | 2 | ||||||
92 | 2 | // Get the template ready.... not really much else to do. |
|||||
93 | 2 | $context['page_title'] = $txt['login']; |
|||||
94 | $_REQUEST['u'] = isset($_REQUEST['u']) ? Util::htmlspecialchars($_REQUEST['u']) : ''; |
||||||
95 | $context['default_username'] = &$_REQUEST['u']; |
||||||
96 | $context['default_password'] = ''; |
||||||
97 | 2 | $context['never_expire'] = false; |
|||||
98 | |||||||
99 | // Add the login chain to the link tree. |
||||||
100 | $context['breadcrumbs'][] = [ |
||||||
101 | 'url' => getUrl('action', ['action' => 'login']), |
||||||
102 | 'name' => $txt['login'], |
||||||
103 | 2 | ]; |
|||||
104 | |||||||
105 | // Set the login URL - will be used when the login process is done (but careful not to send us to an attachment). |
||||||
106 | if (isset($_SESSION['old_url']) && validLoginUrl($_SESSION['old_url'], true)) |
||||||
107 | 2 | { |
|||||
108 | 2 | $_SESSION['login_url'] = $_SESSION['old_url']; |
|||||
109 | } |
||||||
110 | else |
||||||
111 | { |
||||||
112 | unset($_SESSION['login_url']); |
||||||
113 | } |
||||||
114 | |||||||
115 | // Create a one time token. |
||||||
116 | createToken('login'); |
||||||
117 | } |
||||||
118 | |||||||
119 | /** |
||||||
120 | * Actually logs you in. |
||||||
121 | * |
||||||
122 | * What it does: |
||||||
123 | * |
||||||
124 | 2 | * - Checks credentials and checks that login was successful. |
|||||
125 | * - It employs protection against a specific IP or user trying to brute force |
||||||
126 | 2 | * a login to an account. |
|||||
127 | * - Upgrades password encryption on login, if necessary. |
||||||
128 | * - After successful login, redirects you to $_SESSION['login_url']. |
||||||
129 | 2 | * - Accessed from ?action=login2, by forms. |
|||||
130 | * |
||||||
131 | * @uses the same templates action_login() |
||||||
132 | 2 | */ |
|||||
133 | public function action_login2() |
||||||
134 | { |
||||||
135 | global $txt, $modSettings, $context; |
||||||
136 | |||||||
137 | // Load cookie authentication and all stuff. |
||||||
138 | 2 | require_once(SUBSDIR . '/Auth.subs.php'); |
|||||
139 | 2 | ||||||
140 | 2 | // Beyond this point you are assumed to be a guest trying to login. |
|||||
141 | if (empty(User::$info->is_guest)) |
||||||
0 ignored issues
–
show
The property
is_guest does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||||
142 | { |
||||||
143 | 2 | redirectexit(); |
|||||
144 | } |
||||||
145 | |||||||
146 | // Are you guessing with a script? |
||||||
147 | checkSession(); |
||||||
148 | validateToken('login'); |
||||||
149 | 2 | spamProtection('login'); |
|||||
150 | |||||||
151 | // Set the login_url if it's not already set (but careful not to send us to an attachment). |
||||||
152 | if (empty($_SESSION['login_url']) && isset($_SESSION['old_url']) && validLoginUrl($_SESSION['old_url'], true)) |
||||||
153 | { |
||||||
154 | $_SESSION['login_url'] = $_SESSION['old_url']; |
||||||
155 | 2 | } |
|||||
156 | |||||||
157 | // Been guessing a lot, haven't we? |
||||||
158 | if (isset($_SESSION['failed_login']) && $_SESSION['failed_login'] >= $modSettings['failed_login_threshold'] * 3) |
||||||
159 | 2 | { |
|||||
160 | throw new Exception('login_threshold_fail', 'critical'); |
||||||
161 | } |
||||||
162 | |||||||
163 | // Set up the cookie length. (if it's invalid, just fall through and use the default.) |
||||||
164 | 2 | if (isset($_POST['cookieneverexp'])) |
|||||
165 | { |
||||||
166 | $modSettings['cookieTime'] = 3153600; |
||||||
167 | 2 | } |
|||||
168 | 2 | ||||||
169 | 2 | Txt::load('Login'); |
|||||
170 | |||||||
171 | // Load the template stuff |
||||||
172 | 2 | theme()->getTemplates()->load('Login'); |
|||||
173 | 2 | $context['sub_template'] = 'login'; |
|||||
174 | 2 | ||||||
175 | 2 | // Set up the default/fallback stuff. |
|||||
176 | 2 | $context['default_username'] = isset($_POST['user']) ? preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($_POST['user'], ENT_COMPAT, 'UTF-8')) : ''; |
|||||
177 | $context['default_password'] = ''; |
||||||
178 | $context['never_expire'] = $modSettings['cookieTime'] === 525600 || $modSettings['cookieTime'] === 3153600; |
||||||
179 | 2 | $context['login_errors'] = array($txt['error_occurred']); |
|||||
180 | 2 | $context['page_title'] = $txt['login']; |
|||||
181 | 2 | ||||||
182 | // Add the login chain to the link tree. |
||||||
183 | $context['breadcrumbs'][] = [ |
||||||
184 | 'url' => getUrl('action', ['action' => 'login']), |
||||||
185 | 2 | 'name' => $txt['login'], |
|||||
186 | ]; |
||||||
187 | |||||||
188 | // You forgot to type your username, dummy! |
||||||
189 | if (!isset($_POST['user']) || $_POST['user'] === '') |
||||||
190 | { |
||||||
191 | $context['login_errors'] = array($txt['need_username']); |
||||||
192 | |||||||
193 | return false; |
||||||
194 | } |
||||||
195 | |||||||
196 | // No one needs a username that long, plus we only support 80 chars in the db |
||||||
197 | if (Util::strlen($_POST['user']) > 80) |
||||||
198 | { |
||||||
199 | $_POST['user'] = Util::substr($_POST['user'], 0, 80); |
||||||
200 | } |
||||||
201 | |||||||
202 | 2 | // Can't use a password > 64 characters sorry, to long and only good for a DoS attack |
|||||
203 | if (isset($_POST['passwrd']) && strlen($_POST['passwrd']) > 64) |
||||||
204 | { |
||||||
205 | $context['login_errors'] = array($txt['improper_password']); |
||||||
206 | |||||||
207 | return false; |
||||||
208 | } |
||||||
209 | |||||||
210 | 2 | // Hmm... maybe 'admin' will login with no password. Uhh... NO! |
|||||
211 | if (!isset($_POST['passwrd']) || $_POST['passwrd'] === '') |
||||||
212 | { |
||||||
213 | $context['login_errors'] = array($txt['no_password']); |
||||||
214 | |||||||
215 | return false; |
||||||
216 | } |
||||||
217 | 2 | ||||||
218 | // No funky symbols either. |
||||||
219 | if (preg_match('~[<>&"\'=\\\]~', preg_replace('~(&#(\\d{1,7}|x[0-9a-fA-F]{1,6});)~', '', $_POST['user'])) != 0) |
||||||
220 | { |
||||||
221 | $context['login_errors'] = array($txt['error_invalid_characters_username']); |
||||||
222 | |||||||
223 | return false; |
||||||
224 | } |
||||||
225 | 2 | ||||||
226 | // Are we using any sort of integration to validate the login? |
||||||
227 | if (in_array('retry', call_integration_hook('integrate_validate_login', array($_POST['user'], null, $modSettings['cookieTime'])), true)) |
||||||
228 | { |
||||||
229 | $context['login_errors'] = array($txt['login_hash_error']); |
||||||
230 | |||||||
231 | return false; |
||||||
232 | } |
||||||
233 | 2 | ||||||
234 | // Find them... if we can |
||||||
235 | $member_found = loadExistingMember($_POST['user']); |
||||||
236 | $db = database(); |
||||||
237 | $cache = Cache::instance(); |
||||||
238 | $req = Request::instance(); |
||||||
239 | |||||||
240 | $user = new UserSettingsLoader($db, $cache, $req); |
||||||
241 | 2 | $user->loadUserById($member_found === false ? 0 : $member_found['id_member'], true, ''); |
|||||
242 | |||||||
243 | $user_setting = $user->getSettings(); |
||||||
244 | |||||||
245 | // User using 2FA for login? Let's validate the token... |
||||||
246 | if (!empty($modSettings['enableOTP']) && !empty($user_setting['enable_otp']) && empty($_POST['otp_token'])) |
||||||
247 | { |
||||||
248 | $context['login_errors'] = array($txt['otp_required']); |
||||||
249 | |||||||
250 | 2 | return false; |
|||||
251 | 2 | } |
|||||
252 | 2 | ||||||
253 | 2 | if (!empty($_POST['otp_token'])) |
|||||
254 | { |
||||||
255 | 2 | require_once(EXTDIR . '/GoogleAuthenticator.php'); |
|||||
256 | 2 | $ga = new \GoogleAuthenticator(); |
|||||
0 ignored issues
–
show
The type
GoogleAuthenticator was not found. Maybe you did not declare it correctly or list all dependencies?
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
257 | 2 | ||||||
258 | $ga->getCode($user_setting['otp_secret']); |
||||||
259 | $checkResult = $ga->verifyCode($user_setting['otp_secret'], $_POST['otp_token'], 2); |
||||||
260 | 2 | if (!$checkResult) |
|||||
261 | { |
||||||
262 | $context['login_errors'] = array($txt['invalid_otptoken']); |
||||||
263 | |||||||
264 | return false; |
||||||
265 | } |
||||||
266 | |||||||
267 | 2 | // OTP already used? Sorry, but this is a ONE TIME password.. |
|||||
268 | if ($user_setting['otp_used'] === $_POST['otp_token']) |
||||||
269 | { |
||||||
270 | $context['login_errors'] = array($txt['otp_used']); |
||||||
271 | |||||||
272 | return false; |
||||||
273 | } |
||||||
274 | } |
||||||
275 | |||||||
276 | // Let them try again, it didn't match anything... |
||||||
277 | if (empty($member_found)) |
||||||
278 | { |
||||||
279 | $context['login_errors'] = array($txt['username_no_exist']); |
||||||
280 | |||||||
281 | return false; |
||||||
282 | } |
||||||
283 | |||||||
284 | // validateLoginPassword will hash this and check its valid |
||||||
285 | $sha_passwd = $_POST['passwrd']; |
||||||
286 | $valid_password = $user->validatePassword($sha_passwd); |
||||||
287 | |||||||
288 | require_once(SUBSDIR . '/Members.subs.php'); |
||||||
289 | |||||||
290 | 2 | // Bad password! Thought you could fool the database?! |
|||||
291 | if (!$valid_password) |
||||||
292 | 2 | { |
|||||
293 | // Let's be cautious, no hacking please. thanx. |
||||||
294 | 2 | validatePasswordFlood($user_setting['id_member'], $user_setting['passwd_flood']); |
|||||
295 | |||||||
296 | // Maybe we were too hasty... let's try some other authentication methods. |
||||||
297 | $other_passwords = $this->_other_passwords($_POST['passwrd'], $user_setting['password_salt'], $user_setting['passwd'], $user_setting['member_name']); |
||||||
298 | |||||||
299 | // Whichever encryption it was using, let's make it use ElkArte's now ;). |
||||||
300 | if (in_array($user_setting['passwd'], $other_passwords, true)) |
||||||
301 | { |
||||||
302 | $user->rehashPassword($sha_passwd); |
||||||
303 | |||||||
304 | // Update the password hash and set up the salt. |
||||||
305 | updateMemberData($user_setting['id_member'], array('passwd' => $user_setting['passwd'], 'password_salt' => $user_setting['password_salt'], 'passwd_flood' => '')); |
||||||
306 | } |
||||||
307 | // Okay, they for sure didn't enter the password! |
||||||
308 | else |
||||||
309 | { |
||||||
310 | // They've messed up again - keep a count to see if they need a hand. |
||||||
311 | $_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? ($_SESSION['failed_login'] + 1) : 1; |
||||||
312 | |||||||
313 | // Hmm... don't remember it, do you? Here, try the password reminder ;). |
||||||
314 | if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold']) |
||||||
315 | { |
||||||
316 | redirectexit('action=reminder'); |
||||||
317 | } |
||||||
318 | // We'll give you another chance... |
||||||
319 | else |
||||||
320 | { |
||||||
321 | // Log an error so we know that it didn't go well in the error log. |
||||||
322 | Errors::instance()->log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_setting['member_name'] . '</span>', 'user'); |
||||||
323 | |||||||
324 | $context['login_errors'] = array($txt['incorrect_password']); |
||||||
325 | |||||||
326 | return false; |
||||||
327 | } |
||||||
328 | } |
||||||
329 | } |
||||||
330 | elseif (!empty($user_setting['passwd_flood'])) |
||||||
331 | { |
||||||
332 | // Let's be sure they weren't a little hacker. |
||||||
333 | validatePasswordFlood($user_setting['id_member'], $user_setting['passwd_flood'], true); |
||||||
334 | |||||||
335 | // If we got here then we can reset the flood counter. |
||||||
336 | updateMemberData($user_setting['id_member'], array('passwd_flood' => '')); |
||||||
337 | } |
||||||
338 | |||||||
339 | if ($user_setting->fixSalt() === true) |
||||||
340 | { |
||||||
341 | updateMemberData($user_setting['id_member'], array('password_salt' => $user_setting['password_salt'])); |
||||||
342 | } |
||||||
343 | |||||||
344 | // Let's track the last used one-time password. |
||||||
345 | if (!empty($_POST['otp_token'])) |
||||||
346 | { |
||||||
347 | updateMemberData($user_setting['id_member'], array('otp_used' => (int) $_POST['otp_token'])); |
||||||
348 | } |
||||||
349 | |||||||
350 | // Check their activation status. |
||||||
351 | if ($user->checkActivation(isset($_REQUEST['undelete']))) |
||||||
352 | { |
||||||
353 | doLogin($user); |
||||||
354 | } |
||||||
355 | |||||||
356 | return false; |
||||||
357 | } |
||||||
358 | |||||||
359 | /** |
||||||
360 | * Loads other possible password hash / crypts using the post data |
||||||
361 | * |
||||||
362 | * What it does: |
||||||
363 | * |
||||||
364 | * - Used when a board is converted to see if the user credentials and a 3rd |
||||||
365 | * party hash satisfy whats in the db passwd field |
||||||
366 | * |
||||||
367 | * @param string $member_name |
||||||
368 | * @param string $passwrd |
||||||
369 | * @param string $password_salt |
||||||
370 | * |
||||||
371 | * @return array |
||||||
372 | */ |
||||||
373 | private function _other_passwords($posted_password, $member_name, $passwrd, $password_salt) |
||||||
374 | { |
||||||
375 | global $modSettings; |
||||||
376 | |||||||
377 | // What kind of data are we dealing with |
||||||
378 | $pw_strlen = strlen($passwrd); |
||||||
379 | |||||||
380 | // Start off with none, that's safe |
||||||
381 | $other_passwords = []; |
||||||
382 | |||||||
383 | if (empty($modSettings['enable_password_conversion'])) |
||||||
384 | { |
||||||
385 | return $other_passwords; |
||||||
386 | } |
||||||
387 | |||||||
388 | // None of the below cases will be used most of the time (because the salt is normally set.) |
||||||
389 | if ($password_salt === '') |
||||||
390 | { |
||||||
391 | // YaBB SE, Discus, MD5 (used a lot), SHA-1 (used some), SMF 1.0.x, IkonBoard, and none at all. |
||||||
392 | $other_passwords[] = crypt($posted_password, substr($posted_password, 0, 2)); |
||||||
393 | $other_passwords[] = crypt($posted_password, substr($passwrd, 0, 2)); |
||||||
394 | $other_passwords[] = md5($posted_password); |
||||||
395 | $other_passwords[] = sha1($posted_password); |
||||||
396 | $other_passwords[] = md5_hmac($posted_password, strtolower($member_name)); |
||||||
397 | $other_passwords[] = md5($posted_password . strtolower($member_name)); |
||||||
398 | $other_passwords[] = md5(md5($posted_password)); |
||||||
399 | $other_passwords[] = $posted_password; |
||||||
400 | |||||||
401 | // This one is a strange one... MyPHP, crypt() on the MD5 hash. |
||||||
402 | $other_passwords[] = crypt(md5($posted_password), md5($posted_password)); |
||||||
403 | |||||||
404 | // SHA-256 |
||||||
405 | if ($pw_strlen === 64) |
||||||
406 | { |
||||||
407 | // Snitz style |
||||||
408 | $other_passwords[] = bin2hex(hash('sha256', $posted_password, true)); |
||||||
409 | |||||||
410 | // Normal SHA-256 |
||||||
411 | $other_passwords[] = hash('sha256', $posted_password); |
||||||
412 | } |
||||||
413 | |||||||
414 | // phpBB3 users new hashing. We now support it as well ;). |
||||||
415 | $other_passwords[] = phpBB3_password_check($posted_password, $passwrd); |
||||||
416 | |||||||
417 | // APBoard 2 Login Method. |
||||||
418 | $other_passwords[] = md5(crypt($posted_password, 'CRYPT_MD5')); |
||||||
419 | |||||||
420 | // Xenforo 1.2+ |
||||||
421 | $other_passwords[] = crypt($posted_password, $passwrd); |
||||||
422 | } |
||||||
423 | // The hash should be 40 if it's SHA-1, so we're safe with more here too. |
||||||
424 | elseif ($pw_strlen === 32) |
||||||
425 | { |
||||||
426 | // vBulletin 3 style hashing? Let's welcome them with open arms \o/. |
||||||
427 | $other_passwords[] = md5(md5($posted_password) . stripslashes($password_salt)); |
||||||
428 | |||||||
429 | // Hmm.. p'raps it's Invision 2 style? |
||||||
430 | $other_passwords[] = md5(md5($password_salt) . md5($posted_password)); |
||||||
431 | |||||||
432 | // Some common md5 ones. |
||||||
433 | $other_passwords[] = md5($password_salt . $posted_password); |
||||||
434 | $other_passwords[] = md5($posted_password . $password_salt); |
||||||
435 | } |
||||||
436 | // The hash is 40 characters, lets try some SHA-1 style auth |
||||||
437 | elseif ($pw_strlen === 40) |
||||||
438 | { |
||||||
439 | // Maybe they are using a hash from before our password upgrade |
||||||
440 | $other_passwords[] = sha1(strtolower($member_name) . un_htmlspecialchars($posted_password)); |
||||||
441 | $other_passwords[] = sha1($passwrd . $_SESSION['session_value']); |
||||||
442 | |||||||
443 | if (!empty($modSettings['enable_password_conversion'])) |
||||||
444 | { |
||||||
445 | // BurningBoard3 style of hashing. |
||||||
446 | $other_passwords[] = sha1($password_salt . sha1($password_salt . sha1($posted_password))); |
||||||
447 | |||||||
448 | // PunBB 1.4 and later |
||||||
449 | $other_passwords[] = sha1($password_salt . sha1($posted_password)); |
||||||
450 | } |
||||||
451 | |||||||
452 | // Perhaps we converted from a non UTF-8 db and have a valid password being hashed differently. |
||||||
453 | if (!empty($modSettings['previousCharacterSet']) && $modSettings['previousCharacterSet'] !== 'utf8') |
||||||
454 | { |
||||||
455 | // Try iconv first, for no particular reason. |
||||||
456 | if (function_exists('iconv')) |
||||||
457 | { |
||||||
458 | $other_passwords['iconv'] = sha1(strtolower(iconv('UTF-8', $modSettings['previousCharacterSet'], $member_name)) . un_htmlspecialchars(iconv('UTF-8', $modSettings['previousCharacterSet'], $posted_password))); |
||||||
459 | } |
||||||
460 | |||||||
461 | // Say it aint so, iconv failed! |
||||||
462 | if (empty($other_passwords['iconv']) && function_exists('mb_convert_encoding')) |
||||||
463 | { |
||||||
464 | $other_passwords[] = sha1(strtolower(mb_convert_encoding($member_name, 'UTF-8', $modSettings['previousCharacterSet'])) . un_htmlspecialchars(mb_convert_encoding($posted_password, 'UTF-8', $modSettings['previousCharacterSet']))); |
||||||
0 ignored issues
–
show
It seems like
mb_convert_encoding($mem...previousCharacterSet']) can also be of type array ; however, parameter $string of strtolower() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
mb_convert_encoding($pos...previousCharacterSet']) can also be of type array ; however, parameter $string of un_htmlspecialchars() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
465 | } |
||||||
466 | } |
||||||
467 | } |
||||||
468 | // SHA-256 will be 64 characters long, lets check some of these possibilities |
||||||
469 | elseif ($pw_strlen === 64) |
||||||
470 | { |
||||||
471 | // PHP-Fusion7 |
||||||
472 | $other_passwords[] = hash_hmac('sha256', $posted_password, $password_salt); |
||||||
473 | |||||||
474 | // Plain SHA-256? |
||||||
475 | $other_passwords[] = hash('sha256', $posted_password . $password_salt); |
||||||
476 | |||||||
477 | // Xenforo? |
||||||
478 | $other_passwords[] = sha1(sha1($posted_password) . $password_salt); |
||||||
479 | $other_passwords[] = hash('sha256', (hash('sha256', ($posted_password) . $password_salt))); |
||||||
480 | } |
||||||
481 | |||||||
482 | // ElkArte's sha1 function can give a funny result on Linux (Not our fault!). If we've now got the real one let the old one be valid! |
||||||
483 | if (strpos(PHP_OS_FAMILY, 'Win') !== 0) |
||||||
484 | { |
||||||
485 | $other_passwords[] = bin2hex(hash('sha1', strtolower($member_name) . un_htmlspecialchars($posted_password), true)); |
||||||
486 | } |
||||||
487 | |||||||
488 | // Allows mods to easily extend the $other_passwords array |
||||||
489 | call_integration_hook('integrate_other_passwords', array(&$other_passwords)); |
||||||
490 | |||||||
491 | return $other_passwords; |
||||||
492 | } |
||||||
493 | |||||||
494 | /** |
||||||
495 | * Logs the current user out of their account. |
||||||
496 | * |
||||||
497 | * What it does: |
||||||
498 | * |
||||||
499 | * - It requires that the session hash is sent as well, to prevent automatic logouts by images or javascript. |
||||||
500 | * - It redirects back to $_SESSION['logout_url'], if it exists. |
||||||
501 | * - It is accessed via ?action=logout;session_var=... |
||||||
502 | * |
||||||
503 | * @param bool $internal if true, it doesn't check the session |
||||||
504 | * @param bool $redirect if true, redirect to the board index |
||||||
505 | * @throws \ElkArte\Exceptions\Exception |
||||||
506 | */ |
||||||
507 | public function action_logout($internal = false, $redirect = true) |
||||||
508 | { |
||||||
509 | // Make sure they aren't being auto-logged out. |
||||||
510 | if (!$internal) |
||||||
511 | { |
||||||
512 | checkSession('get'); |
||||||
513 | } |
||||||
514 | |||||||
515 | require_once(SUBSDIR . '/Auth.subs.php'); |
||||||
516 | |||||||
517 | if (isset($_SESSION['ftp_connection'])) |
||||||
518 | { |
||||||
519 | $_SESSION['ftp_connection'] = null; |
||||||
520 | } |
||||||
521 | |||||||
522 | // It won't be first login anymore. |
||||||
523 | unset($_SESSION['first_login']); |
||||||
524 | |||||||
525 | // Just ensure they aren't a guest! |
||||||
526 | if (empty(User::$info['is_guest'])) |
||||||
527 | { |
||||||
528 | // Pass the logout information to integrations. |
||||||
529 | call_integration_hook('integrate_logout', array(User::$settings['member_name'])); |
||||||
530 | |||||||
531 | // If you log out, you aren't online anymore :P. |
||||||
532 | require_once(SUBSDIR . '/Logging.subs.php'); |
||||||
533 | logOnline(User::$info['id'], false); |
||||||
534 | } |
||||||
535 | |||||||
536 | // Logout? Let's kill the admin/moderate/other sessions, too. |
||||||
537 | $types = array('admin', 'moderate'); |
||||||
538 | call_integration_hook('integrate_validateSession', array(&$types)); |
||||||
539 | foreach ($types as $type) |
||||||
540 | { |
||||||
541 | unset($_SESSION[$type . '_time']); |
||||||
542 | } |
||||||
543 | |||||||
544 | $_SESSION['log_time'] = 0; |
||||||
545 | |||||||
546 | // Empty the cookie! (set it in the past, and for id_member = 0) |
||||||
547 | setLoginCookie(-3600, 0); |
||||||
548 | |||||||
549 | // And some other housekeeping while we're at it. |
||||||
550 | session_destroy(); |
||||||
551 | if (!empty(User::$info['id'])) |
||||||
552 | { |
||||||
553 | User::$settings->fixSalt(true); |
||||||
0 ignored issues
–
show
The method
fixSalt() does not exist on ElkArte\Helper\ValuesContainerReadOnly . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
554 | require_once(SUBSDIR . '/Members.subs.php'); |
||||||
555 | updateMemberData(User::$info['id'], array('password_salt' => User::$settings['password_salt'])); |
||||||
556 | } |
||||||
557 | |||||||
558 | // Off to the merry board index we go! |
||||||
559 | if ($redirect) |
||||||
560 | { |
||||||
561 | if (empty($_SESSION['logout_url'])) |
||||||
562 | { |
||||||
563 | redirectexit(); |
||||||
564 | } |
||||||
565 | elseif ((strpos($_SESSION['logout_url'], 'http://') !== 0 && strpos($_SESSION['logout_url'], 'https://') !== 0)) |
||||||
566 | { |
||||||
567 | unset($_SESSION['logout_url']); |
||||||
568 | redirectexit(); |
||||||
569 | } |
||||||
570 | else |
||||||
571 | { |
||||||
572 | $temp = $_SESSION['logout_url']; |
||||||
573 | unset($_SESSION['logout_url']); |
||||||
574 | |||||||
575 | redirectexit($temp); |
||||||
576 | } |
||||||
577 | } |
||||||
578 | } |
||||||
579 | |||||||
580 | /** |
||||||
581 | * Throws guests out to the login screen when guest access is off. |
||||||
582 | * |
||||||
583 | * What it does: |
||||||
584 | * |
||||||
585 | * - It sets $_SESSION['login_url'] to $_SERVER['REQUEST_URL']. |
||||||
586 | * |
||||||
587 | * @uses 'kick_guest' sub template found in Login.template.php. |
||||||
588 | */ |
||||||
589 | public function action_kickguest() |
||||||
590 | { |
||||||
591 | global $txt, $context; |
||||||
592 | |||||||
593 | Txt::load('Login'); |
||||||
594 | theme()->getTemplates()->load('Login'); |
||||||
595 | createToken('login'); |
||||||
596 | |||||||
597 | // Never redirect to an attachment |
||||||
598 | if (validLoginUrl($_SERVER['REQUEST_URL'])) |
||||||
599 | { |
||||||
600 | $_SESSION['login_url'] = $_SERVER['REQUEST_URL']; |
||||||
601 | } |
||||||
602 | |||||||
603 | $context['sub_template'] = 'kick_guest'; |
||||||
604 | $context['page_title'] = $txt['login']; |
||||||
605 | $context['default_password'] = ''; |
||||||
606 | } |
||||||
607 | |||||||
608 | /** |
||||||
609 | * Display a message about the forum being in maintenance mode. |
||||||
610 | * |
||||||
611 | * What it does: |
||||||
612 | * |
||||||
613 | * - Displays a login screen with sub template 'maintenance'. |
||||||
614 | * - It sends a 503 header, so search engines don't index while we're in maintenance mode. |
||||||
615 | */ |
||||||
616 | public function action_maintenance_mode() |
||||||
617 | { |
||||||
618 | global $txt, $mtitle, $mmessage, $context; |
||||||
619 | |||||||
620 | Txt::load('Login'); |
||||||
621 | theme()->getTemplates()->load('Login'); |
||||||
622 | createToken('login'); |
||||||
623 | |||||||
624 | // Send a 503 header, so search engines don't bother indexing while we're in maintenance mode. |
||||||
625 | Headers::instance() |
||||||
626 | ->headerSpecial('HTTP/1.1 503 Service Temporarily Unavailable') |
||||||
627 | ->header('Status', '503 Service Temporarily Unavailable') |
||||||
628 | ->header('Retry-After', '3600'); |
||||||
629 | |||||||
630 | // Basic template stuff.. |
||||||
631 | $context['sub_template'] = 'maintenance'; |
||||||
632 | $context['title'] = &$mtitle; |
||||||
633 | $context['description'] = un_htmlspecialchars($mmessage); |
||||||
634 | $context['page_title'] = $txt['maintain_mode']; |
||||||
635 | } |
||||||
636 | |||||||
637 | /** |
||||||
638 | * Double check the cookie. |
||||||
639 | */ |
||||||
640 | public function action_check() |
||||||
641 | { |
||||||
642 | // Only our members, please. |
||||||
643 | if ($this->user->is_guest === false) |
||||||
0 ignored issues
–
show
The property
is_guest does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||||
644 | { |
||||||
645 | // Strike! You're outta there! |
||||||
646 | if ($_GET['member'] != $this->user->id) |
||||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||||
647 | { |
||||||
648 | throw new Exception('login_cookie_error', false); |
||||||
649 | } |
||||||
650 | |||||||
651 | // Some pass listing for login_url... |
||||||
652 | $temp = empty($_SESSION['login_url']) || validLoginUrl($_SESSION['login_url']) === false ? '' : $_SESSION['login_url']; |
||||||
653 | unset($_SESSION['login_url']); |
||||||
654 | redirectexit($temp); |
||||||
655 | } |
||||||
656 | |||||||
657 | 2 | // It'll never get here... until it does :P |
|||||
658 | redirectexit(); |
||||||
659 | 2 | } |
|||||
660 | |||||||
661 | 2 | /** |
|||||
662 | 2 | * Ping the server to keep the session alive and not let it disappear. |
|||||
663 | 2 | */ |
|||||
664 | 2 | public function action_keepalive() |
|||||
665 | { |
||||||
666 | dieGif(); |
||||||
667 | 2 | } |
|||||
668 | } |
||||||
669 | 2 | ||||||
670 | /** |
||||||
671 | * Check activation status of the current user. |
||||||
672 | 2 | * |
|||||
673 | 2 | * What it does: |
|||||
674 | 2 | * |
|||||
675 | 2 | * is_activated value key is as follows: |
|||||
676 | * - > 10 Banned with activation status as value - 10 |
||||||
677 | * - 5 = Awaiting COPPA consent |
||||||
678 | * - 4 = Awaiting Deletion approval |
||||||
679 | * - 3 = Awaiting Admin approval |
||||||
680 | * - 2 = Awaiting reactivation from email change |
||||||
681 | * - 1 = Approved and active |
||||||
682 | * - 0 = Not active |
||||||
683 | * |
||||||
684 | * @package Authorization |
||||||
685 | */ |
||||||
686 | function checkActivation() |
||||||
687 | { |
||||||
688 | global $context, $txt, $modSettings; |
||||||
689 | |||||||
690 | if (!isset($context['login_errors'])) |
||||||
691 | { |
||||||
692 | $context['login_errors'] = array(); |
||||||
693 | } |
||||||
694 | |||||||
695 | // What is the true activation status of this account? |
||||||
696 | $activation_status = User::$settings->getActivationStatus(); |
||||||
0 ignored issues
–
show
The method
getActivationStatus() does not exist on ElkArte\Helper\ValuesContainerReadOnly . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
697 | |||||||
698 | // Check if the account is activated - COPPA first... |
||||||
699 | if ($activation_status === 5) |
||||||
700 | { |
||||||
701 | $context['login_errors'][] = $txt['coppa_no_concent'] . ' <a href="' . getUrl('action', ['action' => 'register', 'sa' => 'coppa', 'member' => User::$settings['id_member']]) . '">' . $txt['coppa_need_more_details'] . '</a>'; |
||||||
702 | |||||||
703 | return false; |
||||||
704 | } |
||||||
705 | |||||||
706 | // Awaiting approval still? |
||||||
707 | if ($activation_status === 3) |
||||||
708 | { |
||||||
709 | throw new Exception('still_awaiting_approval', 'user'); |
||||||
710 | } |
||||||
711 | |||||||
712 | if ($activation_status === 4) |
||||||
713 | { |
||||||
714 | if (isset($_REQUEST['undelete'])) |
||||||
715 | { |
||||||
716 | require_once(SUBSDIR . '/Members.subs.php'); |
||||||
717 | updateMemberData(User::$settings['id_member'], array('is_activated' => 1)); |
||||||
718 | updateSettings(array('unapprovedMembers' => ($modSettings['unapprovedMembers'] > 0 ? $modSettings['unapprovedMembers'] - 1 : 0))); |
||||||
719 | } |
||||||
720 | else |
||||||
721 | { |
||||||
722 | $context['login_errors'][] = $txt['awaiting_delete_account']; |
||||||
723 | $context['login_show_undelete'] = true; |
||||||
724 | |||||||
725 | return false; |
||||||
726 | } |
||||||
727 | } |
||||||
728 | // Awaiting deletion, changed their mind? |
||||||
729 | // Standard activation? |
||||||
730 | elseif ($activation_status !== 1) |
||||||
731 | { |
||||||
732 | Errors::instance()->log_error($txt['activate_not_completed1'] . ' - <span class="remove">' . User::$settings['member_name'] . '</span>', false); |
||||||
733 | |||||||
734 | $context['login_errors'][] = $txt['activate_not_completed1'] . ' <a class="linkbutton" href="' . getUrl('action', ['action' => 'register', 'sa' => 'activate', 'resend', 'u' => User::$settings['id_member']]) . '">' . $txt['activate_not_completed2'] . '</a>'; |
||||||
735 | |||||||
736 | return false; |
||||||
737 | } |
||||||
738 | |||||||
739 | return true; |
||||||
740 | } |
||||||
741 | |||||||
742 | /** |
||||||
743 | * This function performs the logging in. |
||||||
744 | * |
||||||
745 | * What it does: |
||||||
746 | * - It sets the cookie, it call hooks, updates runtime settings for the user. |
||||||
747 | * |
||||||
748 | * @param UserSettingsLoader $user |
||||||
749 | * |
||||||
750 | * @throws Exception |
||||||
751 | * @package Authorization |
||||||
752 | */ |
||||||
753 | function doLogin(UserSettingsLoader $user) |
||||||
754 | { |
||||||
755 | global $maintenance, $modSettings, $context; |
||||||
756 | |||||||
757 | // Load authentication stuffs. |
||||||
758 | require_once(SUBSDIR . '/Auth.subs.php'); |
||||||
759 | |||||||
760 | User::reloadByUser($user, true); |
||||||
761 | |||||||
762 | // Call login integration functions. |
||||||
763 | call_integration_hook('integrate_login', array(User::$settings['member_name'], $modSettings['cookieTime'])); |
||||||
764 | |||||||
765 | // Bam! Cookie set. A session too, just in case. |
||||||
766 | setLoginCookie(60 * $modSettings['cookieTime'], User::$settings['id_member'], hash('sha256', (User::$settings['passwd'] . User::$settings['password_salt']))); |
||||||
767 | |||||||
768 | // Reset the login threshold. |
||||||
769 | if (isset($_SESSION['failed_login'])) |
||||||
770 | { |
||||||
771 | unset($_SESSION['failed_login']); |
||||||
772 | } |
||||||
773 | |||||||
774 | // Are you banned? |
||||||
775 | is_not_banned(true); |
||||||
776 | |||||||
777 | // Don't stick the language or theme after this point. |
||||||
778 | unset($_SESSION['language'], $_SESSION['theme']); |
||||||
779 | |||||||
780 | // We want to know if this is first login |
||||||
781 | if (User::$info->isFirstLogin()) |
||||||
0 ignored issues
–
show
The method
isFirstLogin() does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
782 | { |
||||||
783 | $_SESSION['first_login'] = true; |
||||||
784 | } |
||||||
785 | else |
||||||
786 | { |
||||||
787 | unset($_SESSION['first_login']); |
||||||
788 | } |
||||||
789 | |||||||
790 | // You're one of us: need to know all about you now, IP, stuff. |
||||||
791 | $req = Request::instance(); |
||||||
792 | |||||||
793 | // You've logged in, haven't you? |
||||||
794 | require_once(SUBSDIR . '/Members.subs.php'); |
||||||
795 | updateMemberData(User::$info->id, array('last_login' => time(), 'member_ip' => User::$info->ip, 'member_ip2' => $req->ban_ip())); |
||||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() The property
ip does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||||
796 | |||||||
797 | // Get rid of the online entry for that old guest.... |
||||||
798 | require_once(SUBSDIR . '/Logging.subs.php'); |
||||||
799 | deleteOnline('ip' . User::$info->ip); |
||||||
800 | $_SESSION['log_time'] = 0; |
||||||
801 | |||||||
802 | // Log this entry, only if we have it enabled. |
||||||
803 | if (!empty($modSettings['loginHistoryDays'])) |
||||||
804 | { |
||||||
805 | logLoginHistory(User::$info->id, User::$info->ip, User::$info->ip2); |
||||||
0 ignored issues
–
show
The property
ip2 does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||||
806 | } |
||||||
807 | |||||||
808 | // Just log you back out if it's in maintenance mode and you AREN'T an admin. |
||||||
809 | if (empty($maintenance) || allowedTo('admin_forum')) |
||||||
810 | { |
||||||
811 | redirectexit('action=auth;sa=check;member=' . User::$info->id); |
||||||
812 | } |
||||||
813 | else |
||||||
814 | { |
||||||
815 | redirectexit('action=logout;' . $context['session_var'] . '=' . $context['session_id']); |
||||||
816 | } |
||||||
817 | } |
||||||
818 | |||||||
819 | /** |
||||||
820 | * MD5 Encryption used for older passwords. (SMF 1.0.x/YaBB SE 1.5.x hashing) |
||||||
821 | * |
||||||
822 | * @param string $data |
||||||
823 | * @param string $key |
||||||
824 | * @return string the HMAC MD5 of data with key |
||||||
825 | * @package Authorization |
||||||
826 | */ |
||||||
827 | function md5_hmac($data, $key) |
||||||
828 | { |
||||||
829 | $key = str_pad(strlen($key) <= 64 ? $key : pack('H*', md5($key)), 64, chr(0x00)); |
||||||
830 | |||||||
831 | return md5(($key ^ str_repeat(chr(0x5c), 64)) . pack('H*', md5(($key ^ str_repeat(chr(0x36), 64)) . $data))); |
||||||
832 | } |
||||||
833 | |||||||
834 | /** |
||||||
835 | * Custom encryption for phpBB3 based passwords. |
||||||
836 | * |
||||||
837 | * @param string $passwd |
||||||
838 | * @param string $passwd_hash |
||||||
839 | * @return string |
||||||
840 | * @package Authorization |
||||||
841 | */ |
||||||
842 | function phpBB3_password_check($passwd, $passwd_hash) |
||||||
843 | { |
||||||
844 | // Too long or too short? |
||||||
845 | if (strlen($passwd_hash) !== 34) |
||||||
846 | { |
||||||
847 | return false; |
||||||
0 ignored issues
–
show
|
|||||||
848 | } |
||||||
849 | |||||||
850 | // Range of characters allowed. |
||||||
851 | $range = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; |
||||||
852 | |||||||
853 | // Tests |
||||||
854 | $strpos = strpos($range, $passwd_hash[3]); |
||||||
855 | $count = 1 << $strpos; |
||||||
856 | $salt = substr($passwd_hash, 4, 8); |
||||||
857 | |||||||
858 | $hash = md5($salt . $passwd, true); |
||||||
859 | for (; $count !== 0; --$count) |
||||||
860 | { |
||||||
861 | $hash = md5($hash . $passwd, true); |
||||||
862 | } |
||||||
863 | |||||||
864 | $output = substr($passwd_hash, 0, 12); |
||||||
865 | $i = 0; |
||||||
866 | while ($i < 16) |
||||||
867 | { |
||||||
868 | $value = ord($hash[$i++]); |
||||||
869 | $output .= $range[$value & 0x3f]; |
||||||
870 | |||||||
871 | if ($i < 16) |
||||||
872 | { |
||||||
873 | $value |= ord($hash[$i]) << 8; |
||||||
874 | } |
||||||
875 | |||||||
876 | $output .= $range[($value >> 6) & 0x3f]; |
||||||
877 | |||||||
878 | if ($i++ >= 16) |
||||||
879 | { |
||||||
880 | break; |
||||||
881 | } |
||||||
882 | |||||||
883 | if ($i < 16) |
||||||
884 | { |
||||||
885 | $value |= ord($hash[$i]) << 16; |
||||||
886 | } |
||||||
887 | |||||||
888 | $output .= $range[($value >> 12) & 0x3f]; |
||||||
889 | |||||||
890 | if ($i++ >= 16) |
||||||
891 | { |
||||||
892 | break; |
||||||
893 | } |
||||||
894 | |||||||
895 | $output .= $range[($value >> 18) & 0x3f]; |
||||||
896 | } |
||||||
897 | |||||||
898 | // Return now. |
||||||
899 | return $output; |
||||||
900 | } |
||||||
901 |
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