This is somewhat related to issue 2867320 Password Policy History module wrong behaviour.
As I was looking into it, I found that the update hook in password_history is being fired twice resulting in the password hash being inserted into the password_policy_history table twice.
| Comment | File | Size | Author |
|---|---|---|---|
| #15 | password_policy-3079583-15.patch | 3.04 KB | the_g_bomb |
| #9 | 3095980-9.patch | 2.92 KB | iyyappan.govind |
| #4 | Password_history_policy_inserts_password_twice_in_password_policy_history_table-3095980-4.patch | 2.88 KB | wells |
Issue fork password_policy-3095980
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3095980-password-history-policy
changes, plain diff MR !30
Comments
Comment #2
taeoey commentedThe number of passwords between reuse can be configured in password_policy_recycle_constraint.
The root cause is that when the password is updated, the password is inserted twice in the password_policy_history table shown in the image below.

This patch is to prevent a password inserting twice when password is updated and depends on 2867320-4-password-recycle.patch. When the patch is applied, a password is inserted once ant it results the expected behavior as stated above. See the image below.
Comment #3
taeoey commentedComment #4
wellsThe root cause of this issue is that
$user->save()is called in a submit handler that Password Policy adds,_password_policy_user_profile_form_submit. Updating the User entity inside the submit handler causes the save hook to be called twice (and generally seems like bad practice).To avoid this, it is better to make those updates to the
$form_stateobject in a validation handler (so any other submit hooks will have correct information). This approach is used in core and some contrib modules I checked (e.g. Webform) and the Password Policy module looks those fields to the User form display so I think it makes the most sense here. I am attaching a patch to resolve this issue using that approach.This patch is a different approach (and does not rely on #2867320: Password Policy History module wrong behaviour) so I am not including an interdiff.
Comment #5
aohrvetpv commentedThanks, taeoey@epam and wells. I have a question about #4: Why is an array of an array needed for setting the value of
field_last_password_reset? It works, but I spent some time trying to understand why the array of an array is needed, and couldn't figure it out.Comment #6
wells@AohRveTPV - I can't say I know exactly why it has to be that way, but I can say that if it just a single array, saving the form throws an exception and fails (e.g.
Error: Cannot use object of type Drupal\Core\Datetime\DrupalDateTime as array in Drupal\Core\Field\WidgetBase->extractFormValues() (line 381 of /var/www/drupalvm/drupal/web/core/lib/Drupal/Core/Field/WidgetBase.php)).Comment #7
aohrvetpv commentedwells, have you seen some other code that does this? Trying to avoid "programming by coincidence"--if there were some precedence for this code I suppose that'd be good enough.
Comment #8
wellsThis has to do with how the value is handled from the widget when
#accessisFALSE. See TimestampDatetimeWidget::massageFormValues and #2326533: The structure of values received in WidgetInterface::massageFormValues() varies depending on #access TRUE/FALSE.Comment #9
iyyappan.govindI have re-rolled the patch. Please review this. Thanks
Comment #10
ultrabob commentedI reviewed the patches, and see where a call to user->save() had been called in the submit handler, but is now removed in favor of setting up the necessary information during validation, so that it can be handled at a later stage, avoiding a double save.
I fired up my database password_policy_history table, and confirmed that before the patch 2 identical rows were written for each password change. Then I applied the patch, and changed my password again. After the patch, a single row is written after each password change.
One caveat I should mention: the environment I have set up for this kind of testing right now is on 9.1.x, so I tested against that version of core rather than the latest 8. If a further test is needed, here are the testing steps.
1. Load up the site with password_policy_history enabled, bring up the password_policy_history table in the db, and observe what the id of the latest entry is.
2. Change you password, and observe that 2 identical rows (timestamp could vary very slightly) are written for the password change
3. Apply the patch.
4. Change password and check that a single row is added for each change.
This one seems ready to go.
Comment #11
iyyappan.govindHi ultrabob, Thanks for making the review on my patch.
Comment #12
james.williamsI've also just tested this under D8.9.13 and it works for me :-)
A possible issue is that existing entries in the history table remain as they were, so using constraints that might limit according a particular number of recent passwords (e.g. for #2867320: Password Policy History module wrong behaviour), will be inaccurate unless the existing data is also cleaned up. That could be done from a database update hook, but won't necessarily be _perfectly_ accurate. For example, we could iterate and remove every other row that is identical to the row next to it (aside from the
idprimary key), as thetimestampwould normally be identical. But it is technically possible for the timestamp to be different for identical submissions (e.g. in case of some delay in the server between validation+submission), or identical for different passwords (e.g. if the password was changed incredibly quickly)!This is only going to affect people with existing password history entries that want to limit the checking, so is probably outside of the scope of what needs fixing here. But those people may want to consider how they wish to handle that! (In my case, I'm just going to double the limit that I use with the recycled passwords constraint, so our site is at least as secure as my client's written policy requirements, because being 'more' secure won't be considered a problem.)
Comment #14
paulocsIMHO we could create a hook update to remove duplicate rows to delete rows that have same uid, pass_hash and timestamp.
As @james.williams pointed, some of them will not have equal column values but those will be exceptions.
I think it is in the issue scope of this issue.
Except for that, patch #9 looks good.
Comment #15
the_g_bomb commentedRerolling to apply since changes in 8.x-3.x
Comment #16
paulocsI created a follow-up issue to implement the hook_update. See: #3266140: Create hook_update to remove duplicated entries in password_policy_history
Patch #15 looks good! Thanks.
Comment #18
paulocs