'. t('The password policy module allows you to enforce a specific level of password complexity for the user passwords on the system.') .'
';
}
}
/**
* Implements hook_init().
*/
function password_policy_init() {
global $user;
// Timing issues require reloading the user object
// to get the password_change property set.
$account = user_load($user->uid);
// Check password reset status and force a reset if needed.
$change_password_url = 'user/'. $account->uid .'/'. (module_exists('password_policy_password_tab') ? 'password' : 'edit');
if ($account->force_password_change && $_GET['q'] != $change_password_url) {
// let users log out
if (current_path() != 'user/logout') {
drupal_set_message(t('Your password has expired. You must change your password to proceed on the site.'), 'error', FALSE);
drupal_goto($change_password_url, drupal_get_destination());
}
}
}
/**
* 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/%pp_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/%pp_policy/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/config/people/password_policy/%pp_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/%pp_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/%pp_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;
}
/**
* Load policy array from the database.
*
* @param $pid
* The policy id
*
* @return
* A populated policy array or NULL if not found.
*/
function pp_policy_load($pid) {
static $policies = array();
if (is_numeric($pid)) {
if (isset($policies[$pid])) {
return $policies[$pid];
}
else {
$policy = _password_policy_load_policy_by_pid($pid);
if ($policy) {
$policy['policy'] = unserialize($policy['policy']);
$policies[$pid] = $policy;
return $policy;
}
}
}
return FALSE;
}
/**
* Load user object from the database.
*
* @param $id
* The user id
*
* @return
* A populated user object or NULL if not found.
*/
function pp_uid_load($uid) {
if (is_numeric($uid)) {
$account = user_load($uid);
if ($account) {
return $account;
}
}
return FALSE;
}
/**
* Display a password policy form title.
*
* @param $policy
* Policy array
*
* @return
* A policy's title string
*/
function password_policy_format_title($policy) {
return $policy['name'];
}
/**
* Implements hook_user_load().
*/
function password_policy_user_load($users) {
foreach ($users as $uid => $user) {
$user->force_password_change = db_select('password_policy_force_change', 'p', array('target' => 'slave'))
->fields('p', array(
'force_change',
))
->condition('uid', $user->uid)
->execute()
->fetchField();
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 < 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_update().
*/
function password_policy_user_update(&$edit, $account, $category) {
global $user;
// If the current user is being forced to change their password and is
// changing their password, toggle the force_change field off.
if (isset($account->force_password_change) && $account->force_password_change && ($account->pass != $account->original->pass) && $user->uid == $account->uid) {
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'])) {
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)));
}
watchdog('password policy', '@user flagged to change password on next login by @admin', array('@user' => $account->name, '@admin' => $user->name), WATCHDOG_NOTICE);
}
if (isset($edit['status']) && $edit['status'] != $account->status && $edit['status'] == 1) {
// Account is being unblocked.
db_update('password_policy_expiration')
->fields(array(
'unblocked' => time(),
))
->condition('uid', $account->uid)
->execute();
}
}
/**
* 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);
// A value $edit['name'] is NULL for a one time login
if ($policy && ((!empty($account->uid) && $account->uid > 1) || variable_get('password_policy_admin', 0)) && !empty($edit['name'])) {
// Calculate expiration and warning times.
$expiration = $policy['expiration'];
$warning = 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.
$result = db_query_range("SELECT * FROM {password_policy_history} WHERE uid = :uid ORDER BY created DESC", 0, 1, array(':uid' => $account->uid));
if ($row = $result->fetchObject()) {
$last_change = $row->created;
}
else {
// A user has not changed his pwd after this module had been enabled.
$last_change = $account->created;
}
$time = time();
if ($time > max($policy_start, $last_change) + $expiration_seconds) {
if (variable_get('password_policy_block', 0) == 0) {
// User is blocked imediately and cannot change his 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();
drupal_goto();
}
}
elseif ($time > max($policy_start, $last_change) + $expiration_seconds - $warning_seconds) {
// The warning is shown on login and the user is transfered 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.'));
$destination = drupal_get_destination();
unset($_REQUEST['destination']);
drupal_goto('user/'. $account->uid .'/'. (module_exists('password_policy_password_tab') ? 'password' : 'edit'), $destination);
}
}
}
}
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) {
switch ($form_id) {
case 'user_profile_form':
case 'user_register_form':
// Force password change on user account.
if (user_access('force password change')) {
if ($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();
}
else {
$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'),
'#default_value' => $force_change,
);
}
// Password change form.
$uid = isset($form['#user']->uid) ? $form['#user']->uid : NULL;
//if ($uid == 1 && !variable_get('password_policy_admin', 0)) { break; }
$roles = isset($form['#user']->roles) ? array_keys($form['#user']->roles) : array(DRUPAL_AUTHENTICATED_RID);
if($form_id == 'user_register_form') {
$roles = array(DRUPAL_AUTHENTICATED_RID);
unset($form['account']['pass']['#description']);
}
$policy = _password_policy_load_active_policy($roles);
$translate = array();
if (!empty($policy['policy'])) {
// Some policy constraints are active.
password_policy_add_policy_js($policy, $uid);
foreach ($policy['policy'] as $key => $value) {
$translate['constraint_'. $key] = _password_policy_constraint_error($key, $value);
}
}
// Printing out the restrictions.
if (variable_get('password_policy_show_restrictions', 0) && isset($translate)) {
$restriction_html = '' . theme('item_list', array('items' => $translate, 'title' => t('Password Requirements'))) . '
';
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';
$form['#submit'][] = 'password_policy_password_submit';
break;
case 'password_policy_password_tab':
$form['submit']['#weight'] = 10;
break;
}
}
/**
* Implements hook_cron().
*/
function password_policy_cron() {
// Short circuit if no policies are active that use expiration.
if (!db_select('password_policy', 'p', array('target' => 'slave'))
->condition('enabled', 1)
->condition('expiration', 0, '>')
->countQuery()
->execute()) {
return;
}
$accounts = array();
$warns = array();
$unblocks = array();
$pids = array();
// 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();
foreach ($result as $row) {
if ($row->uid == 1 && !variable_get('password_policy_admin', 0))
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;
// 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;
}
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;
}
$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 (variable_get('password_policy_begin', 0) == 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) {
// 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);
$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);
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 = password_policy_mail_tokens($params, $language);
$message['subject'] .= _password_policy_mail_text($key .'_subject', $language, $variables);
$message['body'][] = _password_policy_mail_text($key .'_body', $language, $variables);
}
//////////////////////////////////////////////////////////////////////////////
// FAPI
/**
* Password save validate handler.
*/
function password_policy_password_validate($form, &$form_state) {
global $user;
$values = $form_state['values'];
$account = isset($form['#user']) ? $form['#user'] : (object)array('uid' => 0);
if($account->uid == 0) {
$account->roles = array(DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID);
}
if (isset($account->uid) && isset($account->force_password_change) && $account->force_password_change == 1) {
// Admins can edit accounts without having to reset passwords.
if ($values['pass'] == '' && $user->uid == $account->uid) {
form_set_error('pass', t('Your password has expired. You must change your password to proceed on the site.'));
}
}
if (!empty($values['pass']) && !isset($values['auth_openid'])) {
$error = _password_policy_constraint_validate($values['pass'], $account);
if ($error) {
form_set_error('pass', t('Your password has not met the following requirement(s):') .'- '. implode('
- ', $error) .'
');
}
}
}
/**
* Password save submit handler.
*/
function password_policy_password_submit($form, &$form_state) {
global $user;
$values = $form_state['values'];
$account = isset($form['#user']) ? $form['#user'] : (object)array('uid' => 0);
// Track the hashed password values which can then be used in the history constraint.
if ($account->uid && !empty($values['pass'])) {
_password_policy_store_password($account->uid, $values['pass']);
}
}
//////////////////////////////////////////////////////////////////////////////
// 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, '>')
->range(0,PASSWORD_POLICY_ENTRIES_PER_PAGE)
->extend('TableSort')
->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', array('element' => 0));
return $page;
}
/**
* Confirm 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) {
// Unblock the user
_password_policy_unblock($form_state['values']['account']);
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()) {
$langcode = isset($language) ? $language->language : NULL;
if ($admin_setting = variable_get('password_policy_'. $key, FALSE)) {
// An admin setting overrides the default string.
return strtr($admin_setting, $variables);
}
else {
// No override, return with default strings.
switch ($key) {
case 'warning_subject':
return t('Password expiration warning for !username at !site', $variables, array('langcode' => $langcode));
case 'warning_body':
return t("!username,\n\nYour password at !site will expire in less than !days_left day(s).\n\nPlease go to !edit_uri to change your password.", $variables, array('langcode' => $langcode));
}
}
}
/**
* Return an array of token to value mappings for user e-mail messages.
*
* @param $params
* Structured array with the parameters.
* @param $language
* Language object to generate the tokens with.
*
* @return
* Array of mappings from token names to values (for use with strtr()).
*/
function password_policy_mail_tokens($params, $language) {
global $base_url;
$account = $params['account'];
$tokens = array(
'!username' => $account->name,
'!site' => variable_get('site_name', 'Drupal'),
'!uri' => $base_url,
'!uri_brief' => drupal_substr($base_url, drupal_strlen('http://')),
'!date' => format_date(time(), 'medium', '', NULL, $language->language),
'!login_uri' => url('user', array('absolute' => TRUE)),
'!edit_uri' => url('user/'. $account->uid .'/'. (module_exists('password_policy_password_tab') ? 'password' : 'edit'), array('absolute' => TRUE)),
'!days_left' => isset($params['days_left']) ? $params['days_left'] : NULL,
'!login_url' => isset($params['login_url']) ? $params['login_url'] : NULL,
);
return $tokens;
}
//////////////////////////////////////////////////////////////////////////////
// Constraints API
/**
* Validates user password. Returns NULL on success or array with error messages
* from the constraints on failure.
*
* @param $pass
* Clear text password.
* @param &$account
* Populated user object.
*
* @return
* 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);
if (!empty($policy['policy'])) {
foreach ($policy['policy'] as $key => $value) {
if (!call_user_func('password_policy_constraint_'. $key .'_validate', $pass, $value, $account->uid)) {
$error[] = call_user_func('password_policy_constraint_'. $key .'_error', $value);
}
}
}
return $error;
}
/**
* Gets the constraint's name and description.
*
* @param $name
* Name of the constraint.
*
* @return
* 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 $name
* Name of the constraint.
* @param $constraint
* Constraint value.
*
* @return
* Error message.
*/
function _password_policy_constraint_error($name, $constraint) {
_password_policy_constraints();
return call_user_func('password_policy_constraint_'. $name .'_error', $constraint);
}
/**
* Gets the javascript code from the constraint to be added to the password validation.
*
* @param $name
* Name of the constraint.
* @param $constraint
* Constraint value.
* @param $uid
* User's id.
*
* @return
* Javascript code snippet for the constraint.
*/
function _password_policy_constraint_js($name, $constraint, $uid) {
_password_policy_constraints();
if (function_exists('password_policy_constraint_'. $name .'_js')) {
return call_user_func('password_policy_constraint_'. $name .'_js', $constraint, $uid);
}
}
//////////////////////////////////////////////////////////////////////////////
// Auxiliary functions
/**
* Load contraints inc files.
*/
function _password_policy_constraints() {
static $_password_policy;
if (!isset($_password_policy)) {
// Save all available constrains 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.
*
* @param $pid
* The policy id.
*
* @return
* A policy array, or NULL if no policy was found.
*/
function _password_policy_load_policy_by_pid($pid) {
$row = db_select('password_policy', 'p', array('fetch' => PDO::FETCH_ASSOC, 'target' => 'slave'))
->fields('p')
->condition('pid', $pid)
->execute()
->fetch();
if ($row) {
// Fetch roles
$row['roles'] = array();
$result = db_select('password_policy_role', 'p', array('target' => 'slave'))
->fields('p', array('rid'))
->condition('pid', $pid)
->execute();
foreach ($result as $role) {
$row['roles'][$role->rid] = $role->rid;
}
return $row;
}
return NULL;
}
/**
* Loads the first enabled policy that matches the specified roles.
*
* @param $roles
* An array of role IDs.
*
* @return
* A policy array, or NULL if no active policy exists.
*/
function _password_policy_load_active_policy($roles) {
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 PDO::FETCH_ASSOC, 'target' => 'slave'));
$row = $query->fields('p.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('fetch' => PDO::FETCH_ASSOC, '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)) {
$policy = $row['policy'];
$policy = unserialize($policy);
$row['policy'] = $policy;
$cache[$key] = $row;
}
else {
$cache[$key] = NULL;
}
}
return $cache[$key];
}
/**
* Stores user password hash.
*
* @param $uid
* User id.
* @param $pass
* Clear text password.
*/
function _password_policy_store_password($uid, $pass) {
require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
db_insert('password_policy_history')
->fields(array(
'uid' => $uid,
'pass' => user_hash_password($pass),
'created' => time(),
))
->execute();
}
/**
* Block the expired account.
*
* @param $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_query("UPDATE {password_policy_expiration} SET blocked = %d WHERE uid = :uid", time(), array(':uid' => $account->uid));
}
else {
db_query("INSERT INTO {password_policy_expiration} (uid, blocked) VALUES (:uid, :blocked)", array(':uid' => $account->uid, ':blocked' => time()));
}
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();
}
}
}
/**
* Unblocks the expired account.
*
* @param $account
* User object.
*/
function _password_policy_unblock($account) {
// Check if user was blocked via this module.
$pp_blocked = db_select('password_policy_expiration', 'ppe')
->fields('ppe', array('pid'))
->condition('blocked', '0', '<>')
->isNull('unblocked')
->condition('uid', $account->uid)
->execute()
->fetchField();
if ($pp_blocked) {
// Unblock the user.
db_update('password_policy_expiration')
->fields(array(
'unblocked' => time(),
))
->condition('uid', $account->uid)
->execute();
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');
}
}
/**
* Add password policy JS
*
* @param $policy
* A policy array.
* @param $uid
* A user ID for which the policy is applied.
*/
function password_policy_add_policy_js($policy, $uid) {
// Print out the javascript which checks the strength of the password.
// It overwrites the defaut core javascript function.
$s = "/**\n";
$s .= " * Evaluate the strength of a user's password.\n";
$s .= " *\n";
$s .= " * Returns the estimated strength and the relevant output message.\n";
$s .= " */\n";
$s .= "Drupal.evaluatePasswordStrength = function(value) {\n";
$s .= " var strength = \"high\", msg = [], translate = Drupal.settings.password_policy;\n";
// Print out each constraint's javascript password strength evaluation.
foreach ($policy['policy'] as $key => $value) {
$s .= _password_policy_constraint_js($key, $value, $uid);
// Constraints' error messages are used in javascript.
$translate['constraint_'. $key] = _password_policy_constraint_error($key, $value);
}
$s .= " msg = msg.length > 0 ? translate.needsMoreVariation +\"\" : \"\";\n";
$s .= " if(strength == 'high') { level = 100; } else { level = 10; } ";
$s .= " if(strength == 'high') { strength = 'High'; } ";
$s .= " if(strength == 'medium') { strength = 'Medium'; } ";
$s .= " if(strength == 'low') { strength = 'Low'; } ";
$s .= " return { strength: level, indicatorText: strength, message: msg };\n";
$s .= "};\n";
drupal_add_js($s, array('scope'=>'header','type' => 'inline', 'weight' => 10));
drupal_add_js(array(
'password_policy' => array_merge(array(
'strengthTitle' => t('Password quality:'),
'lowStrength' => t('Bad'),
'mediumStrength' => t('Medium'),
'highStrength' => t('Good'),
'needsMoreVariation' => t('The password does not include enough variation to be secure.'),
'confirmSuccess' => t('Yes'),
'confirmFailure' => t('No'),
'confirmTitle' => t('Passwords match:')), $translate)),
'setting');
}