1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Handles sending out of password reminders, as well as the answer / question |
||
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 | namespace ElkArte\Controller; |
||
18 | |||
19 | use ElkArte\AbstractController; |
||
20 | use ElkArte\Errors\Errors; |
||
0 ignored issues
–
show
|
|||
21 | use ElkArte\Exceptions\Exception; |
||
22 | use ElkArte\Helper\Util; |
||
23 | use ElkArte\Languages\Txt; |
||
24 | use ElkArte\Profile\Profile; |
||
25 | |||
26 | /** |
||
27 | * Handles sending out reminders, and checking the secret answer and question. |
||
28 | */ |
||
29 | class Reminder extends AbstractController |
||
30 | { |
||
31 | /** |
||
32 | * This is the pre-dispatch function |
||
33 | * |
||
34 | * @uses Profile language files and Reminder template |
||
35 | */ |
||
36 | public function pre_dispatch() |
||
37 | { |
||
38 | global $txt, $context; |
||
39 | |||
40 | Txt::load('Profile'); |
||
41 | theme()->getTemplates()->load('Reminder'); |
||
42 | |||
43 | $context['page_title'] = $txt['authentication_reminder']; |
||
44 | $context['robot_no_index'] = true; |
||
45 | } |
||
46 | |||
47 | /** |
||
48 | * Default action for reminder. |
||
49 | * |
||
50 | * @uses template_reminder() sub template |
||
51 | */ |
||
52 | public function action_index() |
||
53 | { |
||
54 | global $context; |
||
55 | |||
56 | $context['sub_template'] = 'reminder'; |
||
57 | |||
58 | // Nothing to do, the template will ask for an action to pick |
||
59 | createToken('remind'); |
||
60 | } |
||
61 | |||
62 | /** |
||
63 | * Pick a reminder type. |
||
64 | * |
||
65 | * Accessed by sa=picktype |
||
66 | */ |
||
67 | public function action_picktype() |
||
68 | { |
||
69 | global $context, $txt, $webmaster_email, $language, $modSettings; |
||
70 | |||
71 | // Security |
||
72 | checkSession(); |
||
73 | validateToken('remind'); |
||
74 | require_once(SUBSDIR . '/Auth.subs.php'); |
||
75 | |||
76 | // No where params just yet |
||
77 | $where_params = array(); |
||
78 | $where = ''; |
||
79 | |||
80 | // Coming with a known ID? |
||
81 | if (!empty($this->_req->post->uid)) |
||
82 | { |
||
83 | $where = 'id_member = {int:id_member}'; |
||
84 | $where_params['id_member'] = (int) $this->_req->post->uid; |
||
85 | } |
||
86 | elseif ($this->_req->getPost('user') !== '') |
||
87 | { |
||
88 | $where = 'member_name = {string:member_name}'; |
||
89 | $where_params['member_name'] = $this->_req->getPost('user', 'trim|\\ElkArte\\Helper\\Util::htmlspecialchars[ENT_QUOTES]'); |
||
90 | $where_params['email_address'] = $this->_req->getPost('user', 'trim|\\ElkArte\\Helper\\Util::htmlspecialchars[ENT_QUOTES]'); |
||
91 | } |
||
92 | |||
93 | // You must enter a username/email address. |
||
94 | if (empty($where)) |
||
95 | { |
||
96 | throw new Exception('username_no_exist', false); |
||
97 | } |
||
98 | |||
99 | // Make sure we are not being slammed |
||
100 | // Don't call this if you're coming from the "Choose a reminder type" page - otherwise you'll likely get an error |
||
101 | if (!isset($this->_req->post->reminder_type) || !in_array($this->_req->post->reminder_type, array('email', 'secret'))) |
||
102 | { |
||
103 | spamProtection('remind'); |
||
104 | } |
||
105 | |||
106 | // Find this member |
||
107 | $member = findUser($where, $where_params); |
||
108 | |||
109 | $context['account_type'] = 'password'; |
||
110 | |||
111 | // If the user isn't activated/approved, give them some feedback on what to do next. |
||
112 | if ($member['is_activated'] != 1) |
||
113 | { |
||
114 | // Awaiting approval... |
||
115 | if (trim($member['validation_code']) === '') |
||
116 | { |
||
117 | throw new Exception($txt['registration_not_approved'] . ' <a class="linkbutton" href="' . getUrl('action', ['action' => 'register', 'sa' => 'activate', 'user' => $this->_req->post->user]) . '">' . $txt['here'] . '</a>.', false); |
||
118 | } |
||
119 | |||
120 | throw new Exception($txt['registration_not_activated'] . ' <a class="linkbutton" href="' . getUrl('action', ['action' => 'register', 'sa' => 'activate', 'user' => $this->_req->post->user]) . '">' . $txt['here'] . '</a>.', false); |
||
121 | } |
||
122 | |||
123 | // You can't get emailed if you have no email address. |
||
124 | $member['email_address'] = trim($member['email_address']); |
||
125 | if ($member['email_address'] === '') |
||
126 | { |
||
127 | throw new Exception($txt['no_reminder_email'] . '<br />' . $txt['send_email'] . ' <a href="mailto:' . $webmaster_email . '">webmaster</a> ' . $txt['to_ask_password'] . '.'); |
||
128 | } |
||
129 | |||
130 | // If they have no secret question then they can only get emailed the item, or they are requesting the email, send them an email. |
||
131 | if (empty($member['secret_question']) || ($this->_req->getPost('reminder_type') === 'email')) |
||
132 | { |
||
133 | // Randomly generate a new password, with only alpha numeric characters that is a max length of 14 chars. |
||
134 | $password = generateValidationCode(14); |
||
135 | require_once(SUBSDIR . '/Mail.subs.php'); |
||
136 | $replacements = array( |
||
137 | 'REALNAME' => $member['real_name'], |
||
138 | 'REMINDLINK' => getUrl('action', ['action' => 'reminder', 'sa' => 'setpassword', 'u' => $member['id_member'], 'code' => $password]), |
||
139 | 'IP' => $this->user->ip, |
||
140 | 'MEMBERNAME' => $member['member_name'], |
||
141 | ); |
||
142 | |||
143 | // Email them their new password |
||
144 | $emaildata = loadEmailTemplate('forgot_' . $context['account_type'], $replacements, empty($member['lngfile']) || empty($modSettings['userLanguage']) ? $language : $member['lngfile']); |
||
145 | sendmail($member['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 1); |
||
146 | |||
147 | // Set up the template. |
||
148 | $context['description'] = $txt['reminder_sent']; |
||
149 | $context['sub_template'] = 'sent'; |
||
150 | |||
151 | // Don't really. |
||
152 | return null; |
||
153 | } |
||
154 | |||
155 | // Otherwise are ready to answer the question? |
||
156 | if (isset($this->_req->post->reminder_type) && $this->_req->post->reminder_type === 'secret') |
||
157 | { |
||
158 | return secretAnswerInput(); |
||
159 | } |
||
160 | |||
161 | // No we're here setup the context for template number 2! |
||
162 | createToken('remind'); |
||
163 | $context['sub_template'] = 'reminder_pick'; |
||
164 | $context['current_member'] = array( |
||
165 | 'id' => $member['id_member'], |
||
166 | 'name' => $member['member_name'], |
||
167 | ); |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Set your new password after you asked for a reset link |
||
172 | * |
||
173 | * sa=setpassword |
||
174 | */ |
||
175 | public function action_setpassword() |
||
176 | { |
||
177 | global $txt, $context; |
||
178 | |||
179 | Txt::load('Login'); |
||
180 | |||
181 | // You need a code! |
||
182 | if (!isset($this->_req->query->code)) |
||
183 | { |
||
184 | throw new Exception('no_access', false); |
||
185 | } |
||
186 | |||
187 | // Fill the context array. |
||
188 | $context += array( |
||
189 | 'page_title' => $txt['reminder_set_password'], |
||
190 | 'sub_template' => 'set_password', |
||
191 | 'code' => Util::htmlspecialchars($this->_req->query->code), |
||
192 | 'memID' => (int) $this->_req->query->u |
||
193 | ); |
||
194 | |||
195 | // Some extra js is needed |
||
196 | loadJavascriptFile('register.js'); |
||
197 | |||
198 | // Tokens! |
||
199 | createToken('remind-sp'); |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * Handle the password change. |
||
204 | * |
||
205 | * sa=setpassword2 |
||
206 | */ |
||
207 | public function action_setpassword2() |
||
208 | { |
||
209 | global $context, $txt; |
||
210 | |||
211 | checkSession(); |
||
212 | validateToken('remind-sp'); |
||
213 | |||
214 | if (empty($this->_req->post->u) || !isset($this->_req->post->passwrd1, $this->_req->post->passwrd2)) |
||
215 | { |
||
216 | throw new Exception('no_access', false); |
||
217 | } |
||
218 | |||
219 | if ($this->_req->post->passwrd1 !== $this->_req->post->passwrd2) |
||
220 | { |
||
221 | throw new Exception('passwords_dont_match', false); |
||
222 | } |
||
223 | |||
224 | if ($this->_req->post->passwrd1 === '') |
||
225 | { |
||
226 | throw new Exception('no_password', false); |
||
227 | } |
||
228 | |||
229 | $member_id = $this->_req->getPost('u', 'intval', -1); |
||
230 | $code = $this->_req->getPost('code', 'trim', ''); |
||
231 | |||
232 | Txt::load('Login'); |
||
233 | |||
234 | // Get the code as it should be from the database. |
||
235 | require_once(SUBSDIR . '/Members.subs.php'); |
||
236 | $member = getBasicMemberData($member_id, array('authentication' => true)); |
||
237 | |||
238 | // Does this user exist at all? Is he activated? Does he have a validation code? |
||
239 | if (empty($member) || $member['is_activated'] != 1 || $member['validation_code'] === '') |
||
240 | { |
||
241 | throw new Exception('invalid_userid', false); |
||
242 | } |
||
243 | |||
244 | // Is the password actually valid to the forums rules? |
||
245 | require_once(SUBSDIR . '/Auth.subs.php'); |
||
246 | $passwordError = validatePassword($this->_req->post->passwrd1, $member['member_name'], array($member['email_address'])); |
||
247 | |||
248 | // What - it's not? |
||
249 | if ($passwordError !== null) |
||
250 | { |
||
251 | throw new Exception('profile_error_password_' . $passwordError, false); |
||
252 | } |
||
253 | |||
254 | // Quit if this code is not right. |
||
255 | if (empty($code) || $member['validation_code'] !== substr(hash('sha256', $code), 0, 10)) |
||
256 | { |
||
257 | // Stop brute force attacks like this. |
||
258 | validatePasswordFlood($member_id, $member['passwd_flood'], false); |
||
259 | |||
260 | throw new Exception($txt['invalid_activation_code'], false); |
||
261 | } |
||
262 | |||
263 | // Just in case, flood control. |
||
264 | validatePasswordFlood($member_id, $member['passwd_flood'], true); |
||
265 | |||
266 | // User validated. Update the database! |
||
267 | require_once(SUBSDIR . '/Auth.subs.php'); |
||
268 | $sha_passwd = $this->_req->post->passwrd1; |
||
269 | require_once(SUBSDIR . '/Members.subs.php'); |
||
270 | if (isset($this->_req->post->otp)) |
||
271 | { |
||
272 | updateMemberData($member_id, array('validation_code' => '', 'passwd' => validateLoginPassword($sha_passwd, '', $member['member_name'], true), 'enable_otp' => 0)); |
||
273 | } |
||
274 | else |
||
275 | { |
||
276 | updateMemberData($member_id, array('validation_code' => '', 'passwd' => validateLoginPassword($sha_passwd, '', $member['member_name'], true))); |
||
277 | } |
||
278 | |||
279 | call_integration_hook('integrate_reset_pass', array($member['member_name'], $member['member_name'], $this->_req->post->passwrd1)); |
||
280 | |||
281 | theme()->getTemplates()->load('Login'); |
||
282 | $context += array( |
||
283 | 'page_title' => $txt['reminder_password_set'], |
||
284 | 'sub_template' => 'login', |
||
285 | 'default_username' => $member['member_name'], |
||
286 | 'default_password' => $this->_req->post->passwrd1, |
||
287 | 'never_expire' => false, |
||
288 | 'description' => $txt['reminder_password_set'] |
||
289 | ); |
||
290 | createToken('login'); |
||
291 | } |
||
292 | |||
293 | /** |
||
294 | * Verify the answer to the secret question. |
||
295 | * Accessed with sa=secret2 |
||
296 | */ |
||
297 | public function action_secret2() |
||
298 | { |
||
299 | global $txt, $context; |
||
300 | |||
301 | checkSession(); |
||
302 | validateToken('remind-sai'); |
||
303 | |||
304 | // Hacker? How did you get this far without an email or username? |
||
305 | if (empty($this->_req->post->uid)) |
||
306 | { |
||
307 | throw new Exception('username_no_exist', false); |
||
308 | } |
||
309 | |||
310 | Txt::load('Login'); |
||
311 | |||
312 | // Get the information from the database. |
||
313 | require_once(SUBSDIR . '/Members.subs.php'); |
||
314 | $member = getBasicMemberData((int) $this->_req->post->uid, array('authentication' => true)); |
||
315 | if (empty($member)) |
||
316 | { |
||
317 | throw new Exception('username_no_exist', false); |
||
318 | } |
||
319 | |||
320 | // Check if the secret answer is correct. |
||
321 | if ($member['secret_question'] === '' || $member['secret_answer'] === '' || md5($this->_req->post->secret_answer) !== $member['secret_answer']) |
||
322 | { |
||
323 | Errors::instance()->log_error(sprintf($txt['reminder_error'], $member['member_name']), 'user'); |
||
324 | throw new Exception('incorrect_answer', false); |
||
325 | } |
||
326 | |||
327 | // You can't use a blank one! |
||
328 | if (trim($this->_req->post->passwrd1) === '') |
||
329 | { |
||
330 | throw new Exception('no_password', false); |
||
331 | } |
||
332 | |||
333 | // They have to be the same too. |
||
334 | if ($this->_req->post->passwrd1 !== $this->_req->post->passwrd2) |
||
335 | { |
||
336 | throw new Exception('passwords_dont_match', false); |
||
337 | } |
||
338 | |||
339 | // Make sure they have a strong enough password. |
||
340 | require_once(SUBSDIR . '/Auth.subs.php'); |
||
341 | $passwordError = validatePassword($this->_req->post->passwrd1, $member['member_name'], array($member['email_address'])); |
||
342 | |||
343 | // Invalid? |
||
344 | if ($passwordError !== null) |
||
345 | { |
||
346 | throw new Exception('profile_error_password_' . $passwordError, false); |
||
347 | } |
||
348 | |||
349 | // Alright, so long as 'yer sure. |
||
350 | require_once(SUBSDIR . '/Auth.subs.php'); |
||
351 | $sha_passwd = $this->_req->post->passwrd1; |
||
352 | require_once(SUBSDIR . '/Members.subs.php'); |
||
353 | updateMemberData($member['id_member'], array('passwd' => validateLoginPassword($sha_passwd, '', $member['member_name'], true))); |
||
354 | |||
355 | call_integration_hook('integrate_reset_pass', array($member['member_name'], $member['member_name'], $this->_req->post->passwrd1)); |
||
356 | |||
357 | // Tell them it went fine. |
||
358 | theme()->getTemplates()->load('Login'); |
||
359 | $context += array( |
||
360 | 'page_title' => $txt['reminder_password_set'], |
||
361 | 'sub_template' => 'login', |
||
362 | 'default_username' => $member['member_name'], |
||
363 | 'default_password' => $this->_req->post->passwrd1, |
||
364 | 'never_expire' => false, |
||
365 | 'description' => $txt['reminder_password_set'] |
||
366 | ); |
||
367 | |||
368 | createToken('login'); |
||
369 | } |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * Get the secret answer. |
||
374 | */ |
||
375 | function secretAnswerInput() |
||
376 | { |
||
377 | global $context; |
||
378 | |||
379 | checkSession(); |
||
380 | |||
381 | // Strings for the register auto javascript clever stuffy wuffy. |
||
382 | Txt::load('Login'); |
||
383 | |||
384 | // Check they entered something... |
||
385 | if (empty($_POST['uid'])) |
||
386 | { |
||
387 | throw new Exception('username_no_exist', false); |
||
388 | } |
||
389 | |||
390 | // Get the stuff.... |
||
391 | require_once(SUBSDIR . '/Members.subs.php'); |
||
392 | $member = getBasicMemberData((int) $_POST['uid'], array('authentication' => true)); |
||
393 | if (empty($member)) |
||
394 | { |
||
395 | throw new Exception('username_no_exist', false); |
||
396 | } |
||
397 | |||
398 | $context['account_type'] = 'password'; |
||
399 | |||
400 | // If there is NO secret question - then throw an error. |
||
401 | if (trim($member['secret_question']) === '') |
||
402 | { |
||
403 | throw new Exception('registration_no_secret_question', false); |
||
404 | } |
||
405 | |||
406 | // Ask for the answer... |
||
407 | $context['remind_user'] = $member['id_member']; |
||
408 | $context['remind_type'] = ''; |
||
409 | $context['secret_question'] = $member['secret_question']; |
||
410 | $context['sub_template'] = 'ask'; |
||
411 | |||
412 | loadJavascriptFile('register.js'); |
||
413 | |||
414 | createToken('remind-sai'); |
||
415 | |||
416 | return true; |
||
417 | } |
||
418 |
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