1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * This file has functions in it to do with authentication, user handling, and the like. |
||||
5 | * |
||||
6 | * @package ElkArte Forum |
||||
7 | * @copyright ElkArte Forum contributors |
||||
8 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||||
9 | * |
||||
10 | * This file contains code covered by: |
||||
11 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||||
12 | * |
||||
13 | * @version 2.0 dev |
||||
14 | * |
||||
15 | */ |
||||
16 | |||||
17 | use ElkArte\Errors\ErrorContext; |
||||
18 | use ElkArte\Helper\TokenHash; |
||||
19 | use ElkArte\Helper\Util; |
||||
20 | use ElkArte\Languages\Txt; |
||||
21 | use ElkArte\Request; |
||||
22 | use ElkArte\User; |
||||
23 | |||||
24 | /** |
||||
25 | * Sets the login cookie and session based on the id_member and password passed. |
||||
26 | * |
||||
27 | * What it does: |
||||
28 | * |
||||
29 | * - password should be already encrypted with the cookie salt. |
||||
30 | * - logs the user out if id_member is zero. |
||||
31 | * - sets the cookie and session to last the number of seconds specified by cookie_length. |
||||
32 | * - when logging out, if the globalCookies setting is enabled, attempts to clear the subdomain's cookie too. |
||||
33 | * |
||||
34 | * @param int $cookie_length |
||||
35 | * @param int $id The id of the member |
||||
36 | * @param string $password = '' |
||||
37 | * @package Authorization |
||||
38 | */ |
||||
39 | function setLoginCookie($cookie_length, $id, $password = '') |
||||
40 | { |
||||
41 | global $cookiename, $boardurl, $modSettings; |
||||
42 | |||||
43 | // If changing state force them to re-address some permission caching. |
||||
44 | $_SESSION['mc']['time'] = 0; |
||||
45 | |||||
46 | // Let's be sure it is an int to simplify the regexp used to validate the cookie |
||||
47 | $id = (int) $id; |
||||
48 | |||||
49 | // The cookie may already exist, and have been set with different options. |
||||
50 | $cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2); |
||||
51 | |||||
52 | if (isset($_COOKIE[$cookiename])) |
||||
53 | { |
||||
54 | $array = serializeToJson($_COOKIE[$cookiename], static function ($array_from) use ($cookiename) { |
||||
55 | global $modSettings; |
||||
56 | |||||
57 | require_once(SUBSDIR . '/Auth.subs.php'); |
||||
58 | $_COOKIE[$cookiename] = json_encode($array_from); |
||||
59 | setLoginCookie(60 * $modSettings['cookieTime'], $array_from[0], $array_from[1]); |
||||
60 | }); |
||||
61 | |||||
62 | // Out with the old, in with the new! |
||||
63 | if (isset($array[3]) && $array[3] != $cookie_state) |
||||
64 | { |
||||
65 | $cookie_url = url_parts($array[3] & 1 > 0, $array[3] & 2 > 0); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() $array[3] & 1 > 0 of type integer is incompatible with the type boolean expected by parameter $local of url_parts() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
66 | elk_setcookie($cookiename, json_encode(array(0, '', 0)), time() - 3600, $cookie_url[1], $cookie_url[0]); |
||||
67 | } |
||||
68 | } |
||||
69 | |||||
70 | // Get the data and path to set it on. |
||||
71 | $data = json_encode(empty($id) ? array(0, '', 0) : array($id, $password, time() + $cookie_length, $cookie_state)); |
||||
72 | $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
||||
73 | |||||
74 | // Set the cookie, $_COOKIE, and session variable. |
||||
75 | elk_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]); |
||||
76 | |||||
77 | // If subdomain-independent cookies are on, unset the subdomain-dependent cookie too. |
||||
78 | if (empty($id) && !empty($modSettings['globalCookies'])) |
||||
79 | { |
||||
80 | elk_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1]); |
||||
81 | } |
||||
82 | |||||
83 | // Any alias URLs? This is mainly for use with frames, etc. |
||||
84 | if (!empty($modSettings['forum_alias_urls'])) |
||||
85 | { |
||||
86 | $aliases = explode(',', $modSettings['forum_alias_urls']); |
||||
87 | |||||
88 | $temp = $boardurl; |
||||
89 | foreach ($aliases as $alias) |
||||
90 | { |
||||
91 | // Fake the $boardurl so we can set a different cookie. |
||||
92 | $alias = strtr(trim($alias), array('http://' => '', 'https://' => '')); |
||||
93 | $boardurl = 'http://' . $alias; |
||||
94 | |||||
95 | $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
||||
96 | |||||
97 | if ($cookie_url[0] == '') |
||||
98 | { |
||||
99 | $cookie_url[0] = strtok($alias, '/'); |
||||
100 | } |
||||
101 | |||||
102 | elk_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]); |
||||
103 | } |
||||
104 | |||||
105 | $boardurl = $temp; |
||||
106 | } |
||||
107 | |||||
108 | $_COOKIE[$cookiename] = $data; |
||||
109 | |||||
110 | // Make sure the user logs in with a new session ID. |
||||
111 | if (!isset($_SESSION['login_' . $cookiename]) || $_SESSION['login_' . $cookiename] !== $data) |
||||
112 | { |
||||
113 | // We need to meddle with the session. |
||||
114 | require_once(SOURCEDIR . '/Session.php'); |
||||
115 | |||||
116 | // Backup the old session. |
||||
117 | $oldSessionData = $_SESSION; |
||||
118 | |||||
119 | // Remove the old session data and file / db entry |
||||
120 | $_SESSION = array(); |
||||
121 | session_destroy(); |
||||
122 | |||||
123 | // Recreate and restore the new session. |
||||
124 | loadSession(); |
||||
125 | |||||
126 | // Get a new session id, and load it with the data |
||||
127 | session_regenerate_id(); |
||||
128 | |||||
129 | // If we generated new session values, be sure to use them as well |
||||
130 | $oldSessionData['session_value'] = $_SESSION['session_value'] ?? $oldSessionData['session_value']; |
||||
131 | $oldSessionData['session_var'] = $_SESSION['session_var'] ?? $oldSessionData['session_var']; |
||||
132 | $_SESSION = $oldSessionData; |
||||
133 | |||||
134 | $_SESSION['login_' . $cookiename] = $data; |
||||
135 | } |
||||
136 | } |
||||
137 | |||||
138 | /** |
||||
139 | * Get the domain and path for the cookie |
||||
140 | * |
||||
141 | * What it does: |
||||
142 | * |
||||
143 | * - normally, local and global should be the localCookies and globalCookies settings, respectively. |
||||
144 | * - uses boardurl to determine these two things. |
||||
145 | * |
||||
146 | * @param bool $local |
||||
147 | * @param bool $global |
||||
148 | * |
||||
149 | * @return array |
||||
150 | * @package Authorization |
||||
151 | * |
||||
152 | */ |
||||
153 | function url_parts($local, $global) |
||||
154 | { |
||||
155 | global $boardurl, $modSettings; |
||||
156 | |||||
157 | // Parse the URL with PHP to make life easier. |
||||
158 | $parsed_url = parse_url($boardurl); |
||||
159 | |||||
160 | // Is local cookies off? |
||||
161 | if (empty($parsed_url['path']) || !$local) |
||||
162 | { |
||||
163 | $parsed_url['path'] = ''; |
||||
164 | } |
||||
165 | |||||
166 | if (!empty($modSettings['globalCookiesDomain']) && strpos($boardurl, $modSettings['globalCookiesDomain']) !== false) |
||||
167 | { |
||||
168 | $parsed_url['host'] = $modSettings['globalCookiesDomain']; |
||||
169 | } |
||||
170 | |||||
171 | // Globalize cookies across domains (filter out IP-addresses)? |
||||
172 | elseif ($global && preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1) |
||||
173 | { |
||||
174 | $parsed_url['host'] = '.' . $parts[1]; |
||||
175 | } |
||||
176 | |||||
177 | // We shouldn't use a host at all if both options are off. |
||||
178 | elseif (!$local && !$global) |
||||
179 | { |
||||
180 | $parsed_url['host'] = ''; |
||||
181 | } |
||||
182 | |||||
183 | // The host also shouldn't be set if there aren't any dots in it. |
||||
184 | elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false) |
||||
185 | { |
||||
186 | $parsed_url['host'] = ''; |
||||
187 | } |
||||
188 | |||||
189 | return array($parsed_url['host'], $parsed_url['path'] . '/'); |
||||
190 | } |
||||
191 | |||||
192 | /** |
||||
193 | * Question the verity of the admin by asking for his or her password. |
||||
194 | * |
||||
195 | * What it does: |
||||
196 | * |
||||
197 | * - loads Login.template.php and uses the admin_login sub template. |
||||
198 | * - sends data to template so the admin is sent on to the page they |
||||
199 | * wanted if their password is correct, otherwise they can try again. |
||||
200 | * |
||||
201 | * @param string $type = 'admin' |
||||
202 | * @package Authorization |
||||
203 | */ |
||||
204 | function adminLogin($type = 'admin') |
||||
205 | { |
||||
206 | global $context, $txt; |
||||
207 | |||||
208 | Txt::load('Admin'); |
||||
209 | theme()->getTemplates()->load('Login'); |
||||
210 | |||||
211 | // Validate what type of session check this is. |
||||
212 | $types = array(); |
||||
213 | call_integration_hook('integrate_validateSession', array(&$types)); |
||||
214 | $type = in_array($type, $types) || $type === 'moderate' ? $type : 'admin'; |
||||
215 | |||||
216 | // They used a wrong password, log it and unset that. |
||||
217 | if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass'])) |
||||
218 | { |
||||
219 | // log some info along with it! referer, user agent |
||||
220 | $req = Request::instance(); |
||||
221 | $txt['security_wrong'] = sprintf($txt['security_wrong'], $_SERVER['HTTP_REFERER'] ?? $txt['unknown'], $req->user_agent(), User::$info->ip); |
||||
0 ignored issues
–
show
The property
ip does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
222 | \ElkArte\Errors\Errors::instance()->log_error($txt['security_wrong'], 'critical'); |
||||
0 ignored issues
–
show
The type
ElkArte\Errors\Errors 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 ![]() |
|||||
223 | |||||
224 | if (isset($_POST[$type . '_hash_pass'])) |
||||
225 | { |
||||
226 | unset($_POST[$type . '_hash_pass']); |
||||
227 | } |
||||
228 | |||||
229 | if (isset($_POST[$type . '_pass'])) |
||||
230 | { |
||||
231 | unset($_POST[$type . '_pass']); |
||||
232 | } |
||||
233 | |||||
234 | $context['incorrect_password'] = true; |
||||
235 | } |
||||
236 | |||||
237 | createToken('admin-login'); |
||||
238 | |||||
239 | // Figure out the get data and post data. |
||||
240 | $context['get_data'] = '?' . construct_query_string($_GET); |
||||
241 | $context['post_data'] = ''; |
||||
242 | |||||
243 | // Now go through $_POST. Make sure the session hash is sent. |
||||
244 | $_POST[$context['session_var']] = $context['session_id']; |
||||
245 | foreach ($_POST as $k => $v) |
||||
246 | { |
||||
247 | $context['post_data'] .= adminLogin_outputPostVars($k, $v); |
||||
248 | } |
||||
249 | |||||
250 | // Now we'll use the admin_login sub template of the Login template. |
||||
251 | $context['sub_template'] = 'admin_login'; |
||||
252 | |||||
253 | // And title the page something like "Login". |
||||
254 | if (!isset($context['page_title'])) |
||||
255 | { |
||||
256 | $context['page_title'] = $txt['admin_login']; |
||||
257 | } |
||||
258 | |||||
259 | // The type of action. |
||||
260 | $context['sessionCheckType'] = $type; |
||||
261 | |||||
262 | obExit(); |
||||
263 | |||||
264 | // We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged. |
||||
265 | trigger_error('Hacking attempt...', E_USER_ERROR); |
||||
266 | } |
||||
267 | |||||
268 | /** |
||||
269 | * Used by the adminLogin() function. |
||||
270 | * |
||||
271 | * What it does: |
||||
272 | * - if 'value' is an array, the function is called recursively. |
||||
273 | * |
||||
274 | * @param string $k key |
||||
275 | * @param string|bool $v value |
||||
276 | * @return string 'hidden' HTML form fields, containing key-value-pairs |
||||
277 | * @package Authorization |
||||
278 | */ |
||||
279 | function adminLogin_outputPostVars($k, $v) |
||||
280 | { |
||||
281 | if (!is_array($v)) |
||||
0 ignored issues
–
show
|
|||||
282 | { |
||||
283 | return ' |
||||
284 | <input type="hidden" name="' . htmlspecialchars($k, ENT_COMPAT, 'UTF-8') . '" value="' . strtr($v, array('"' => '"', '<' => '<', '>' => '>')) . '" />'; |
||||
0 ignored issues
–
show
It seems like
$v can also be of type boolean ; however, parameter $str of strtr() 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
![]() |
|||||
285 | } |
||||
286 | else |
||||
287 | { |
||||
288 | $ret = ''; |
||||
289 | foreach ($v as $k2 => $v2) |
||||
290 | { |
||||
291 | $ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2); |
||||
292 | } |
||||
293 | |||||
294 | return $ret; |
||||
295 | } |
||||
296 | } |
||||
297 | |||||
298 | /** |
||||
299 | * Properly urlencodes a string to be used in a query |
||||
300 | * |
||||
301 | * @param array $get associative array from $_GET |
||||
302 | * @return string query string |
||||
303 | * @package Authorization |
||||
304 | */ |
||||
305 | function construct_query_string($get) |
||||
306 | { |
||||
307 | global $scripturl; |
||||
308 | |||||
309 | $query_string = ''; |
||||
310 | |||||
311 | // Awww, darn. The $scripturl contains GET stuff! |
||||
312 | $q = strpos($scripturl, '?'); |
||||
313 | if ($q !== false) |
||||
314 | { |
||||
315 | parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr($scripturl, $q + 1), ';', '&')), $temp); |
||||
316 | |||||
317 | foreach ($get as $k => $v) |
||||
318 | { |
||||
319 | // Only if it's not already in the $scripturl! |
||||
320 | if (!isset($temp[$k])) |
||||
321 | { |
||||
322 | $query_string .= urlencode($k) . '=' . urlencode($v) . ';'; |
||||
323 | } |
||||
324 | // If it changed, put it out there, but with an ampersand. |
||||
325 | elseif ($temp[$k] != $v) |
||||
326 | { |
||||
327 | $query_string .= urlencode($k) . '=' . urlencode($v) . '&'; |
||||
328 | } |
||||
329 | } |
||||
330 | } |
||||
331 | else |
||||
332 | { |
||||
333 | // Add up all the data from $_GET into get_data. |
||||
334 | foreach ($get as $k => $v) |
||||
335 | { |
||||
336 | $query_string .= urlencode($k) . '=' . urlencode($v) . ';'; |
||||
337 | } |
||||
338 | } |
||||
339 | |||||
340 | return substr($query_string, 0, -1); |
||||
341 | } |
||||
342 | |||||
343 | /** |
||||
344 | * Finds members by email address, username, or real name. |
||||
345 | * |
||||
346 | * What it does: |
||||
347 | * |
||||
348 | * - searches for members whose username, display name, or e-mail address match the given pattern of array names. |
||||
349 | * - searches only buddies if buddies_only is set. |
||||
350 | * |
||||
351 | * @param string[]|string $names |
||||
352 | * @param bool $use_wildcards = false, accepts wildcards ? and * in the pattern if true |
||||
353 | * @param bool $buddies_only = false, |
||||
354 | * @param int $max = 500 retrieves a maximum of max members, if passed |
||||
355 | * @return array containing information about the matching members |
||||
356 | * @package Authorization |
||||
357 | */ |
||||
358 | function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500) |
||||
359 | 2 | { |
|||
360 | global $scripturl; |
||||
361 | 2 | ||||
362 | $db = database(); |
||||
363 | |||||
364 | 2 | // If it's not already an array, make it one. |
|||
365 | if (!is_array($names)) |
||||
366 | { |
||||
367 | $names = explode(',', $names); |
||||
368 | } |
||||
369 | 2 | ||||
370 | 2 | $maybe_email = false; |
|||
371 | foreach ($names as $i => $name) |
||||
372 | { |
||||
373 | 2 | // Trim, and fix wildcards for each name. |
|||
374 | $names[$i] = trim(Util::strtolower($name)); |
||||
375 | 2 | ||||
376 | $maybe_email |= strpos($name, '@') !== false; |
||||
377 | |||||
378 | 2 | // Make it so standard wildcards will work. (* and ?) |
|||
379 | if ($use_wildcards) |
||||
380 | { |
||||
381 | $names[$i] = strtr($names[$i], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => ''')); |
||||
382 | } |
||||
383 | else |
||||
384 | 2 | { |
|||
385 | $names[$i] = strtr($names[$i], array('\'' => ''')); |
||||
386 | } |
||||
387 | 2 | ||||
388 | $names[$i] = $db->quote('{string:name}', array('name' => $names[$i])); |
||||
389 | } |
||||
390 | |||||
391 | 2 | // What are we using to compare? |
|||
392 | $comparison = $use_wildcards ? 'LIKE' : '='; |
||||
393 | |||||
394 | 2 | // Nothing found yet. |
|||
395 | $results = array(); |
||||
396 | |||||
397 | 2 | // This ensures you can't search someones email address if you can't see it. |
|||
398 | $email_condition = allowedTo('moderate_forum') ? '' : '1=0 AND '; |
||||
399 | 2 | ||||
400 | if ($use_wildcards || $maybe_email) |
||||
0 ignored issues
–
show
The expression
$maybe_email of type false|integer is loosely compared to true ; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||||
401 | { |
||||
402 | $email_condition = ' |
||||
403 | OR (' . $email_condition . 'email_address ' . $comparison . ' ' . implode(') OR (' . $email_condition . ' email_address ' . $comparison . ' ', $names) . ')'; |
||||
404 | } |
||||
405 | |||||
406 | 2 | // Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise. |
|||
407 | $member_name = '{column_case_insensitive:member_name}'; |
||||
408 | $real_name = '{column_case_insensitive:real_name}'; |
||||
409 | |||||
410 | 2 | // Search by username, display name, and email address. |
|||
411 | 2 | $db->fetchQuery(' |
|||
412 | SELECT |
||||
413 | id_member, member_name, real_name, email_address |
||||
414 | 2 | FROM {db_prefix}members |
|||
415 | WHERE ({raw:member_name_search} |
||||
416 | OR {raw:real_name_search} {raw:email_condition}) |
||||
417 | ' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . ' |
||||
418 | AND is_activated IN (1, 11) |
||||
419 | LIMIT {int:limit}', |
||||
420 | 2 | array( |
|||
421 | 'buddy_list' => User::$info->buddies, |
||||
0 ignored issues
–
show
The property
buddies does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
422 | 'member_name_search' => $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names), |
||||
423 | 'real_name_search' => $real_name . ' ' . $comparison . ' ' . implode(' OR ' . $real_name . ' ' . $comparison . ' ', $names), |
||||
424 | 2 | 'email_condition' => $email_condition, |
|||
425 | 2 | 'limit' => $max, |
|||
426 | 2 | 'recursive' => true, |
|||
427 | 2 | ) |
|||
428 | 2 | )->fetch_callback( |
|||
429 | function ($row) use (&$results, $scripturl) { |
||||
430 | $results[$row['id_member']] = array( |
||||
431 | 2 | 'id' => (int) $row['id_member'], |
|||
432 | 'name' => $row['real_name'], |
||||
433 | 2 | 'username' => $row['member_name'], |
|||
434 | 2 | 'email' => showEmailAddress($row['id_member']) ? $row['email_address'] : '', |
|||
435 | 2 | 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], |
|||
436 | 2 | 'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' |
|||
437 | 2 | ); |
|||
438 | 2 | } |
|||
439 | 2 | ); |
|||
440 | |||||
441 | 2 | // Return all the results. |
|||
442 | return $results; |
||||
443 | } |
||||
444 | |||||
445 | 2 | /** |
|||
446 | * Generates a random password for a user and emails it to them. |
||||
447 | * |
||||
448 | * What it does: |
||||
449 | * |
||||
450 | * - called by ProfileOptions controller when changing someone's username. |
||||
451 | * - checks the validity of the new username. |
||||
452 | * - generates and sets a new password for the given user. |
||||
453 | * - mails the new password to the email address of the user. |
||||
454 | * - if username is not set, only a new password is generated and sent. |
||||
455 | * |
||||
456 | * @param int $memID |
||||
457 | * @param string|null $username = null |
||||
458 | * |
||||
459 | * @throws \ElkArte\Exceptions\Exception |
||||
460 | * @package Authorization |
||||
461 | * |
||||
462 | */ |
||||
463 | function resetPassword($memID, $username = null) |
||||
464 | { |
||||
465 | global $modSettings, $language; |
||||
466 | |||||
467 | // Language... and a required file. |
||||
468 | Txt::load('Login'); |
||||
469 | require_once(SUBSDIR . '/Mail.subs.php'); |
||||
470 | |||||
471 | // Get some important details. |
||||
472 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
473 | $result = getBasicMemberData($memID, array('preferences' => true)); |
||||
474 | $user = $result['member_name']; |
||||
475 | $email = $result['email_address']; |
||||
476 | $lngfile = $result['lngfile']; |
||||
477 | $old_user = ''; |
||||
478 | |||||
479 | if ($username !== null) |
||||
480 | { |
||||
481 | $old_user = $user; |
||||
482 | $user = trim($username); |
||||
483 | } |
||||
484 | |||||
485 | // Generate a random password. |
||||
486 | $tokenizer = new TokenHash(); |
||||
487 | $newPassword = $tokenizer->generate_hash(14); |
||||
488 | |||||
489 | // Create a db hash for the generated password |
||||
490 | $newPassword_sha256 = hash('sha256', strtolower($user) . $newPassword); |
||||
491 | $db_hash = password_hash($newPassword_sha256, PASSWORD_BCRYPT, ['cost' => 8]); |
||||
492 | |||||
493 | // Do some checks on the username if needed. |
||||
494 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
495 | if ($username !== null) |
||||
496 | { |
||||
497 | $errors = ErrorContext::context('reset_pwd', 0); |
||||
498 | validateUsername($memID, $user, 'reset_pwd'); |
||||
499 | |||||
500 | // If there are "important" errors and you are not an admin: log the first error |
||||
501 | // Otherwise grab all of them and don't log anything |
||||
502 | $error_severity = $errors->hasErrors(1) && User::$info->is_admin === false ? 1 : null; |
||||
0 ignored issues
–
show
The property
is_admin does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
503 | foreach ($errors->prepareErrors($error_severity) as $error) |
||||
504 | { |
||||
505 | throw new \ElkArte\Exceptions\Exception($error, $error_severity === null ? false : 'general'); |
||||
506 | } |
||||
507 | |||||
508 | // Update the database... |
||||
509 | updateMemberData($memID, array('member_name' => $user, 'passwd' => $db_hash)); |
||||
510 | } |
||||
511 | else |
||||
512 | { |
||||
513 | updateMemberData($memID, array('passwd' => $db_hash)); |
||||
514 | } |
||||
515 | |||||
516 | call_integration_hook('integrate_reset_pass', array($old_user, $user, $newPassword)); |
||||
517 | |||||
518 | $replacements = array( |
||||
519 | 'USERNAME' => $user, |
||||
520 | 'PASSWORD' => $newPassword, |
||||
521 | ); |
||||
522 | |||||
523 | $emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile); |
||||
524 | |||||
525 | // Send them the email informing them of the change - then we're done! |
||||
526 | sendmail($email, $emaildata['subject'], $emaildata['body'], null, null, false, 0); |
||||
527 | } |
||||
528 | |||||
529 | /** |
||||
530 | * Checks a username obeys a load of rules |
||||
531 | * |
||||
532 | * - Returns null if fine |
||||
533 | * |
||||
534 | * @param int $memID |
||||
535 | * @param string $username |
||||
536 | * @param string $ErrorContext |
||||
537 | * @param bool $check_reserved_name |
||||
538 | * @param bool $fatal pass through to isReservedName |
||||
539 | * @return string |
||||
540 | * @package Authorization |
||||
541 | */ |
||||
542 | function validateUsername($memID, $username, $ErrorContext = 'register', $check_reserved_name = true, $fatal = true) |
||||
543 | { |
||||
544 | global $txt; |
||||
545 | |||||
546 | $errors = ErrorContext::context($ErrorContext, 0); |
||||
547 | |||||
548 | 6 | // Don't use too long a name. |
|||
549 | if (Util::strlen($username) > 25) |
||||
550 | 6 | { |
|||
551 | $errors->addError('error_long_name'); |
||||
552 | } |
||||
553 | 6 | ||||
554 | // No name?! How can you register with no name? |
||||
555 | if ($username === '') |
||||
556 | { |
||||
557 | $errors->addError('need_username'); |
||||
558 | } |
||||
559 | 6 | ||||
560 | // Only these characters are permitted. |
||||
561 | if (in_array($username, array('_', '|')) || preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $username)) != 0 || strpos($username, '[code') !== false || strpos($username, '[/code') !== false) |
||||
562 | { |
||||
563 | $errors->addError('error_invalid_characters_username'); |
||||
564 | } |
||||
565 | 6 | ||||
566 | if (stripos($username, $txt['guest_title']) !== false) |
||||
567 | { |
||||
568 | $errors->addError(array('username_reserved', array($txt['guest_title'])), 1); |
||||
569 | } |
||||
570 | 6 | ||||
571 | if ($check_reserved_name) |
||||
572 | { |
||||
573 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
574 | if (isReservedName($username, $memID, false, $fatal)) |
||||
575 | 6 | { |
|||
576 | $errors->addError(array('name_in_use', array(htmlspecialchars($username, ENT_COMPAT, 'UTF-8')))); |
||||
577 | 2 | } |
|||
578 | 2 | } |
|||
579 | } |
||||
580 | |||||
581 | /** |
||||
582 | * Checks whether a password meets the current forum rules |
||||
583 | 6 | * |
|||
584 | * What it does: |
||||
585 | * |
||||
586 | * - called when registering/choosing a password. |
||||
587 | * - checks the password obeys the current forum settings for password strength. |
||||
588 | * - if password checking is enabled, will check that none of the words in restrict_in appear in the password. |
||||
589 | * - returns an error identifier if the password is invalid, or null. |
||||
590 | * |
||||
591 | * @param string $password |
||||
592 | * @param string $username |
||||
593 | * @param string[] $restrict_in = array() |
||||
594 | * @return string an error identifier if the password is invalid |
||||
595 | * @package Authorization |
||||
596 | */ |
||||
597 | function validatePassword($password, $username, $restrict_in = array()) |
||||
598 | { |
||||
599 | global $modSettings, $txt; |
||||
600 | |||||
601 | // Perform basic requirements first. |
||||
602 | if (Util::strlen($password) < (empty($modSettings['password_strength']) ? 4 : 8)) |
||||
603 | 2 | { |
|||
604 | Txt::load('Errors'); |
||||
605 | $txt['profile_error_password_short'] = sprintf($txt['profile_error_password_short'], empty($modSettings['password_strength']) ? 4 : 8); |
||||
606 | 2 | ||||
607 | return 'short'; |
||||
608 | } |
||||
609 | |||||
610 | // Is this enough? |
||||
611 | if (empty($modSettings['password_strength'])) |
||||
612 | { |
||||
613 | return null; |
||||
614 | } |
||||
615 | 2 | ||||
616 | // Otherwise, perform the medium strength test - checking if password appears in the restricted string. |
||||
617 | 2 | if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0) |
|||
618 | { |
||||
619 | return 'restricted_words'; |
||||
620 | } |
||||
621 | elseif (Util::strpos($password, $username) !== false) |
||||
622 | { |
||||
623 | return 'restricted_words'; |
||||
624 | } |
||||
625 | |||||
626 | // If just medium, we're done. |
||||
627 | if ($modSettings['password_strength'] == 1) |
||||
628 | { |
||||
629 | return null; |
||||
630 | } |
||||
631 | |||||
632 | // Otherwise, hard test next, check for numbers and letters, uppercase too. |
||||
633 | $good = preg_match('~(\D\d|\d\D)~', $password) === 1; |
||||
634 | $good = $good && Util::strtolower($password) !== $password; |
||||
635 | |||||
636 | return $good ? null : 'chars'; |
||||
637 | } |
||||
638 | |||||
639 | /** |
||||
640 | * Checks whether an entered password is correct for the user |
||||
641 | * |
||||
642 | * What it does: |
||||
643 | * |
||||
644 | * - called when logging in or whenever a password needs to be validated for a user |
||||
645 | * - used to generate a new hash for the db, used during registration or any password changes |
||||
646 | * - if a non SHA256 password is sent, will generate one with SHA256(user + password) and return it in password |
||||
647 | * |
||||
648 | * @param string $password user password if not already 64 characters long will be SHA256 with the user name |
||||
649 | * @param string $hash hash as generated from a SHA256 password |
||||
650 | * @param string $user user name only required if creating a SHA-256 password |
||||
651 | * @param bool $returnhash flag to determine if we are returning a hash suitable for the database |
||||
652 | * |
||||
653 | * @return bool|string |
||||
654 | * @package Authorization |
||||
655 | * |
||||
656 | */ |
||||
657 | function validateLoginPassword(&$password, $hash, $user = '', $returnhash = false) |
||||
658 | { |
||||
659 | // If the password is not 64 characters, lets make it a (SHA-256) |
||||
660 | if (strlen($password) !== 64) |
||||
661 | { |
||||
662 | $password = hash('sha256', Util::strtolower($user) . un_htmlspecialchars($password)); |
||||
663 | } |
||||
664 | 10 | ||||
665 | // They need a password hash, something to save in the db? |
||||
666 | 10 | if ($returnhash) |
|||
667 | { |
||||
668 | return password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]); |
||||
669 | } |
||||
670 | 10 | ||||
671 | // Doing a password check? |
||||
672 | 6 | return password_verify($password, $hash); |
|||
673 | } |
||||
674 | |||||
675 | /** |
||||
676 | 4 | * Quickly find out what moderation authority this user has |
|||
677 | * |
||||
678 | * What it does: |
||||
679 | * |
||||
680 | * - builds the moderator, group and board level querys for the user |
||||
681 | * - stores the information on the current users moderation powers in User::$info->mod_cache and $_SESSION['mc'] |
||||
682 | * |
||||
683 | * @package Authorization |
||||
684 | */ |
||||
685 | function rebuildModCache() |
||||
686 | { |
||||
687 | $db = database(); |
||||
688 | |||||
689 | // What groups can they moderate? |
||||
690 | $group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1'; |
||||
691 | 1 | ||||
692 | if ($group_query === '0=1') |
||||
693 | { |
||||
694 | 1 | $groups = $db->fetchQuery(' |
|||
695 | SELECT |
||||
696 | 1 | id_group |
|||
697 | FROM {db_prefix}group_moderators |
||||
698 | 1 | WHERE id_member = {int:current_member}', |
|||
699 | array( |
||||
700 | 'current_member' => User::$info->id, |
||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
701 | ) |
||||
702 | )->fetch_callback( |
||||
703 | function ($row) { |
||||
704 | 1 | return $row['id_group']; |
|||
705 | } |
||||
706 | 1 | ); |
|||
707 | |||||
708 | $group_query = empty($groups) ? '0=1' : 'id_group IN (' . implode(',', $groups) . ')'; |
||||
709 | 1 | } |
|||
710 | |||||
711 | // Then, same again, just the boards this time! |
||||
712 | 1 | $board_query = allowedTo('moderate_forum') ? '1=1' : '0=1'; |
|||
713 | |||||
714 | if ($board_query === '0=1') |
||||
715 | { |
||||
716 | 1 | $boards = boardsAllowedTo('moderate_board'); |
|||
717 | |||||
718 | 1 | $board_query = empty($boards) ? '0=1' : 'id_board IN (' . implode(',', $boards) . ')'; |
|||
719 | } |
||||
720 | 1 | ||||
721 | // What boards are they the moderator of? |
||||
722 | 1 | $boards_mod = array(); |
|||
723 | if (User::$info->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.
![]() |
|||||
724 | { |
||||
725 | require_once(SUBSDIR . '/Boards.subs.php'); |
||||
726 | 1 | $boards_mod = boardsModerated(User::$info->id); |
|||
727 | 1 | } |
|||
728 | |||||
729 | $mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')'; |
||||
730 | |||||
731 | $_SESSION['mc'] = array( |
||||
732 | 'time' => time(), |
||||
733 | 1 | // This looks a bit funny but protects against the login redirect. |
|||
734 | 'id' => User::$info->id && User::$info->name ? User::$info->id : 0, |
||||
0 ignored issues
–
show
The property
name does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
735 | 1 | // If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php. |
|||
736 | 1 | 'gq' => $group_query, |
|||
737 | 'bq' => $board_query, |
||||
738 | 1 | 'ap' => boardsAllowedTo('approve_posts'), |
|||
739 | 'mb' => $boards_mod, |
||||
740 | 1 | 'mq' => $mod_query, |
|||
741 | 1 | ); |
|||
742 | 1 | call_integration_hook('integrate_mod_cache'); |
|||
743 | 1 | ||||
744 | 1 | User::$info->mod_cache = $_SESSION['mc']; |
|||
0 ignored issues
–
show
The property
mod_cache does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __set , consider adding a @property annotation.
![]() |
|||||
745 | |||||
746 | 1 | // Might as well clean up some tokens while we are at it. |
|||
747 | cleanTokens(); |
||||
748 | 1 | } |
|||
749 | |||||
750 | /** |
||||
751 | 1 | * The same thing as setcookie but allows for integration hook |
|||
752 | 1 | * |
|||
753 | * @param string $name |
||||
754 | * @param string $value = '' |
||||
755 | * @param int $expire = 0 |
||||
756 | * @param string $path = '' |
||||
757 | * @param string $domain = '' |
||||
758 | * @param bool|null $secure = false |
||||
759 | * @param bool|null $httponly = null |
||||
760 | * @param string|null $samesite = null |
||||
761 | * |
||||
762 | * @return bool |
||||
763 | * @package Authorization |
||||
764 | * |
||||
765 | */ |
||||
766 | function elk_setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = null, $httponly = null, $samesite = null) |
||||
767 | { |
||||
768 | global $modSettings; |
||||
769 | |||||
770 | // In case a customization wants to override the default settings |
||||
771 | if ($httponly === null) |
||||
772 | { |
||||
773 | $httponly = !empty($modSettings['httponlyCookies']); |
||||
774 | } |
||||
775 | |||||
776 | if ($secure === null) |
||||
777 | { |
||||
778 | $secure = !empty($modSettings['secureCookies']); |
||||
779 | } |
||||
780 | |||||
781 | // Default value in modern browsers is Lax |
||||
782 | // @todo admin panel setting? |
||||
783 | $samesite = empty($samesite) ? 'Lax' : $samesite; |
||||
784 | |||||
785 | // Using SameSite=None requires Secure attribute in latest browser versions. |
||||
786 | $samesite = (!$secure && $samesite === 'None') ? 'Lax' : $samesite; |
||||
787 | |||||
788 | // Intercept cookie? |
||||
789 | call_integration_hook('integrate_cookie', array($name, $value, $expire, $path, $domain, $secure, $httponly, $samesite)); |
||||
790 | |||||
791 | if (PHP_VERSION_ID < 70300) |
||||
792 | { |
||||
793 | return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); |
||||
794 | } |
||||
795 | |||||
796 | return setcookie($name, $value, array( |
||||
797 | 'expires' => $expire, |
||||
798 | 'path' => $path, |
||||
799 | 'domain' => $domain, |
||||
800 | 'secure' => $secure, |
||||
801 | 'httponly' => $httponly, |
||||
802 | 'samesite' => $samesite)); |
||||
803 | } |
||||
804 | |||||
805 | /** |
||||
806 | * This functions determines whether this is the first login of the given user. |
||||
807 | * |
||||
808 | * @param int $id_member the id of the member to check for |
||||
809 | * @return bool |
||||
810 | * @deprecated replaced by \ElkArte\User::$info->isFirstLogin() |
||||
811 | * |
||||
812 | * @package Authorization |
||||
813 | * |
||||
814 | */ |
||||
815 | function isFirstLogin($id_member) |
||||
816 | { |
||||
817 | // First login? |
||||
818 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
819 | $member = getBasicMemberData($id_member, array('moderation' => true)); |
||||
820 | |||||
821 | return !empty($member) && $member['last_login'] == 0; |
||||
822 | } |
||||
823 | |||||
824 | /** |
||||
825 | * Search for a member by given criteria |
||||
826 | * |
||||
827 | * @param string $where |
||||
828 | * @param array $where_params array of values to used in the where statement |
||||
829 | * @param bool $fatal |
||||
830 | * |
||||
831 | * @return array|bool array of members data or false on failure |
||||
832 | * @throws \ElkArte\Exceptions\Exception no_user_with_email |
||||
833 | * @package Authorization |
||||
834 | * |
||||
835 | */ |
||||
836 | function findUser($where, $where_params, $fatal = true) |
||||
837 | { |
||||
838 | $db = database(); |
||||
839 | |||||
840 | // Find the user! |
||||
841 | $request = $db->fetchQuery(' |
||||
842 | SELECT |
||||
843 | id_member, real_name, member_name, email_address, is_activated, validation_code, |
||||
844 | lngfile, secret_question, passwd |
||||
845 | FROM {db_prefix}members |
||||
846 | WHERE ' . $where . ' |
||||
847 | LIMIT 1', |
||||
848 | array_merge($where_params, array()) |
||||
849 | ); |
||||
850 | |||||
851 | // Maybe email? |
||||
852 | if ($request->num_rows() === 0 && empty($_REQUEST['uid']) && isset($where_params['email_address'])) |
||||
853 | { |
||||
854 | $request->free_result(); |
||||
855 | |||||
856 | $request = $db->fetchQuery(' |
||||
857 | SELECT |
||||
858 | id_member, real_name, member_name, email_address, is_activated, validation_code, |
||||
859 | lngfile, secret_question |
||||
860 | FROM {db_prefix}members |
||||
861 | WHERE email_address = {string:email_address} |
||||
862 | LIMIT 1', |
||||
863 | array_merge($where_params, array()) |
||||
864 | ); |
||||
865 | if ($request->num_rows() === 0) |
||||
866 | { |
||||
867 | if ($fatal) |
||||
868 | { |
||||
869 | throw new \ElkArte\Exceptions\Exception('no_user_with_email', false); |
||||
870 | } |
||||
871 | |||||
872 | return false; |
||||
873 | } |
||||
874 | } |
||||
875 | |||||
876 | 10 | $member = $request->fetch_assoc(); |
|||
877 | $member['id_member'] = (int) $member['id_member']; |
||||
878 | 10 | $member['is_activated'] = (int) $member['is_activated']; |
|||
879 | |||||
880 | $request->free_result(); |
||||
881 | |||||
882 | 10 | return $member; |
|||
883 | 10 | } |
|||
884 | |||||
885 | /** |
||||
886 | 10 | * Find users by their email address. |
|||
887 | 10 | * |
|||
888 | * @param string $email |
||||
889 | * @param string|null $username |
||||
890 | * @return false|int on failure, int of member on success |
||||
891 | 10 | * @package Authorization |
|||
892 | 10 | */ |
|||
893 | function userByEmail($email, $username = null) |
||||
894 | 10 | { |
|||
895 | $db = database(); |
||||
896 | |||||
897 | $return = false; |
||||
898 | $db->fetchQuery(' |
||||
899 | SELECT |
||||
900 | id_member |
||||
901 | FROM {db_prefix}members |
||||
902 | WHERE email_address = {string:email_address}' . ($username === null ? '' : ' |
||||
903 | OR email_address = {string:username}') . ' |
||||
904 | LIMIT 1', |
||||
905 | array( |
||||
906 | 'email_address' => $email, |
||||
907 | 'username' => $username, |
||||
908 | 1 | ) |
|||
909 | )->fetch_callback( |
||||
910 | 1 | function ($row) use (&$return) { |
|||
911 | $return = (int) $row['id_member']; |
||||
912 | } |
||||
913 | ); |
||||
914 | |||||
915 | return $return; |
||||
916 | } |
||||
917 | |||||
918 | /** |
||||
919 | * Generate a random validation code. |
||||
920 | * |
||||
921 | * @param int $length the number of characters to return |
||||
922 | * |
||||
923 | * @return string |
||||
924 | 6 | * @package Authorization |
|||
925 | * |
||||
926 | 6 | */ |
|||
927 | function generateValidationCode($length = 10) |
||||
928 | 2 | { |
|||
929 | $tokenizer = new TokenHash(); |
||||
930 | |||||
931 | return $tokenizer->generate_hash((int) $length); |
||||
932 | } |
||||
933 | |||||
934 | /** |
||||
935 | * This function loads many settings of a user given by name or email. |
||||
936 | 2 | * |
|||
937 | * @param string $name |
||||
938 | * @param bool $is_id if true it treats $name as a member ID and try to load the data for that ID |
||||
939 | * @return array|false false if nothing is found |
||||
940 | * @package Authorization |
||||
941 | */ |
||||
942 | function loadExistingMember($name, $is_id = false) |
||||
943 | 4 | { |
|||
944 | $db = database(); |
||||
945 | |||||
946 | if ($is_id) |
||||
947 | { |
||||
948 | $request = $db->fetchQuery(' |
||||
949 | SELECT |
||||
950 | passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, |
||||
951 | 4 | passwd_flood, otp_secret, enable_otp, otp_used |
|||
952 | FROM {db_prefix}members |
||||
953 | WHERE id_member = {int:id_member} |
||||
954 | LIMIT 1', |
||||
955 | 4 | array( |
|||
956 | 'id_member' => (int) $name, |
||||
957 | ) |
||||
958 | ); |
||||
959 | } |
||||
960 | else |
||||
961 | { |
||||
962 | // Try to find the user, assuming a member_name was passed... |
||||
963 | $request = $db->fetchQuery(' |
||||
964 | SELECT |
||||
965 | passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, |
||||
966 | passwd_flood, otp_secret, enable_otp, otp_used |
||||
967 | FROM {db_prefix}members |
||||
968 | WHERE {column_case_insensitive:member_name} = {string_case_insensitive:user_name} |
||||
969 | LIMIT 1', |
||||
970 | array( |
||||
971 | 'user_name' => $name, |
||||
972 | ) |
||||
973 | ); |
||||
974 | 6 | // Didn't work. Try it as an email address. |
|||
975 | if ($request->num_rows() === 0 && strpos($name, '@') !== false) |
||||
976 | 2 | { |
|||
977 | $request->free_result(); |
||||
978 | |||||
979 | $request = $db->fetchQuery(' |
||||
980 | 4 | SELECT |
|||
981 | 4 | passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, |
|||
982 | passwd_flood, otp_secret, enable_otp, otp_used |
||||
983 | FROM {db_prefix}members |
||||
984 | 6 | WHERE email_address = {string:user_name} |
|||
985 | LIMIT 1', |
||||
986 | 6 | array( |
|||
987 | 'user_name' => $name, |
||||
988 | ) |
||||
989 | ); |
||||
990 | } |
||||
991 | } |
||||
992 | |||||
993 | // Nothing? Ah the horror... |
||||
994 | if ($request->num_rows() === 0) |
||||
995 | { |
||||
996 | $user_auth_data = false; |
||||
997 | } |
||||
998 | else |
||||
999 | { |
||||
1000 | $user_auth_data = $request->fetch_assoc(); |
||||
1001 | $user_auth_data['id_member'] = (int) $user_auth_data['id_member']; |
||||
1002 | } |
||||
1003 | |||||
1004 | $request->free_result(); |
||||
1005 | |||||
1006 | return $user_auth_data; |
||||
1007 | } |
||||
1008 |