Problem/Motivation

When the same mailbox requests to recover the password 5 times, it will prompt Password reset form was submitted with an unknown or inactive account: test@drupal.com.. But the user is actually locked. This problem needs to be debugged to find out, and will not directly prompt the user to be locked

Steps to reproduce

  1. install new drupal 9 latest
  2. login and goto /admin/people/create create user, username and email is test@drupal.com and set active
  3. logout and go to /user/password enter email test@drupal.com 5 times
  4. will now prompt: If test@drupal.com is a valid account, an email will be sent with instructions to reset your password.the truth is that this mailbox has been blocked

Proposed resolution

We should tell the user that this email is locked. Instead of other prompts

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

xiukun.zhou created an issue. See original summary.

xiukun.zhou’s picture

xiukun.zhou’s picture

avpaderno’s picture

Status: Active » Needs work
Issue tags: +Needs issue summary update

An issue summary needs to describe what the bug is and provide any information necessary to replicate the issue on a plain Drupal site.

Also, looking at the provided patch, this does not seem a bug report.

avpaderno’s picture

       if (!$this->flood->isAllowed('user.password_request_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
+        $form_state->setErrorByName('name', $this->t('Too many password recovery requests from your IP address. It is temporarily blocked. Try again later or contact the site administrator.'));
         return;
       }

user.password_request_user is not a limit per IP, but per user. The Too many password recovery requests from your IP address. message is wrong; it should say the user has done too many password recovery requests.

xiukun.zhou’s picture

thanks apaderno
i will update summary.

xiukun.zhou’s picture

Category: Bug report » Task
Issue summary: View changes
xiukun.zhou’s picture

Issue summary: View changes
xiukun.zhou’s picture

StatusFileSize
new886 bytes

fix message

cilefen’s picture

Title: Prompt user to be locked » Password reset form error makes no sense when the account is locked
xiukun.zhou’s picture

Status: Needs work » Needs review
dpi’s picture

Issue tags: +Needs tests

There is no concept of locking in core. Please update the issue to use 'blocked'/'blocking'/'active' where appropriate.

+++ b/core/modules/user/src/Form/UserPasswordForm.php
@@ -194,6 +194,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
+        $form_state->setErrorByName('name', $this->t('Too many password recovery requests from your account. It is temporarily blocked. Try again later or contact the site administrator.'));

Grammar nits.

Its not 'from' your account, it would be 'for', as the user is presumably not authenticated as this account.

And we do not know it is 'your' account, use 'this', as we do not know the user owns this account. This contrasts to the IP address language this message was likely based from, where we can roughly confirm it is 'your IP address'.

Needs tests.

xiukun.zhou’s picture

hi dpi
the flood check identifier is a user account id. so not ip address

// Register flood events based on the uid only, so they apply for any
// IP address. This allows them to be cleared on successful reset (from
// any IP).
smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

Was tagged for issue summary update and tests which still need to happen.

For the issue summary if stuck would recommend using the default template for issues.

xiukun.zhou’s picture

StatusFileSize
new895 bytes

update new patch name and fix message

xiukun.zhou’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

Ticket was tagged for issue summary update and test both which still appear needed. Thanks

xiukun.zhou’s picture

Issue summary: View changes
Status: Needs work » Needs review
xiukun.zhou’s picture

thanks smustgrave
The summary and test steps have been updated

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: -Needs issue summary update

Thanks but need an actual test case or assertion in the code.

xiukun.zhou’s picture

StatusFileSize
new3.39 KB

add test case.

xiukun.zhou’s picture

StatusFileSize
new3.41 KB

new patch

xiukun.zhou’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

Thanks. FYI you don't have to run all the tests just the default one is fine.

Also all patches should be attached with an interdiff.

+  public function testUserResetPasswordEmailFloodControl() {
+    $account = $this->drupalCreateUser();
+    for ($i = 0; $i < 5; $i++) {
+      $this->drupalGet('user/password');
+      $edit = ['name' => $account->getEmail()];
+      $this->submitForm($edit, 'Submit');
+    }
+
+    // The next request should trigger flood control.
+    $this->drupalGet('user/password');
+    $edit = ['name' => $account->getEmail()];
+    $this->submitForm($edit, 'Submit');
+    $this->assertPasswordEmailOrUsernameFlood();
+
+    // delete test account.
+    $account->delete();
+  }
+
+  /**
+   * Tests password reset flood control for one username.
+   */
+  public function testUserResetPasswordUsernameFloodControl() {
+    $account = $this->drupalCreateUser();
+    for ($i = 0; $i < 5; $i++) {
+      $this->drupalGet('user/password');
+      $edit = ['name' => $account->getAccountName()];
+      $this->submitForm($edit, 'Submit');
+    }
+
+    // The next request should trigger flood control.
+    $this->drupalGet('user/password');
+    $edit = ['name' => $account->getAccountName()];
+    $this->submitForm($edit, 'Submit');
+    $this->assertPasswordEmailOrUsernameFlood();
+
+    // delete test account.
+    $account->delete();
+  }

So way the functional tests work each method is it's own bootstrap and install. So could this be converted to kernel tests? If not could they be added to existing tests? At minimum should combine these two

xiukun.zhou’s picture

Status: Needs work » Needs review
StatusFileSize
new2.38 KB

I referred to two methods

File: core/modules/user/tests/src/Functional/UserPasswordResetTest.php

  1. testUserResetPasswordIpFloodControl
  2. testUserResetPasswordUserFloodControl

i think this can be add to kernel test

There is an attach interdiff file below. thanks smustgrave

Status: Needs review » Needs work

The last submitted patch, 25: 3353185-password-reset-form-interdiff.patch, failed testing. View results

xiukun.zhou’s picture

Status: Needs work » Needs review
StatusFileSize
new2.38 KB
smustgrave’s picture

Status: Needs review » Needs work

If you were trying to upload a test only patch should also include the full patch.

Wording for the patch should also change.

Typically [issue-number]-[optional:summary]-[comment-number].patch

Tests only patches will be [issue-number]-[optional:summary]-[comment-number]-tests-only.patch

xiukun.zhou’s picture

Status: Needs work » Needs review
StatusFileSize
new2.38 KB
new3.41 KB
new886 bytes

Uploaded all patches again

prasanth_kp’s picture

StatusFileSize
new67.1 KB

I have applied the #29 patch on 9.5.x dev version and it works fine .

xiukun.zhou’s picture

Status: Needs review » Reviewed & tested by the community
avpaderno’s picture

Status: Reviewed & tested by the community » Needs review

The person who provided the patch cannot change status to Reviewed & tested by the community.

avpaderno’s picture

Status: Needs review » Needs work
       $identifier = $account->id();
       if (!$this->flood->isAllowed('user.password_request_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
+        $form_state->setErrorByName('name', $this->t('Too many password recovery requests for this username or email. It is temporarily blocked. Try again later or contact the site administrator.'));
         return;

It is temporarily blocked. should be about the account, but the previous sentence ends with for this username or email, which makes it a substitution for this username or email.

nikhil_110’s picture

Status: Needs work » Needs review
StatusFileSize
new992 bytes
new3.27 KB

I have added patch for Drupal 9.5.x and address #33..
please review..

nikhil_110’s picture

StatusFileSize
new3.29 KB
new1019 bytes

Fixed #34 Cc Failed

avpaderno’s picture

Status: Needs review » Needs work
mrinalini9’s picture

Status: Needs work » Needs review
StatusFileSize
new3.29 KB
new1.01 KB

Fixing custom command failure issues in #35, please review it.

Thanks!

Status: Needs review » Needs work

The last submitted patch, 37: 3353185-37.patch, failed testing. View results

xiukun.zhou’s picture

StatusFileSize
new3.53 KB
new1.53 KB
xiukun.zhou’s picture

StatusFileSize
new3.52 KB
new875 bytes
zeeshan_khan’s picture

StatusFileSize
new3.52 KB
new376 bytes
nikhil_110’s picture

Status: Needs work » Needs review
StatusFileSize
new3.35 KB
new2.19 KB

I fixed issue #41 Please review

avpaderno’s picture

       if (!$this->flood->isAllowed('user.password_request_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
+        $form_state->setErrorByName('name', $this->t('Too many password recovery requests for this (%name) username or email. It is temporarily blocked. Try again later or contact the site administrator.', ['%name' => $name]));
         return;

It is preferable to say The account has been temporarily blocked. Otherwise the sentence would be understood as This username or email is temporarily blocked. which does not make much sense since it is the account that is blocked.
I find it preferable to say Too many password recovery requests for %name.

smustgrave’s picture

Version: 9.5.x-dev » 11.x-dev
Status: Needs review » Needs work

Agree with #43

nikhil_110’s picture

Status: Needs work » Needs review
StatusFileSize
new2.2 KB
new3.38 KB
new239.69 KB

I have updated the patch with latest update suggested by user @apaderno and user @smustgrave, I would appreciate if someone can review these updates and provide feedback

avpaderno’s picture

Status: Needs review » Needs work
       if (!$this->flood->isAllowed('user.password_request_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
+        $form_state->setErrorByName('name', $this->t('Too many password recovery requests for (%name) this username or email. The account has been temporarily blocked. Try again later or contact the site administrator.', ['%name' => $name]));
         return;
       }

The suggested words for the first sentence were Too many password recovery requests for %name.
Actually, to make it a sentence, the words should be Too many password recovery have been requested for %name. In this case, the next sentence should be The account is temporarily blocked. (just to avoid to use the Present Perfect twice in row).

sourabhjain’s picture

Assigned: Unassigned » sourabhjain

Let me work on the #46

sourabhjain’s picture

Assigned: sourabhjain » Unassigned
Status: Needs work » Needs review
StatusFileSize
new3.54 KB
new1.88 KB

I have fixed the #46 issue. Please review.

avpaderno’s picture

Status: Needs review » Needs work
       if (!$this->flood->isAllowed('user.password_request_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
+        $form_state->setErrorByName('name', $this->t('Too many password recovery have been requested for (%name) this username or email. The account is temporarily blocked. Try again later or contact the site administrator.', ['%name' => $name]));
         return;

I apologize if what I suggested was not clear: The string passed to $this->t() (or t()) needs to contain the following text (and only this).

Too many password recovery have been requested for %name. The account is temporarily blocked. Try again later or contact the site administrator.

sourabhjain’s picture

Assigned: Unassigned » sourabhjain
sourabhjain’s picture

Assigned: sourabhjain » Unassigned
Status: Needs work » Needs review
StatusFileSize
new3.49 KB
new1.84 KB

I have fixed the #49 issue. Please review.

avpaderno’s picture

The patch is fine for me. Let us wait for somebody else's opinion.

jan kellermann’s picture

Issue tags: +privacy

You cannot write "The account has been temporarily blocked." because then you say that this account exists and you have a privacy data breach!

But this means you have to count the password-requests for every mail address - regardless of whether this account exists or not. If you count only the requests for existing accounts a malicious robot can examine which user accounts exists or not. Imagine this e.g. for a HIV/AIDS Selfhelp portal!

Please be careful with such information to avoid an information disclosure and a privacy violation.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs security review

Agreed will have to tweak the language

avpaderno’s picture

In that case, the message should not even say Too many password recovery have been requested for %name. because that could possibly give information about the account.
Imagine I tried once a password recovery using a username or an email and I got that message. I would gather that somebody else tried to recover the password using the same username or email, which could make me think the username or the email is effectively used (or that trying that username/email is a common practice for somebody who tries to find out which accounts exist on a site). If then I know the site considers attempts made from the same IP address, and I am at my work place, that message could help me to guess username or email used by a colleague.

To avoid giving information about existing accounts, the message should be the same in any case, whether the username/email is not associated to any account, too many attempts have done, or the username/email is associated with an account and the recovery email has been sent. For example, showing this message in every case would not allow to understand if the username/email is effectively associated with an account.

If %identifier is a valid account, an email will be sent with instructions to reset your password, if there have not been too many attempts to recover the password.

jan kellermann’s picture

We may (optionally) consider sending an email when an account is suspended.
Then we have no information disclosure and the account is informed that someone tried something bad and can inform the administrator.

avpaderno’s picture

Actually, the account for which the password recovery is asked is never blocked. The code in UserPasswordForm.php increases the number of requests done to recover the password; that eventually avoids further password recovery requests can be done.
This means that the The account is temporarily blocked. part is wrong. Taking off that part, the message would be Too many password recovery have been requested for %name. Try again later or contact the site administrator. which could still give out information about a possible account.

As for sending an email when the number of password recovery requests reached the limit, that would mean flooding the account's email address with a new email each time a new password recovery request is done after the limit is reached.
An email is already sent to the account's email address for the password recovery, and that email says:

[user:display-name],

A request to reset the password for your account has been made at [site:name].

You may now log in by clicking this link or copying and pasting it into your browser:

[user:one-time-login-url]

This link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.

-- [site:name] team

Eventually, that email message should also contain a sentence that explains what to do when the person who is reading the email did not request the password recovery (If you did not request a password recovery […].).

atul4drupal’s picture

One suggestion that I think of to frame the error message considering all the constraints and concerns raised in the thread is :

"If {account ID} is a valid and active account, an email will be sent with instructions to reset your password."

account ID will be the value entered by user in "username or Email" field.

Kindly share thoughts on using this... or some thing on this line.

ressa’s picture

In case you find this issue trying to figure out what "Password reset form was submitted ..." means, and realize you need to clear the flood table, you can do it with this:

drush sql:query "DELETE FROM flood;".

poker10’s picture

Re: #58

"If {account ID} is a valid and active account, an email will be sent with instructions to reset your password."

I am not sure if this is entirely correct. Account under flood control is active and its status is not "blocked", as mentioned by @apaderno in #57. So we are missing at least one possible scenario in the proposed text.

The idea with sending an email to user from #56 is interesting, however I think we would need to ensure that the email is sent only once. Otherwise it would be possible to spam user with such emails.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.