Index: password_policy.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.admin.inc,v retrieving revision 1.11 diff -u -p -r1.11 password_policy.admin.inc --- password_policy.admin.inc 31 Jul 2009 16:24:51 -0000 1.11 +++ password_policy.admin.inc 10 Mar 2010 17:17:25 -0000 @@ -113,66 +113,58 @@ function password_policy_admin_settings_ } } +function _password_policy_admin_list_roles($pid) { + $roles = array(); + $result = db_query('SELECT name FROM {role} r INNER JOIN {password_policy_role} p ON r.rid = p.rid WHERE p.pid = %d', $pid); + while ($row = db_fetch_object($result)) { + $roles[] = $row->name; + } + return $roles; +} + /** * The list of the password policies. */ function password_policy_admin_list() { - $header = array(t('Default'), array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'), t('Enabled'), array('data' => t('Operations'), 'colspan' => 3)); $form = array(); $options = array(); - $result = db_query('SELECT pid, name, enabled, description, created FROM {password_policy}'. tablesort_sql($header)); + $enabled = array(); + $result = db_query('SELECT pid, name, enabled, description, created, weight FROM {password_policy} ORDER BY weight'); while ($row = db_fetch_array($result)) { $pid = $row['pid']; - $options[$pid] = ''; + $options[$pid] = $row['enabled'] ? format_date($row['created'], 'medium') : ''; if ($row['enabled']) { - $default_pid = $pid; - $form[$pid]['created'] = array('#value' => format_date($row['created'], 'medium')); + $enabled[] = $pid; } $form[$pid]['name'] = array('#value' => $row['name']); + $form[$pid]['roles'] = array('#value' => theme('item_list', _password_policy_admin_list_roles($pid))); + $form['weight'][$pid] = array('#type' => 'weight', '#default_value' => $row['weight']); $form[$pid]['view'] = array('#value' => l(t('view'), 'admin/settings/password_policy/'. $pid)); $form[$pid]['edit'] = array('#value' => l(t('edit'), 'admin/settings/password_policy/'. $pid .'/edit')); $form[$pid]['delete'] = array('#value' => l(t('delete'), 'admin/settings/password_policy/delete/'. $pid)); } - $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => isset($default_pid) ? $default_pid : 0); - $form['submit'] = array('#type' => 'submit', '#value' => t('Set default policy')); - $form['clear'] = array('#type' => 'submit', '#value' => t('Clear default policy')); + $form['weight']['#tree'] = TRUE; + $form['enabled'] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $enabled); + $form['submit'] = array('#type' => 'submit', '#value' => t('Save changes')); return $form; } /** - * Submit hook for the form on the default list view for the password policy module. From the - * default view, the user can set a new default password policy or clear the default so - * that no policy is active and the default drupal password mechanism takes affect. + * Submit hook for the form on the default list view for the password policy module. */ function password_policy_admin_list_submit($form, &$form_state) { - switch ($form_state['clicked_button']['#value']) { - case "Clear default policy": - _password_policy_admin_clear_default(); - drupal_set_message(t('No policy is active, all user passwords will be accepted (Drupal default).')); - break; - case "Set default policy": - $pid = $form_state['values']['default']; - if (is_numeric($pid)) { - $policy = _password_policy_load_policy_by_pid($pid); - if ($policy) { - _password_policy_admin_clear_default(); - $time = time(); - db_query("UPDATE {password_policy} SET enabled = %d, created = %d WHERE pid = %d", 1, $time, $pid); - drupal_set_message(t('%name has been set as the default password policy.', array('%name' => $policy['name']))); - } - } - else { - drupal_set_message(t('Default password policy was not set.'), 'warning'); - } - break; + foreach ($form_state['values']['enabled'] as $pid => $enabled) { + db_query("UPDATE {password_policy} SET weight = %d WHERE pid = %d", $form_state['values']['weight'][$pid], $pid); + // Ensure we don't reset the timestamp on policies that are already enabled. + if ($enabled) { + db_query("UPDATE {password_policy} SET enabled = 1, created = %d WHERE pid = %d AND enabled = 0", time(), $pid); + } + else { + db_query("UPDATE {password_policy} SET enabled = 0 WHERE pid = %d", $pid); + } } -} -/** - * Resets the enabled flag for all policies in the database to 0. - */ -function _password_policy_admin_clear_default() { - db_query("UPDATE {password_policy} SET enabled = %d WHERE enabled = %d", 0, 1); + drupal_set_message(t('The changes have been saved.')); } /** @@ -182,6 +174,10 @@ function password_policy_admin_view($pol $output = check_plain($policy['description']); $header = array(t('Name'), t('Constraint')); $rows = array(); + $roles = _password_policy_admin_list_roles($policy['pid']); + if (!empty($roles)) { + $rows[] = array(t('Roles'), theme('item_list', $roles)); + } if (!empty($policy['expiration'])) { $rows[] = array(t('Expiration'), $policy['expiration']); } @@ -216,6 +212,21 @@ function password_policy_admin_form($for '#title' => t('Description'), '#default_value' => $policy['description'], ); + + $form['roles'] = array( + '#type' => 'fieldset', + '#title' => t('Roles'), + '#collapsible' => TRUE, + '#collapsed' => FALSE + ); + $form['roles']['roles'] = array( + '#type' => 'checkboxes', + '#title' => t('Roles'), + '#options' => user_roles(), + '#default_value' => isset($policy['roles']) ? $policy['roles'] : array(), + '#description' => t('Select the roles that this policy will apply to.'), + ); + $form['expiration'] = array( '#type' => 'fieldset', '#title' => t('Expiration'), @@ -276,6 +287,16 @@ function password_policy_admin_form($for } /** + * Form validation hook for new or edited password policies. + */ +function password_policy_admin_form_validate($form, &$form_state) { + $roles = array_filter($form_state['values']['roles']); + if (empty($roles)) { + form_set_error('roles', t('You must select at least one role for a policy to apply to.')); + } +} + +/** * Form submission hook for new or edited password policies. */ function password_policy_admin_form_submit($form, &$form_state) { @@ -299,6 +320,8 @@ function password_policy_admin_form_subm db_query("UPDATE {password_policy} SET name = '%s', description = '%s', policy = '%s', expiration = %d, warning = '%s' WHERE pid = %d", $form_state['values']['name'], $form_state['values']['description'], serialize($policy), trim($form_state['values']['expiration']), str_replace(' ', '', $form_state['values']['warning']), $form_state['values']['pid']); drupal_set_message(t('Policy %name has been updated.', array('%name' => $form_state['values']['name']))); watchdog('password_policy', 'Policy %name updated.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/settings/password_policy/'. $form_state['values']['pid'] .'/edit')); + $pid = $form_state['values']['pid']; + db_query("DELETE FROM {password_policy_role} WHERE pid = %d", $pid); } else { db_query("INSERT INTO {password_policy} (name, description, enabled, policy, expiration, warning) VALUES ('%s', '%s', %d, '%s', %d, '%s')", $form_state['values']['name'], $form_state['values']['description'], 0, serialize($policy), trim($form_state['values']['expiration']), str_replace(' ', '', $form_state['values']['warning'])); @@ -307,6 +330,10 @@ function password_policy_admin_form_subm watchdog('password_policy', 'New policy %name created.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/settings/password_policy/'. $pid .'/edit')); } + foreach (array_filter($form_state['values']['roles']) as $rid => $enabled) { + db_query("INSERT INTO {password_policy_role} (pid, rid) VALUES (%d, %d)", $pid, $rid); + } + drupal_goto('admin/settings/password_policy/list'); } @@ -334,6 +361,7 @@ function password_policy_admin_delete_su $policy = _password_policy_load_policy_by_pid($pid); db_query("DELETE FROM {password_policy} WHERE pid = %d", $pid); + db_query("DELETE FROM {password_policy_role} WHERE pid = %d", $pid); drupal_set_message(t('Password policy %policy was deleted.', array('%policy' => $policy['name']))); watchdog('password_policy', 'Policy %name was deleted.', array('%name' => $policy['name']), WATCHDOG_NOTICE); drupal_goto('admin/settings/password_policy/list'); Index: password_policy.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.install,v retrieving revision 1.7 diff -u -p -r1.7 password_policy.install --- password_policy.install 26 Oct 2008 11:21:06 -0000 1.7 +++ password_policy.install 10 Mar 2010 17:17:25 -0000 @@ -35,6 +35,46 @@ function password_policy_uninstall() { variable_del('password_policy_warning_body'); } +function password_policy_update_6000() { + $ret = array(); + + db_add_field($ret, 'password_policy', 'weight', array( + 'description' => t("Weight of the policy, used to order active policies."), + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + )); + + db_create_table($ret, 'password_policy_role', array( + 'description' => t("Links policies with roles."), + 'fields' => array( + 'rid' => array( + 'description' => t("Role ID."), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'pid' => array( + 'description' => t("Policy ID."), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('rid', 'pid'), + )); + + // Ensure existing policies are active for all roles. + $result = db_query("SELECT pid FROM {password_policy}"); + while ($row = db_fetch_object($result)) { + $ret[] = update_sql("INSERT INTO {password_policy_role} (pid, rid) VALUES (". $row->pid .", ". DRUPAL_AUTHENTICATED_RID .")"); + $ret[] = update_sql("INSERT INTO {password_policy_role} (pid, rid) VALUES (". $row->pid .", ". DRUPAL_ANONYMOUS_RID .")"); + } + + return $ret; +} + ////////////////////////////////////////////////////////////////////////////// // Schema API hooks @@ -92,6 +132,13 @@ function password_policy_schema() { 'type' => 'varchar', 'length' => 64, ), + 'weight' => array( + 'description' => t("Weight of the policy, used to order active policies."), + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), ), 'primary key' => array('pid'), ), @@ -153,6 +200,25 @@ function password_policy_schema() { 'primary key' => array('pid'), 'indexes' => array('uid' => array('uid')), ), + 'password_policy_role' => array( + 'description' => t("Links policies with roles."), + 'fields' => array( + 'rid' => array( + 'description' => t("Role ID."), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'pid' => array( + 'description' => t("Policy ID."), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('rid', 'pid'), + ), ); -} + return $schema; +} Index: password_policy.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.module,v retrieving revision 1.25 diff -u -p -r1.25 password_policy.module --- password_policy.module 9 Feb 2010 14:15:18 -0000 1.25 +++ password_policy.module 10 Mar 2010 17:17:26 -0000 @@ -220,7 +220,8 @@ function password_policy_user($op, &$edi } break; case "login": - $policy = _password_policy_load_active_policy(); + $roles = is_array($account->roles) ? array_keys($account->roles) : array(); + $policy = _password_policy_load_active_policy($roles); // A value $edit['name'] is NULL for a one time login if ($policy && ($account->uid > 1 || PASSWORD_POLICY_ADMIN) && !empty($edit['name'])) { // Calculate expiration and warning times. @@ -229,7 +230,10 @@ function password_policy_user($op, &$edi $expiration_seconds = $expiration*60*60*24; $warning_seconds = $warning*60*60*24; // The policy was enabled - $policy_start = _password_policy_start($expiration_seconds); + $policy_start = $policy['created']; + if (PASSWORD_POLICY_BEGIN == 1) { + $policy_start -= $expiration_seconds; + } if (!empty($expiration)) { // Account expiration is active. // Get the last password change time. @@ -301,7 +305,9 @@ function password_policy_form_alter(&$fo // Password change form. $uid = isset($form['#uid']) ? $form['#uid'] : NULL; //if ($uid == 1 && !PASSWORD_POLICY_ADMIN) { break; } - $policy = _password_policy_load_active_policy(); + $roles = isset($form['_account']['#value']) ? array_keys($form['_account']['#value']->roles) : array(); + $policy = _password_policy_load_active_policy($roles); + $translate = array(); if (!empty($policy['policy'])) { // Some policy constraints are active. @@ -327,34 +333,48 @@ function password_policy_form_alter(&$fo * Implementation of hook_cron(). */ function password_policy_cron() { - $policy = _password_policy_load_active_policy(); - if ($policy) { - $expiration = $policy['expiration']; - $warnings = !empty($policy['warning']) ? explode(',', $policy['warning']) : array(); - if (!empty($expiration)) { - // Accounts expiration is active. - // Get all users' last password change time. We don't touch blocked accounts - $result = db_query("SELECT u.uid AS uid, u.created AS created_u, p.created AS created_p, e.pid AS pid, e.warning AS warning, e.unblocked AS unblocked FROM {users} u LEFT JOIN {password_policy_history} p ON u.uid = p.uid LEFT JOIN {password_policy_expiration} e ON u.uid = e.uid WHERE u.uid > 0 AND u.status = '1' ORDER BY p.created ASC"); - while ($row = db_fetch_object($result)) { - if ($row->uid == 1 && !PASSWORD_POLICY_ADMIN) - continue; + // Short circuit if no policies are active that use expiration. + if (!db_result(db_query("SELECT COUNT(*) FROM {password_policy} WHERE enabled = 1 AND expiration > 0"))) { + return; + } - // Use account creation timestamp if there is no entry in password history table. - $accounts[$row->uid] = empty($row->created_p) ? $row->created_u : $row->created_p; - // Last time a warning was mailed out (if was). We need it because we send warnings only once a day, not on all cron runs. - $warns[$row->uid] = $row->warning; - // The user was last time unblocked (if was). We don't block this account again for some period of time. - $unblocks[$row->uid] = $row->unblocked; - // The user was last time unblocked (if was). We don't block this account again for some period of time. - $pids[$row->uid] = $row->pid; + // Get all users' last password change time. We don't touch blocked accounts + $result = db_query("SELECT u.uid AS uid, u.created AS created_u, p.created AS created_p, e.pid AS pid, e.warning AS warning, e.unblocked AS unblocked FROM {users} u LEFT JOIN {password_policy_history} p ON u.uid = p.uid LEFT JOIN {password_policy_expiration} e ON u.uid = e.uid WHERE u.uid > 0 AND u.status = '1' ORDER BY p.created ASC"); + while ($row = db_fetch_object($result)) { + if ($row->uid == 1 && !PASSWORD_POLICY_ADMIN) + continue; + + // Use account creation timestamp if there is no entry in password history table. + $accounts[$row->uid] = empty($row->created_p) ? $row->created_u : $row->created_p; + // Last time a warning was mailed out (if was). We need it because we send warnings only once a day, not on all cron runs. + $warns[$row->uid] = $row->warning; + // The user was last time unblocked (if was). We don't block this account again for some period of time. + $unblocks[$row->uid] = $row->unblocked; + // The user was last time unblocked (if was). We don't block this account again for some period of time. + $pids[$row->uid] = $row->pid; + } + + if ($accounts) { + foreach ($accounts as $uid => $last_change) { + /* Alternative: $result = db_query("SELECT p.* FROM {password_policy} p INNER JOIN {password_policy_role} r ON p.pid = r.pid INNER JOIN {users_roles} u ON r.rid = u.rid WHERE p.enabled = 1 AND u.uid = %d ORDER BY p.weight LIMIT 1", $uid); */ + $roles = array(DRUPAL_AUTHENTICATED_RID); + $result = db_query("SELECT rid FROM {users_roles} WHERE uid = %d ORDER BY rid", $uid); + while ($row = db_fetch_object($result)) { + $roles[] = $row->rid; } - // Calculate expiration time. - $expiration_seconds = $expiration*60*60*24; - $policy_start = _password_policy_start($expiration_seconds); - rsort($warnings, SORT_NUMERIC); - $time = time(); - if ($accounts) { - foreach ($accounts as $uid => $last_change) { + $policy = _password_policy_load_active_policy($roles); + if ($policy) { + $expiration = $policy['expiration']; + $warnings = !empty($policy['warning']) ? explode(',', $policy['warning']) : array(); + if (!empty($expiration)) { + // Calculate expiration time. + $expiration_seconds = $expiration*60*60*24; + $policy_start = $policy['created']; + if (PASSWORD_POLICY_BEGIN == 1) { + $policy_start -= $expiration_seconds; + } + rsort($warnings, SORT_NUMERIC); + $time = time(); // Check expiration and warning days for each account. if (!empty($warnings)) { foreach ($warnings as $warning) { @@ -572,7 +592,8 @@ function password_policy_mail_tokens($pa */ function _password_policy_constraint_validate($pass, &$account) { $error = NULL; - $policy = _password_policy_load_active_policy(); + $roles = is_array($account->roles) ? array_keys($account->roles) : array(); + $policy = _password_policy_load_active_policy($roles); if (!empty($policy['policy'])) { foreach ($policy['policy'] as $key => $value) { if (!call_user_func('password_policy_constraint_'. $key .'_validate', $pass, $value, $account->uid)) { @@ -648,49 +669,46 @@ function _password_policy_load_policy_by if (is_array($row)) { // fetch and unserialize the serialized policy $row['policy'] = unserialize($row['policy']); + // Fetch roles + $row['roles'] = array(); + $result = db_query('SELECT rid FROM {password_policy_role} WHERE pid = %d', $pid); + while ($role = db_fetch_object($result)) { + $row['roles'][$role->rid] = $role->rid; + } return $row; } return NULL; } /** - * Loads the default (enabled and active) policy. - * - * @return - * A policy array, or NULL if no active policy exists. - */ -function _password_policy_load_active_policy() { - $result = db_query('SELECT * FROM {password_policy} p WHERE p.enabled = 1'); - $row = db_fetch_array($result); - if (is_array($row)) { - // fetch and unserialize the serialized policy - $row['policy'] = unserialize($row['policy']); - return $row; - } - return NULL; -} - -/** - * Returns a timestamp when the policy was enabled. - * It might be pushed back by the expiration time for the retroactive behaviour - * based on the configuration options. + * Loads the first enabled policy that matches the specified roles. * - * @param $expiration - * Expiration time in seconds. + * @param $roles + * An array of role IDs. * * @return - * A timestamp when a policy was enabled + * A policy array, or NULL if no active policy exists. */ -function _password_policy_start($expiration = 0) { - $result = db_query_range("SELECT created FROM {password_policy} WHERE enabled = 1 ORDER BY enabled DESC", 0, 1); - if ($row = db_fetch_object($result)) { - $policy_start = $row->created; - } - if (PASSWORD_POLICY_BEGIN == 1) { - // Password older than expiration time expires starting from setting the policy. - $policy_start -= $expiration; +function _password_policy_load_active_policy($roles) { + static $cache = array(); + if (empty($roles)) { + $roles = array(DRUPAL_ANONYMOUS_RID); + } + $key = implode(',', $roles); + // Use array_key_exists() instead of isset() as NULLs may be in the array. + if (!array_key_exists($key, $cache)) { + $result = db_query('SELECT p.* FROM {password_policy} p INNER JOIN {password_policy_role} r ON p.pid = r.pid WHERE p.enabled = 1 AND r.rid IN (' . db_placeholders($roles) . ') ORDER BY p.weight LIMIT 1', $roles); + $row = db_fetch_array($result); + if (is_array($row)) { + // fetch and unserialize the serialized policy + $row['policy'] = unserialize($row['policy']); + $cache[$key] = $row; + } + else { + $cache[$key] = NULL; + } } - return $policy_start; + return $cache[$key]; } /** Index: password_policy.theme.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.theme.inc,v retrieving revision 1.3 diff -u -p -r1.3 password_policy.theme.inc --- password_policy.theme.inc 6 Nov 2008 10:50:39 -0000 1.3 +++ password_policy.theme.inc 10 Mar 2010 17:17:26 -0000 @@ -19,9 +19,10 @@ function theme_password_policy_admin_lis foreach ($form as $pid => $element) { if (isset($element['edit']) && is_array($element['edit'])) { $rows[] = array( - drupal_render($form['default'][$pid]), check_plain($form[$pid]['name']['#value']), - drupal_render($form[$pid]['created']), + $form[$pid]['roles']['#value'], + drupal_render($form['enabled'][$pid]), + drupal_render($form['weight'][$pid]), drupal_render($form[$pid]['view']), drupal_render($form[$pid]['edit']), drupal_render($form[$pid]['delete']) @@ -34,7 +35,7 @@ function theme_password_policy_admin_lis unset($form['submit']); unset($form['clear']); } - $header = array(t('Default'), array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'), t('Enabled'), array('data' => t('Operations'), 'colspan' => 3)); + $header = array(t('Policy'), t('Roles'), t('Enabled'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3)); $output = theme('table', $header, $rows); $output .= drupal_render($form);