/** * @see user_pass_reset() * @param $uid * @param $timestamp * @param $hashed_pass * @internal param $token * @return User */ public function getUsernameForHashedPassword($uid, $timestamp, $hashed_pass) { // Time out, in seconds, until login URL expires. Defaults to 24 hours = // 86400 seconds. $timeout = variable_get('user_password_reset_timeout', 86400); $current = REQUEST_TIME; // Some redundant checks for extra security ? $users = user_load_multiple(array($uid), array('status' => '1')); if ($timestamp <= $current && ($account = reset($users))) { // No time out for first time login. if ($account->login && $current - $timestamp > $timeout) { } elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { return $account->name; } } }
/** * Confirms cancelling a user account via an email link. * * @param \Drupal\user\UserInterface $user * The user account. * @param int $timestamp * The timestamp. * @param string $hashed_pass * The hashed password. * * @return \Symfony\Component\HttpFoundation\RedirectResponse * A redirect response. */ public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass = '') { // Time out in seconds until cancel URL expires; 24 hours = 86400 seconds. $timeout = 86400; $current = REQUEST_TIME; // Basic validation of arguments. $account_data = $this->userData->get('user', $user->id()); if (isset($account_data['cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) { // Validate expiration and hashed password/login. if ($timestamp <= $current && $current - $timestamp < $timeout && $user->id() && $timestamp >= $user->getLastLoginTime() && Crypt::hashEquals($hashed_pass, user_pass_rehash($user, $timestamp))) { $edit = array('user_cancel_notify' => isset($account_data['cancel_notify']) ? $account_data['cancel_notify'] : $this->config('user.settings')->get('notify.status_canceled')); user_cancel($edit, $user->id(), $account_data['cancel_method']); // Since user_cancel() is not invoked via Form API, batch processing // needs to be invoked manually and should redirect to the front page // after completion. return batch_process(''); } else { drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error'); return $this->redirect('entity.user.cancel_form', ['user' => $user->id()], ['absolute' => TRUE]); } } throw new AccessDeniedHttpException(); }
/** * Delete account and remove all content. */ function testUserDelete() { $node_storage = $this->container->get('entity.manager')->getStorage('node'); $this->config('user.settings')->set('cancel_method', 'user_cancel_delete')->save(); \Drupal::service('module_installer')->install(array('comment')); $this->resetAll(); $this->addDefaultCommentField('node', 'page'); $user_storage = $this->container->get('entity.manager')->getStorage('user'); // Create a user. $account = $this->drupalCreateUser(array('cancel account', 'post comments', 'skip comment approval')); $this->drupalLogin($account); // Load a real user object. $user_storage->resetCache(array($account->id())); $account = $user_storage->load($account->id()); // Create a simple node. $node = $this->drupalCreateNode(array('uid' => $account->id())); // Create comment. $edit = array(); $edit['subject[0][value]'] = $this->randomMachineName(8); $edit['comment_body[0][value]'] = $this->randomMachineName(16); $this->drupalPostForm('comment/reply/node/' . $node->id() . '/comment', $edit, t('Preview')); $this->drupalPostForm(NULL, array(), t('Save')); $this->assertText(t('Your comment has been posted.')); $comments = entity_load_multiple_by_properties('comment', array('subject' => $edit['subject[0][value]'])); $comment = reset($comments); $this->assertTrue($comment->id(), 'Comment found.'); // Create a node with two revisions, the initial one belonging to the // cancelling user. $revision_node = $this->drupalCreateNode(array('uid' => $account->id())); $revision = $revision_node->getRevisionId(); $settings = get_object_vars($revision_node); $settings['revision'] = 1; $settings['uid'] = 1; // Set new/current revision to someone else. $revision_node = $this->drupalCreateNode($settings); // Attempt to cancel account. $this->drupalGet('user/' . $account->id() . '/edit'); $this->drupalPostForm(NULL, NULL, t('Cancel account')); $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.'); $this->assertText(t('Your account will be removed and all account information deleted. All of your content will also be deleted.'), 'Informs that all content will be deleted.'); // Confirm account cancellation. $timestamp = time(); $this->drupalPostForm(NULL, NULL, t('Cancel account')); $this->assertText(t('A confirmation request to cancel your account has been sent to your email address.'), 'Account cancellation request mailed message displayed.'); // Confirm account cancellation request. $this->drupalGet("user/" . $account->id() . "/cancel/confirm/{$timestamp}/" . user_pass_rehash($account, $timestamp)); $user_storage->resetCache(array($account->id())); $this->assertFalse($user_storage->load($account->id()), 'User is not found in the database.'); // Confirm that user's content has been deleted. $node_storage->resetCache(array($node->id())); $this->assertFalse($node_storage->load($node->id()), 'Node of the user has been deleted.'); $this->assertFalse(node_revision_load($revision), 'Node revision of the user has been deleted.'); $node_storage->resetCache(array($revision_node->id())); $this->assertTrue($node_storage->load($revision_node->id()), "Current revision of the user's node was not deleted."); \Drupal::entityManager()->getStorage('comment')->resetCache(array($comment->id())); $this->assertFalse(Comment::load($comment->id()), 'Comment of the user has been deleted.'); // Confirm that the confirmation message made it through to the end user. $this->assertRaw(t('%name has been deleted.', array('%name' => $account->getUsername())), "Confirmation message displayed to user."); }
/** * Tests password reset functionality. */ function testUserPasswordReset() { // Try to reset the password for an invalid account. $this->drupalGet('user/password'); $edit = array('name' => $this->randomMachineName(32)); $this->drupalPostForm(NULL, $edit, t('Email new password')); $this->assertText(t('Sorry, @name is not recognized as a username or an email address.', array('@name' => $edit['name'])), 'Validation error message shown when trying to request password for invalid account.'); $this->assertEqual(count($this->drupalGetMails(array('id' => 'user_password_reset'))), 0, 'No email was sent when requesting a password for an invalid account.'); // Reset the password by username via the password reset page. $edit['name'] = $this->account->getUsername(); $this->drupalPostForm(NULL, $edit, t('Email new password')); // Verify that the user was sent an email. $this->assertMail('to', $this->account->getEmail(), 'Password email sent to user.'); $subject = t('Replacement login information for @username at @site', array('@username' => $this->account->getUsername(), '@site' => \Drupal::config('system.site')->get('name'))); $this->assertMail('subject', $subject, 'Password reset email subject is correct.'); $resetURL = $this->getResetURL(); $this->drupalGet($resetURL); // Check the one-time login page. $this->assertText($this->account->getUsername(), 'One-time login page contains the correct username.'); $this->assertText(t('This login can be used only once.'), 'Found warning about one-time login.'); // Check successful login. $this->drupalPostForm(NULL, NULL, t('Log in')); $this->assertLink(t('Log out')); $this->assertTitle(t('@name | @site', array('@name' => $this->account->getUsername(), '@site' => \Drupal::config('system.site')->get('name'))), 'Logged in using password reset link.'); // Change the forgotten password. $password = user_password(); $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.'); // Verify that the password reset session has been destroyed. $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertText(t('Your current password is missing or incorrect; it\'s required to change the Password.'), 'Password needed to make profile changes.'); // Log out, and try to log in again using the same one-time link. $this->drupalLogout(); $this->drupalGet($resetURL); $this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.'); // Request a new password again, this time using the email address. $this->drupalGet('user/password'); // Count email messages before to compare with after. $before = count($this->drupalGetMails(array('id' => 'user_password_reset'))); $edit = array('name' => $this->account->getEmail()); $this->drupalPostForm(NULL, $edit, t('Email new password')); $this->assertTrue(count($this->drupalGetMails(array('id' => 'user_password_reset'))) === $before + 1, 'Email sent when requesting password reset using email address.'); // Create a password reset link as if the request time was 60 seconds older than the allowed limit. $timeout = \Drupal::config('user.settings')->get('password_reset_timeout'); $bogus_timestamp = REQUEST_TIME - $timeout - 60; $_uid = $this->account->id(); $this->drupalGet("user/reset/{$_uid}/{$bogus_timestamp}/" . user_pass_rehash($this->account->getPassword(), $bogus_timestamp, $this->account->getLastLoginTime())); $this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.'); // Create a user, block the account, and verify that a login link is denied. $timestamp = REQUEST_TIME - 1; $blocked_account = $this->drupalCreateUser()->block(); $blocked_account->save(); $this->drupalGet("user/reset/" . $blocked_account->id() . "/{$timestamp}/" . user_pass_rehash($blocked_account->getPassword(), $timestamp, $blocked_account->getLastLoginTime())); $this->assertResponse(403); }
/** * Returns the user password reset page. * * @param int $uid * UID of user requesting reset. * @param int $timestamp * The current timestamp. * @param string $hash * Login link hash. * * @return array|\Symfony\Component\HttpFoundation\RedirectResponse * The form structure or a redirect response. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * If the login link is for a blocked user or invalid user ID. */ public function resetPass($uid, $timestamp, $hash) { $account = $this->currentUser(); $config = $this->config('user.settings'); // When processing the one-time login link, we have to make sure that a user // isn't already logged in. if ($account->isAuthenticated()) { // The current user is already logged in. if ($account->id() == $uid) { drupal_set_message($this->t('You are logged in as %user. <a href="!user_edit">Change your password.</a>', array('%user' => $account->getUsername(), '!user_edit' => $this->url('user.edit', array('user' => $account->id()))))); } else { if ($reset_link_user = $this->userStorage->load($uid)) { drupal_set_message($this->t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please <a href="!logout">logout</a> and try using the link again.', array('%other_user' => $account->getUsername(), '%resetting_user' => $reset_link_user->getUsername(), '!logout' => $this->url('user.logout')))); } else { // Invalid one-time link specifies an unknown user. drupal_set_message($this->t('The one-time login link you clicked is invalid.')); } } return $this->redirect('<front>'); } else { // The current user is not logged in, so check the parameters. // Time out, in seconds, until login URL expires. $timeout = $config->get('password_reset_timeout'); $current = REQUEST_TIME; /* @var \Drupal\user\UserInterface $user */ $user = $this->userStorage->load($uid); // Verify that the user exists and is active. if ($user && $user->isActive()) { // No time out for first time login. if ($user->getLastLoginTime() && $current - $timestamp > $timeout) { drupal_set_message($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.')); return $this->redirect('user.pass'); } elseif ($user->isAuthenticated() && $timestamp >= $user->getLastLoginTime() && $timestamp <= $current && $hash === user_pass_rehash($user->getPassword(), $timestamp, $user->getLastLoginTime())) { $expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL; return $this->formBuilder()->getForm('Drupal\\user\\Form\\UserPasswordResetForm', $user, $expiration_date, $timestamp, $hash); } else { drupal_set_message($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.')); return $this->redirect('user.pass'); } } } // Blocked or invalid user ID, so deny access. The parameters will be in the // watchdog's URL for the administrator to check. throw new AccessDeniedHttpException(); }
/** * Login a user, resetting their password. * * Can be used if user is unverified and does not yet have a password. * * @param \Drupal\user\UserInterface $user * The user to login. */ protected function resetPassLogin($user) { $uid = $user->id(); $timestamp = REQUEST_TIME; $hash = user_pass_rehash($user, $timestamp); $this->drupalPostForm("/user/reset/{$uid}/{$timestamp}/{$hash}", array(), t('Log in')); }
/** * Tests password reset functionality. */ function testUserPasswordReset() { // Try to reset the password for an invalid account. $this->drupalGet('user/password'); $edit = array('name' => $this->randomMachineName(32)); $this->drupalPostForm(NULL, $edit, t('Submit')); $this->assertText(t('@name is not recognized as a username or an email address.', array('@name' => $edit['name'])), 'Validation error message shown when trying to request password for invalid account.'); $this->assertEqual(count($this->drupalGetMails(array('id' => 'user_password_reset'))), 0, 'No email was sent when requesting a password for an invalid account.'); // Reset the password by username via the password reset page. $edit['name'] = $this->account->getUsername(); $this->drupalPostForm(NULL, $edit, t('Submit')); // Verify that the user was sent an email. $this->assertMail('to', $this->account->getEmail(), 'Password email sent to user.'); $subject = t('Replacement login information for @username at @site', array('@username' => $this->account->getUsername(), '@site' => $this->config('system.site')->get('name'))); $this->assertMail('subject', $subject, 'Password reset email subject is correct.'); $resetURL = $this->getResetURL(); $this->drupalGet($resetURL); $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); // Ensure the password reset URL is not cached. $this->drupalGet($resetURL); $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); // Check the one-time login page. $this->assertText($this->account->getUsername(), 'One-time login page contains the correct username.'); $this->assertText(t('This login can be used only once.'), 'Found warning about one-time login.'); $this->assertTitle(t('Reset password | Drupal'), 'Page title is "Reset password".'); // Check successful login. $this->drupalPostForm(NULL, NULL, t('Log in')); $this->assertLink(t('Log out')); $this->assertTitle(t('@name | @site', array('@name' => $this->account->getUsername(), '@site' => $this->config('system.site')->get('name'))), 'Logged in using password reset link.'); // Make sure the ajax request from uploading a user picture does not // invalidate the reset token. $image = current($this->drupalGetTestFiles('image')); $edit = array('files[user_picture_0]' => drupal_realpath($image->uri)); $this->drupalPostAjaxForm(NULL, $edit, 'user_picture_0_upload_button'); // Change the forgotten password. $password = user_password(); $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.'); // Verify that the password reset session has been destroyed. $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertText(t('Your current password is missing or incorrect; it\'s required to change the Password.'), 'Password needed to make profile changes.'); // Log out, and try to log in again using the same one-time link. $this->drupalLogout(); $this->drupalGet($resetURL); $this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.'); // Request a new password again, this time using the email address. $this->drupalGet('user/password'); // Count email messages before to compare with after. $before = count($this->drupalGetMails(array('id' => 'user_password_reset'))); $edit = array('name' => $this->account->getEmail()); $this->drupalPostForm(NULL, $edit, t('Submit')); $this->assertTrue(count($this->drupalGetMails(array('id' => 'user_password_reset'))) === $before + 1, 'Email sent when requesting password reset using email address.'); // Visit the user edit page without pass-reset-token and make sure it does // not cause an error. $resetURL = $this->getResetURL(); $this->drupalGet($resetURL); $this->drupalPostForm(NULL, NULL, t('Log in')); $this->drupalGet('user/' . $this->account->id() . '/edit'); $this->assertNoText('Expected user_string to be a string, NULL given'); $this->drupalLogout(); // Create a password reset link as if the request time was 60 seconds older than the allowed limit. $timeout = $this->config('user.settings')->get('password_reset_timeout'); $bogus_timestamp = REQUEST_TIME - $timeout - 60; $_uid = $this->account->id(); $this->drupalGet("user/reset/{$_uid}/{$bogus_timestamp}/" . user_pass_rehash($this->account, $bogus_timestamp)); $this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.'); // Create a user, block the account, and verify that a login link is denied. $timestamp = REQUEST_TIME - 1; $blocked_account = $this->drupalCreateUser()->block(); $blocked_account->save(); $this->drupalGet("user/reset/" . $blocked_account->id() . "/{$timestamp}/" . user_pass_rehash($blocked_account, $timestamp)); $this->assertResponse(403); // Verify a blocked user can not request a new password. $this->drupalGet('user/password'); // Count email messages before to compare with after. $before = count($this->drupalGetMails(array('id' => 'user_password_reset'))); $edit = array('name' => $blocked_account->getUsername()); $this->drupalPostForm(NULL, $edit, t('Submit')); $this->assertRaw(t('%name is blocked or has not been activated yet.', array('%name' => $blocked_account->getUsername())), 'Notified user blocked accounts can not request a new password'); $this->assertTrue(count($this->drupalGetMails(array('id' => 'user_password_reset'))) === $before, 'No email was sent when requesting password reset for a blocked account'); // Verify a password reset link is invalidated when the user's email address changes. $this->drupalGet('user/password'); $edit = array('name' => $this->account->getUsername()); $this->drupalPostForm(NULL, $edit, t('Submit')); $old_email_reset_link = $this->getResetURL(); $this->account->setEmail("1" . $this->account->getEmail()); $this->account->save(); $this->drupalGet($old_email_reset_link); $this->assertText(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'One-time link is no longer valid.'); }
/** * Test user password reset while logged in. */ public function testUserPasswordResetLoggedIn() { $another_account = $this->drupalCreateUser(); $this->drupalLogin($another_account); $this->drupalGet('user/password'); $this->drupalPostForm(NULL, NULL, t('Submit')); // Click the reset URL while logged and change our password. $resetURL = $this->getResetURL(); // Log in as a different user. $this->drupalLogin($this->account); $this->drupalGet($resetURL); $this->assertRaw(new FormattableMarkup('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please <a href=":logout">log out</a> and try using the link again.', ['%other_user' => $this->account->getUsername(), '%resetting_user' => $another_account->getUsername(), ':logout' => Url::fromRoute('user.logout')->toString()])); $another_account->delete(); $this->drupalGet($resetURL); $this->assertText('The one-time login link you clicked is invalid.'); // Log in. $this->drupalLogin($this->account); // Reset the password by username via the password reset page. $this->drupalGet('user/password'); $this->drupalPostForm(NULL, NULL, t('Submit')); // Click the reset URL while logged and change our password. $resetURL = $this->getResetURL(); $this->drupalGet($resetURL); $this->drupalPostForm(NULL, NULL, t('Log in')); // Change the password. $password = user_password(); $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertText(t('The changes have been saved.'), 'Password changed.'); // Logged in users should not be able to access the user.reset.login or the // user.reset.form routes. $timestamp = REQUEST_TIME - 1; $this->drupalGet("user/reset/" . $this->account->id() . "/{$timestamp}/" . user_pass_rehash($this->account, $timestamp) . '/login'); $this->assertResponse(403); $this->drupalGet("user/reset/" . $this->account->id()); $this->assertResponse(403); }
/** * Prepare data for the \XLite\Controller\Customer\Login * * @param \stdClass $user Drupal user profile * @param array|null $edit Data from request * * @return array */ protected function getProfileDataLogin(\stdClass $user, $edit) { $data = $this->getProfileData($user, $edit, false); // On the "Reset password" page user can log in without entering a password. // It's the reason to introduce the "log in using secret token" approach list($result, $timestamp, $hash) = $this->isResetPasswordPage(); // Only start LC log in procedure after Drupal hash string is checked if ($result && user_pass_rehash($data['password'], $timestamp, $data['login_time']) === $hash) { $token = \XLite\Core\Converter::generateRandomToken(); // Save token in session and pass it to LC controller. Strings must match $data[\XLite\Controller\Customer\Login::SECURE_TOKEN] = $token; \XLite\Core\Auth::getInstance()->setSecureHash($token); } return $data; }
function os_poker_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action = NULL) { global $user; // Check if the user is already logged in. The back button is often the culprit here. if ($user->uid) { drupal_set_message(t('You have already used this one-time login link. It is not necessary to use this link to login anymore. You are already logged in.')); drupal_goto(); } else { // Time out, in seconds, until login URL expires. 24 hours = 86400 seconds. $timeout = 86400; $current = time(); // Some redundant checks for extra security ? if ($timestamp < $current && ($account = user_load(array('uid' => $uid, 'status' => 1)))) { // Deny one-time login to blocked accounts. if (drupal_is_denied('user', $account->name) || drupal_is_denied('mail', $account->mail)) { drupal_set_message(t('You have tried to use a one-time login for an account which has been blocked.'), 'error'); drupal_goto(); } // No time out for first time login. if ($account->login && $current - $timestamp > $timeout) { drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.')); drupal_goto('poker/forgot-password'); } else { if ($account->uid && $timestamp > $account->login && $timestamp < $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { // First stage is a confirmation form, then login if ($action == 'login') { watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp)); // Set the new user. $user = $account; // user_authenticate_finalize() also updates the login timestamp of the // user, which invalidates further use of the one-time login link. user_authenticate_finalize($form_state['values']); drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to login. !settings-page.', array('!settings-page' => l('Please change your password', 'poker/profile/settings', array('attributes' => array('onclick' => "(function(a){var url = a.href; tb_remove();setTimeout(function(){tb_show('',url, false)},201);})(this);return false;"), 'query' => array('height' => 442, 'width' => 603, 'TB_iframe' => 'true')))))); // drupal_goto('poker/profile/settings/'. $user->uid); drupal_goto('<front>'); } else { $form['message'] = array('#value' => t('<p>This is a one-time login for %user_name and will expire on %expiration_date.</p><p>Click on this button to login to the site and change your password.</p>', array('%user_name' => $account->name, '%expiration_date' => format_date($timestamp + $timeout)))); $form['help'] = array('#value' => '<p>' . t('This login can be used only once.') . '</p>'); $form['submit'] = array('#type' => 'submit', '#value' => t('Log in')); $form['#action'] = url("user/reset/{$uid}/{$timestamp}/{$hashed_pass}/login"); return $form; } } else { drupal_set_message(t('You have tried to use a one-time login link which has either been used or is no longer valid. Please request a new one using the form below.')); drupal_goto('poker/forgot-password'); } } } else { // Deny access, no more clues. // Everything will be in the watchdog's URL for the administrator to check. drupal_access_denied(); } } }