Problem/Motivation
When user_email_verification setting is ON and on registration user get's system-generated password.
It is important to allow changing password in this case.
Proposed resolution
Skip "Delay" constraint if user has only system-generated password.
Comments
Comment #2
l0keComment #3
br0kenI would recommend get rid of variable and simplify construction to:
Comment #4
l0keAgree, let's simplify condition.
Comment #5
br0ken@l0ke, don't you want put every condition inside of
ifstatement to own line? This form allow to nicely comment everyone of them.Example:
It's just my proposition - changes are RTBC-ready.
Comment #6
badjava commentedA quick look at the coding style from this module shows that it follows the Drupal coding standards. The conditions should not be wrapped into multiple lines.
Comment #7
br0kenComment #8
br0ken@badjava, do you see any errors related to changes (
phpcshas scanned the code with applied patch)? I am not.Drupal CS says that conditions "should" not be multiline, not "must". No need to set the status to NW if you aren't love some piece of code only from coding standards perspective (especially in case when module not follows them). The main thing - possibilities provided by patch.
Comment #9
l0keWell, in my opinion breaking long conditions in multiple lines make them more readable and coding standards, actually, allow this.
I don't want to postpone resolution of this issue because of styling discussion, that could last forever. So attaching a new patch with a single-line condition.
Comment #10
aohrvetpv commentedI think the patch has a problem.
My understanding is the purpose of the delay constraint is to prevent the user from changing their password multiple times to bypass the past password constraint. For instance, suppose a policy that disallowed using the past 3 passwords. Suppose a user's password is "foo" and they are forced to change their password.
- User immediately changes password to "foo1".
- User immediately changes password to "foo2".
- User immediately changes password to "foo3".
- User immediately changes password to "foo".
The user has bypassed the past password constraint. The delay constraint, however, prevents doing that. They will have to wait between each password reset.
The patch as written would seem to give the user a way to bypass the delay constraint, and thereby bypass the past password constraint:
- User requests a one-time login link.
- User immediately logs in using one-time login link and changes password to "foo1".
- User requests a one-time login link.
- User immediately logs in using one-time login link and changes password to "foo2".
- User requests a one-time login link.
- User immediately logs in using one-time login link and changes password to "foo3".
- User requests a one-time login link.
- User immediately logs in using one-time login link and changes password to "foo".
It seems the patch would need to make an exception to the delay constraint only for certain cases:
- Drupal has generated a password and the user is setting it for the first time.
- An administrator has forced the user to change their password.
- etc.
Comment #11
br0kenComment #12
l0keComment #13
aohrvetpv commentedJust want to comment that there is probably some better way to solve this than adding exceptions as I described in the last paragraph of #10. Main point of #10 is the patch provides a way to bypass the delay constraint. Thanks for the bug report and patches.
Comment #14
l0kepassword_policy-password-reset-skip-delay-2779135-14.patchCondition changed, now bypassing this constraint available only if
user_email_verificationsetting is ON and user haven't change a password yet. In addition it is possible only if one-time login link is used.password_policy-password-reset-skip-delay-2779135-14-with-threshold.patch@BR0kEN proposed a neat idea – add setting to allow changing password few times in a desired period of time. So by default it works as it was, allowing password change only once, but this patch makes it more flexible.
What do you think?
Comment #17
l0keRe-rolled patches.
Added threshold check to test.
Comment #18
l0keComment #19
l0keComment #20
br0kenI do like #17!
Use strict comparison. Also, you can switch the operands.
Pass an array to
user_save()and get rid of$editvariable.Remove this empty line.
Leave blank line before return statement.
Not need to use
t()for test messages. But I'm seeing that its used everywhere in this module.Can we set to 1?
I'd suggest: "Maximum number of attempts to change the password per time window".
I'd suggest: "Configure an amount of attempts for changing the password per given time frame."
We need
$period_startvariable only once and only if some conditions were passed. I'd suggest to remove the variable and usestrtotime('-' . $constraint->config['delay'])directly at place.Comment #21
aohrvetpv commentedI created an issue for this:
#2863756: Unnecessary t() calls in tests
Comment #22
l0ke#20
$editvariable used later to set$account->pass_rawthat is used byDrupalWebTestCase->drupalLogin()Comment #23
br0kenLooks nice to me. Let's wait the tests to pass and tomorrow I'll be able to "touch" the patch locally.
Comment #24
aohrvetpv commentedReduced #22 to the new threshold functionality. I think it will be easier (for me at least) to focus on just this change first. I removed a few style changes that seem incidental--they can be committed separately.
Comment #25
aohrvetpv commentedRemoved an inadvertent change from #24.
Comment #26
l0keIt is arguable whether it necessary or not to change it here but if we doing a lot of changes in .test file we could also fix some coding standards issues!
This changes came as follow up to using strict comparison used in:
This part is the solution for initial problem. While you can say it could be solved by setting threshold to 2+, it is looks like using workaround where real solution could be provided.
So what is the particular reason to not have this part?
To sum up: What is the reason and purpose of reducing the changes?
Comment #27
aohrvetpv commentedWe can make the other changes too, separately. I just want to focus on the threshold feature first because it is complicated in itself. It is harder to understand patches/commits if they have several purposes.
This updated patch changes more text/messages for the threshold change.
Also removed the comment "// Pass query parameters for correct handling of password reset." This refers specifically to the addition of window.location.search, but I do not think that would be clear to someone reading the code.
It is strange to me that
element_validate_integer_positive()allows a blank entry.Comment #28
aohrvetpv commentedLast patch didn't have all my changes. Please disregard.
Comment #29
br0kenWhy not
var threshold = $('input[name="threshold"]', context).val() || 1?Comment #30
aohrvetpv commentedBecause I barely know JS. :) That looks better.
Comment #31
aohrvetpv commentedAny reason we can't make the threshold field required? Then we could do away with the parenthetical and the extra logic for handling empty input.
I notice none of the other constraint fields are required, but if the default is set to 1, I don't see why it would be a problem.
Comment #32
aohrvetpv commentedNew patch that makes threshold required.
Also changed a description from one sentence to two. (The parenthetical was really a separate sentence.)
Comment #33
l0ke@AohRveTPV Thank you for paying attention to this issue!
I didn't want to create a lot of issues first but you are right let's make things more transparent. So to keep everything clear:
Comment #34
aohrvetpv commentedThanks. I will try to review #33 in the coming days.
Comment #35
aohrvetpv commented#33 is imperfect because there is a case where
$is_password_generatedcould be TRUE when the password was not system-generated:1. User sets password.
2. Administrator enables email verification.
3. User logs in via password reset link and can change password in violation of delay constraint. (The password set in #1 is treated as system-generated even though it is not.)
However, I cannot think of a better solution. One idea I had was to not save the password to the password history if email verification is enabled. But this would allow a user to change their password back to the system-generated password in violation of the past passwords constraint.
A downside of this change is it allows a user to change the password back to the system-generated password faster than an administrator might expect from their delay constraint and past passwords constraint settings. For instance, if the delay constraint is set to 1 day and the past passwords constraint is set to 2, an administrator might expect a user cannot re-use a password in less than 2 days. But the user can actually re-use the system-generated password after 1 day.
Updated patch to apply to latest code. Fixed a grammatical error in comment and rewrapped it.
Comment #37
aohrvetpv commentedCommitted. Let's see how it goes. I'd like to do a new alpha release soon.
Even though #35 is imperfect, it does solve a major problem with using the delay constraint.
Comment #39
aohrvetpv commentedReverted and recommitted to credit BR0kEN also.
Comment #40
l0keThank you @AohRveTPV, a bit late, as I'm experiencing the lack of time, but I want to propose an improvement into handling of system-generated password.
What I think could make it better is not sanding on current value of
user_email_verificationvariable but storing it inpassword_policy_history. I added the corresponding column in database and by default it isFALSE, the only case when it actually could beTRUEis when new user is created.What I'm thinking of is whether we should check the existing users for system-generated passwords in
hook_update_N(), but if we do then we should stand on the assumption thatuser_email_verificationvariable haven't been changed, while it is possible as you mentioned in #35. So I propose to avoid it by simply taking all existing password as NOT system-generated, that way we can be sure that constraint cannot be violated.Comment #41
aohrvetpv commentedI like the solution in #40. I'll more closely review the patch in the coming days.
Comment #43
l0keThis patch should fix the test and ensure that correct values are always passed to
db_insert.Comment #44
br0kenisset()seems more logical here except we do need to know that exact number is 1, don't we?Why reset is no longer needed? Maybe it's a good idea to describe this in comments to code?
Comment #45
l0keComment #46
l0keThis change is not redundant indeed. Thanks @BR0kEN for the catch!
Comment #47
br0kenAnyway, I'm proposing to add a comment like "we need to load the user skipping the cache to reflect saved password".
Also, I think we can avoid loading of users without the cache on every candidate's iteration. Here is the PoC, but we need the other issue created for this.
Comment #48
aohrvetpv commentedRe the suggestion in #47 to instead use
user_load_multiple(): For sites with very many users (e.g. 1000s), loading all the expired accounts into memory can cause the PHP memory limit to be exceeded. It is a longstanding bug in 7.x-1.x, 7.x-2.x, and 8.x-3.x that the expiration code does not scale well: #2252541: Expiration cron does not scale for large numbers of users. So I'm not sure we can useuser_load_multiple()for all expired users.Recently I committed a change that passes
$reset = TRUEtouser_load()in the loop. It is intended to prevent the user cache from growing so large it exceeds the PHP memory limit.Comment #49
aohrvetpv commentedI'm not sure I understand this code. What are the circumstances where there would be no password history?
My guess is there would be no password history for a user whose password was set before the Password Policy module is enabled. In that case, we do not know whether the password was system-generated or not, so setting it to FALSE seems inaccurate. It's also inaccurate to record the password as created at the time of the request. (I realize that is not your code.)
I am wondering if it would be better to remove this entire code block, perhaps as a separate issue. If a user has no password history, they would have no password history until they set a new password, and the module would have to handle it accordingly.
Please let me know what you think.
Comment #50
aohrvetpv commentedMinor grammatical improvement to comment.
It's possessive so it needs to be either "New users' passwords" or "New user's password".
Comment #51
l0ke#49 That way of initialization of
password_historydoes seem a little bit inaccurate, but probably it is there for a reason, and removing that would require a lot more changes so I'd better move it to another issue and do some investigation first. I've created an issue #2912631: Properly initialize password history so we can work on this there.Meanwhile with spelling correction is looks good to me.
Comment #52
aohrvetpv commentedI plan to commit #50 soon.
Comment #53
aohrvetpv commentedThis code is necessary because otherwise passwords set before Password Policy is installed will never expire. (Expiration is determined by subtracting the current time from the password history's most recent password change time.)
This code is flawed regardless of the patch in #50. One problem: If Password Policy is installed and the Delay constraint is enabled, users will be unable to change their password until the delay has passed since Password Policy was enabled--even if the last time they changed their password was years ago.
The issue l0ke opened can be used for fixing this code. In 7.x-1.x, rather than initializing a password history entry for all users, passwords are expired when the expiration period has elapsed since the policy was enabled. 7.x-2.x should probably work in a similar way.
Setting
is_generatedtoFALSEseems good enough to me, because setting it to TRUE would allow the user to bypass the Delay constraint. This whole code block needs to be removed, anyway.Comment #55
aohrvetpv commentedThanks for the contributions.
Ideally this would probably have a test, but there is probably lots of functionality needing tests in this code.