For Drupal running over PostgreSQL, I've found that on certain conditions the expiration warnings are sent multiple times.
This seems to depend on the way password policy get the data from the database in the function password_policy_cron() in password_policy.module:561:
// Get all users' last password change time. We don't touch blocked accounts.
$query = db_select('users', 'u', array('target' => 'slave'));
$query->leftJoin('password_policy_history', 'p', 'u.uid = p.uid');
$query->leftJoin('password_policy_expiration', 'e', 'u.uid = e.uid');
$result = $query->fields('u', array('uid', 'created'))
->fields('p', array('created'))
->fields('e', array('pid', 'unblocked', 'warning'))
->condition('u.uid', 0, '>')
->condition('u.status', 1)
->orderBy('p.created')
->execute();
This code results in the following query:
SELECT u.uid AS uid, u.created AS created, p.created AS p_created, e.pid AS pid, e.unblocked AS unblocked, e.warning AS warning
FROM
drupal_users u
LEFT OUTER JOIN drupal_password_policy_history p ON u.uid = p.uid
LEFT OUTER JOIN drupal_password_policy_expiration e ON u.uid = e.uid
WHERE (u.uid > 1) AND (u.status = 1) ORDER BY p.created;
This query will not produce the expected results in every situation. For example, I report the last lines for an user in our database:
uid | created | p_created | pid | unblocked | warning
-----+------------+------------+-----+-----------+------------
92 | 1424875056 | 1473749500 | | | 1481108701
92 | 1424875056 | 1473749500 | | | 1480247401
92 | 1424875056 | 1473749500 | | | 1480243801
92 | 1424875056 | 1473749500 | | | 1438788782
92 | 1424875056 | 1473749500 | | | 1438810385
92 | 1424875056 | 1473749500 | | | 1481105101
92 | 1424875056 | 1473749500 | | | 1480240201
92 | 1424875056 | 1473749500 | | | 1438828382
92 | 1424875056 | 1473749500 | | | 1480236601
92 | 1424875056 | 1473749500 | | | 1438795982
92 | 1424875056 | 1473749500 | | | 1452877718
92 | 1424875056 | 1473749500 | | | 1480233001
92 | 1424875056 | 1473749500 | | | 1452527842
As you can see, the results are only ordered by p_created column. We need to order them also for the warning column, to obtain the last time a expiration warning was sent for the user.
In the above situation, if the cron run more than a time during the day, the expiration warning is sent more than once.
The proposed solution is to add the column e.warning for ordering the mentioned query.
// Get all users' last password change time. We don't touch blocked accounts.
$query = db_select('users', 'u', array('target' => 'slave'));
$query->leftJoin('password_policy_history', 'p', 'u.uid = p.uid');
$query->leftJoin('password_policy_expiration', 'e', 'u.uid = e.uid');
$result = $query->fields('u', array('uid', 'created'))
->fields('p', array('created'))
->fields('e', array('pid', 'unblocked', 'warning'))
->condition('u.uid', 0, '>')
->condition('u.status', 1)
->orderBy('p.created, e.warning')
->execute();
Regards,
Comment | File | Size | Author |
---|---|---|---|
#5 | password_policy-7.x-1.x-fix_multiple_warnings_mail-2833776-5.patch | 376 bytes | AohRveTPV |
#2 | password_policy-fix-multiple-warnings-mail-2833776-2.patch | 752 bytes | mithenks |
|
Comments
Comment #2
mithenks CreditAttribution: mithenks commentedHere is my patch for 7.x-1.x
Comment #4
mithenks CreditAttribution: mithenks commentedThe patch submitted on comment #2 fails the test against Drupal 7.43, but it works well with Drupal 7.52/7.53 .
Furthermore, the actual 7.x-1.x-dev fails test against Drupal 7.52/7.53.
Comment #5
AohRveTPV CreditAttribution: AohRveTPV commentedChanged to use multiple
orderBy()
calls to sort on multiple fields. From https://api.drupal.org/api/drupal/includes!database!select.inc/function/..., it looks like it is the preferred way to do that. mithenks, is it easy for you to test that this patch also works?Thanks for the bug report and fix. I have not tried reproducing the problem but it makes sense that the order of the warnings column is not guaranteed by the unpatched code.
Comment #7
AohRveTPV CreditAttribution: AohRveTPV commentedFeel confident my patch is functionally equivalent, so I've gone ahead and committed. Thanks for the bug report and patch.
The tests failed before likely due to #2857748: Password expiration tests do not reliably pass.