password_policy.module in Password Policy 7
Same filename and directory in other branches
Allows enforcing restrictions on user passwords by defining policies.
File
password_policy.moduleView source
<?php
/**
* @file
* Allows enforcing restrictions on user passwords by defining policies.
*/
/****************************************************************************/
include_once 'password_policy.authmap.inc';
include_once 'password_policy.time.inc';
define('PASSWORD_POLICY_DEFAULT_WARNING_SUBJECT', 'Password expiration warning for [user:name] at [site:name]');
define('PASSWORD_POLICY_DEFAULT_WARNING_BODY', "[user:name],\n\nYour password at [site:name] will expire in less than [password-policy:days-left] day(s).\n\nPlease go to [password-policy:password-edit-url] to change your password.");
/****************************************************************************/
/* Core API hooks */
/****************************************************************************/
/**
* Implements hook_help().
*/
function password_policy_help($path, $arg) {
switch ($path) {
case 'admin/help#password_policy':
return '<p>' . t('The Password Policy module allows you to enforce a specific level of password complexity for the user passwords on the system.') . '</p>';
}
}
/**
* Implements hook_init().
*/
function password_policy_init() {
global $user;
// Check password reset status and force a reset if needed.
if (_password_policy_is_password_change_forced($user->uid) && !_password_policy_is_path_allowed_when_password_change_forced()) {
_password_policy_set_password_change_forced_message();
_password_policy_go_to_password_change_page();
}
}
/**
* Implements hook_permission().
*/
function password_policy_permission() {
return array(
'administer password policies' => array(
'title' => t('Administer policies'),
),
'unblock expired accounts' => array(
'title' => t('Unlock expired accounts'),
),
'force password change' => array(
'title' => t('Force password change'),
),
);
}
/**
* Implements hook_theme().
*/
function password_policy_theme() {
return array(
'password_policy_admin_list' => array(
'render element' => 'form',
'file' => 'password_policy.admin.inc',
),
);
}
/**
* Implements hook_menu().
*/
function password_policy_menu() {
$items['admin/config/people/password_policy'] = array(
'title' => 'Password policies',
'description' => 'Configures policies for user account passwords.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'password_policy_admin_settings',
),
'access arguments' => array(
'administer password policies',
),
'file' => 'password_policy.admin.inc',
);
$items['admin/config/people/password_policy/configure'] = array(
'title' => 'Settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/config/people/password_policy/list'] = array(
'title' => 'List',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'password_policy_admin_list',
),
'access arguments' => array(
'administer password policies',
),
'weight' => 1,
'file' => 'password_policy.admin.inc',
);
$items['admin/config/people/password_policy/add'] = array(
'title' => 'Add',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'password_policy_admin_form',
NULL,
),
'access arguments' => array(
'administer password policies',
),
'weight' => 2,
'file' => 'password_policy.admin.inc',
);
$items['admin/config/people/password_policy/%password_policy_policy'] = array(
'title' => 'Password policy',
'title callback' => 'password_policy_format_title',
'title arguments' => array(
4,
),
'page callback' => 'password_policy_admin_view',
'page arguments' => array(
4,
),
'access arguments' => array(
'administer password policies',
),
'file' => 'password_policy.admin.inc',
);
$items['admin/config/people/password_policy/%password_policy_policy/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/config/people/password_policy/%password_policy_policy/edit'] = array(
'title' => 'Edit',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'password_policy_admin_form',
4,
),
'access arguments' => array(
'administer password policies',
),
'weight' => 1,
'file' => 'password_policy.admin.inc',
);
$items['admin/config/people/password_policy/%password_policy_policy/delete'] = array(
'title' => 'Delete',
'type' => MENU_CALLBACK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'password_policy_admin_delete',
4,
),
'access arguments' => array(
'administer password policies',
),
'file' => 'password_policy.admin.inc',
);
$items['admin/config/people/password_policy/password_change'] = array(
'title' => 'Force password change',
'description' => 'Force users to change their password',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'password_policy_password_change_settings',
),
'access arguments' => array(
'force password change',
),
'file' => 'password_policy.admin.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 10,
);
$items['admin/people/expired'] = array(
'title' => 'Expired accounts',
'type' => MENU_LOCAL_TASK,
'description' => 'Lists all expired accounts.',
'page callback' => 'password_policy_expired_list',
'page arguments' => array(
'password_policy_list_expired',
),
'access arguments' => array(
'unblock expired accounts',
),
);
$items['admin/people/expired/unblock/%password_policy_uid'] = array(
'title' => 'Unblock',
'type' => MENU_CALLBACK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'password_policy_expired_unblock_confirm',
4,
),
'access arguments' => array(
'unblock expired accounts',
),
);
return $items;
}
/**
* Implements hook_drupal_goto_alter().
*/
function password_policy_drupal_goto_alter(&$path, &$options) {
global $user;
// If user is using a one-time login link, force a password change
// immediately.
if ($user->uid && isset($options['query']['pass-reset-token']) && variable_get('password_policy_force_change_reset', 0) && isset($_SESSION['pass_reset_' . $user->uid])) {
db_merge('password_policy_force_change')
->key(array(
'uid' => $user->uid,
))
->fields(array(
'force_change' => 1,
))
->execute();
}
}
/**
* Loads policy array from the database.
*
* @param int $pid
* The policy id.
*
* @return array|false
* A populated policy array or FALSE if not found.
*/
function password_policy_policy_load($pid) {
return _password_policy_load_policy_by_pid($pid);
}
/**
* Loads user object from the database.
*
* @param int $uid
* The user id.
*
* @return object|false
* A populated user object or FALSE if not found.
*/
function password_policy_uid_load($uid) {
if (is_numeric($uid)) {
$account = user_load($uid);
if ($account) {
return $account;
}
}
return FALSE;
}
/**
* Displays a password policy form title.
*
* @param array $policy
* Policy array.
*
* @return string
* A policy's title string
*/
function password_policy_format_title(array $policy) {
return $policy['name'];
}
/**
* Implements hook_user_load().
*/
function password_policy_user_load($users) {
foreach ($users as $uid => $user) {
$user->force_password_change = _password_policy_is_password_change_forced($uid);
if (empty($user->force_password_change)) {
$blocked = db_select('password_policy_expiration', 'p', array(
'target' => 'slave',
))
->fields('p', array(
'blocked',
))
->condition('uid', $user->uid)
->execute()
->fetchField();
if (!empty($blocked)) {
if ($blocked <= _password_policy_get_request_time()) {
$user->force_password_change = 1;
}
}
}
}
}
/**
* Implements hook_user_insert().
*/
function password_policy_user_insert(&$edit, $account, $category) {
$force = isset($edit['force_password_change']) ? $edit['force_password_change'] : variable_get('password_policy_new_login_change', 0);
db_insert('password_policy_force_change')
->fields(array(
'uid' => $account->uid,
'force_change' => $force,
))
->execute();
if (!empty($edit['pass'])) {
// New users do not yet have an uid during the validation step, but they do
// have at this insert step. Store their first password in the system for
// use with the history constraint (if used).
if ($account->uid) {
_password_policy_store_password($account->uid, $edit['pass']);
}
}
}
/**
* Implements hook_user_presave().
*
* Adds entry to password history when password is changed for a user. This
* should work whether the password is changed via the User module forms or
* programmatically via user_save().
*/
function password_policy_user_presave(&$edit, $account, $category) {
// If there is a pass value...
if (!empty($edit['pass'])) {
// And if this is not a newly created user...
if (!$account->is_new) {
// And if the pass value is not the same as before...
if ($edit['pass'] != $account->pass) {
// Then store the password hash to history.
_password_policy_store_password($account->uid, $edit['pass']);
}
}
}
}
/**
* Implements hook_user_update().
*/
function password_policy_user_update(&$edit, $account, $category) {
global $user;
// If the user is being forced to change their password and is changing their
// password, toggle the force_change field off.
if (isset($account->original->force_password_change) && $account->original->force_password_change && isset($edit['pass'])) {
db_update('password_policy_force_change')
->fields(array(
'force_change' => 0,
))
->condition('uid', $account->uid)
->execute();
db_delete('password_policy_expiration')
->condition('uid', $account->uid)
->execute();
}
elseif (!empty($edit['force_password_change'])) {
// Check if user already has a force change entry.
// If any users were created after this module was enabled, they will not
// yet have an entry in this table.
$user_exists = db_select('password_policy_force_change')
->condition('uid', $account->uid)
->countQuery()
->execute()
->fetchField();
if ($user_exists == 0) {
db_insert('password_policy_force_change')
->fields(array(
'uid' => $account->uid,
'force_change' => 1,
))
->execute();
}
else {
db_update('password_policy_force_change')
->fields(array(
'force_change' => 1,
))
->condition('uid', $account->uid)
->execute();
}
if ($user->uid != $account->uid) {
drupal_set_message(t('@user will be required to change their password the next time they log in.', array(
'@user' => $account->name,
)));
}
if ($user->uid) {
watchdog('password_policy', '@user flagged to change password on next login by @admin.', array(
'@user' => $account->name,
'@admin' => $user->name,
), WATCHDOG_NOTICE);
}
else {
// The user may be updated programmatically (e.g., via Drupal cron). In
// this case, there is no administrator forcing the password change.
watchdog('password_policy', '@user flagged to change password on next login.', array(
'@user' => $account->name,
), WATCHDOG_NOTICE);
}
}
elseif (isset($edit['force_password_change'])) {
db_update('password_policy_force_change')
->fields(array(
'force_change' => 0,
))
->condition('uid', $account->uid)
->execute();
}
if (_password_policy_was_updated_user_unblocked($account)) {
_password_policy_handle_unblock($account);
}
}
/**
* Determines whether updated user was unblocked.
*
* @param object $account
* User object.
*/
function _password_policy_was_updated_user_unblocked($account) {
return $account->status == 1 && isset($account->original) && $account->original->status == 0;
}
/**
* Implements hook_user_login().
*/
function password_policy_user_login(&$edit, $account) {
$roles = is_array($account->roles) ? array_keys($account->roles) : array();
$policy = _password_policy_load_active_policy($roles, $account);
// A value $edit['name'] is NULL for a one time login.
if ($policy && (!empty($account->uid) && $account->uid > 1 || variable_get('password_policy_admin', 1)) && !empty($edit['values']['name'])) {
// Calculate expiration and warning times.
$expiration = $policy['expiration'];
$warning = empty($policy['warning']) ? 0 : max(explode(',', $policy['warning']));
$expiration_seconds = $expiration * (60 * 60 * 24);
$warning_seconds = $warning * (60 * 60 * 24);
// The policy was enabled.
$policy_start = $policy['created'];
if (variable_get('password_policy_begin', 0) == 1) {
$policy_start -= $expiration_seconds;
}
if (!empty($expiration)) {
// Account expiration is active.
// Get the last password change time.
$last_change = db_query_range('SELECT created FROM {password_policy_history} WHERE uid = :uid ORDER BY created DESC', 0, 1, array(
':uid' => $account->uid,
))
->fetchField();
if (empty($last_change)) {
// User has not changed their password since this module was enabled.
$last_change = _password_policy_get_user_created_time($account);
}
$time = _password_policy_get_request_time();
if ($time > max($policy_start, $last_change) + $expiration_seconds) {
if (variable_get('password_policy_block', 0) == 0) {
$cron_blocked = db_query_range('SELECT blocked FROM {password_policy_expiration} WHERE uid = :uid ORDER BY blocked DESC', 0, 1, array(
':uid' => $account->uid,
))
->fetchField();
if ($cron_blocked > _password_policy_get_user_login_time($account)) {
// User is blocked immediately and cannot change their password
// after expiration.
_password_policy_block_account($account);
}
}
else {
// Redirect user and let password force change handle.
db_update('password_policy_force_change')
->fields(array(
'force_change' => 1,
))
->condition('uid', $account->uid)
->execute();
_password_policy_set_password_change_forced_message();
_password_policy_go_to_password_change_page();
}
}
elseif ($time > max($policy_start, $last_change) + $expiration_seconds - $warning_seconds) {
// The warning is shown on login and the user is transferred to the
// password change page.
$days_left = ceil((max($policy_start, $last_change) + $expiration_seconds - $time) / (60 * 60 * 24));
drupal_set_message(format_plural($days_left, 'Your password will expire in less than one day. Please change it.', 'Your password will expire in less than @count days. Please change it.'));
_password_policy_go_to_password_change_page();
}
}
}
}
/**
* Implements hook_user_delete().
*/
function password_policy_user_delete($account) {
$txn = db_transaction();
// Ensure all deletes occur.
try {
db_delete('password_policy_history')
->condition('uid', $account->uid)
->execute();
db_delete('password_policy_expiration')
->condition('uid', $account->uid)
->execute();
db_delete('password_policy_force_change')
->condition('uid', $account->uid)
->execute();
} catch (Exception $e) {
// Something went wrong somewhere, so roll back now.
$txn
->rollback();
// Log the exception to watchdog.
watchdog_exception('type', $e);
}
}
/**
* Implements hook_form_alter().
*/
function password_policy_form_alter(&$form, &$form_state, $form_id) {
global $user;
if (_password_policy_has_account_password_element($form)) {
// Timing issues require reloading the user object to get the
// password_change property set.
$account = user_load($user->uid);
// Force password change on user account.
if (user_access('force password change')) {
if (isset($form['#user_category']) && $form['#user_category'] == 'account') {
$force_change = db_query_range('SELECT force_change FROM {password_policy_force_change} WHERE uid=:uid', 0, 1, array(
':uid' => $form['#user']->uid,
))
->fetchField();
// If we didn't get a valid result, use the default.
if (is_null($force_change) || $force_change === FALSE) {
$force_change = variable_get('password_policy_new_login_change', 0);
}
$form['password_policy'] = array(
'#type' => 'fieldset',
'#title' => t('Password settings'),
);
$form['password_policy']['force_password_change'] = array(
'#type' => 'checkbox',
'#title' => t('Force password change on next login'),
'#description' => t('If already logged in, the user will be forced to change their password upon their next page request.'),
'#default_value' => $force_change,
);
}
}
// Password change form.
$account = $form['#user'];
$roles = isset($account->roles) ? array_keys($account->roles) : array(
DRUPAL_AUTHENTICATED_RID,
);
if ($form_id == 'user_register_form') {
$roles = array(
DRUPAL_AUTHENTICATED_RID,
);
}
$policy = _password_policy_load_active_policy($roles, $account);
$translate = array();
if (!empty($policy['constraints'])) {
// Some policy constraints are active.
password_policy_add_policy_js($policy, $account, $form);
foreach ($policy['constraints'] as $key => $value) {
if ($value) {
$translate['constraint_' . $key] = _password_policy_constraint_error($key, $value);
}
}
}
// Printing out the restrictions.
if (variable_get('password_policy_show_restrictions', 0) && isset($translate) && (isset($form['pass']) || isset($form['account']['pass']))) {
$restriction_html = '<div id="account-pass-restrictions">' . theme('item_list', array(
'items' => array_values($translate),
'title' => t('Password Requirements'),
)) . '</div>';
if (isset($form['account']) && is_array($form['account'])) {
$form['account']['pass']['#prefix'] = $restriction_html;
}
else {
$form['pass']['#prefix'] = $restriction_html;
}
}
// Set a custom form validate and submit handlers.
$form['#validate'][] = 'password_policy_password_validate';
}
if ($form_id == 'password_policy_password_tab') {
$form['submit']['#weight'] = 10;
}
}
/**
* Implements hook_cron().
*/
function password_policy_cron() {
// Short circuit if no policies are active that use expiration.
$expiration_policies = db_select('password_policy', 'p', array(
'target' => 'slave',
))
->condition('enabled', 1)
->condition('expiration', 0, '>')
->countQuery()
->execute()
->fetchField();
if ($expiration_policies == 0) {
return;
}
$accounts = array();
$warns = array();
$unblocks = array();
$pids = array();
// Get all users' last password change time. 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',
'name',
'created',
))
->fields('p', array(
'created',
))
->fields('e', array(
'pid',
'unblocked',
'warning',
))
->condition('u.uid', 0, '>')
->condition('u.status', 1)
->orderBy('p.created')
->orderBy('e.warning')
->execute();
foreach ($result as $row) {
if ($row->uid == 1 && !variable_get('password_policy_admin', 1)) {
continue;
}
// Use account creation timestamp if there is no entry in password history
// table.
$accounts[$row->uid] = empty($row->p_created) ? $row->created : $row->p_created;
// 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;
// Last time user was unblocked (if was). We don't block this account again
// for some period of time.
$unblocks[$row->uid] = $row->unblocked;
// Unique password policy expirations ID.
$pids[$row->uid] = $row->pid;
// Usernames.
$names[$row->uid] = $row->name;
}
foreach ($accounts as $uid => $last_change) {
$roles = array(
DRUPAL_AUTHENTICATED_RID,
);
$result = db_select('users_roles', 'u', array(
'target' => 'slave',
))
->fields('u', array(
'rid',
))
->condition('uid', $uid)
->orderBy('u.rid')
->execute();
foreach ($result as $row) {
$roles[] = $row->rid;
}
$name = $names[$uid];
$dummy_account = (object) array(
'name' => $name,
'uid' => $uid,
);
$policy = _password_policy_load_active_policy($roles, $dummy_account);
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 (variable_get('password_policy_begin', 0) == 1) {
$policy_start -= $expiration_seconds;
}
rsort($warnings, SORT_NUMERIC);
$time = _password_policy_get_request_time();
// Check expiration and warning days for each account.
if (!empty($warnings)) {
foreach ($warnings as $warning) {
// Loop through all configured warning send-out days. If today is
// the day, we send out the warning.
$warning_seconds = $warning * (60 * 60 * 24);
// Warning start time.
$start_period = max($policy_start, $last_change) + $expiration_seconds - $warning_seconds;
// Warning end time. We create a one day window for cron to run.
$end_period = $start_period + 60 * 60 * 24;
if ($warns[$uid] && $warns[$uid] > $start_period && $warns[$uid] < $end_period) {
// A warning was already mailed out.
continue;
}
if ($time > $start_period && $time < $end_period) {
// A warning falls in the one day window, so we send out the
// warning.
$account = user_load($uid, TRUE);
$message = drupal_mail('password_policy', 'warning', $account->mail, user_preferred_language($account), array(
'account' => $account,
'days_left' => $warning,
));
if ($message['result']) {
// The mail was sent out successfully.
watchdog('password_policy', 'Password expiration warning mailed to %username at %email.', array(
'%username' => $account->name,
'%email' => $account->mail,
));
}
if ($pids[$uid]) {
db_update('password_policy_expiration')
->fields(array(
'warning' => $time,
))
->condition('uid', $uid)
->execute();
}
else {
db_insert('password_policy_expiration')
->fields(array(
'uid' => $uid,
'warning' => $time,
))
->execute();
}
}
}
}
if ($time > max($policy_start, $last_change) + $expiration_seconds && $time > $unblocks[$uid] + 60 * 60 * 24 && variable_get('password_policy_block', 0) == 0) {
// Block expired accounts. Unblocked accounts are not blocked for 24h.
// One time login lasts for a 24h.
db_update('users')
->fields(array(
'status' => 0,
))
->condition('uid', $uid)
->execute();
if ($pids[$uid]) {
db_update('password_policy_expiration')
->fields(array(
'blocked' => $time,
))
->condition('uid', $uid)
->execute();
}
else {
db_insert('password_policy_expiration')
->fields(array(
'uid' => $uid,
'blocked' => $time,
))
->execute();
}
$account = user_load($uid, TRUE);
watchdog('password_policy', 'Password for user %name has expired.', array(
'%name' => $account->name,
), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit'));
}
}
}
}
}
/**
* Implements hook_mail().
*/
function password_policy_mail($key, &$message, $params) {
$language = $message['language'];
$variables = array(
'user' => $params['account'],
'days_left' => $params['days_left'],
);
$message['subject'] .= _password_policy_mail_text($key . '_subject', $language, $variables);
$message['body'][] = _password_policy_mail_text($key . '_body', $language, $variables);
}
/**
* Implements hook_field_extra_fields().
*/
function password_policy_field_extra_fields() {
$extra['user']['user'] = array(
'form' => array(
'password_policy' => array(
'label' => t('Password policy'),
'description' => t('Password policy module settings form elements.'),
'weight' => 0,
),
),
);
return $extra;
}
/****************************************************************************/
/* FAPI */
/****************************************************************************/
/**
* Determines whether form has an account password element.
*/
function _password_policy_has_account_password_element($form) {
return isset($form['account']['pass']['#type']) && $form['account']['pass']['#type'] == 'password_confirm';
}
/**
* Password save validate handler.
*/
function password_policy_password_validate($form, &$form_state) {
$account = _password_policy_get_user_from_form($form, $form_state);
if (_password_policy_is_force_password_change_set($account)) {
// Admins can edit accounts without having to reset passwords.
if (_password_policy_password_field_is_empty($form_state) && _password_policy_is_current_user($account)) {
form_set_error('pass', t('Your password has expired. You must change your password to proceed on the site.'));
}
}
// If a password is set and OpenID is not being used, validate password.
$values = $form_state['values'];
if (!empty($values['pass']) && !isset($values['auth_openid'])) {
// Validate length.
// Short-circuit validation if password exceeds Drupal maximum length as
// safeguard against potential DoS attacks.
if (_password_policy_is_form_password_too_long($form_state)) {
form_set_error('pass', t('Password exceeds maximum length. Please choose a shorter password.'));
return;
}
// Validate constraints.
_password_policy_validate_constraints($form_state, $account);
}
}
/**
* Gets from form user for whom password is being validated.
*
* @return object
* Custom user object for validating constraints.
*/
function _password_policy_get_user_from_form($form, &$form_state) {
$account = isset($form['#user']) ? $form['#user'] : (object) array(
'uid' => 0,
);
if ($account->uid == 0) {
$account->roles = array(
DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID,
);
}
$values = $form_state['values'];
if (isset($values['name'])) {
$account->name = $values['name'];
}
if (!_password_policy_is_current_user($account)) {
// Administrator is changing password for another user. For validating
// constraints, use roles selected on form for the user.
_password_policy_add_selected_roles_to_account($form_state, $account);
}
return $account;
}
/**
* Determines whether user has been flagged for forced password change.
*
* @param object $account
* Custom user object for validating constraints.
*/
function _password_policy_is_force_password_change_set($account) {
return isset($account->uid) && isset($account->force_password_change) && $account->force_password_change == 1;
}
/**
* Adds roles selected for user on form to the user object.
*
* This function has no effect if the form does not have a roles field. For
* instance, the Password Change Tab password form has no roles field.
*
* @param array $form_state
* Form state.
* @param object $account
* Custom user object for validating constraints.
*/
function _password_policy_add_selected_roles_to_account(array $form_state, &$account) {
if (isset($form_state['values']['roles'])) {
$rids = array_keys(array_filter($form_state['values']['roles']));
$roles = array_combine($rids, $rids);
$account->roles = $roles;
}
}
/**
* Determines whether password field is empty.
*
* @param array $form_state
* Form state.
*/
function _password_policy_password_field_is_empty(array $form_state) {
$values = $form_state['values'];
return isset($values['pass']) && $values['pass'] == '';
}
/**
* Determines whether given user is the current user.
*
* @param object $account
* User object.
*/
function _password_policy_is_current_user($account) {
global $user;
return $user->uid == $account->uid;
}
/**
* Determines whether password on form exceeds Drupal maximum length.
*
* The maximum length is copied from includes/password.inc.
*
* @param array $form_state
* Form state.
*
* @return bool
* TRUE if password exceeds Drupal maximum length, FALSE otherwise.
*
* @see _password_crypt()
*/
function _password_policy_is_form_password_too_long(array $form_state) {
$pass = $form_state['values']['pass'];
return strlen($pass) > 512;
}
/**
* Validates constraints.
*
* Gets error message for each constraint that fails validation and displays
* them all in list format as a form error.
*
* @param array $form_state
* Form state.
* @param object $account
* Custom user object for validating constraints.
*/
function _password_policy_validate_constraints(array $form_state, $account) {
$pass = $form_state['values']['pass'];
$error = _password_policy_constraint_validate($pass, $account);
if ($error) {
form_set_error('pass', t('Your password has not met the following requirement(s):') . '<ul><li>' . implode('</li><li>', $error) . '</li></ul>');
}
}
/****************************************************************************/
/* Force password change functions */
/****************************************************************************/
/**
* Determines whether user is to be forced to change their password.
*
* Uses static variable to avoid redundantly querying database in single
* request, which can happen when both password_policy_init() and
* password_policy_user_load() are called.
*
* @param int $uid
* User ID.
*
* @return bool
* TRUE if a password change is to be forced, FALSE otherwise.
*/
function _password_policy_is_password_change_forced($uid) {
static $force_change = array();
if ($uid == 0) {
return FALSE;
}
if (!isset($force_change[$uid])) {
$force_change[$uid] = db_select('password_policy_force_change', 'p', array(
'target' => 'slave',
))
->fields('p', array(
'force_change',
))
->condition('uid', $uid)
->execute()
->fetchField();
}
return $force_change[$uid];
}
/**
* Determines whether access to path allowed when user password change forced.
*
* @return bool
* TRUE if the path is allowed, FALSE otherwise.
*/
function _password_policy_is_path_allowed_when_password_change_forced() {
$allowed_paths = _password_policy_get_allowed_paths();
$patterns = implode("\n", $allowed_paths);
return drupal_match_path(current_path(), $patterns);
}
/**
* Gets paths allowed when password change forced.
*
* The allowed paths comprise configurable and non-configurable paths.
* Configurable paths can be changed by the administrator; non-configurable
* paths are built into the module and cannot be changed.
*
* Modules can add, modify, or delete paths by implementing
* hook_password_policy_force_change_allowed_paths_alter(). For instance, a
* module could add the path of a JavaScript file needed for the page to behave
* properly. Implementors should consider the security implications of altering
* the paths. There is increased risk that allowed paths will be accessed by
* attackers.
*
* @return string[]
* Array of path patterns in the form expected by the $patterns parameter of
* drupal_match_path().
*/
function _password_policy_get_allowed_paths() {
$configurable_allowed_paths = _password_policy_get_configurable_allowed_paths();
$nonconfigurable_allowed_paths = _password_policy_get_nonconfigurable_allowed_paths();
$allowed_paths = array_merge($configurable_allowed_paths, $nonconfigurable_allowed_paths);
drupal_alter('password_policy_force_change_allowed_paths', $allowed_paths);
return array_unique($allowed_paths);
}
/**
* Gets configurable paths allowed when password change forced.
*
* These are extra paths the administrator chooses to allow when a user is
* forced to change their password.
*
* @return string[]
* Array of path patterns in the form expected by the $patterns parameter of
* drupal_match_path().
*/
function _password_policy_get_configurable_allowed_paths() {
$unprocessed_paths = variable_get('password_policy_force_change_extra_allowed_paths', _password_policy_default_force_change_extra_allowed_paths());
$paths = array_filter(preg_split("/(\n|\r)/", $unprocessed_paths));
return $paths;
}
/**
* Gets non-configurable paths allowed when password change forced.
*
* These paths are the ones users should be able to access at a minimum,
* regardless of site configuration, when being forced to change their
* password.
*
* @return string[]
* Array of path patterns in the form expected by the $patterns parameter of
* drupal_match_path().
*/
function _password_policy_get_nonconfigurable_allowed_paths() {
$password_change_paths = _password_policy_get_password_change_paths();
$logout_paths = array(
'user/logout',
);
return array_merge($password_change_paths, $logout_paths);
}
/**
* Sets message indicating a password change is forced.
*/
function _password_policy_set_password_change_forced_message() {
drupal_set_message(t('Your password has expired. You must change your password to proceed on the site.'), 'error', FALSE);
}
/**
* Redirects user to password change page.
*/
function _password_policy_go_to_password_change_page() {
$password_change_path = _password_policy_get_preferred_password_change_path();
// Set query to redirect user back to their original destination after
// leaving password change page.
$options = array(
'query' => drupal_get_destination(),
);
unset($_GET['destination']);
// Add password reset token, if it is available, to query so user is not
// prompted for their current password on password change page unnecessarily.
_password_policy_add_pass_reset_token_if_available($options);
drupal_goto($password_change_path, $options);
}
/**
* Gets preferred path for password change page.
*
* The password change page path depends on whether Password Policy Password
* Tab is enabled. When there are multiple paths, the first is assumed to be
* preferred.
*/
function _password_policy_get_preferred_password_change_path() {
$password_change_paths = _password_policy_get_password_change_paths();
return $password_change_paths[0];
}
/**
* Gets paths that allow user to change their password.
*/
function _password_policy_get_password_change_paths() {
global $user;
return _password_policy_get_password_edit_paths_for_user($user);
}
/**
* Gets password edit paths for the given user.
*/
function _password_policy_get_password_edit_paths_for_user($account) {
$password_change_path = variable_get('password_policy_force_change_path', NULL);
if (!empty($password_change_path)) {
// Support replacement patterns for password_policy_force_change_path.
$password_change_path = str_replace('[user:uid]', $account->uid, $password_change_path);
$password_change_path = str_replace('[user:name]', $account->name, $password_change_path);
return array(
$password_change_path,
);
}
elseif (module_exists('password_policy_password_tab')) {
return array(
"user/{$account->uid}/password",
);
}
else {
return array(
"user/{$account->uid}/edit/account",
"user/{$account->uid}/edit",
);
}
}
/**
* Returns default value for password_policy_force_change_extra_allowed_paths.
*
* It is not necessary to allow system/ajax when password changes are forced,
* but historically this module has allowed this path by non-configurable
* default. We keep this path as a default but it is now configurable.
*
* @return string
* Default value.
*/
function _password_policy_default_force_change_extra_allowed_paths() {
return 'system/ajax';
}
/**
* Adds password reset token, if available, to query.
*
* This is for the case that the user is forced to change their password on
* reset, but attempts to browse away from the password change path before
* changing their password. Adding the reset token makes it so the user will
* not be prompted for their current password.
*
* @param array $options
* Options to be passed to drupal_goto().
*/
function _password_policy_add_pass_reset_token_if_available(array &$options) {
global $user;
if (isset($_SESSION['pass_reset_' . $user->uid])) {
$pass_reset_token = $_SESSION['pass_reset_' . $user->uid];
$options['query']['pass-reset-token'] = $pass_reset_token;
}
}
/****************************************************************************/
/* Expired accounts UI */
/****************************************************************************/
/**
* Lists all expired accounts.
*/
function password_policy_expired_list() {
$header[] = array(
'data' => t('Username'),
'field' => 'name',
);
$header[] = array(
'data' => t('Blocked'),
'field' => 'blocked',
'sort' => 'desc',
);
$header[] = array(
'data' => t('Unblocked'),
'field' => 'unblocked',
);
$header[] = array(
'data' => t('Action'),
);
$query = db_select('password_policy_expiration', 'p', array(
'target' => 'slave',
));
$query
->innerJoin('users', 'u', 'p.uid = u.uid');
$result = $query
->fields('p')
->fields('u', array(
'name',
))
->condition('p.blocked', 0, '>')
->extend('PagerDefault')
->extend('TableSort')
->limit(variable_get('password_policy_expired_account_entries', 10))
->orderByHeader($header)
->execute();
foreach ($result as $row) {
$entry[$row->uid]['name'] = l($row->name, 'user/' . $row->uid);
$entry[$row->uid]['blocked'] = format_date($row->blocked, 'medium');
$entry[$row->uid]['unblocked'] = $row->unblocked < $row->blocked ? '' : format_date($row->unblocked, 'medium');
$entry[$row->uid]['action'] = $row->unblocked < $row->blocked ? l(t('unblock'), 'admin/people/expired/unblock/' . $row->uid, array(
'query' => array(
'destination' => 'admin/people/expired',
),
)) : '';
}
if (!isset($entry)) {
$colspan = '4';
$entry[] = array(
array(
'data' => t('No entries'),
'colspan' => $colspan,
),
);
}
$page = theme('table', array(
'header' => $header,
'rows' => $entry,
));
$page .= theme('pager');
return $page;
}
/**
* Confirms unblocking the expired account.
*/
function password_policy_expired_unblock_confirm($form_id, $form, $account) {
return confirm_form(array(
'account' => array(
'#type' => 'value',
'#value' => $account,
),
), t('Are you sure you would like to unblock the user %user?', array(
'%user' => $account->name,
)), 'admin/people/expired', t('This action cannot be undone.'), t('Unblock user'), t('Cancel'));
}
/**
* Unblocks the expired account.
*/
function password_policy_expired_unblock_confirm_submit($form, &$form_state) {
$account = $form_state['values']['account'];
if (_password_policy_was_user_blocked_due_to_expiration($account)) {
user_save($account, array(
'status' => 1,
));
drupal_set_message(t('The user %name has been unblocked.', array(
'%name' => $account->name,
)));
}
else {
drupal_set_message(t('The user %name was not blocked using the Password Policy module. This account has not been unblocked.', array(
'%name' => $account->name,
)), 'warning');
}
drupal_goto('admin/people/expired');
}
/****************************************************************************/
/* Mail handling */
/****************************************************************************/
/**
* Returns a mail string for a variable name.
*
* Used by password_policy_mail() and the settings forms to retrieve strings.
*/
function _password_policy_mail_text($key, $language = NULL, $variables = array(), $replace = TRUE) {
$langcode = isset($language) ? $language->language : NULL;
switch ($key) {
case 'warning_subject':
if (function_exists('i18n_variable_get')) {
$text = i18n_variable_get('password_policy_warning_subject', $langcode, PASSWORD_POLICY_DEFAULT_WARNING_SUBJECT);
}
else {
$text = variable_get('password_policy_warning_subject', PASSWORD_POLICY_DEFAULT_WARNING_SUBJECT);
}
break;
case 'warning_body':
if (function_exists('i18n_variable_get')) {
$text = i18n_variable_get('password_policy_warning_body', $langcode, PASSWORD_POLICY_DEFAULT_WARNING_BODY);
}
else {
$text = variable_get('password_policy_warning_body', PASSWORD_POLICY_DEFAULT_WARNING_BODY);
}
break;
}
if ($replace) {
// We do not sanitize the token replacement, since the output of this
// replacement is intended for an e-mail message, not a web browser.
return token_replace($text, $variables, array(
'language' => $language,
'callback' => 'password_policy_mail_tokens',
'sanitize' => FALSE,
'clear' => TRUE,
));
}
return $text;
}
/**
* Token callback to add Password Policy tokens for user e-mails.
*
* This function is used by the token_replace() call at the end of
* _password_policy_mail_text() to set up some additional tokens that can be
* used in email messages generated by password_policy_mail().
*
* @param array $replacements
* An associative array variable containing mappings from token names to
* values (for use with strtr()).
* @param array $data
* An associative array of token replacement values.
* @param array $options
* Unused parameter required by the token_replace() function.
*/
function password_policy_mail_tokens(array &$replacements, array $data, array $options) {
if (isset($data['days_left'])) {
$replacements['[password-policy:days-left]'] = $data['days_left'];
}
$password_edit_url_token = '[password-policy:password-edit-url]';
$account = $data['user'];
$password_edit_url = _password_policy_get_preferred_password_edit_url_for_user($account);
$replacements[$password_edit_url_token] = $password_edit_url;
}
/**
* Gets preferred password edit URL for user.
*
* The password edit page URL depends on whether Password Policy Password Tab
* is enabled. There can be multiple equivalent password edit URLs, and we have
* to choose one. The one we choose is the "preferred" edit URL.
*
* Builds URL in the same way as the URL for [user:edit-url] in
* user.tokens.inc.
*
* @param object $account
* User object.
*
* @return string
* Absolute URL for preferred password edit page.
*/
function _password_policy_get_preferred_password_edit_url_for_user($account) {
global $language;
$edit_path = _password_policy_get_preferred_password_edit_path_for_user($account);
$url_options = array(
'absolute' => TRUE,
'language' => $language,
);
return url($edit_path, $url_options);
}
/**
* Gets preferred password edit path for given user.
*/
function _password_policy_get_preferred_password_edit_path_for_user($account) {
$edit_paths = _password_policy_get_password_edit_paths_for_user($account);
return $edit_paths[0];
}
/****************************************************************************/
/* Constraints API */
/****************************************************************************/
/**
* Validates user password.
*
* Returns NULL on success or array with error messages
* from the constraints on failure.
*
* @param string $pass
* Clear text password.
* @param object $account
* Populated user object.
*
* @return null|string[]
* NULL or array with error messages.
*/
function _password_policy_constraint_validate($pass, &$account) {
_password_policy_constraints();
$error = NULL;
$roles = @is_array($account->roles) ? array_keys($account->roles) : array();
$policy = _password_policy_load_active_policy($roles, $account);
if (!empty($policy['constraints'])) {
foreach ($policy['constraints'] as $key => $value) {
if (!call_user_func('password_policy_constraint_' . $key . '_validate', $pass, $value, $account)) {
$error[] = call_user_func('password_policy_constraint_' . $key . '_error', $value);
}
}
}
return $error;
}
/**
* Gets the constraint's name and description.
*
* @param string $name
* Name of the constraint.
*
* @return array
* Array containing the name and description.
*/
function _password_policy_constraint_description($name) {
_password_policy_constraints();
return call_user_func('password_policy_constraint_' . $name . '_description');
}
/**
* Gets the constraint's error message.
*
* @param string $name
* Name of the constraint.
* @param string $constraint
* Constraint value.
*
* @return string
* Error message.
*/
function _password_policy_constraint_error($name, $constraint) {
_password_policy_constraints();
return call_user_func('password_policy_constraint_' . $name . '_error', $constraint);
}
/**
* Gets JavaScript code from the constraint to be added to password validation.
*
* @param string $name
* Name of the constraint.
* @param string $constraint
* Constraint value.
* @param object $account
* User object.
*
* @return string
* JavaScript code snippet for the constraint.
*/
function _password_policy_constraint_js($name, $constraint, $account) {
_password_policy_constraints();
if (function_exists('password_policy_constraint_' . $name . '_js')) {
return call_user_func('password_policy_constraint_' . $name . '_js', $constraint, $account);
}
}
/****************************************************************************/
/* Features integration */
/****************************************************************************/
/**
* Implements hook_features_api().
*/
function password_policy_features_api() {
return array(
'password_policy' => array(
'name' => 'Password Policy',
'file' => drupal_get_path('module', 'password_policy') . '/password_policy.features.inc',
'default_hook' => 'password_policy_features_default_policies',
'feature_source' => TRUE,
),
);
}
/****************************************************************************/
/* Auxiliary functions */
/****************************************************************************/
/**
* Loads contraints inc files.
*/
function _password_policy_constraints() {
static $_password_policy;
if (!isset($_password_policy)) {
// Save all available constraints in a static variable.
$dir = drupal_get_path('module', 'password_policy') . '/constraints';
$constraints = file_scan_directory($dir, '/^constraint.*\\.inc$/');
$_password_policy = array();
foreach ($constraints as $file) {
if (is_file($file->uri)) {
include_once $file->uri;
$_password_policy[] = drupal_substr($file->name, 11);
}
}
}
return $_password_policy;
}
/**
* Loads the policy with the specified id.
*
* Attempts to load the policy from a static cache variable. If not found,
* loads the policy from the database.
*
* @param int $pid
* The policy id.
*
* @return array|false
* A populated policy array or FALSE if not found.
*/
function _password_policy_load_policy_by_pid($pid) {
static $policies = array();
if (is_numeric($pid)) {
if (isset($policies[$pid])) {
return $policies[$pid];
}
else {
$policy = _password_policy_load_policy_from_db(array(
'pid' => $pid,
));
if ($policy) {
$policies[$pid] = $policy;
return $policy;
}
}
}
return FALSE;
}
/**
* Loads the policy with the specified name.
*
* Attempts to load the policy from a static cache variable. If not found,
* loads the policy from the database.
*
* @param string $name
* The name of the policy.
*
* @return array|false
* A populated policy array or FALSE if not found.
*/
function password_policy_load_policy_by_name($name) {
static $policies = array();
if (isset($policies[$name])) {
return $policies[$name];
}
else {
$policy = _password_policy_load_policy_from_db(array(
'name' => $name,
));
if ($policy) {
$policies[$name] = $policy;
return $policy;
}
}
return FALSE;
}
/**
* Loads the policy that meets the specified conditions from the database.
*
* @param array $conditions
* Associative array of conditions where keys are field names and values are
* field values.
*
* @return array|false
* A policy array or FALSE if no policy was found.
*/
function _password_policy_load_policy_from_db(array $conditions) {
$query = db_select('password_policy', 'p', array(
'target' => 'slave',
))
->fields('p');
foreach ($conditions as $field => $value) {
$query
->condition($field, $value);
}
$row = $query
->execute()
->fetchAssoc();
if ($row) {
$row['constraints'] = unserialize($row['constraints']);
// Fetch roles.
$row['roles'] = array();
$result = db_select('password_policy_role', 'p', array(
'target' => 'slave',
))
->fields('p', array(
'rid',
))
->condition('pid', $row['pid'])
->execute();
foreach ($result as $role) {
$row['roles'][$role->rid] = $role->rid;
}
// Fetch authentication modules.
_password_policy_load_policy_excluded_authentication_modules($row);
return $row;
}
return FALSE;
}
/**
* Loads the first enabled policy that matches the specified roles.
*
* @param int[] $roles
* An array of role IDs.
* @param object $account
* Populated user object.
*
* @return array|false
* A policy array, or FALSE if no active policy exists.
*/
function _password_policy_load_active_policy(array $roles, &$account) {
static $cache = array();
if (empty($roles)) {
$roles = array(
DRUPAL_ANONYMOUS_RID,
);
}
// If the role is a name, not an ID, replace with the ID.
for ($i = 0; $i < count($roles); $i++) {
if (!is_numeric($roles[$i])) {
$row = db_select('role', 'r', array(
'target' => 'slave',
))
->fields('r', array(
'rid',
))
->condition('r.name', $roles[$i])
->execute()
->fetchAssoc();
if (!empty($row['rid'])) {
$roles[$i] = $row['rid'];
}
}
}
$key = implode(',', $roles);
// Use array_key_exists() instead of isset() as NULLs may be in the array.
if (!array_key_exists($key, $cache)) {
$query = db_select('password_policy', 'p', array(
'target' => 'slave',
));
$query
->innerJoin('password_policy_role', 'r', 'p.pid = r.pid');
$row = $query
->fields('p')
->condition('p.enabled', 1)
->condition('r.rid', $roles, 'IN')
->orderBy('p.weight')
->range(0, 1)
->execute()
->fetchAssoc();
if (is_array($row)) {
$constraints = $row['constraints'];
$constraints = unserialize($constraints);
$row['constraints'] = $constraints;
_password_policy_load_policy_excluded_authentication_modules($row);
$cache[$key] = $row;
}
else {
$cache[$key] = FALSE;
}
}
$policy = $cache[$key];
if ($policy && _password_policy_policy_excludes_authentication_module_of_user($policy, $account)) {
$policy = FALSE;
}
return $policy;
}
/**
* Saves a policy.
*
* @param array $policy
* A policy array.
*/
function password_policy_save_policy(array $policy) {
if (isset($policy['pid']) && $policy['pid']) {
$fields = array(
'name' => $policy['name'],
'description' => $policy['description'],
'constraints' => serialize($policy['constraints']),
'created' => isset($policy['created']) ? $policy['created'] : 0,
'expiration' => !empty($policy['expiration']) ? $policy['expiration'] : 0,
'warning' => str_replace(' ', '', $policy['warning']),
'weight' => !empty($policy['weight']) ? $policy['weight'] : 0,
);
// On policy edit form we have no 'enabled' param, so modify update query.
if (isset($policy['enabled'])) {
$fields += array(
'enabled' => $policy['enabled'],
);
}
db_update('password_policy')
->fields($fields)
->condition('pid', $policy['pid'])
->execute();
watchdog('password_policy', 'Policy %name updated.', array(
'%name' => $policy['name'],
), WATCHDOG_NOTICE, l(t('edit'), 'admin/config/people/password_policy/' . $policy['pid'] . '/edit'));
db_delete('password_policy_role')
->condition('pid', $policy['pid'])
->execute();
db_delete('password_policy_excluded_authentication_modules')
->condition('pid', $policy['pid'])
->execute();
$pid = $policy['pid'];
}
else {
$pid = db_insert('password_policy')
->fields(array(
'name' => $policy['name'],
'description' => $policy['description'],
'enabled' => $policy['enabled'],
'constraints' => serialize($policy['constraints']),
'created' => isset($policy['created']) ? $policy['created'] : 0,
'expiration' => !empty($policy['expiration']) ? $policy['expiration'] : 0,
'warning' => str_replace(' ', '', $policy['warning']),
'weight' => !empty($policy['weight']) ? $policy['weight'] : 0,
))
->execute();
watchdog('password_policy', 'New policy %name created.', array(
'%name' => $policy['name'],
), WATCHDOG_NOTICE, l(t('edit'), 'admin/config/people/password_policy/' . $pid . '/edit'));
}
foreach (array_filter($policy['roles']) as $rid => $enabled) {
db_insert('password_policy_role')
->fields(array(
'pid' => $pid,
'rid' => $rid,
))
->execute();
}
foreach (array_filter($policy['excluded_authentication_modules']) as $module => $excluded) {
db_insert('password_policy_excluded_authentication_modules')
->fields(array(
'pid' => $pid,
'module' => $module,
))
->execute();
}
}
/**
* Stores user password hash.
*
* @param int $uid
* User id.
* @param string $pass
* Password hash.
*/
function _password_policy_store_password($uid, $pass) {
db_insert('password_policy_history')
->fields(array(
'uid' => $uid,
'pass' => $pass,
'created' => _password_policy_get_request_time(),
))
->execute();
}
/**
* Blocks the expired account.
*
* @param object $account
* User object.
*/
function _password_policy_block_account($account) {
if ($account->uid > 1) {
// We never block the superuser account.
db_update('users')
->fields(array(
'status' => 0,
))
->condition('uid', $account->uid)
->execute();
// Check if user is already blocked.
$blocked = db_select('password_policy_expiration', 'p', array(
'target' => 'slave',
))
->fields('p', array(
'pid',
))
->condition('uid', $account->uid)
->isNull('unblocked')
->execute()
->fetchField();
if ($blocked) {
db_update('password_policy_expiration')
->fields(array(
'blocked' => _password_policy_get_request_time(),
))
->condition('uid', $account->uid)
->execute();
}
else {
db_insert('password_policy_expiration')
->fields(array(
'uid' => $account->uid,
'blocked' => _password_policy_get_request_time(),
))
->execute();
}
watchdog('password_policy', 'Password for user %name has expired.', array(
'%name' => $account->name,
), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit'));
// Bypass logout process when executed via Drush.
if (!function_exists('drush_verify_cli') || !drush_verify_cli()) {
include_once drupal_get_path('module', 'user') . '/user.pages.inc';
user_logout();
}
}
}
/**
* Handles unblocking of an account.
*
* If the account was blocked by this module due to password expiration, we (1)
* record the time it was unblocked and (2) force the user to change their
* password upon next login.
*
* @param object $account
* User object.
*/
function _password_policy_handle_unblock($account) {
// Check if user was blocked via this module.
if (_password_policy_was_user_blocked_due_to_expiration($account)) {
db_update('password_policy_expiration')
->fields(array(
'unblocked' => _password_policy_get_request_time(),
))
->condition('uid', $account->uid)
->execute();
db_update('password_policy_force_change')
->fields(array(
'force_change' => 1,
))
->condition('uid', $account->uid)
->execute();
}
}
/**
* Determines whether user was blocked due to expiration.
*
* @param object $account
* User object.
*/
function _password_policy_was_user_blocked_due_to_expiration($account) {
return db_select('password_policy_expiration', 'ppe')
->fields('ppe', array(
'pid',
))
->condition(db_or()
->condition(db_and()
->isNull('unblocked')
->condition('blocked', '0', '<>'))
->condition(db_and()
->isNotNull('unblocked')
->where('blocked > unblocked')))
->condition('uid', $account->uid)
->execute()
->fetchField();
}
/**
* Adds password policy JS.
*
* @param array $policy
* A policy array.
* @param object $account
* User object of user for which the policy is applied.
* @param array $render_array
* (Optional) A renderable array to attach the JavaScript to. If not
* provided, the JavaScript will be added to the page directly.
*/
function password_policy_add_policy_js(array $policy, $account, array &$render_array = NULL) {
$s = <<<JS
/**
* Evaluates the strength of a user's password.
*
* Returns the estimated strength and the relevant output message.
*/
Drupal.evaluatePasswordStrength = function (value) {
var strength = 'high';
var msg = [];
var translate = Drupal.settings.password;
// Merge Password Policy translations.
for (var setting in Drupal.settings.passwordPolicy) {
translate[setting] = Drupal.settings.passwordPolicy[setting];
}
var trimmedSpaces = /^\\s+|\\s+\$/.test(value);
if (/^\\s+\$/.test(value)) {
return {
strength: 10,
indicatorText: translate.lowStrength,
message: translate.allSpaces
};
}
value = value.replace(/^\\s+|\\s+\$/g, '');
JS;
// Print out each constraint's javascript password strength evaluation.
foreach ($policy['constraints'] as $key => $value) {
$s .= _password_policy_constraint_js($key, $value, $account);
// Constraints' error messages are used in javascript.
$translate['constraint_' . $key] = _password_policy_constraint_error($key, $value);
}
$s .= <<<JS
if (msg.length > 0) {
msg = translate.needsMoreVariation + '<ul><li>' + msg.join('</li><li>') + '</li></ul>';
}
else {
msg = '';
}
if (trimmedSpaces) {
msg = msg.concat(translate.trimmedSpaces);
}
var level = '';
if (strength === 'high') {
level = 100;
}
else {
level = 10;
}
if (strength === 'high') {
strength = translate.highStrength;
}
if (strength === 'medium') {
strength = translate.mediumStrength;
}
if (strength === 'low') {
strength = translate.lowStrength;
}
return {
strength: level,
indicatorText: strength,
message: msg
};
}
JS;
$options = array(
'scope' => 'header',
'type' => 'inline',
'weight' => 10,
);
if (isset($render_array)) {
$options['data'] = $s;
$render_array['#attached']['js'][] = $options;
}
else {
drupal_add_js($s, $options);
}
$data = array(
// Override some core 'password' settings.
// Drupal by default rates passwords in terms of strength. However, a
// password that meets Password Policy constraints is not necessarily a
// strong password. So we rate the password in terms of "quality". A
// password is "bad" if it does not meet constraints, "good" if it does.
'password' => array(
'strengthTitle' => t('Password quality:'),
'lowStrength' => t('Bad'),
'mediumStrength' => t('Good'),
'highStrength' => t('Good'),
),
// Add new settings for this module.
'passwordPolicy' => array_merge(array(
'trimmedSpaces' => t('The password has spaces at the beginning or end which are ignored.'),
'allSpaces' => t('The password is all spaces and will not be saved.'),
'needsMoreVariation' => t('The password does not include enough variation to be secure.'),
), $translate),
);
if (isset($render_array)) {
$options = array(
'data' => $data,
'type' => 'setting',
);
$render_array['#attached']['js'][] = $options;
}
else {
drupal_add_js($data, 'setting');
}
}
Functions
Name | Description |
---|---|
password_policy_add_policy_js | Adds password policy JS. |
password_policy_cron | Implements hook_cron(). |
password_policy_drupal_goto_alter | Implements hook_drupal_goto_alter(). |
password_policy_expired_list | Lists all expired accounts. |
password_policy_expired_unblock_confirm | Confirms unblocking the expired account. |
password_policy_expired_unblock_confirm_submit | Unblocks the expired account. |
password_policy_features_api | Implements hook_features_api(). |
password_policy_field_extra_fields | Implements hook_field_extra_fields(). |
password_policy_format_title | Displays a password policy form title. |
password_policy_form_alter | Implements hook_form_alter(). |
password_policy_help | Implements hook_help(). |
password_policy_init | Implements hook_init(). |
password_policy_load_policy_by_name | Loads the policy with the specified name. |
password_policy_mail | Implements hook_mail(). |
password_policy_mail_tokens | Token callback to add Password Policy tokens for user e-mails. |
password_policy_menu | Implements hook_menu(). |
password_policy_password_validate | Password save validate handler. |
password_policy_permission | Implements hook_permission(). |
password_policy_policy_load | Loads policy array from the database. |
password_policy_save_policy | Saves a policy. |
password_policy_theme | Implements hook_theme(). |
password_policy_uid_load | Loads user object from the database. |
password_policy_user_delete | Implements hook_user_delete(). |
password_policy_user_insert | Implements hook_user_insert(). |
password_policy_user_load | Implements hook_user_load(). |
password_policy_user_login | Implements hook_user_login(). |
password_policy_user_presave | Implements hook_user_presave(). |
password_policy_user_update | Implements hook_user_update(). |
_password_policy_add_pass_reset_token_if_available | Adds password reset token, if available, to query. |
_password_policy_add_selected_roles_to_account | Adds roles selected for user on form to the user object. |
_password_policy_block_account | Blocks the expired account. |
_password_policy_constraints | Loads contraints inc files. |
_password_policy_constraint_description | Gets the constraint's name and description. |
_password_policy_constraint_error | Gets the constraint's error message. |
_password_policy_constraint_js | Gets JavaScript code from the constraint to be added to password validation. |
_password_policy_constraint_validate | Validates user password. |
_password_policy_default_force_change_extra_allowed_paths | Returns default value for password_policy_force_change_extra_allowed_paths. |
_password_policy_get_allowed_paths | Gets paths allowed when password change forced. |
_password_policy_get_configurable_allowed_paths | Gets configurable paths allowed when password change forced. |
_password_policy_get_nonconfigurable_allowed_paths | Gets non-configurable paths allowed when password change forced. |
_password_policy_get_password_change_paths | Gets paths that allow user to change their password. |
_password_policy_get_password_edit_paths_for_user | Gets password edit paths for the given user. |
_password_policy_get_preferred_password_change_path | Gets preferred path for password change page. |
_password_policy_get_preferred_password_edit_path_for_user | Gets preferred password edit path for given user. |
_password_policy_get_preferred_password_edit_url_for_user | Gets preferred password edit URL for user. |
_password_policy_get_user_from_form | Gets from form user for whom password is being validated. |
_password_policy_go_to_password_change_page | Redirects user to password change page. |
_password_policy_handle_unblock | Handles unblocking of an account. |
_password_policy_has_account_password_element | Determines whether form has an account password element. |
_password_policy_is_current_user | Determines whether given user is the current user. |
_password_policy_is_force_password_change_set | Determines whether user has been flagged for forced password change. |
_password_policy_is_form_password_too_long | Determines whether password on form exceeds Drupal maximum length. |
_password_policy_is_password_change_forced | Determines whether user is to be forced to change their password. |
_password_policy_is_path_allowed_when_password_change_forced | Determines whether access to path allowed when user password change forced. |
_password_policy_load_active_policy | Loads the first enabled policy that matches the specified roles. |
_password_policy_load_policy_by_pid | Loads the policy with the specified id. |
_password_policy_load_policy_from_db | Loads the policy that meets the specified conditions from the database. |
_password_policy_mail_text | Returns a mail string for a variable name. |
_password_policy_password_field_is_empty | Determines whether password field is empty. |
_password_policy_set_password_change_forced_message | Sets message indicating a password change is forced. |
_password_policy_store_password | Stores user password hash. |
_password_policy_validate_constraints | Validates constraints. |
_password_policy_was_updated_user_unblocked | Determines whether updated user was unblocked. |
_password_policy_was_user_blocked_due_to_expiration | Determines whether user was blocked due to expiration. |