elkarte /
Elkarte
| 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 = []; |
||
| 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, ['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 = [ |
||
| 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'] = [ |
||
| 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(): void |
||
| 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 += [ |
||
| 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(): void |
||
| 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, ['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'], [$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, ['validation_code' => '', 'passwd' => validateLoginPassword($sha_passwd, '', $member['member_name'], true), 'enable_otp' => 0]); |
||
| 273 | } |
||
| 274 | else |
||
| 275 | { |
||
| 276 | updateMemberData($member_id, ['validation_code' => '', 'passwd' => validateLoginPassword($sha_passwd, '', $member['member_name'], true)]); |
||
| 277 | } |
||
| 278 | |||
| 279 | call_integration_hook('integrate_reset_pass', [$member['member_name'], $member['member_name'], $this->_req->post->passwrd1]); |
||
| 280 | |||
| 281 | theme()->getTemplates()->load('Login'); |
||
| 282 | $context += [ |
||
| 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(): void |
||
| 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, ['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'], [$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'], ['passwd' => validateLoginPassword($sha_passwd, '', $member['member_name'], true)]); |
||
| 354 | |||
| 355 | call_integration_hook('integrate_reset_pass', [$member['member_name'], $member['member_name'], $this->_req->post->passwrd1]); |
||
| 356 | |||
| 357 | // Tell them it went fine. |
||
| 358 | theme()->getTemplates()->load('Login'); |
||
| 359 | $context += [ |
||
| 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'], ['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