diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php
index be15fac..495edaa 100644
--- a/core/modules/user/src/Controller/UserController.php
+++ b/core/modules/user/src/Controller/UserController.php
@@ -119,12 +119,11 @@ public function resetPass(Request $request, $uid, $timestamp, $hash) {
// A different user is already logged in on the computer.
else {
/** @var \Drupal\user\UserInterface $reset_link_user */
- if ($reset_link_user = $this->userStorage->load($uid)) {
+ if ($this->validPassResetHash($uid, $timestamp, $hash) && $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 log out and try using the link again.',
['%other_user' => $account->getUsername(), '%resetting_user' => $reset_link_user->getUsername(), ':logout' => $this->url('user.logout')]), 'warning');
}
else {
- // Invalid one-time link specifies an unknown user.
drupal_set_message($this->t('The one-time login link you clicked is invalid.'), 'error');
}
return $this->redirect('');
@@ -170,10 +169,35 @@ public function getResetPassForm(Request $request, $uid) {
/** @var \Drupal\user\UserInterface $user */
$user = $this->userStorage->load($uid);
- if ($user === NULL || !$user->isActive()) {
- // 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();
+
+ // Check whether the password reset URL is valid.
+ if (!$this->validPassResetHash($uid, $timestamp, $hash)) {
+ $current = REQUEST_TIME;
+ // Time out, in seconds, until login URL expires.
+ $timeout = $this->config('user.settings')->get('password_reset_timeout');
+ $current_user = \Drupal::currentUser();
+ if ($current_user->isAuthenticated() && $user != $current_user) {
+ // User is logged in - redirect to My Account.
+ 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.'), 'error');
+ throw new AccessDeniedHttpException();
+ }
+ elseif ($user->getLastLoginTime() && $current - $timestamp > $timeout) {
+ // No time out for first time login.
+ 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.'), 'error');
+ return $this->redirect('user.pass');
+ }
+ // Verify that the user exists and is active.
+ elseif (!$user->isActive()) {
+ // Blocked or invalid user ID, so deny access. The parameters will be in
+ // the watchdog's URL for the administrator to check.
+ drupal_set_message($this->t('The one-time login link you clicked is invalid.'), 'error');
+ return $this->redirect('user.pass');
+ }
+ else {
+ // Redirect to the password reset form.
+ 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.'), 'error');
+ return $this->redirect('user.pass');
+ }
}
// Time out, in seconds, until login URL expires.
@@ -203,44 +227,55 @@ public function getResetPassForm(Request $request, $uid) {
*/
public function resetPassLogin($uid, $timestamp, $hash) {
// The current user is not logged in, so check the parameters.
- $current = REQUEST_TIME;
/** @var \Drupal\user\UserInterface $user */
$user = $this->userStorage->load($uid);
- // Verify that the user exists and is active.
if ($user === NULL || !$user->isActive()) {
- // 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();
- }
-
- // Time out, in seconds, until login URL expires.
- $timeout = $this->config('user.settings')->get('password_reset_timeout');
- // 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.'), 'error');
return $this->redirect('user.pass');
}
- elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) {
- user_login_finalize($user);
- $this->logger->notice('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]);
- drupal_set_message($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
- // Let the user's password be changed without the current password
- // check.
- $token = Crypt::randomBytesBase64(55);
- $_SESSION['pass_reset_' . $user->id()] = $token;
- return $this->redirect(
- 'entity.user.edit_form',
- ['user' => $user->id()],
- [
- 'query' => ['pass-reset-token' => $token],
- 'absolute' => TRUE,
- ]
- );
- }
+
+ user_login_finalize($user);
+ $this->logger->notice('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]);
+ drupal_set_message($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
+ // Let the user's password be changed without the current password
+ // check.
+ $token = Crypt::randomBytesBase64(55);
+ $_SESSION['pass_reset_' . $user->id()] = $token;
+ return $this->redirect(
+ 'entity.user.edit_form',
+ ['user' => $user->id()],
+ [
+ 'query' => ['pass-reset-token' => $token],
+ 'absolute' => TRUE,
+ ]
+ );
+ }
- 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.'), 'error');
- return $this->redirect('user.pass');
+ /**
+ * Validate a password reset request.
+ *
+ * @param int $uid
+ * User ID of the user requesting reset.
+ * @param int $timestamp
+ * The current timestamp.
+ * @param string $hash
+ * Login link hash.
+ *
+ * @return bool
+ * Returns TRUE if the password reset request is valid.
+ *
+ * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ * If $uid is for a blocked user or invalid user ID.
+ */
+ protected function validPassResetHash($uid, $timestamp, $hash) {
+ /** @var \Drupal\user\UserInterface $user */
+ $user = $this->userStorage->load($uid);
+ $current = \Drupal::time()->getRequestTime();
+
+ if ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp)) && $user->isActive()) {
+ return TRUE;
+ }
+ return FALSE;
}
/**
diff --git a/core/modules/user/src/Tests/UserPasswordResetTest.php b/core/modules/user/src/Tests/UserPasswordResetTest.php
index 528d38b..cdf6007 100644
--- a/core/modules/user/src/Tests/UserPasswordResetTest.php
+++ b/core/modules/user/src/Tests/UserPasswordResetTest.php
@@ -135,7 +135,7 @@ public function testUserPasswordReset() {
// Log out, and try to log in again using the same one-time link.
$this->drupalLogout();
$this->drupalGet($resetURL);
- $this->drupalPostForm(NULL, NULL, t('Log in'));
+ $this->assertUrl(Url::fromRoute('user.pass'));
$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.
@@ -160,15 +160,15 @@ public function testUserPasswordReset() {
$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->drupalPostForm(NULL, NULL, t('Log in'));
- $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.');
+ $this->assertUrl(Url::fromRoute('user.pass'));
+ $this->assertText(t('You have tried to use a one-time login link that has expired.'), '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);
+ $this->assertText(t('The one-time login link you clicked is invalid.'), 'Blocked password reset request rejected.');
// Verify a blocked user can not request a new password.
$this->drupalGet('user/password');
@@ -187,7 +187,7 @@ public function testUserPasswordReset() {
$this->account->setEmail("1" . $this->account->getEmail());
$this->account->save();
$this->drupalGet($old_email_reset_link);
- $this->drupalPostForm(NULL, NULL, t('Log in'));
+ $this->assertUrl(Url::fromRoute('user.pass'));
$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.');
// Verify a password reset link will automatically log a user when /login is
@@ -207,11 +207,11 @@ public function testUserPasswordReset() {
$blocked_account = $this->drupalCreateUser()->block();
$blocked_account->save();
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login');
- $this->assertResponse(403);
+ $this->assertText(t('The one-time login link you clicked is invalid.'), 'One-time link for blocked account is not valid.');
$blocked_account->delete();
$this->drupalGet("user/reset/" . $blocked_account->id() . "/$timestamp/" . user_pass_rehash($blocked_account, $timestamp) . '/login');
- $this->assertResponse(403);
+ $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 for deleted account is not valid.');
}
/**