|
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); |
|
|
|
|
|
|
66
|
|
|
elk_setcookie($cookiename, json_encode([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) ? [0, '', 0] : [$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), ['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 = []; |
|
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']) && str_contains($boardurl, $modSettings['globalCookiesDomain'])) |
|
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']) || !str_contains($parsed_url['host'], '.')) |
|
185
|
|
|
{ |
|
186
|
|
|
$parsed_url['host'] = ''; |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
return [$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'): never |
|
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 = []; |
|
213
|
|
|
call_integration_hook('integrate_validateSession', [&$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); |
|
|
|
|
|
|
222
|
|
|
\ElkArte\Errors\Errors::instance()->log_error($txt['security_wrong'], 'critical'); |
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
282
|
|
|
{ |
|
283
|
|
|
return ' |
|
284
|
|
|
<input type="hidden" name="' . htmlspecialchars($k, ENT_COMPAT, 'UTF-8') . '" value="' . strtr($v, ['"' => '"', '<' => '<', '>' => '>']) . '" />'; |
|
|
|
|
|
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
$ret = ''; |
|
288
|
|
|
foreach ($v as $k2 => $v2) |
|
289
|
|
|
{ |
|
290
|
|
|
$ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2); |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
return $ret; |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
/** |
|
297
|
|
|
* Properly urlencodes a string to be used in a query |
|
298
|
|
|
* |
|
299
|
|
|
* @param array $get associative array from $_GET |
|
300
|
|
|
* @return string query string |
|
301
|
|
|
* @package Authorization |
|
302
|
|
|
*/ |
|
303
|
|
|
function construct_query_string($get) |
|
304
|
|
|
{ |
|
305
|
|
|
global $scripturl; |
|
306
|
|
|
|
|
307
|
|
|
$query_string = ''; |
|
308
|
|
|
|
|
309
|
|
|
// Awww, darn. The $scripturl contains GET stuff! |
|
310
|
|
|
$q = strpos($scripturl, '?'); |
|
311
|
|
|
if ($q !== false) |
|
312
|
|
|
{ |
|
313
|
|
|
parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', str_replace(';', '&', substr($scripturl, $q + 1))), $temp); |
|
314
|
|
|
|
|
315
|
|
|
foreach ($get as $k => $v) |
|
316
|
|
|
{ |
|
317
|
|
|
// Only if it's not already in the $scripturl! |
|
318
|
|
|
if (!isset($temp[$k])) |
|
319
|
|
|
{ |
|
320
|
|
|
$query_string .= urlencode($k) . '=' . urlencode($v) . ';'; |
|
321
|
|
|
} |
|
322
|
|
|
// If it changed, put it out there, but with an ampersand. |
|
323
|
|
|
elseif ($temp[$k] != $v) |
|
324
|
|
|
{ |
|
325
|
|
|
$query_string .= urlencode($k) . '=' . urlencode($v) . '&'; |
|
326
|
|
|
} |
|
327
|
|
|
} |
|
328
|
|
|
} |
|
329
|
|
|
else |
|
330
|
|
|
{ |
|
331
|
|
|
// Add up all the data from $_GET into get_data. |
|
332
|
|
|
foreach ($get as $k => $v) |
|
333
|
|
|
{ |
|
334
|
|
|
$query_string .= urlencode($k) . '=' . urlencode($v) . ';'; |
|
335
|
|
|
} |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
return substr($query_string, 0, -1); |
|
339
|
|
|
} |
|
340
|
|
|
|
|
341
|
|
|
/** |
|
342
|
|
|
* Finds members by email address, username, or real name. |
|
343
|
|
|
* |
|
344
|
|
|
* What it does: |
|
345
|
|
|
* |
|
346
|
|
|
* - searches for members whose username, display name, or e-mail address match the given pattern of array names. |
|
347
|
|
|
* - searches only buddies if buddies_only is set. |
|
348
|
|
|
* |
|
349
|
|
|
* @param string[]|string $names |
|
350
|
|
|
* @param bool $use_wildcards = false, accepts wildcards ? and * in the pattern if true |
|
351
|
|
|
* @param bool $buddies_only = false, |
|
352
|
|
|
* @param int $max = 500 retrieves a maximum of max members, if passed |
|
353
|
|
|
* @return array containing information about the matching members |
|
354
|
|
|
* @package Authorization |
|
355
|
|
|
*/ |
|
356
|
|
|
function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500) |
|
357
|
|
|
{ |
|
358
|
|
|
global $scripturl; |
|
359
|
2 |
|
|
|
360
|
|
|
$db = database(); |
|
361
|
2 |
|
|
|
362
|
|
|
// If it's not already an array, make it one. |
|
363
|
|
|
if (!is_array($names)) |
|
364
|
2 |
|
{ |
|
365
|
|
|
$names = explode(',', $names); |
|
366
|
|
|
} |
|
367
|
|
|
|
|
368
|
|
|
$maybe_email = false; |
|
369
|
2 |
|
foreach ($names as $i => $name) |
|
370
|
2 |
|
{ |
|
371
|
|
|
// Trim, and fix wildcards for each name. |
|
372
|
|
|
$names[$i] = trim(Util::strtolower($name)); |
|
373
|
2 |
|
|
|
374
|
|
|
$maybe_email |= str_contains($name, '@'); |
|
375
|
2 |
|
|
|
376
|
|
|
// Make it so standard wildcards will work. (* and ?) |
|
377
|
|
|
if ($use_wildcards) |
|
378
|
2 |
|
{ |
|
379
|
|
|
$names[$i] = strtr($names[$i], ['%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => ''']); |
|
380
|
|
|
} |
|
381
|
|
|
else |
|
382
|
|
|
{ |
|
383
|
|
|
$names[$i] = strtr($names[$i], ['\'' => ''']); |
|
384
|
2 |
|
} |
|
385
|
|
|
|
|
386
|
|
|
$names[$i] = $db->quote('{string:name}', ['name' => $names[$i]]); |
|
387
|
2 |
|
} |
|
388
|
|
|
|
|
389
|
|
|
// What are we using to compare? |
|
390
|
|
|
$comparison = $use_wildcards ? 'LIKE' : '='; |
|
391
|
2 |
|
|
|
392
|
|
|
// Nothing found yet. |
|
393
|
|
|
$results = []; |
|
394
|
2 |
|
|
|
395
|
|
|
// This ensures you can't search someones email address if you can't see it. |
|
396
|
|
|
$email_condition = allowedTo('moderate_forum') ? '' : '1=0 AND '; |
|
397
|
2 |
|
|
|
398
|
|
|
if ($use_wildcards || $maybe_email) |
|
|
|
|
|
|
399
|
2 |
|
{ |
|
400
|
|
|
$email_condition = ' |
|
401
|
|
|
OR (' . $email_condition . 'email_address ' . $comparison . ' ' . implode(') OR (' . $email_condition . ' email_address ' . $comparison . ' ', $names) . ')'; |
|
402
|
|
|
} |
|
403
|
|
|
|
|
404
|
|
|
// Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise. |
|
405
|
|
|
$member_name = '{column_case_insensitive:member_name}'; |
|
406
|
2 |
|
$real_name = '{column_case_insensitive:real_name}'; |
|
407
|
|
|
|
|
408
|
|
|
// Search by username, display name, and email address. |
|
409
|
|
|
$db->fetchQuery(' |
|
410
|
2 |
|
SELECT |
|
411
|
2 |
|
id_member, member_name, real_name, email_address |
|
412
|
|
|
FROM {db_prefix}members |
|
413
|
|
|
WHERE ({raw:member_name_search} |
|
414
|
2 |
|
OR {raw:real_name_search} {raw:email_condition}) |
|
415
|
|
|
' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . ' |
|
416
|
|
|
AND is_activated IN (1, 11) |
|
417
|
|
|
LIMIT {int:limit}', |
|
418
|
|
|
[ |
|
419
|
|
|
'buddy_list' => User::$info->buddies, |
|
|
|
|
|
|
420
|
2 |
|
'member_name_search' => $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names), |
|
421
|
|
|
'real_name_search' => $real_name . ' ' . $comparison . ' ' . implode(' OR ' . $real_name . ' ' . $comparison . ' ', $names), |
|
422
|
|
|
'email_condition' => $email_condition, |
|
423
|
|
|
'limit' => $max, |
|
424
|
2 |
|
'recursive' => true, |
|
425
|
2 |
|
] |
|
426
|
2 |
|
)->fetch_callback( |
|
427
|
2 |
|
function ($row) use (&$results, $scripturl) { |
|
428
|
2 |
|
$results[$row['id_member']] = [ |
|
429
|
|
|
'id' => (int) $row['id_member'], |
|
430
|
|
|
'name' => $row['real_name'], |
|
431
|
2 |
|
'username' => $row['member_name'], |
|
432
|
|
|
'email' => showEmailAddress($row['id_member']) ? $row['email_address'] : '', |
|
433
|
2 |
|
'href' => $scripturl . '?action=profile;u=' . $row['id_member'], |
|
434
|
2 |
|
'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' |
|
435
|
2 |
|
]; |
|
436
|
2 |
|
} |
|
437
|
2 |
|
); |
|
438
|
2 |
|
|
|
439
|
2 |
|
// Return all the results. |
|
440
|
|
|
return $results; |
|
441
|
2 |
|
} |
|
442
|
|
|
|
|
443
|
|
|
/** |
|
444
|
|
|
* Generates a random password for a user and emails it to them. |
|
445
|
2 |
|
* |
|
446
|
|
|
* What it does: |
|
447
|
|
|
* |
|
448
|
|
|
* - called by ProfileOptions controller when changing someone's username. |
|
449
|
|
|
* - checks the validity of the new username. |
|
450
|
|
|
* - generates and sets a new password for the given user. |
|
451
|
|
|
* - mails the new password to the email address of the user. |
|
452
|
|
|
* - if username is not set, only a new password is generated and sent. |
|
453
|
|
|
* |
|
454
|
|
|
* @param int $memID |
|
455
|
|
|
* @param string|null $username = null |
|
456
|
|
|
* |
|
457
|
|
|
* @throws \ElkArte\Exceptions\Exception |
|
458
|
|
|
* @package Authorization |
|
459
|
|
|
* |
|
460
|
|
|
*/ |
|
461
|
|
|
function resetPassword($memID, $username = null) |
|
462
|
|
|
{ |
|
463
|
|
|
global $modSettings, $language; |
|
464
|
|
|
|
|
465
|
|
|
// Language... and a required file. |
|
466
|
|
|
Txt::load('Login'); |
|
467
|
|
|
require_once(SUBSDIR . '/Mail.subs.php'); |
|
468
|
|
|
|
|
469
|
|
|
// Get some important details. |
|
470
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
|
471
|
|
|
$result = getBasicMemberData($memID, ['preferences' => true]); |
|
472
|
|
|
$user = $result['member_name']; |
|
473
|
|
|
$email = $result['email_address']; |
|
474
|
|
|
$lngfile = $result['lngfile']; |
|
475
|
|
|
$old_user = ''; |
|
476
|
|
|
|
|
477
|
|
|
if ($username !== null) |
|
478
|
|
|
{ |
|
479
|
|
|
$old_user = $user; |
|
480
|
|
|
$user = trim($username); |
|
481
|
|
|
} |
|
482
|
|
|
|
|
483
|
|
|
// Generate a random password. |
|
484
|
|
|
$tokenizer = new TokenHash(); |
|
485
|
|
|
$newPassword = $tokenizer->generate_hash(14); |
|
486
|
|
|
|
|
487
|
|
|
// Create a db hash for the generated password |
|
488
|
|
|
$newPassword_sha256 = hash('sha256', strtolower($user) . $newPassword); |
|
489
|
|
|
$db_hash = password_hash($newPassword_sha256, PASSWORD_BCRYPT, ['cost' => 8]); |
|
490
|
|
|
|
|
491
|
|
|
// Do some checks on the username if needed. |
|
492
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
|
493
|
|
|
if ($username !== null) |
|
494
|
|
|
{ |
|
495
|
|
|
$errors = ErrorContext::context('reset_pwd', 0); |
|
496
|
|
|
validateUsername($memID, $user, 'reset_pwd'); |
|
497
|
|
|
|
|
498
|
|
|
// If there are "important" errors and you are not an admin: log the first error |
|
499
|
|
|
// Otherwise grab all of them and don't log anything |
|
500
|
|
|
$error_severity = $errors->hasErrors(1) && User::$info->is_admin === false ? 1 : null; |
|
|
|
|
|
|
501
|
|
|
foreach ($errors->prepareErrors($error_severity) as $error) |
|
502
|
|
|
{ |
|
503
|
|
|
throw new \ElkArte\Exceptions\Exception($error, $error_severity === null ? false : 'general'); |
|
504
|
|
|
} |
|
505
|
|
|
|
|
506
|
|
|
// Update the database... |
|
507
|
|
|
updateMemberData($memID, ['member_name' => $user, 'passwd' => $db_hash]); |
|
508
|
|
|
} |
|
509
|
|
|
else |
|
510
|
|
|
{ |
|
511
|
|
|
updateMemberData($memID, ['passwd' => $db_hash]); |
|
512
|
|
|
} |
|
513
|
|
|
|
|
514
|
|
|
call_integration_hook('integrate_reset_pass', [$old_user, $user, $newPassword]); |
|
515
|
|
|
|
|
516
|
|
|
$replacements = [ |
|
517
|
|
|
'USERNAME' => $user, |
|
518
|
|
|
'PASSWORD' => $newPassword, |
|
519
|
|
|
]; |
|
520
|
|
|
|
|
521
|
|
|
$emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile); |
|
522
|
|
|
|
|
523
|
|
|
// Send them the email informing them of the change - then we're done! |
|
524
|
|
|
sendmail($email, $emaildata['subject'], $emaildata['body'], null, null, false, 0); |
|
525
|
|
|
} |
|
526
|
|
|
|
|
527
|
|
|
/** |
|
528
|
|
|
* Checks a username obeys a load of rules |
|
529
|
|
|
* |
|
530
|
|
|
* - Returns null if fine |
|
531
|
|
|
* |
|
532
|
|
|
* @param int $memID |
|
533
|
|
|
* @param string $username |
|
534
|
|
|
* @param string $ErrorContext |
|
535
|
|
|
* @param bool $check_reserved_name |
|
536
|
|
|
* @param bool $fatal pass through to isReservedName |
|
537
|
|
|
* @return string |
|
538
|
|
|
* @package Authorization |
|
539
|
|
|
*/ |
|
540
|
|
|
function validateUsername($memID, $username, $ErrorContext = 'register', $check_reserved_name = true, $fatal = true) |
|
541
|
|
|
{ |
|
542
|
|
|
global $txt; |
|
543
|
|
|
|
|
544
|
|
|
$errors = ErrorContext::context($ErrorContext, 0); |
|
545
|
|
|
|
|
546
|
|
|
// Don't use too long a name. |
|
547
|
|
|
if (Util::strlen($username) > 25) |
|
548
|
6 |
|
{ |
|
549
|
|
|
$errors->addError('error_long_name'); |
|
550
|
6 |
|
} |
|
551
|
|
|
|
|
552
|
|
|
// No name?! How can you register with no name? |
|
553
|
6 |
|
if ($username === '') |
|
554
|
|
|
{ |
|
555
|
|
|
$errors->addError('need_username'); |
|
556
|
|
|
} |
|
557
|
|
|
|
|
558
|
|
|
// Only these characters are permitted. |
|
559
|
6 |
|
if (in_array($username, ['_', '|']) || preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $username)) != 0 || str_contains($username, '[code') || str_contains($username, '[/code')) |
|
560
|
|
|
{ |
|
561
|
|
|
$errors->addError('error_invalid_characters_username'); |
|
562
|
|
|
} |
|
563
|
|
|
|
|
564
|
|
|
if (stripos($username, $txt['guest_title']) !== false) |
|
565
|
6 |
|
{ |
|
566
|
|
|
$errors->addError(['username_reserved', [$txt['guest_title']]], 1); |
|
567
|
|
|
} |
|
568
|
|
|
|
|
569
|
|
|
if ($check_reserved_name) |
|
570
|
6 |
|
{ |
|
571
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
|
572
|
|
|
if (isReservedName($username, $memID, false, $fatal)) |
|
573
|
|
|
{ |
|
574
|
|
|
$errors->addError(['name_in_use', [htmlspecialchars($username, ENT_COMPAT, 'UTF-8')]]); |
|
575
|
6 |
|
} |
|
576
|
|
|
} |
|
577
|
2 |
|
} |
|
578
|
2 |
|
|
|
579
|
|
|
/** |
|
580
|
|
|
* Checks whether a password meets the current forum rules |
|
581
|
|
|
* |
|
582
|
|
|
* What it does: |
|
583
|
6 |
|
* |
|
584
|
|
|
* - called when registering/choosing a password. |
|
585
|
|
|
* - checks the password obeys the current forum settings for password strength. |
|
586
|
|
|
* - if password checking is enabled, will check that none of the words in restrict_in appear in the password. |
|
587
|
|
|
* - returns an error identifier if the password is invalid, or null. |
|
588
|
|
|
* |
|
589
|
|
|
* @param string $password |
|
590
|
|
|
* @param string $username |
|
591
|
|
|
* @param string[] $restrict_in = array() |
|
592
|
|
|
* @return string an error identifier if the password is invalid |
|
593
|
|
|
* @package Authorization |
|
594
|
|
|
*/ |
|
595
|
|
|
function validatePassword($password, $username, $restrict_in = []) |
|
596
|
|
|
{ |
|
597
|
|
|
global $modSettings, $txt; |
|
598
|
|
|
|
|
599
|
|
|
// Perform basic requirements first. |
|
600
|
|
|
if (Util::strlen($password) < (empty($modSettings['password_strength']) ? 4 : 8)) |
|
601
|
|
|
{ |
|
602
|
|
|
Txt::load('Errors'); |
|
603
|
2 |
|
$txt['profile_error_password_short'] = sprintf($txt['profile_error_password_short'], empty($modSettings['password_strength']) ? 4 : 8); |
|
604
|
|
|
|
|
605
|
|
|
return 'short'; |
|
606
|
2 |
|
} |
|
607
|
|
|
|
|
608
|
|
|
if (Util::strlen($password) > 64) |
|
609
|
|
|
{ |
|
610
|
|
|
Txt::load('Errors'); |
|
611
|
|
|
$txt['profile_error_password_long'] = sprintf($txt['profile_error_password_long'], 64); |
|
612
|
|
|
return 'short'; |
|
613
|
|
|
} |
|
614
|
|
|
|
|
615
|
2 |
|
// Is this enough? |
|
616
|
|
|
if (empty($modSettings['password_strength'])) |
|
617
|
2 |
|
{ |
|
618
|
|
|
return null; |
|
619
|
|
|
} |
|
620
|
|
|
|
|
621
|
|
|
// Otherwise, perform the medium strength test - checking if password appears in the restricted string. |
|
622
|
|
|
if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0) |
|
623
|
|
|
{ |
|
624
|
|
|
return 'restricted_words'; |
|
625
|
|
|
} |
|
626
|
|
|
|
|
627
|
|
|
if (Util::strpos($password, $username) !== false) |
|
628
|
|
|
{ |
|
629
|
|
|
return 'restricted_words'; |
|
630
|
|
|
} |
|
631
|
|
|
|
|
632
|
|
|
// If just medium, we're done. |
|
633
|
|
|
if ($modSettings['password_strength'] == 1) |
|
634
|
|
|
{ |
|
635
|
|
|
return null; |
|
636
|
|
|
} |
|
637
|
|
|
|
|
638
|
|
|
// Otherwise, hard test next, check for numbers and letters, uppercase too. |
|
639
|
|
|
$good = preg_match('~(\D\d|\d\D)~', $password) === 1; |
|
640
|
|
|
$good = $good && Util::strtolower($password) !== $password; |
|
641
|
|
|
|
|
642
|
|
|
return $good ? null : 'chars'; |
|
643
|
|
|
} |
|
644
|
|
|
|
|
645
|
|
|
/** |
|
646
|
|
|
* Checks whether an entered password is correct for the user |
|
647
|
|
|
* |
|
648
|
|
|
* What it does: |
|
649
|
|
|
* |
|
650
|
|
|
* - called when logging in or whenever a password needs to be validated for a user |
|
651
|
|
|
* - used to generate a new hash for the db, used during registration or any password changes |
|
652
|
|
|
* - if a non SHA256 password is sent, will generate one with SHA256(user + password) and return it in password |
|
653
|
|
|
* |
|
654
|
|
|
* @param string $password user password if not already 64 characters long will be SHA256 with the user name |
|
655
|
|
|
* @param string $hash hash as generated from a SHA256 password |
|
656
|
|
|
* @param string $user user name only required if creating a SHA-256 password |
|
657
|
|
|
* @param bool $returnhash flag to determine if we are returning a hash suitable for the database |
|
658
|
|
|
* |
|
659
|
|
|
* @return bool|string |
|
660
|
|
|
* @package Authorization |
|
661
|
|
|
* |
|
662
|
|
|
*/ |
|
663
|
|
|
function validateLoginPassword(&$password, $hash, $user = '', $returnhash = false) |
|
664
|
10 |
|
{ |
|
665
|
|
|
// If the password is not 64 characters, lets make it a (SHA-256) |
|
666
|
10 |
|
if (strlen($password) !== 64) |
|
667
|
|
|
{ |
|
668
|
|
|
$password = hash('sha256', Util::strtolower($user) . un_htmlspecialchars($password)); |
|
669
|
|
|
} |
|
670
|
10 |
|
|
|
671
|
|
|
// They need a password hash, something to save in the db? |
|
672
|
6 |
|
if ($returnhash) |
|
673
|
|
|
{ |
|
674
|
|
|
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]); |
|
675
|
|
|
} |
|
676
|
4 |
|
|
|
677
|
|
|
// Doing a password check? |
|
678
|
|
|
return password_verify($password, $hash); |
|
679
|
|
|
} |
|
680
|
|
|
|
|
681
|
|
|
/** |
|
682
|
|
|
* Quickly find out what moderation authority this user has |
|
683
|
|
|
* |
|
684
|
|
|
* What it does: |
|
685
|
|
|
* |
|
686
|
|
|
* - builds the moderator, group and board level querys for the user |
|
687
|
|
|
* - stores the information on the current users moderation powers in User::$info->mod_cache and $_SESSION['mc'] |
|
688
|
|
|
* |
|
689
|
|
|
* @package Authorization |
|
690
|
|
|
*/ |
|
691
|
1 |
|
function rebuildModCache() |
|
692
|
|
|
{ |
|
693
|
|
|
$db = database(); |
|
694
|
1 |
|
|
|
695
|
|
|
// What groups can they moderate? |
|
696
|
1 |
|
$group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1'; |
|
697
|
|
|
|
|
698
|
1 |
|
if ($group_query === '0=1') |
|
699
|
|
|
{ |
|
700
|
|
|
$groups = $db->fetchQuery(' |
|
701
|
|
|
SELECT |
|
702
|
|
|
id_group |
|
703
|
|
|
FROM {db_prefix}group_moderators |
|
704
|
1 |
|
WHERE id_member = {int:current_member}', |
|
705
|
|
|
[ |
|
706
|
1 |
|
'current_member' => User::$info->id, |
|
|
|
|
|
|
707
|
|
|
] |
|
708
|
|
|
)->fetch_callback( |
|
709
|
1 |
|
function ($row) { |
|
710
|
|
|
return $row['id_group']; |
|
711
|
|
|
} |
|
712
|
1 |
|
); |
|
713
|
|
|
|
|
714
|
|
|
$group_query = empty($groups) ? '0=1' : 'id_group IN (' . implode(',', $groups) . ')'; |
|
715
|
|
|
} |
|
716
|
1 |
|
|
|
717
|
|
|
// Then, same again, just the boards this time! |
|
718
|
1 |
|
$board_query = allowedTo('moderate_forum') ? '1=1' : '0=1'; |
|
719
|
|
|
|
|
720
|
1 |
|
if ($board_query === '0=1') |
|
721
|
|
|
{ |
|
722
|
1 |
|
$boards = boardsAllowedTo('moderate_board'); |
|
723
|
|
|
|
|
724
|
|
|
$board_query = empty($boards) ? '0=1' : 'id_board IN (' . implode(',', $boards) . ')'; |
|
725
|
|
|
} |
|
726
|
1 |
|
|
|
727
|
1 |
|
// What boards are they the moderator of? |
|
728
|
|
|
$boards_mod = []; |
|
729
|
|
|
if (User::$info->is_guest === false) |
|
|
|
|
|
|
730
|
|
|
{ |
|
731
|
|
|
require_once(SUBSDIR . '/Boards.subs.php'); |
|
732
|
|
|
$boards_mod = boardsModerated(User::$info->id); |
|
733
|
1 |
|
} |
|
734
|
|
|
|
|
735
|
1 |
|
$mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')'; |
|
736
|
1 |
|
|
|
737
|
|
|
$_SESSION['mc'] = [ |
|
738
|
1 |
|
'time' => time(), |
|
739
|
|
|
// This looks a bit funny but protects against the login redirect. |
|
740
|
1 |
|
'id' => User::$info->id && User::$info->name ? User::$info->id : 0, |
|
|
|
|
|
|
741
|
1 |
|
// If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php. |
|
742
|
1 |
|
'gq' => $group_query, |
|
743
|
1 |
|
'bq' => $board_query, |
|
744
|
1 |
|
'ap' => boardsAllowedTo('approve_posts'), |
|
745
|
|
|
'mb' => $boards_mod, |
|
746
|
1 |
|
'mq' => $mod_query, |
|
747
|
|
|
]; |
|
748
|
1 |
|
call_integration_hook('integrate_mod_cache'); |
|
749
|
|
|
|
|
750
|
|
|
User::$info->mod_cache = $_SESSION['mc']; |
|
|
|
|
|
|
751
|
1 |
|
|
|
752
|
1 |
|
// Might as well clean up some tokens while we are at it. |
|
753
|
|
|
cleanTokens(); |
|
754
|
|
|
} |
|
755
|
|
|
|
|
756
|
|
|
/** |
|
757
|
|
|
* The same thing as setcookie but allows for integration hook |
|
758
|
|
|
* |
|
759
|
|
|
* @param string $name |
|
760
|
|
|
* @param string $value = '' |
|
761
|
|
|
* @param int $expire = 0 |
|
762
|
|
|
* @param string $path = '' |
|
763
|
|
|
* @param string $domain = '' |
|
764
|
|
|
* @param bool|null $secure = false |
|
765
|
|
|
* @param bool|null $httponly = null |
|
766
|
|
|
* @param string|null $samesite = null |
|
767
|
|
|
* |
|
768
|
|
|
* @return bool |
|
769
|
|
|
* @package Authorization |
|
770
|
|
|
* |
|
771
|
|
|
*/ |
|
772
|
|
|
function elk_setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = null, $httponly = null, $samesite = null) |
|
773
|
|
|
{ |
|
774
|
|
|
global $modSettings; |
|
775
|
|
|
|
|
776
|
|
|
// In case a customization wants to override the default settings |
|
777
|
|
|
if ($httponly === null) |
|
778
|
|
|
{ |
|
779
|
|
|
$httponly = !empty($modSettings['httponlyCookies']); |
|
780
|
|
|
} |
|
781
|
|
|
|
|
782
|
|
|
if ($secure === null) |
|
783
|
|
|
{ |
|
784
|
|
|
$secure = !empty($modSettings['secureCookies']); |
|
785
|
|
|
} |
|
786
|
|
|
|
|
787
|
|
|
// Default value in modern browsers is Lax |
|
788
|
|
|
// @todo admin panel setting? |
|
789
|
|
|
$samesite = empty($samesite) ? 'Lax' : $samesite; |
|
790
|
|
|
|
|
791
|
|
|
// Using SameSite=None requires Secure attribute in latest browser versions. |
|
792
|
|
|
$samesite = (!$secure && $samesite === 'None') ? 'Lax' : $samesite; |
|
793
|
|
|
|
|
794
|
|
|
// Intercept cookie? |
|
795
|
|
|
call_integration_hook('integrate_cookie', [$name, $value, $expire, $path, $domain, $secure, $httponly, $samesite]); |
|
796
|
|
|
|
|
797
|
|
|
if (PHP_VERSION_ID < 70300) |
|
798
|
|
|
{ |
|
799
|
|
|
return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); |
|
800
|
|
|
} |
|
801
|
|
|
|
|
802
|
|
|
return setcookie($name, $value, [ |
|
803
|
|
|
'expires' => $expire, |
|
804
|
|
|
'path' => $path, |
|
805
|
|
|
'domain' => $domain, |
|
806
|
|
|
'secure' => $secure, |
|
807
|
|
|
'httponly' => $httponly, |
|
808
|
|
|
'samesite' => $samesite]); |
|
809
|
|
|
} |
|
810
|
|
|
|
|
811
|
|
|
/** |
|
812
|
|
|
* This functions determines whether this is the first login of the given user. |
|
813
|
|
|
* |
|
814
|
|
|
* @param int $id_member the id of the member to check for |
|
815
|
|
|
* @return bool |
|
816
|
|
|
* @deprecated replaced by \ElkArte\User::$info->isFirstLogin() |
|
817
|
|
|
* |
|
818
|
|
|
* @package Authorization |
|
819
|
|
|
* |
|
820
|
|
|
*/ |
|
821
|
|
|
function isFirstLogin($id_member) |
|
822
|
|
|
{ |
|
823
|
|
|
// First login? |
|
824
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
|
825
|
|
|
$member = getBasicMemberData($id_member, ['moderation' => true]); |
|
826
|
|
|
|
|
827
|
|
|
return !empty($member) && $member['last_login'] == 0; |
|
828
|
|
|
} |
|
829
|
|
|
|
|
830
|
|
|
/** |
|
831
|
|
|
* Search for a member by given criteria |
|
832
|
|
|
* |
|
833
|
|
|
* @param string $where |
|
834
|
|
|
* @param array $where_params array of values to used in the where statement |
|
835
|
|
|
* @param bool $fatal |
|
836
|
|
|
* |
|
837
|
|
|
* @return array|bool array of members data or false on failure |
|
838
|
|
|
* @throws \ElkArte\Exceptions\Exception no_user_with_email |
|
839
|
|
|
* @package Authorization |
|
840
|
|
|
* |
|
841
|
|
|
*/ |
|
842
|
|
|
function findUser($where, $where_params, $fatal = true) |
|
843
|
|
|
{ |
|
844
|
|
|
$db = database(); |
|
845
|
|
|
|
|
846
|
|
|
// Find the user! |
|
847
|
|
|
$request = $db->fetchQuery(' |
|
848
|
|
|
SELECT |
|
849
|
|
|
id_member, real_name, member_name, email_address, is_activated, validation_code, |
|
850
|
|
|
lngfile, secret_question, passwd |
|
851
|
|
|
FROM {db_prefix}members |
|
852
|
|
|
WHERE ' . $where . ' |
|
853
|
|
|
LIMIT 1', |
|
854
|
|
|
array_merge($where_params, []) |
|
855
|
|
|
); |
|
856
|
|
|
|
|
857
|
|
|
// Maybe email? |
|
858
|
|
|
if ($request->num_rows() === 0 && empty($_REQUEST['uid']) && isset($where_params['email_address'])) |
|
859
|
|
|
{ |
|
860
|
|
|
$request->free_result(); |
|
861
|
|
|
|
|
862
|
|
|
$request = $db->fetchQuery(' |
|
863
|
|
|
SELECT |
|
864
|
|
|
id_member, real_name, member_name, email_address, is_activated, validation_code, |
|
865
|
|
|
lngfile, secret_question |
|
866
|
|
|
FROM {db_prefix}members |
|
867
|
|
|
WHERE email_address = {string:email_address} |
|
868
|
|
|
LIMIT 1', |
|
869
|
|
|
array_merge($where_params, []) |
|
870
|
|
|
); |
|
871
|
|
|
if ($request->num_rows() === 0) |
|
872
|
|
|
{ |
|
873
|
|
|
if ($fatal) |
|
874
|
|
|
{ |
|
875
|
|
|
throw new \ElkArte\Exceptions\Exception('no_user_with_email', false); |
|
876
|
10 |
|
} |
|
877
|
|
|
|
|
878
|
10 |
|
return false; |
|
879
|
|
|
} |
|
880
|
|
|
} |
|
881
|
|
|
|
|
882
|
10 |
|
$member = $request->fetch_assoc(); |
|
883
|
10 |
|
$member['id_member'] = (int) $member['id_member']; |
|
884
|
|
|
$member['is_activated'] = (int) $member['is_activated']; |
|
885
|
|
|
|
|
886
|
10 |
|
$request->free_result(); |
|
887
|
10 |
|
|
|
888
|
|
|
return $member; |
|
889
|
|
|
} |
|
890
|
|
|
|
|
891
|
10 |
|
/** |
|
892
|
10 |
|
* Find users by their email address. |
|
893
|
|
|
* |
|
894
|
10 |
|
* @param string $email |
|
895
|
|
|
* @param string|null $username |
|
896
|
|
|
* @return false|int on failure, int of member on success |
|
897
|
|
|
* @package Authorization |
|
898
|
|
|
*/ |
|
899
|
|
|
function userByEmail($email, $username = null) |
|
900
|
|
|
{ |
|
901
|
|
|
$db = database(); |
|
902
|
|
|
|
|
903
|
|
|
$return = false; |
|
904
|
|
|
$db->fetchQuery(' |
|
905
|
|
|
SELECT |
|
906
|
|
|
id_member |
|
907
|
|
|
FROM {db_prefix}members |
|
908
|
1 |
|
WHERE email_address = {string:email_address}' . ($username === null ? '' : ' |
|
909
|
|
|
OR email_address = {string:username}') . ' |
|
910
|
1 |
|
LIMIT 1', |
|
911
|
|
|
[ |
|
912
|
|
|
'email_address' => $email, |
|
913
|
|
|
'username' => $username, |
|
914
|
|
|
] |
|
915
|
|
|
)->fetch_callback( |
|
916
|
|
|
function ($row) use (&$return) { |
|
917
|
|
|
$return = (int) $row['id_member']; |
|
918
|
|
|
} |
|
919
|
|
|
); |
|
920
|
|
|
|
|
921
|
|
|
return $return; |
|
922
|
|
|
} |
|
923
|
|
|
|
|
924
|
6 |
|
/** |
|
925
|
|
|
* Generate a random validation code. |
|
926
|
6 |
|
* |
|
927
|
|
|
* @param int $length the number of characters to return |
|
928
|
2 |
|
* |
|
929
|
|
|
* @return string |
|
930
|
|
|
* @package Authorization |
|
931
|
|
|
* |
|
932
|
|
|
*/ |
|
933
|
|
|
function generateValidationCode($length = 10) |
|
934
|
|
|
{ |
|
935
|
|
|
$tokenizer = new TokenHash(); |
|
936
|
2 |
|
|
|
937
|
|
|
return $tokenizer->generate_hash((int) $length); |
|
938
|
|
|
} |
|
939
|
|
|
|
|
940
|
|
|
/** |
|
941
|
|
|
* This function loads many settings of a user given by name or email. |
|
942
|
|
|
* |
|
943
|
4 |
|
* @param string $name |
|
944
|
|
|
* @param bool $is_id if true it treats $name as a member ID and try to load the data for that ID |
|
945
|
|
|
* @return array|false false if nothing is found |
|
946
|
|
|
* @package Authorization |
|
947
|
|
|
*/ |
|
948
|
|
|
function loadExistingMember($name, $is_id = false) |
|
949
|
|
|
{ |
|
950
|
|
|
$db = database(); |
|
951
|
4 |
|
|
|
952
|
|
|
if ($is_id) |
|
953
|
|
|
{ |
|
954
|
|
|
$request = $db->fetchQuery(' |
|
955
|
4 |
|
SELECT |
|
956
|
|
|
passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, |
|
957
|
|
|
passwd_flood, otp_secret, enable_otp, otp_used |
|
958
|
|
|
FROM {db_prefix}members |
|
959
|
|
|
WHERE id_member = {int:id_member} |
|
960
|
|
|
LIMIT 1', |
|
961
|
|
|
[ |
|
962
|
|
|
'id_member' => (int) $name, |
|
963
|
|
|
] |
|
964
|
|
|
); |
|
965
|
|
|
} |
|
966
|
|
|
else |
|
967
|
|
|
{ |
|
968
|
|
|
// Try to find the user, assuming a member_name was passed... |
|
969
|
|
|
$request = $db->fetchQuery(' |
|
970
|
|
|
SELECT |
|
971
|
|
|
passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, |
|
972
|
|
|
passwd_flood, otp_secret, enable_otp, otp_used |
|
973
|
|
|
FROM {db_prefix}members |
|
974
|
6 |
|
WHERE {column_case_insensitive:member_name} = {string_case_insensitive:user_name} |
|
975
|
|
|
LIMIT 1', |
|
976
|
2 |
|
[ |
|
977
|
|
|
'user_name' => $name, |
|
978
|
|
|
] |
|
979
|
|
|
); |
|
980
|
4 |
|
// Didn't work. Try it as an email address. |
|
981
|
4 |
|
if ($request->num_rows() === 0 && str_contains($name, '@')) |
|
982
|
|
|
{ |
|
983
|
|
|
$request->free_result(); |
|
984
|
6 |
|
|
|
985
|
|
|
$request = $db->fetchQuery(' |
|
986
|
6 |
|
SELECT |
|
987
|
|
|
passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, |
|
988
|
|
|
passwd_flood, otp_secret, enable_otp, otp_used |
|
989
|
|
|
FROM {db_prefix}members |
|
990
|
|
|
WHERE email_address = {string:user_name} |
|
991
|
|
|
LIMIT 1', |
|
992
|
|
|
[ |
|
993
|
|
|
'user_name' => $name, |
|
994
|
|
|
] |
|
995
|
|
|
); |
|
996
|
|
|
} |
|
997
|
|
|
} |
|
998
|
|
|
|
|
999
|
|
|
// Nothing? Ah the horror... |
|
1000
|
|
|
if ($request->num_rows() === 0) |
|
1001
|
|
|
{ |
|
1002
|
|
|
$user_auth_data = false; |
|
1003
|
|
|
} |
|
1004
|
|
|
else |
|
1005
|
|
|
{ |
|
1006
|
|
|
$user_auth_data = $request->fetch_assoc(); |
|
1007
|
|
|
$user_auth_data['id_member'] = (int) $user_auth_data['id_member']; |
|
1008
|
|
|
} |
|
1009
|
|
|
|
|
1010
|
|
|
$request->free_result(); |
|
1011
|
|
|
|
|
1012
|
|
|
return $user_auth_data; |
|
1013
|
|
|
} |
|
1014
|
|
|
|