Problem/Motivation
The list of modules altering the same user pass reset form callbacks grows each year. When this issue was created only 2 modules existed that have conflicts with the current situation, but more exist by now.
For a more in-dept + historical view of the issue, see comment #9.
Long story short:
username_enumeration_prevention, logintoboggan and user_registrationpassword (and other modules) change the password reset form validations/submit callbacks. This tends to break when these modules are used in combination with each other.
Also note that the current approach of lt/uep is to lookup+replace the callbacks, not hardcode them via a key. This leads to the same problem from another direction. How to replace a callback that does not exist - or has been replaced previously + how to make that mix work with all modules involved (priority in execution) (and especially account lookup depending on conditions. possibly provided by another module, in our case username_enumeration_prevention, feels like a 'should be fixed in core'-thing, but too large for D7 at this stage).
Drupal 7 is probably never going to address this, even in Drupal 8 it's still a problem for contrib. (see #2918984)
To fix this in core a lot has to happen, this issue only focuses on the current module. It's clear by now some parts have to change, while logintoboggan (for example) has a patch available to partially fix compatibility.
Proposed resolution
- Attempt to get logintoboggan issues fixed (at least #1773424).
- Revive the code that used to exist in user_registrationpassword for securing the password reset form (deprecating the need for username_enumeration_prevention once user_registrationpassword 1.6 is released - this prevents overcomplex code in user_registrationpassword to fix this case).
- Implement hook_module_implements_alter() and make sure we run last (or at least, try to) - and - only replace the core handler, not key 0 - and (re-)setting it as the first in the array.
- Once this has all landed, implement fixes for other modules that we want user_registrationpassword to be compatible with.
Remaining tasks
Assist with getting related issues fixed and testing upcoming patches.
Revive deprecated code that drops the need for username_enumeration_prevention once user_registrationpassword 1.6 is released. (Task: > RobC, patch WIP)
Implement hook_module_implements_alter() (Task: > RobC, patch WIP)
User interface changes
None
API changes
None that should break backwards compatibility.
Data model changes
None
Original by: alfaguru
This code in user_registrationpassword,module can cause conflicts with other modules that add their own validation / submit hooks.
<?php
/**
* Implements hook_form_FORM_ID_alter().
*/
function user_registrationpassword_form_user_pass_alter(&$form, &$form_state, $form_id) {
$form['#validate'][0] = '_user_registrationpassword_user_pass_validate';
$form['#submit'][0] = '_user_registrationpassword_user_pass_submit';
}
?>The problem is that in each case the code wipes out the first hook in the array regardless of what it is. If another module puts its own hooks into the array ahead of all others, it will get overwritten. Overall, the outcome is not predictable.
My suggestion would be to search the array for the core callback that is replaced by default and only make the alteration if it is found. That leaves the door open for other module developers to override your callbacks if they need to. Something like this will work:
<?php
$key_validate = array_search('user_pass_validate', $form['#validate']);
if ($key_validate !== FALSE) {
// Provide custom validator.
$form['#validate'][$key_validate] = '_user_registrationpassword_user_pass_validate';
}
... repeat for #submit
?>| Comment | File | Size | Author |
|---|---|---|---|
| #36 | 2585033-36-d8.patch | 3.69 KB | rob c |
| #24 | user_registrationpassword-2585033-23-d7.patch | 4.24 KB | rob c |
| #24 | user_registrationpassword-2585033-23-d8.patch | 3.57 KB | rob c |
Comments
Comment #2
a_thakur commentedYup, hardcoding [0] is conflicting with username_enumeration_preventation module. Will see if I can submit a patch for this.
Comment #3
a_thakur commentedComment #4
a_thakur commentedComment #5
a_thakur commentedPatch attached with fixes. array_search for concerned validation is added which leaves the door open for other module developers to override the callbacks.
Comment #6
a_thakur commentedA new patch has been added which checks if array_search does not return FALSE.
Comment #7
rob c commentedI wrote a rather large comment about why, got so big (with lots of history on the why) i won't post it here.
Install user_registrationpassword and username_enumeration_prevention, apply this patch and step-debug with phpstorm to find the issues i have with this. (cause user_registrationpassword callbacks are never ever executed with this patch, cause they are always replaced now). And i've visited the username_enumeration_prevention issues about this, not resolved for years (and i know why).
The modules are just not compatible, and i believe the functionality provided for flood, privacy, etc for the pass reset form, should be provided by 1 module that contains it all. (not 3 or 4 modules that won't work together, like we have currently).
Sorry for being a bit short, get on IRC, then i'll explain with lots of details.
Comment #8
alfaguru commentedHi Rob, this patch may not directly fix the other problem you mention, but it does improve it by not hard-coding the offsets, which can cause apparently mysterious effects as things stand, depending on whatever modules happen to be installed.
If you have a particular combination of modules in use, it's probable you will need a bridging module that orchestrates the final set of callbacks. Its alter function should run after all others so it can clear the array and rebuild it. There's never going to be one module to rule them all as all sorts of things may be needed as extras: integration with external authentication systems for one, as in the site I'm working on.
Maybe the answer is a separate module which makes all possible callbacks visible to developers and lets them tweak them: but then why not extend that to all forms ...
Comment #9
rob c commented"but it does improve it by not hard-coding the offsets"
Until we have that framework, i don't see how we can change it, cause we need to run, or we need to create some setting.
"If you have a particular combination of modules in use, it's probable you will need a bridging module that orchestrates the final set of callbacks."
Kind of agree, but that would require some effort. And see below.
user_registrationpassword fakes a new state for users (when not logged in / never authenticated and not activated). (access 0 login 0 status 0)
username_enumeration_prevention does not know about this and searches for enabled users, and thus, always sets the wrong message. (the fake one). With or without user_registrationpassword.
So i rather create that module that contains it all with some hooks for other modules to change things, but not a bridge module, cause thats not required if we have 1 dedicated module, something like this, but with a couple hooks. (yes i did do some work in this direction some time ago).
This is my very large post.
(Ok apologies up front to anyone, this looks more like a blog post now, but there's some history involved.)
We expect the callbacks on position 0. This is kinda because a couple of modules should not (just can't) be used together, like logintoboggan, as no decent framework exists for these situations (and some others) AND because the whole enumeration / flood control-bit for the password reset form was kinda not figured out in the opensource drupal world back in the day (at least, the priority / impact wasn't there at that time) (in short: privacy was an issue, but we had bigger fish to fry).
logintoboggan (for example) does something similar like what you propose. See here.
They unset the core submit handler on the 'user_register_form' by checking the key and then they add their callback to the start of the array.
When i visit the code of username_enumeration_prevention here i see that this module does something very similar to what we do.
And when i apply your patch, enable both modules, this is what i experience:
And when i make a simple change, so the callbacks provided by username_enumeration_prevention are executed before or after the callbacks provided by user_registrationpassword, i see another (obvious) issue. The "Further instructions have been sent to your e-mail address." message is now set multiple times and 2 password reset mails are sent, one by core and one by user_registationpassword. So i guess these modules are just not compatible.
I tried to add a (forgive me) B***S*** mode to user_registrationpassword for the password reset form in the past, but i changed the goal to let it become a separate module to prevent over-complicating the code for user_registrationpassword - and because other modules already implemented parts of it, but then my life changed due to required surgery (that was not diagnosed for way to long, still recovering, but all ok now) and thus i didn't completed this module, while i did revert the commit for user_registrationpassword, because of other compatibility/complication issues.
See the code that used to be implemented at some point: here.
Meanwhile username_enumeration_prevention was getting more popular, while containing the same 'glitch', and no debate sprung up to address these issues for D7 (and never will i guess). So contrib now needs to fix this, by creating even better modules, that combine some changes we see modules make to cover these issues, but now let's make them work together. I (for example) would (love to) chop up logintoboggan in a heartbeat, so we have more control over individual items we implement in our projects and not let these things get way to complicated / incompatible (also with lots of new insights we all have gathered in the last couple years), but that would lead to even more individual modules we need to install and lots of corporate sites already run with a boatload of modules, adding more and more just isn't possible.
(Years ago, i also spoke with YesCT and lots of other people about a refactoring the account workflow, so we get to something of a framework for the whole authentication system that would facilitate a structure for these modules/features in D8, but due to my health i never got the time/energy to create a concept for this that actually worked, did had some fun with the inner parts of core, but D8 surely has similar issues and user 'party hats' and lots of other things (pluggable features for your authentication workflow) are still on hold. Read more about this all here and here.)
I'm not so sure how we'r really going to 'fix' this one, nor if we should implement the change here, without making username_enumeration_prevention not execute it's callback if user_registrationpassword is enabled (optionally provide some setting to change this behaviour).
(it might actually be a neat idea to create a setting in user_registrationpassword to enable/disable the provided feature anyway)
And you might also want to start a related issue in the username_enumeration_prevention issue queue and related it to this issue, so we have some reference. I've visited #2172705: username_enumeration_prevention_form_alter can break other contributed modules., but that does not fix the problem, cause when applied, it is runs after user_registrationpassword, and thus, the code provided by username_enumeration_prevention is never executed and if #2589661: Change hook_form_alter to hook_form_FORM_ID_alter lands, we have the same issue i previously described. I rather pursue #1526166: Split enemuration for autocomplete into seperate module. and break it up, so we can create one specific module i figured was required years ago. One that only deals with the password reset form, with flood control / privacy issues covered / etc.
Hope you made it trough this post, and hope this helps a bit.
Comment #10
alfaguru commentedRob, you've clearly spent a lot of time thinking about this. +1 for being able to turn off the integration on the password reset form, as that's all I really need for this particular case.
Comment #11
rob c commentedThanks, indeed i have and understood.
We could simply wrap the stuff in the form alter in an if statement that checks a variable and expose that on admin/config/people/accounts.
Then add a cache clear to the submit of our form alter that alters admin/config/people/accounts and voila.
Result: if username_enumeration_prevention is enabled together with user_registrationpassword you are able to disable the feature provided by user_registrationpassword. (This does lead to the situation where new users that didn't activate their accounts yet won't be able to 'abuse' the password reset form to maintain a new activation mail, but now it's the site builders choice.)
Let me think about this for a couple days, maybe other people have more suggestions.
Comment #12
rob c commentedOk, this patch implements the variable and config. Add it to your drush makefile or patch it by hand. I'm not sure if we should commit this as-is, but at least now we have a temporary fix.
Comment #13
rob c commentedComment #14
alex.bukach commentedAs Rob kindly noticed in #2700341: Password reset executed twice / emails sent twice, that issue is caused by the same reason, hardcoding [0]. Here's the pach that makes the module always replace right callbacks, not the first ones. Please let me know if it doesn't resolve any issue mentioned in this thread.
Comment #15
alex.bukach commentedComment #16
rjjakes commentedConfirmed, the patch in #14 fixes the lost handler.
Comment #17
rob c commented@rjjakes what modules did you use to test this?
Would love to have some feedback from people using the username_enumeration_prevention module. I see similar changes when i look at their code.
Comment #18
mstrelan commentedThis patch fixes a minor whitespace issue in #14.
I have tested this with username_enumeration_prevention. Request a password using either a valid username or email address sends the appropriate email. Invalid usernames or email addresses do not send an email, and display the same message, ie. the intended behavior of username_enumeration_prevention.
RTBC or was there some other issue?
Comment #19
rob c commented@mstrelan sounds like it.
Thanks for testing and for the updated patch.
What do you consider invalid? Unknown to the system? Blocked?
Because blocked and never ever used should sent out an email via user registrationpassword. If thats the case with username enumeration also enabled we can commit this.
Ill review / test / commit later today.
Comment #20
mstrelan commentedI didn't consider the blocked case, only unknown to the system. Good point.
Comment #21
rob c commentedI'm rethinking / proposing a new approach on how to fix this (at least for username enumeration prevention). What if we check for module_exist(username enumeration prevention) in both validate and submit handlers and call the validate and submit within the module_exist() after the user_registrationpassword code didnt return an account. (Like we already do for core). Then both these modules are compatible.
Other exotic cases are more difficult (like logintoboggan). I'm almost sure user_registrationpassword and logintoboggan will not become compatible ever, so let's focus on compatibility with highly used modules and not this overcomplex case.
Comment #22
rob c commentedI'm almost sure user_registrationpassword and logintoboggan will not become compatible everAdding related issues from logintoboggan and username_enumeration_prevention and updating issue description, adding issue template.
I hope logintoboggan commits #1773424 at some point, i do not think username_enumeration_prevention will ever be compatible on a level i'm comfortable with.
So i think we need a new battleplan. I had some time to work on the Drupal 8 port of user_registrationpassword and meanwhile also found some time to work on *this* issue, so here it goes.
Comment #23
rob c commentedMoving to D8.
Comment #24
rob c commentedAdding patches for both the D7 and D8 version.
Created #2928768: username_enumeration_prevention compatiblity as a followup for these patches, also both D7+D8.
I'll create another issue for logintobbogan when i'm ready, working on a patch that makes them semi-work together, but needs more time. (also lt needs some critical other fixes, so i tend to provide the same fix as for username_enumeration_prevention: just replace the callbacks again).
Once the 8.x patch is committed, the 7.x patch should follow soon.
Comment #25
Notament commentedI couldn't apply the last patch with composer because the test file already existed, on Drupal 8. So i rerolled the patch; hoppefully it will work.
Comment #27
Notament commentedI patched the module with #24 on Drupal 8, but it didn't work because a test was missing. I updated it with #25 but failed because some test didn't of class imports. So corrected it on #27.
Comment #28
Notament commentedComment #29
awolfey commentedHere is an updated patch. It works for me in conjunction with https://www.drupal.org/project/simple_pass_reset and some custom code that adds a validation function.
Comment #31
arno2mars commentedHello,
Just a quick one to confirm that the patch in #29 solved the conflict issue I had between this module and the Username Enumeration Prevention module.
Thanks a lot!
Comment #32
rob c commentedArno2Mars thanks for testing and the feedback!
Fixed 1 issue: D9 requires more params. No other changes.
Comment #33
rob c commentedAdding #3167137: Report incompatible modules on status report page as a related issue. This can use some testing / feedback.
Comment #34
arno2mars commentedHey Rob C,
Thanks for this last patch in #32.
I confirm it works again perfectly for me.
One point though (hope it is not too silly):
Applying this patch makes my site work great again with both User Registration Password and User Enumeration Prevention installed:
I understand that the fix provided cannot be a robust long term solution until some deeper changes are made in core, and I understand also the need to warn the user that installing both modules may result in incompatibility (error and/or wrong behavior).
But it works at least in the configuration I have, which is using the latest version of both modules + your patch, so I guess a configuration used by most users. Therefore having this red error in the Status Report appears maybe a bit too strong.
May I suggest the following instead:
Otherwise keeping a red error would be I think detrimental for User Registration Password. This would fear the users and incite them to uninstall of the modules. And users may prefer to keep User Enumeration Prevention which is addressing Security, despite User Registration Password improves greatly the registration workflow.
Another suggestion (hope it is also not silly) would be maybe to merge both modules into one? If you integrate the functionalities of the User Enumeration Prevention in User Registration Password, doesn't it allow you to fully control how the password is hooked?
Hoping it helps.
May you need further testing from me, please continue to post on this issue that I am following, and I will help as much as I can.
Cheers,
Comment #35
rob c commentedAdding related issue.
Comment #36
rob c commentedIgnore #32, i added the code from #3167137, that should have been a separate patch. But we also have #2928768: username_enumeration_prevention compatiblity (totally forgot about that one, i already dove into this some time ago).
This is the same patch as #32 with the hook removed (so only fixes the missing parameters for the test).
Comment #37
spuky commented#36 also helps with Shared Email Password Reset (if you install module in the right order or tweak their weights..)
Comment #39
vladimirausThanks everyone for contributing.
Committed to
2.0.xComment #40
vladimiraus