diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index 2a1b291..b285f75 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -156,6 +156,20 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { // First stage is a confirmation form, then login if ($action == 'login') { + if (variable_get('user_failed_login_identifier_uid_only', FALSE)) { + // Clear flood events based on the uid only if configured. + $identifier = $account->uid; + } + else { + // The default identifier is a combination of uid and IP address. + $identifier = $account->uid . '-' . ip_address(); + } + // Only clear the user specific flood events. We cannot clear the more + // broad IP address flood events because that would open a + // vulnerability where an attacker with a valid account could use that + // to brute force other accounts. + flood_clear_event('failed_login_attempt_user', $identifier); + // Set the new user. $user = $account; // user_login_finalize() also updates the login timestamp of the diff --git a/modules/user/user.test b/modules/user/user.test index 0875e0a..188f546 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -354,16 +354,23 @@ class UserLoginTestCase extends DrupalWebTestCase { // A login with the correct password should also result in a flood error // message. $this->assertFailedLogin($user1, 'ip'); + + // A password reset does not clear the global IP flood control. + $this->resetUserPassword($user1); + $this->drupalLogout(); + $this->assertFailedLogin($user1, 'ip'); } /** * Test the per-user login flood control. */ function testPerUserLoginFloodControl() { + $user_limit = 3; + // Set a high global limit out so that it is not relevant in the test. variable_set('user_failed_login_ip_limit', 4000); // Set the per-user login limit. - variable_set('user_failed_login_user_limit', 3); + variable_set('user_failed_login_user_limit', $user_limit); $user1 = $this->drupalCreateUser(array()); $incorrect_user1 = clone $user1; @@ -393,6 +400,13 @@ class UserLoginTestCase extends DrupalWebTestCase { // Try one more attempt for user 1, it should be rejected, even if the // correct password has been used. $this->assertFailedLogin($user1, 'user'); + + // Try to login as user 1 after resetting the passowrd, it should be + // successful. + $this->resetUserPassword($user1); + $this->drupalLogout(); + $this->drupalLogin($user1); + $this->assertNoRaw('There have been more than ' . $user_limit . ' failed login attempts for this account.'); } /** @@ -449,6 +463,26 @@ class UserLoginTestCase extends DrupalWebTestCase { $this->assertText(t('Sorry, unrecognized username or password. Have you forgotten your password?')); } } + + /** + * Resets the user password and logs the user in. + * + * @param object $user + * The account to reset the password for. + */ + protected function resetUserPassword($user) { + $this->drupalGet('user/password'); + $edit['name'] = $user->name; + $this->drupalPost(NULL, $edit, 'E-mail new password'); + $emails = $this->drupalGetMails(); + $email = end($emails); + $urls = array(); + preg_match('#.+user/reset/.+#', $email['body'], $urls); + $resetURL = $urls[0]; + $this->drupalGet($resetURL); + $this->drupalPost(NULL, NULL, 'Log in'); + } + } /**