diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php
index 779cf90..9f484b1 100644
--- a/core/modules/user/src/Controller/UserController.php
+++ b/core/modules/user/src/Controller/UserController.php
@@ -222,6 +222,20 @@ public function resetPassLogin($uid, $timestamp, $hash) {
return $this->redirect('user.pass');
}
elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) {
+ $flood_config = $this->config('user.flood');
+ if ($flood_config->get('uid_only')) {
+ // Register flood events based on the uid only, so they apply for any
+ // IP address. This is the most secure option.
+ $identifier = $user->id();
+ }
+ else {
+ // The default identifier is a combination of uid and IP address. This
+ // is less secure but more resistant to denial-of-service attacks that
+ // could lock out all users with public user names.
+ $identifier = $user->id() . '-' . \Drupal::request()->getClientIP();
+ }
+ \Drupal::flood()->clear('user.failed_login_ip');
+ \Drupal::flood()->clear('user.failed_login_user', $identifier);
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.'));
diff --git a/core/modules/user/src/Tests/UserLoginTest.php b/core/modules/user/src/Tests/UserLoginTest.php
index af00c74..46a5c99 100644
--- a/core/modules/user/src/Tests/UserLoginTest.php
+++ b/core/modules/user/src/Tests/UserLoginTest.php
@@ -63,6 +63,11 @@ function testGlobalLoginFloodControl() {
// A login with the correct password should also result in a flood error
// message.
$this->assertFailedLogin($user1, 'ip');
+ $this->resetUserPassword($user1);
+ $this->drupalLogout();
+ // Try to login as user 1, it should be successful.
+ $this->drupalLogin($user1);
+ $this->assertNoRaw(t('Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', array(':url' => \Drupal::url('user.pass'))));
}
/**
@@ -103,6 +108,11 @@ function testPerUserLoginFloodControl() {
// Try one more attempt for user 1, it should be rejected, even if the
// correct password has been used.
$this->assertFailedLogin($user1, 'user');
+ $this->resetUserPassword($user1);
+ $this->drupalLogout();
+ // Try to login as user 1, it should be successful.
+ $this->drupalLogin($user1);
+ $this->assertNoRaw(\Drupal::translation()->formatPlural($this->config('user.flood')->get('user_limit'), 'There has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or request a new password.', 'There have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', array(':url' => \Drupal::url('user.pass'))));
}
/**
@@ -175,4 +185,23 @@ function assertFailedLogin($account, $flood_trigger = NULL) {
}
}
+ /**
+ * Reset user password.
+ *
+ * @param object $user
+ * A user object.
+ */
+ public function resetUserPassword($user) {
+ $this->drupalGet('user/password');
+ $edit['name'] = $user->getUsername();
+ $this->drupalPostForm(NULL, $edit, t('Submit'));
+ $_emails = $this->drupalGetMails();
+ $email = end($_emails);
+ $urls = array();
+ preg_match('#.+user/reset/.+#', $email['body'], $urls);
+ $resetURL = $urls[0];
+ $this->drupalGet($resetURL);
+ $this->drupalPostForm(NULL, NULL, t('Log in'));
+ }
+
}