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.

CommentFileSizeAuthor
#50 interdiff-2779135-45-50.txt684 bytesaohrvetpv
#50 password_policy-7.x-2.x-ignore_system_generated_password_for_delay_constraint-2779135-50.patch4.33 KBaohrvetpv
#47 2779135-47--do-not-commit.patch1.78 KBbr0ken
#46 interdiff-43-45.txt672 bytesl0ke
#46 password_policy-7.x-2.x-ignore_system_generated_password_for_delay_constraint-2779135-45.patch4.33 KBl0ke
#43 interdiff-40-43.txt1.22 KBl0ke
#43 password_policy-7.x-2.x-ignore_system_generated_password_for_delay_constraint-2779135-43.patch4.84 KBl0ke
#40 password_policy-7.x-2.x-ignore_system_generated_password_for_delay_constraint-2779135-40.patch3.28 KBl0ke
#35 interdiff-2779135-33-35.txt1.59 KBaohrvetpv
#35 password_policy-7.x-2.x-ignore_system_generated_password_for_delay_constraint-2779135-35.patch1.5 KBaohrvetpv
#33 password_policy-system-generated-skip-delay-2779135-33.patch1.46 KBl0ke
#32 interdiff-22-32.txt3.24 KBaohrvetpv
#32 password_policy-7.x-2.x-allow_configurable_number_of_changes_within_delay_period-2779135-32.patch5.3 KBaohrvetpv
#28 password_policy-7.x-2.x-allow_configurable_number_of_changes_within_delay_period-2779135-28.patch5.36 KBaohrvetpv
#28 interdiff-25-28.txt3.3 KBaohrvetpv
#27 interdiff-25-27.txt3.3 KBaohrvetpv
#27 password_policy-7.x-2.x-allow_configurable_number_of_changes_within_delay_period-2779135-27.patch5.3 KBaohrvetpv
#25 password_policy-7.x-1.x-allow_configurable_number_of_changes_within_delay_period-2779135-25.patch4.27 KBaohrvetpv
#25 interdiff-22-25.txt1.99 KBaohrvetpv
#24 interdiff-22-24.txt1.99 KBaohrvetpv
#24 password_policy-7.x-1.x-allow_configurable_number_of_changes_within_delay_period-2779135-24.patch4.43 KBaohrvetpv
#22 interdiff-17-22.txt4.57 KBl0ke
#22 password_policy-password-reset-skip-delay-2779135-22-with-threshold.patch5.67 KBl0ke
#19 password_policy-password-reset-skip-delay-2779135-15.patch1.58 KBl0ke
#17 password_policy.test24.09 KBl0ke
#17 password_policy-password-reset-skip-delay-2779135-15-with-threshold.patch4.79 KBl0ke
#14 password_policy-password-reset-skip-delay-2779135-14.patch1.6 KBl0ke
#14 interdiff-9-14.txt1.6 KBl0ke
#14 password_policy-password-reset-skip-delay-2779135-14-with-threshold.patch3.02 KBl0ke
#14 interdiff-9-14-with-threshold.txt3.02 KBl0ke
#9 interdiff-4-9.txt793 bytesl0ke
#9 password_policy-password-reset-skip-delay-2779135-9.patch1.47 KBl0ke
#4 interdiff-2-4.txt884 bytesl0ke
#4 password_policy-password-reset-skip-delay-2779135-4.patch1.47 KBl0ke
#2 password_policy-password-reset-skip-delay-2779135-2.patch1.51 KBl0ke

Comments

l0ke created an issue. See original summary.

l0ke’s picture

Status: Active » Needs review
StatusFileSize
new1.51 KB
br0ken’s picture

+++ b/plugins/constraint/delay.inc
@@ -41,6 +41,15 @@ function password_policy_delay_constraint($password, $account, $constraint) {
+  $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);

I would recommend get rid of variable and simplify construction to:

if (
  isset($_GET['pass-reset-token'], $_SESSION['pass_reset_' . $account->uid]) &&
  $_GET['pass-reset-token'] === $_SESSION['pass_reset_' . $account->uid]
) {
  // Don't apply constraint on password reset.
  return TRUE;
}
l0ke’s picture

Agree, let's simplify condition.

br0ken’s picture

Status: Needs review » Reviewed & tested by the community

@l0ke, don't you want put every condition inside of if statement to own line? This form allow to nicely comment everyone of them.

Example:

if (
  // Allow to continue if $string variable is equal to "string".
  'string' === $variable ||
  // Handle a case, when value of variable is number and equal to "10000".
  10000 === $variable
) {
  // Code.
}

It's just my proposition - changes are RTBC-ready.

badjava’s picture

Status: Reviewed & tested by the community » Needs work

A 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.

br0ken’s picture

FILE: ...ar/www/sites/lush_os/password_policy/includes/PasswordPolicy.inc
----------------------------------------------------------------------
FOUND 5 ERRORS AFFECTING 5 LINES
----------------------------------------------------------------------
   1 | ERROR | [x] The PHP open tag must be followed by exactly one
     |       |     blank line
  73 | ERROR | [x] Equals sign not aligned with surrounding
     |       |     assignments; expected 8 spaces but found 1 space
  74 | ERROR | [x] Equals sign not aligned with surrounding
     |       |     assignments; expected 1 space but found 2 spaces
 137 | ERROR | [x] Inline comments must end in full-stops,
     |       |     exclamation marks, colons, question marks, or
     |       |     closing parentheses
 156 | ERROR | [x] Inline comments must end in full-stops,
     |       |     exclamation marks, colons, question marks, or
     |       |     closing parentheses

FILE: ...tes/lush_os/password_policy/includes/PasswordPolicyCondition.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...es/lush_os/password_policy/includes/PasswordPolicyConstraint.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...ww/sites/lush_os/password_policy/includes/PasswordPolicyItem.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: .../shared/var/www/sites/lush_os/password_policy/password_policy.js
----------------------------------------------------------------------
FOUND 3 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------
  44 | ERROR | [x] Expected 1 space after FUNCTION keyword; 0 found
 200 | ERROR | [x] Expected 1 space after FUNCTION keyword; 0 found
 211 | ERROR | [x] Expected 1 space after FUNCTION keyword; 0 found

FILE: .../www/sites/lush_os/password_policy/plugins/condition/authmap.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...var/www/sites/lush_os/password_policy/plugins/condition/role.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: .../sites/lush_os/password_policy/plugins/constraint/alpha_case.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...sites/lush_os/password_policy/plugins/constraint/alpha_count.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...w/sites/lush_os/password_policy/plugins/constraint/blacklist.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: .../sites/lush_os/password_policy/plugins/constraint/char_count.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...sites/lush_os/password_policy/plugins/constraint/consecutive.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...r/www/sites/lush_os/password_policy/plugins/constraint/delay.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...s/lush_os/password_policy/plugins/constraint/drupal_strength.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...w/sites/lush_os/password_policy/plugins/constraint/int_count.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...es/lush_os/password_policy/plugins/constraint/past_passwords.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...ites/lush_os/password_policy/plugins/constraint/symbol_count.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...ww/sites/lush_os/password_policy/plugins/constraint/username.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 1 | ERROR | [x] The PHP open tag must be followed by exactly one
   |       |     blank line

FILE: ...es/lush_os/password_policy/plugins/export_ui/password_policy.inc
----------------------------------------------------------------------
FOUND 2 ERRORS AFFECTING 2 LINES
----------------------------------------------------------------------
  1 | ERROR | [x] The PHP open tag must be followed by exactly one
    |       |     blank line
 12 | ERROR | [x] Inline comments must end in full-stops, exclamation
    |       |     marks, colons, question marks, or closing
    |       |     parentheses

FILE: ...ed/var/www/sites/lush_os/password_policy/plugins/item/expire.inc
----------------------------------------------------------------------
FOUND 2 ERRORS AND 1 WARNING AFFECTING 2 LINES
----------------------------------------------------------------------
   1 | ERROR   | [x] The PHP open tag must be followed by exactly one
     |         |     blank line
 167 | WARNING | [ ] Line exceeds 80 characters; contains 155
     |         |     characters
 167 | ERROR   | [x] Inline comments must end in full-stops,
     |         |     exclamation marks, colons, question marks, or
     |         |     closing parentheses
br0ken’s picture

Status: Needs work » Reviewed & tested by the community

@badjava, do you see any errors related to changes (phpcs has 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.

l0ke’s picture

Well, 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.

aohrvetpv’s picture

Status: Reviewed & tested by the community » Needs work

I 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.

br0ken’s picture

Issue tags: +DevDaysSeville
l0ke’s picture

Assigned: Unassigned » l0ke
aohrvetpv’s picture

Just 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.

l0ke’s picture

Title: Skip "Delay" constraint on password reset » "Delay" constraint allow to change system-generated password
Assigned: l0ke » Unassigned
Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new3.02 KB
new3.02 KB
new1.6 KB
new1.6 KB

password_policy-password-reset-skip-delay-2779135-14.patch
Condition changed, now bypassing this constraint available only if user_email_verification setting 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?

Status: Needs review » Needs work

The last submitted patch, 14: password_policy-password-reset-skip-delay-2779135-14.patch, failed testing.

l0ke’s picture

Re-rolled patches.
Added threshold check to test.

l0ke’s picture

Status: Needs work » Needs review
l0ke’s picture

StatusFileSize
new1.58 KB
br0ken’s picture

I do like #17!

  1. +++ b/password_policy.test
    @@ -22,6 +22,30 @@ class PasswordPolicyBaseTestCase extends DrupalWebTestCase {
    +    if ($account == NULL) {
    

    Use strict comparison. Also, you can switch the operands.

  2. +++ b/password_policy.test
    @@ -22,6 +22,30 @@ class PasswordPolicyBaseTestCase extends DrupalWebTestCase {
    +    $edit = array('pass' => user_password());
    

    Pass an array to user_save() and get rid of $edit variable.

  3. +++ b/password_policy.test
    @@ -22,6 +22,30 @@ class PasswordPolicyBaseTestCase extends DrupalWebTestCase {
    +
    

    Remove this empty line.

  4. +++ b/password_policy.test
    @@ -22,6 +22,30 @@ class PasswordPolicyBaseTestCase extends DrupalWebTestCase {
    +    return $account;
    

    Leave blank line before return statement.

  5. +++ b/password_policy.test
    @@ -228,6 +252,11 @@ class PasswordPolicyConstraintsTestCase extends PasswordPolicyBaseTestCase {
    +    $this->assertFalse($this->checkPolicy($policy, 'password', $this->account), t('Delay constraint fails with new password before delay window expires and threshold is reached.'), t('Constraint'));
    

    Not need to use t() for test messages. But I'm seeing that its used everywhere in this module.

  6. +++ b/plugins/constraint/delay.inc
    @@ -12,6 +12,7 @@ $plugin = array(
    +    'threshold' => NULL,
    

    Can we set to 1?

  7. +++ b/plugins/constraint/delay.inc
    @@ -29,6 +30,13 @@ function password_policy_delay_admin_form($form, &$form_state, $constraint) {
    +    '#title' => t('Maximum number of times password can be changed'),
    

    I'd suggest: "Maximum number of attempts to change the password per time window".

  8. +++ b/plugins/constraint/delay.inc
    @@ -29,6 +30,13 @@ function password_policy_delay_admin_form($form, &$form_state, $constraint) {
    +    '#description' => t('Password can be changed this many times in a period above (if not defined 1 time).'),
    

    I'd suggest: "Configure an amount of attempts for changing the password per given time frame."

  9. +++ b/plugins/constraint/delay.inc
    @@ -52,6 +60,24 @@ function password_policy_delay_constraint($password, $account, $constraint) {
    +  $period_start = strtotime('-' . $constraint->config['delay']);
    

    We need $period_start variable only once and only if some conditions were passed. I'd suggest to remove the variable and use strtotime('-' . $constraint->config['delay']) directly at place.

aohrvetpv’s picture

Not need to use t() for test messages. But I'm seeing that its used everywhere in this module.

I created an issue for this:
#2863756: Unnecessary t() calls in tests

l0ke’s picture

#20

  1. Done
  2. $edit variable used later to set $account->pass_raw that is used by DrupalWebTestCase->drupalLogin()
  3. Done
  4. Done
  5. Done
  6. Yes, makes sense
  7. I'd suggest to go with this variant:

    Maximum number of times password can be changed per time period

  8. I like this more, though it more consistent with style of descriptions in other constraints.

    Password can be changed this amount of times per period above (if not defined 1 time).

  9. Done
br0ken’s picture

Looks nice to me. Let's wait the tests to pass and tomorrow I'll be able to "touch" the patch locally.

aohrvetpv’s picture

Reduced #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.

aohrvetpv’s picture

Removed an inadvertent change from #24.

l0ke’s picture

  1. +++ b/password_policy.test
    @@ -9,7 +9,11 @@
    +  /**
    +   * The password policy.
    +   *
    +   * @var PasswordPolicy
    +   */
    

    It 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!

  2. +++ b/password_policy.test
    @@ -54,7 +83,7 @@ class PasswordPolicyBaseTestCase extends DrupalWebTestCase {
    -    if ($account == NULL) {
    +    if ($account === NULL) {
    
    @@ -76,7 +105,7 @@ class PasswordPolicyBaseTestCase extends DrupalWebTestCase {
    -    if ($account == NULL) {
    +    if ($account === NULL) {
    

    This changes came as follow up to using strict comparison used in:

    +++ b/password_policy.test
    @@ -22,6 +26,31 @@ class PasswordPolicyBaseTestCase extends DrupalWebTestCase {
    +  protected function updateUserPassword($account = NULL) {
    +    if ($account === NULL) {
    +      $account = $this->account;
    +    }
    
  3. +++ b/plugins/constraint/delay.inc
    @@ -52,6 +60,23 @@ function password_policy_delay_constraint($password, $account, $constraint) {
    +  // If user has only system-generated password, uses one-time link
    +  // and have the token in the URL, don't apply constraint.
    +  $is_password_generated = variable_get('user_email_verification', TRUE) && count($account->password_history) == 1;
    +  $is_one_time_login = isset($_SESSION['pass_reset_' . $account->uid], $_GET['pass-reset-token']) && $_GET['pass-reset-token'] === $_SESSION['pass_reset_' . $account->uid];
    +  if ($is_password_generated && $is_one_time_login) {
    +    return TRUE;
    +  }
    

    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?

aohrvetpv’s picture

We 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.

aohrvetpv’s picture

Last patch didn't have all my changes. Please disregard.

br0ken’s picture

+++ b/password_policy.js
@@ -95,8 +95,12 @@
+        var threshold = $('input[name="threshold"]', context).val();
+        if (!threshold) {
+          threshold = 1;
+        }

Why not var threshold = $('input[name="threshold"]', context).val() || 1?

aohrvetpv’s picture

Why not var threshold = $('input[name="threshold"]', context).val() || 1?

Because I barely know JS. :) That looks better.

aohrvetpv’s picture

Any 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.

aohrvetpv’s picture

New patch that makes threshold required.

Also changed a description from one sentence to two. (The parenthetical was really a separate sentence.)

l0ke’s picture

@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:

  1. This issue originates from case with system-generated password so lets keep it so. Attaching patch with changes ONLY for skipping constraint when dealing with system-generated passwords.
  2. I've created follow-up to this issue #2864975: "Delay" constraint add threshold where we can work with adding threshold further. Some small review notes #2864975-2: "Delay" constraint add threshold
  3. And to not put it on the long finger #2864978: Use strict comparison in password_policy.test strict comparisons
aohrvetpv’s picture

Thanks. I will try to review #33 in the coming days.

aohrvetpv’s picture

#33 is imperfect because there is a case where $is_password_generated could 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.

  • AohRveTPV committed a941828 on 7.x-2.x authored by l0ke
    Issue #2779135 by l0ke, AohRveTPV: "Delay" constraint allow to change...
aohrvetpv’s picture

Status: Needs review » Fixed

Committed. 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.

  • AohRveTPV committed 544d82d on 7.x-2.x authored by l0ke
    Issue #2779135 by l0ke, AohRveTPV, BR0kEN: "Delay" constraint allow to...
  • AohRveTPV committed 5607c12 on 7.x-2.x
    Revert "Issue #2779135 by l0ke, AohRveTPV: "Delay" constraint allow to...
aohrvetpv’s picture

Reverted and recommitted to credit BR0kEN also.

l0ke’s picture

Thank 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_verification variable but storing it in password_policy_history. I added the corresponding column in database and by default it is FALSE, the only case when it actually could be TRUE is 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 that user_email_verification variable 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.

aohrvetpv’s picture

Status: Fixed » Needs review

I like the solution in #40. I'll more closely review the patch in the coming days.

Status: Needs review » Needs work
l0ke’s picture

This patch should fix the test and ensure that correct values are always passed to db_insert.

br0ken’s picture

  1. +++ b/plugins/constraint/delay.inc
    @@ -71,7 +71,7 @@ function password_policy_delay_constraint($password, $account, $constraint) {
    +  $is_password_generated = count($account->password_history) == 1 && $account->password_history[0]->is_generated;
    

    isset() seems more logical here except we do need to know that exact number is 1, don't we?

  2. +++ b/plugins/item/expire.inc
    @@ -323,7 +324,7 @@ class PasswordPolicyExpire extends PasswordPolicyItem {
    +        $account = user_load($candidate->uid);
    

    Why reset is no longer needed? Maybe it's a good idea to describe this in comments to code?

l0ke’s picture

  1. It is a key point here that user should have exactly 1 password in history and it should be a generated password. In other cases constraint should be applied.
  2. I will double check that.
l0ke’s picture

+++ b/plugins/item/expire.inc
@@ -323,7 +324,7 @@ class PasswordPolicyExpire extends PasswordPolicyItem {
-        $account = user_load($candidate->uid, TRUE);
+        $account = user_load($candidate->uid);

This change is not redundant indeed. Thanks @BR0kEN for the catch!

br0ken’s picture

StatusFileSize
new1.78 KB

Anyway, I'm proposing to add a comment like "we need to load the user skipping the cache to reflect saved password".

+++ b/plugins/item/expire.inc
@@ -324,7 +324,7 @@ class PasswordPolicyExpire extends PasswordPolicyItem {
       $candidates = _password_policy_expire_query_users($notice_int, $policy_name);
       foreach ($candidates as $candidate) {
-        $account = user_load($candidate->uid);
+        $account = user_load($candidate->uid, TRUE);

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.

aohrvetpv’s picture

Re 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 use user_load_multiple() for all expired users.

Recently I committed a change that passes $reset = TRUE to user_load() in the loop. It is intended to prevent the user cache from growing so large it exceeds the PHP memory limit.

aohrvetpv’s picture

    // If there is no password history, start one.
    if (!isset($account->password_history[0])) {
      $account->password_history[0] = (object) array(
        'uid' => $account->uid,
        'pass' => $account->pass,
        'created' => REQUEST_TIME,
        'is_generated' => FALSE,
      );
      password_policy_update_password_history($account->password_history[0]);
    }

I'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.

aohrvetpv’s picture

Minor grammatical improvement to comment.

It's possessive so it needs to be either "New users' passwords" or "New user's password".

l0ke’s picture

Status: Needs review » Reviewed & tested by the community

#49 That way of initialization of password_history does 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.

aohrvetpv’s picture

I plan to commit #50 soon.

aohrvetpv’s picture

    // If there is no password history, start one.
    if (!isset($account->password_history[0])) {
      $account->password_history[0] = (object) array(
        'uid' => $account->uid,
        'pass' => $account->pass,
        'created' => REQUEST_TIME,
        'is_generated' => FALSE,
      );
      password_policy_update_password_history($account->password_history[0]);
    }

This 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_generated to FALSE seems 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.

  • AohRveTPV committed 3b9acb2 on 7.x-2.x authored by l0ke
    Issue #2779135 by l0ke, BR0kEN: "Delay" constraint allow to change...
aohrvetpv’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for the contributions.

Ideally this would probably have a test, but there is probably lots of functionality needing tests in this code.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.