| 1 |
<?php
|
| 2 |
// $Id: password_policy.module,v 1.23 2009/06/04 21:48:39 deekayen Exp $
|
| 3 |
/**
|
| 4 |
* @file
|
| 5 |
* The password policy module allows you to enforce a specific level of
|
| 6 |
* password complexity for the user passwords on the system.
|
| 7 |
*/
|
| 8 |
|
| 9 |
//////////////////////////////////////////////////////////////////////////////
|
| 10 |
|
| 11 |
define('PASSWORD_POLICY_ADMIN', variable_get('password_policy_admin', 0));
|
| 12 |
define('PASSWORD_POLICY_BEGIN', variable_get('password_policy_begin', 0));
|
| 13 |
define('PASSWORD_POLICY_BLOCK', variable_get('password_policy_block', 0));
|
| 14 |
define('PASSWORD_POLICY_SHOW_RESTRICTIONS', variable_get('password_policy_show_restrictions', 0));
|
| 15 |
|
| 16 |
define('PASSWORD_POLICY_ENTRIES_PER_PAGE', 20);
|
| 17 |
|
| 18 |
//////////////////////////////////////////////////////////////////////////////
|
| 19 |
// Core API hooks
|
| 20 |
|
| 21 |
/**
|
| 22 |
* Implementation of hook_help().
|
| 23 |
*/
|
| 24 |
function password_policy_help($path, $arg) {
|
| 25 |
switch ($path) {
|
| 26 |
case "admin/help#password_policy":
|
| 27 |
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>';
|
| 28 |
}
|
| 29 |
}
|
| 30 |
|
| 31 |
/**
|
| 32 |
* Implementation of hook_init().
|
| 33 |
*/
|
| 34 |
function password_policy_init() {
|
| 35 |
global $_password_policy;
|
| 36 |
|
| 37 |
// Save all available constrains in a global variable.
|
| 38 |
$dir = drupal_get_path('module', 'password_policy') .'/constraints';
|
| 39 |
$constraints = file_scan_directory($dir, '^constraint.*\.inc$');
|
| 40 |
foreach ($constraints as $file) {
|
| 41 |
if (is_file($file->filename)) {
|
| 42 |
include_once($file->filename);
|
| 43 |
$_password_policy[] = drupal_substr($file->name, 11);
|
| 44 |
}
|
| 45 |
}
|
| 46 |
}
|
| 47 |
|
| 48 |
/**
|
| 49 |
* Implementation of hook_theme().
|
| 50 |
*/
|
| 51 |
|
| 52 |
function password_policy_theme() {
|
| 53 |
return array(
|
| 54 |
'password_policy_admin_list' => array(
|
| 55 |
'arguments' => array('form' => NULL),
|
| 56 |
'file' => 'password_policy.theme.inc',
|
| 57 |
),
|
| 58 |
);
|
| 59 |
}
|
| 60 |
|
| 61 |
/**
|
| 62 |
* Implementation of hook_perm().
|
| 63 |
*/
|
| 64 |
function password_policy_perm() {
|
| 65 |
return array('unblock expired accounts');
|
| 66 |
}
|
| 67 |
|
| 68 |
/**
|
| 69 |
* Implementation of hook_menu().
|
| 70 |
*/
|
| 71 |
function password_policy_menu() {
|
| 72 |
$items['admin/settings/password_policy'] = array(
|
| 73 |
'title' => 'Password policies',
|
| 74 |
'description' => 'Configures policies for user account passwords.',
|
| 75 |
'page callback' => 'drupal_get_form',
|
| 76 |
'page arguments' => array('password_policy_admin_settings'),
|
| 77 |
'access arguments' => array('administer site configuration'),
|
| 78 |
'file' => 'password_policy.admin.inc',
|
| 79 |
);
|
| 80 |
$items['admin/settings/password_policy/configure'] = array(
|
| 81 |
'title' => 'Settings',
|
| 82 |
'type' => MENU_DEFAULT_LOCAL_TASK,
|
| 83 |
);
|
| 84 |
$items['admin/settings/password_policy/list'] = array(
|
| 85 |
'title' => 'List',
|
| 86 |
'type' => MENU_LOCAL_TASK,
|
| 87 |
'page callback' => 'drupal_get_form',
|
| 88 |
'page arguments' => array('password_policy_admin_list'),
|
| 89 |
'access arguments' => array('administer site configuration'),
|
| 90 |
'weight' => 1,
|
| 91 |
'file' => 'password_policy.admin.inc',
|
| 92 |
);
|
| 93 |
$items['admin/settings/password_policy/add'] = array(
|
| 94 |
'title' => 'Add',
|
| 95 |
'type' => MENU_LOCAL_TASK,
|
| 96 |
'page callback' => 'drupal_get_form',
|
| 97 |
'page arguments' => array('password_policy_admin_form', NULL),
|
| 98 |
'access arguments' => array('administer site configuration'),
|
| 99 |
'weight' => 2,
|
| 100 |
'file' => 'password_policy.admin.inc',
|
| 101 |
);
|
| 102 |
$items['admin/settings/password_policy/%pp_policy'] = array(
|
| 103 |
'title callback' => 'password_policy_format_title',
|
| 104 |
'title arguments' => array(3),
|
| 105 |
'type' => MENU_CALLBACK,
|
| 106 |
'page callback' => 'password_policy_admin_view',
|
| 107 |
'page arguments' => array(3),
|
| 108 |
'access arguments' => array('administer site configuration'),
|
| 109 |
'file' => 'password_policy.admin.inc',
|
| 110 |
);
|
| 111 |
$items['admin/settings/password_policy/%pp_policy/view'] = array(
|
| 112 |
'title' => 'View',
|
| 113 |
'type' => MENU_DEFAULT_LOCAL_TASK,
|
| 114 |
);
|
| 115 |
$items['admin/settings/password_policy/%pp_policy/edit'] = array(
|
| 116 |
'title' => 'Edit',
|
| 117 |
'type' => MENU_LOCAL_TASK,
|
| 118 |
'page callback' => 'drupal_get_form',
|
| 119 |
'page arguments' => array('password_policy_admin_form', 3),
|
| 120 |
'access arguments' => array('administer site configuration'),
|
| 121 |
'weight' => 1,
|
| 122 |
'file' => 'password_policy.admin.inc',
|
| 123 |
);
|
| 124 |
$items['admin/settings/password_policy/delete'] = array(
|
| 125 |
'title' => 'Delete',
|
| 126 |
'type' => MENU_CALLBACK,
|
| 127 |
'page callback' => 'drupal_get_form',
|
| 128 |
'page arguments' => array('password_policy_admin_delete'),
|
| 129 |
'access arguments' => array('administer site configuration'),
|
| 130 |
'file' => 'password_policy.admin.inc',
|
| 131 |
);
|
| 132 |
$items['admin/user/expired'] = array(
|
| 133 |
'title' => 'Expired accounts',
|
| 134 |
'description' => 'Lists all expired accounts.',
|
| 135 |
'page callback' => 'password_policy_expired_list',
|
| 136 |
'page arguments' => array('password_policy_list_expired'),
|
| 137 |
'access arguments' => array('unblock expired accounts'),
|
| 138 |
);
|
| 139 |
$items['admin/user/expired/unblock/%pp_uid'] = array(
|
| 140 |
'title' => 'Unblock',
|
| 141 |
'type' => MENU_CALLBACK,
|
| 142 |
'page callback' => 'password_policy_expired_unblock',
|
| 143 |
'page arguments' => array(4),
|
| 144 |
'access arguments' => array('unblock expired accounts'),
|
| 145 |
);
|
| 146 |
return $items;
|
| 147 |
}
|
| 148 |
|
| 149 |
/**
|
| 150 |
* Load policy array from the database.
|
| 151 |
*
|
| 152 |
* @param $pid
|
| 153 |
* The policy id
|
| 154 |
*
|
| 155 |
* @return
|
| 156 |
* A populated policy array or NULL if not found.
|
| 157 |
*/
|
| 158 |
function pp_policy_load($pid) {
|
| 159 |
static $policies = array();
|
| 160 |
|
| 161 |
if (is_numeric($pid)) {
|
| 162 |
if (isset($policies[$pid])) {
|
| 163 |
return $policies[$pid];
|
| 164 |
}
|
| 165 |
else {
|
| 166 |
$policy = _password_policy_load_policy_by_pid($pid);
|
| 167 |
if ($policy) {
|
| 168 |
$policies[$pid] = $policy;
|
| 169 |
return $policy;
|
| 170 |
}
|
| 171 |
}
|
| 172 |
}
|
| 173 |
return FALSE;
|
| 174 |
}
|
| 175 |
|
| 176 |
/**
|
| 177 |
* Load user object from the database.
|
| 178 |
*
|
| 179 |
* @param $id
|
| 180 |
* The user id
|
| 181 |
*
|
| 182 |
* @return
|
| 183 |
* A populated user object or NULL if not found.
|
| 184 |
*/
|
| 185 |
function pp_uid_load($id) {
|
| 186 |
if (is_numeric($id)) {
|
| 187 |
$account = user_load(array('uid' => $id));
|
| 188 |
if ($account) {
|
| 189 |
return $account;
|
| 190 |
}
|
| 191 |
}
|
| 192 |
return FALSE;
|
| 193 |
}
|
| 194 |
|
| 195 |
/**
|
| 196 |
* Display a password policy form title.
|
| 197 |
*
|
| 198 |
* @param $policy
|
| 199 |
* Policy array
|
| 200 |
*
|
| 201 |
* @return
|
| 202 |
* A policy's title string
|
| 203 |
*/
|
| 204 |
function password_policy_format_title($policy) {
|
| 205 |
return $policy['name'];
|
| 206 |
}
|
| 207 |
|
| 208 |
/**
|
| 209 |
* Implementation of hook_user().
|
| 210 |
*/
|
| 211 |
function password_policy_user($op, &$edit, &$account, $category = NULL) {
|
| 212 |
switch ($op) {
|
| 213 |
case "insert":
|
| 214 |
if (!empty($edit['pass'])) {
|
| 215 |
// New users do not yet have an uid during the validation step, but they do have at this insert step.
|
| 216 |
// Store their first password in the system for use with the history constraint (if used).
|
| 217 |
if ($account->uid) {
|
| 218 |
_password_policy_store_password($account->uid, $edit['pass']);
|
| 219 |
}
|
| 220 |
}
|
| 221 |
break;
|
| 222 |
case "login":
|
| 223 |
$policy = _password_policy_load_active_policy();
|
| 224 |
// A value $edit['name'] is NULL for a one time login
|
| 225 |
if ($policy && ($account->uid > 1 || PASSWORD_POLICY_ADMIN) && !empty($edit['name'])) {
|
| 226 |
// Calculate expiration and warning times.
|
| 227 |
$expiration = $policy['expiration'];
|
| 228 |
$warning = max(explode(',', $policy['warning']));
|
| 229 |
$expiration_seconds = $expiration*60*60*24;
|
| 230 |
$warning_seconds = $warning*60*60*24;
|
| 231 |
// The policy was enabled
|
| 232 |
$policy_start = _password_policy_start($expiration_seconds);
|
| 233 |
if (!empty($expiration)) {
|
| 234 |
// Account expiration is active.
|
| 235 |
// Get the last password change time.
|
| 236 |
$result = db_query_range("SELECT * FROM {password_policy_history} WHERE uid = %d ORDER BY created DESC", $account->uid, 0, 1);
|
| 237 |
if ($row = db_fetch_object($result)) {
|
| 238 |
$last_change = $row->created;
|
| 239 |
}
|
| 240 |
else {
|
| 241 |
// A user has not changed his pwd after this module had been enabled.
|
| 242 |
$last_change = $account->created;
|
| 243 |
}
|
| 244 |
$time = time();
|
| 245 |
if ($time > max($policy_start, $last_change) + $expiration_seconds) {
|
| 246 |
// The account has expired.
|
| 247 |
db_query("UPDATE {users} SET status = '0' WHERE uid = %d", $account->uid);
|
| 248 |
$result = db_query("SELECT * FROM {password_policy_expiration} WHERE uid = %d", $account->uid);
|
| 249 |
if ($row = db_fetch_array($result)) {
|
| 250 |
db_query("UPDATE {password_policy_expiration} SET blocked = %d WHERE uid = %d", $time, $account->uid);
|
| 251 |
}
|
| 252 |
else {
|
| 253 |
db_query("INSERT INTO {password_policy_expiration} (uid, blocked) VALUES (%d, %d)", $account->uid, $time);
|
| 254 |
}
|
| 255 |
watchdog('password_policy', 'Password for user %name has expired.', array('%name' => $account->name), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit'));
|
| 256 |
if (PASSWORD_POLICY_BLOCK == 0) {
|
| 257 |
// User is blocked imediately and cannot change his password after expiration.
|
| 258 |
include_once(drupal_get_path('module', 'user') .'/user.pages.inc');
|
| 259 |
user_logout();
|
| 260 |
}
|
| 261 |
else {
|
| 262 |
// User is transfered to a password change page. User is unblocked on password change.
|
| 263 |
// If the user don't change password now, he will not be able to login again, since he is still blocked.
|
| 264 |
drupal_set_message(t('Your password has expired. You have to change it now or you won\'t be able to login again.'), 'error');
|
| 265 |
$destination = drupal_get_destination();
|
| 266 |
unset($_REQUEST['destination']);
|
| 267 |
drupal_goto('user/'. $account->uid .'/'. (module_exists('password_policy_password_tab') ? 'password' : 'edit'), $destination);
|
| 268 |
}
|
| 269 |
}
|
| 270 |
elseif ($time > max($policy_start, $last_change) + $expiration_seconds - $warning_seconds) {
|
| 271 |
// The warning is shown on login and the user is transfered to the password change page.
|
| 272 |
$days_left = ceil((max($policy_start, $last_change) + $expiration_seconds - $time)/(60*60*24));
|
| 273 |
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.'));
|
| 274 |
$destination = drupal_get_destination();
|
| 275 |
unset($_REQUEST['destination']);
|
| 276 |
drupal_goto('user/'. $account->uid .'/'. (module_exists('password_policy_password_tab') ? 'password' : 'edit'), $destination);
|
| 277 |
}
|
| 278 |
}
|
| 279 |
}
|
| 280 |
break;
|
| 281 |
case "delete":
|
| 282 |
db_query("DELETE FROM {password_policy_history} WHERE uid = %d", $account->uid);
|
| 283 |
db_query("DELETE FROM {password_policy_expiration} WHERE uid = %d", $account->uid);
|
| 284 |
break;
|
| 285 |
case "submit":
|
| 286 |
if ($account->status == 0 && isset($edit['status']) && $edit['status'] == 1 && user_access('unblock expired accounts')) {
|
| 287 |
// Account is beeing unblocked.
|
| 288 |
_password_policy_unblock($account);
|
| 289 |
}
|
| 290 |
break;
|
| 291 |
}
|
| 292 |
}
|
| 293 |
|
| 294 |
/**
|
| 295 |
* Implementation of hook_form_alter().
|
| 296 |
*/
|
| 297 |
function password_policy_form_alter(&$form, $form_state, $form_id) {
|
| 298 |
switch ($form_id) {
|
| 299 |
case "user_profile_form":
|
| 300 |
case "user_register":
|
| 301 |
// Password change form.
|
| 302 |
$uid = isset($form['#uid']) ? $form['#uid'] : NULL;
|
| 303 |
//if ($uid == 1 && !PASSWORD_POLICY_ADMIN) { break; }
|
| 304 |
$policy = _password_policy_load_active_policy();
|
| 305 |
$translate = array();
|
| 306 |
if (!empty($policy['policy'])) {
|
| 307 |
// Some policy constraints are active.
|
| 308 |
password_policy_add_policy_js($policy, $uid);
|
| 309 |
foreach ($policy['policy'] as $key => $value) {
|
| 310 |
$translate['constraint_'. $key] = _password_policy_constraint_error($key, $value);
|
| 311 |
}
|
| 312 |
}
|
| 313 |
|
| 314 |
// Printing out the restrictions.
|
| 315 |
if (PASSWORD_POLICY_SHOW_RESTRICTIONS && isset($translate)) {
|
| 316 |
$form['account']['pass']['#prefix'] = '<div id="account-pass-restrictions"><ul><li>'. implode('</li><li>', $translate) .'</li></ul></div>';
|
| 317 |
}
|
| 318 |
|
| 319 |
// Set a custom form validate and submit handlers.
|
| 320 |
$form['#validate'][] = 'password_policy_password_validate';
|
| 321 |
$form['#submit'][] = 'password_policy_password_submit';
|
| 322 |
break;
|
| 323 |
}
|
| 324 |
}
|
| 325 |
|
| 326 |
/**
|
| 327 |
* Implementation of hook_cron().
|
| 328 |
*/
|
| 329 |
function password_policy_cron() {
|
| 330 |
$policy = _password_policy_load_active_policy();
|
| 331 |
if ($policy) {
|
| 332 |
$expiration = $policy['expiration'];
|
| 333 |
$warnings = explode(',', $policy['warning']);
|
| 334 |
if (!empty($expiration)) {
|
| 335 |
// Accounts expiration is active.
|
| 336 |
// Get all users' last password change time. We don't touch blocked accounts
|
| 337 |
$result = db_query("SELECT u.uid AS uid, u.created AS created_u, p.created AS created_p, e.pid AS pid, e.warning AS warning, e.unblocked AS unblocked FROM {users} u LEFT JOIN {password_policy_history} p ON u.uid = p.uid LEFT JOIN {password_policy_expiration} e ON u.uid = e.uid WHERE u.uid > 0 AND u.status = '1' ORDER BY p.created ASC");
|
| 338 |
while ($row = db_fetch_object($result)) {
|
| 339 |
if ($row->uid == 1 && !PASSWORD_POLICY_ADMIN)
|
| 340 |
continue;
|
| 341 |
|
| 342 |
// Use account creation timestamp if there is no entry in password history table.
|
| 343 |
$accounts[$row->uid] = empty($row->created_p) ? $row->created_u : $row->created_p;
|
| 344 |
// 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.
|
| 345 |
$warns[$row->uid] = $row->warning;
|
| 346 |
// The user was last time unblocked (if was). We don't block this account again for some period of time.
|
| 347 |
$unblocks[$row->uid] = $row->unblocked;
|
| 348 |
// The user was last time unblocked (if was). We don't block this account again for some period of time.
|
| 349 |
$pids[$row->uid] = $row->pid;
|
| 350 |
}
|
| 351 |
// Calculate expiration time.
|
| 352 |
$expiration_seconds = $expiration*60*60*24;
|
| 353 |
$policy_start = _password_policy_start($expiration_seconds);
|
| 354 |
rsort($warnings, SORT_NUMERIC);
|
| 355 |
$time = time();
|
| 356 |
if ($accounts) {
|
| 357 |
foreach ($accounts as $uid => $last_change) {
|
| 358 |
// Check expiration and warning days for each account.
|
| 359 |
if (!empty($warnings)) {
|
| 360 |
foreach ($warnings as $warning) {
|
| 361 |
// Loop through all configured warning send out days. If today is the day we send out the warning.
|
| 362 |
$warning_seconds = $warning*60*60*24;
|
| 363 |
// Warning start time.
|
| 364 |
$start_period = max($policy_start, $last_change) + $expiration_seconds - $warning_seconds;
|
| 365 |
// Warning end time. We create a one day window for cron to run.
|
| 366 |
$end_period = $start_period + 60*60*24;
|
| 367 |
if ($warns[$uid] && $warns[$uid] > $start_period && $warns[$uid] < $end_period) {
|
| 368 |
// A warning was already mailed out
|
| 369 |
continue;
|
| 370 |
}
|
| 371 |
if ($time > $start_period && $time < $end_period) {
|
| 372 |
// A warning falls in the one day window, so we send out the warning.
|
| 373 |
$account = user_load(array('uid' => $uid));
|
| 374 |
$message = drupal_mail('password_policy', 'warning', $account->mail, user_preferred_language($account), array('account' => $account, 'days_left' => $warning));
|
| 375 |
if ($message['result']) {
|
| 376 |
// The mail was sent out successfully.
|
| 377 |
watchdog('password_policy', 'Password expiration warning mailed to %username at %email.', array('%username' => $account->name, '%email' => $account->mail));
|
| 378 |
}
|
| 379 |
if ($pids[$uid]) {
|
| 380 |
db_query("UPDATE {password_policy_expiration} SET warning = %d WHERE uid = %d", $time, $uid);
|
| 381 |
}
|
| 382 |
else {
|
| 383 |
db_query("INSERT INTO {password_policy_expiration} (uid, warning) VALUES (%d, %d)", $uid, $time);
|
| 384 |
}
|
| 385 |
}
|
| 386 |
}
|
| 387 |
}
|
| 388 |
if ($time > max($policy_start, $last_change) + $expiration_seconds && $time > $unblocks[$uid] + 60*60*24 && PASSWORD_POLICY_BLOCK == 0) {
|
| 389 |
// Block expired accounts. Unblocked accounts are not blocked for 24h.
|
| 390 |
// One time login lasts for a 24h.
|
| 391 |
db_query("UPDATE {users} SET status = '0' WHERE uid = %d", $uid);
|
| 392 |
if ($pids[$uid]) {
|
| 393 |
db_query("UPDATE {password_policy_expiration} SET blocked = %d WHERE uid = %d", $time, $uid);
|
| 394 |
}
|
| 395 |
else {
|
| 396 |
db_query("INSERT INTO {password_policy_expiration} (uid, blocked) VALUES (%d, %d)", $uid, $time);
|
| 397 |
}
|
| 398 |
|
| 399 |
$account = user_load(array('uid' => $uid));
|
| 400 |
watchdog('password_policy', 'Password for user %name has expired.', array('%name' => $account->name), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit'));
|
| 401 |
}
|
| 402 |
}
|
| 403 |
}
|
| 404 |
}
|
| 405 |
}
|
| 406 |
}
|
| 407 |
|
| 408 |
/**
|
| 409 |
* Implementation of hook_mail().
|
| 410 |
*/
|
| 411 |
function password_policy_mail($key, &$message, $params) {
|
| 412 |
$language = $message['language'];
|
| 413 |
$variables = password_policy_mail_tokens($params, $language);
|
| 414 |
$message['subject'] .= _password_policy_mail_text($key .'_subject', $language, $variables);
|
| 415 |
$message['body'][] = _password_policy_mail_text($key .'_body', $language, $variables);
|
| 416 |
}
|
| 417 |
|
| 418 |
/**
|
| 419 |
* Implementation of hook_simpletest().
|
| 420 |
*/
|
| 421 |
function password_policy_simpletest() {
|
| 422 |
// Scan through mymodule/tests directory for any .test files to tell SimpleTest module.
|
| 423 |
$tests = file_scan_directory(drupal_get_path('module', 'password_policy') .'/tests', '\.test');
|
| 424 |
return array_keys($tests);
|
| 425 |
}
|
| 426 |
|
| 427 |
//////////////////////////////////////////////////////////////////////////////
|
| 428 |
// FAPI
|
| 429 |
|
| 430 |
/**
|
| 431 |
* Password save validate handler.
|
| 432 |
*/
|
| 433 |
function password_policy_password_validate($form, &$form_state) {
|
| 434 |
$values = $form_state['values'];
|
| 435 |
$account = isset($form['_account']['#value']) ? $form['_account']['#value'] : (object)array('uid' => 0);
|
| 436 |
|
| 437 |
if (!empty($values['pass']) && !isset($values['auth_openid'])) {
|
| 438 |
$error = _password_policy_constraint_validate($values['pass'], $account);
|
| 439 |
if ($error) {
|
| 440 |
form_set_error('pass', t('Your password has not met the following requirement(s):') .'<ul><li>'. implode('</li><li>', $error) .'</li></ul>');
|
| 441 |
}
|
| 442 |
}
|
| 443 |
}
|
| 444 |
|
| 445 |
/**
|
| 446 |
* Password save submit handler.
|
| 447 |
*/
|
| 448 |
function password_policy_password_submit($form, &$form_state) {
|
| 449 |
global $user;
|
| 450 |
|
| 451 |
$values = $form_state['values'];
|
| 452 |
$account = isset($form['_account']['#value']) ? $form['_account']['#value'] : (object)array('uid' => 0);
|
| 453 |
|
| 454 |
// Track the hashed password values which can then be used in the history constraint.
|
| 455 |
if ($account->uid && !empty($values['pass'])) {
|
| 456 |
_password_policy_store_password($account->uid, $values['pass']);
|
| 457 |
|
| 458 |
// If user successfully changed his password we will unblock the account.
|
| 459 |
if ($user->uid == $account->uid) {
|
| 460 |
db_query("UPDATE {users} SET status = 1 WHERE uid = %d", $account->uid);
|
| 461 |
db_query("DELETE FROM {password_policy_expiration} WHERE uid = %d", $account->uid);
|
| 462 |
}
|
| 463 |
}
|
| 464 |
}
|
| 465 |
|
| 466 |
//////////////////////////////////////////////////////////////////////////////
|
| 467 |
// Expired accounts UI
|
| 468 |
|
| 469 |
/**
|
| 470 |
* Lists all expired accounts.
|
| 471 |
*/
|
| 472 |
function password_policy_expired_list() {
|
| 473 |
$header[] = array('data' => t('Username'), 'field' => 'name');
|
| 474 |
$header[] = array('data' => t('Blocked'), 'field' => 'blocked', 'sort' => 'desc');
|
| 475 |
$header[] = array('data' => t('Unblocked'), 'field' => 'unblocked');
|
| 476 |
$header[] = array('data' => t('Action'));
|
| 477 |
|
| 478 |
$result = pager_query("SELECT p.*, u.name FROM {password_policy_expiration} p INNER JOIN {users} u ON p.uid = u.uid WHERE p.blocked > 0". tablesort_sql($header), PASSWORD_POLICY_ENTRIES_PER_PAGE, 0, NULL);
|
| 479 |
while ($row = db_fetch_object($result)) {
|
| 480 |
$entry[$row->uid]['name'] = l($row->name, 'user/'. $row->uid);
|
| 481 |
$entry[$row->uid]['blocked'] = format_date($row->blocked, 'medium');
|
| 482 |
$entry[$row->uid]['unblocked'] = $row->unblocked < $row->blocked ? '' : format_date($row->unblocked, 'medium');
|
| 483 |
$entry[$row->uid]['action'] = $row->unblocked < $row->blocked ? l(t('unblock'), 'admin/user/expired/unblock/'. $row->uid) : '';
|
| 484 |
}
|
| 485 |
if (!isset($entry)) {
|
| 486 |
$colspan = '4';
|
| 487 |
$entry[] = array(array('data' => t('No entries'), 'colspan' => $colspan));
|
| 488 |
}
|
| 489 |
$page = theme('table', $header, $entry);
|
| 490 |
$page .= theme('pager', NULL, PASSWORD_POLICY_ENTRIES_PER_PAGE, 0);
|
| 491 |
|
| 492 |
return $page;
|
| 493 |
}
|
| 494 |
|
| 495 |
/**
|
| 496 |
* Unblocks the expired account.
|
| 497 |
*/
|
| 498 |
function password_policy_expired_unblock($account) {
|
| 499 |
// Unblock the user
|
| 500 |
_password_policy_unblock($account);
|
| 501 |
drupal_goto('admin/user/expired');
|
| 502 |
}
|
| 503 |
|
| 504 |
//////////////////////////////////////////////////////////////////////////////
|
| 505 |
// Mail handling
|
| 506 |
|
| 507 |
/**
|
| 508 |
* Returns a mail string for a variable name.
|
| 509 |
*
|
| 510 |
* Used by password_policy_mail() and the settings forms to retrieve strings.
|
| 511 |
*/
|
| 512 |
function _password_policy_mail_text($key, $language = NULL, $variables = array()) {
|
| 513 |
$langcode = isset($language) ? $language->language : NULL;
|
| 514 |
|
| 515 |
if ($admin_setting = variable_get('password_policy_'. $key, FALSE)) {
|
| 516 |
// An admin setting overrides the default string.
|
| 517 |
return strtr($admin_setting, $variables);
|
| 518 |
}
|
| 519 |
else {
|
| 520 |
// No override, return with default strings.
|
| 521 |
switch ($key) {
|
| 522 |
case 'warning_subject':
|
| 523 |
return t('Password expiration warning for !username at !site', $variables, $langcode);
|
| 524 |
case 'warning_body':
|
| 525 |
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, $langcode);
|
| 526 |
}
|
| 527 |
}
|
| 528 |
}
|
| 529 |
|
| 530 |
/**
|
| 531 |
* Return an array of token to value mappings for user e-mail messages.
|
| 532 |
*
|
| 533 |
* @param $params
|
| 534 |
* Structured array with the parameters.
|
| 535 |
* @param $language
|
| 536 |
* Language object to generate the tokens with.
|
| 537 |
*
|
| 538 |
* @return
|
| 539 |
* Array of mappings from token names to values (for use with strtr()).
|
| 540 |
*/
|
| 541 |
function password_policy_mail_tokens($params, $language) {
|
| 542 |
global $base_url;
|
| 543 |
$account = $params['account'];
|
| 544 |
$tokens = array(
|
| 545 |
'!username' => $account->name,
|
| 546 |
'!site' => variable_get('site_name', 'Drupal'),
|
| 547 |
'!uri' => $base_url,
|
| 548 |
'!uri_brief' => drupal_substr($base_url, drupal_strlen('http://')),
|
| 549 |
'!date' => format_date(time(), 'medium', '', NULL, $language->language),
|
| 550 |
'!login_uri' => url('user', array('absolute' => TRUE)),
|
| 551 |
'!edit_uri' => url('user/'. $account->uid .'/'. (module_exists('password_policy_password_tab') ? 'password' : 'edit'), array('absolute' => TRUE)),
|
| 552 |
'!days_left' => isset($params['days_left']) ? $params['days_left'] : NULL,
|
| 553 |
'!login_url' => isset($params['login_url']) ? $params['login_url'] : NULL,
|
| 554 |
);
|
| 555 |
return $tokens;
|
| 556 |
}
|
| 557 |
|
| 558 |
//////////////////////////////////////////////////////////////////////////////
|
| 559 |
// Constraints API
|
| 560 |
|
| 561 |
/**
|
| 562 |
* Validates user password. Returns NULL on success or array with error messages
|
| 563 |
* from the constraints on failure.
|
| 564 |
*
|
| 565 |
* @param $pass
|
| 566 |
* Clear text password.
|
| 567 |
* @param &$account
|
| 568 |
* Populated user object.
|
| 569 |
*
|
| 570 |
* @return
|
| 571 |
* NULL or array with error messages.
|
| 572 |
*/
|
| 573 |
function _password_policy_constraint_validate($pass, &$account) {
|
| 574 |
$error = NULL;
|
| 575 |
$policy = _password_policy_load_active_policy();
|
| 576 |
if (!empty($policy['policy'])) {
|
| 577 |
foreach ($policy['policy'] as $key => $value) {
|
| 578 |
if (!call_user_func('password_policy_constraint_'. $key .'_validate', $pass, $value, $account->uid)) {
|
| 579 |
$error[] = call_user_func('password_policy_constraint_'. $key .'_error', $value);
|
| 580 |
}
|
| 581 |
}
|
| 582 |
}
|
| 583 |
return $error;
|
| 584 |
}
|
| 585 |
|
| 586 |
/**
|
| 587 |
* Gets the constraint's name and description.
|
| 588 |
*
|
| 589 |
* @param $name
|
| 590 |
* Name of the constraint.
|
| 591 |
*
|
| 592 |
* @return
|
| 593 |
* Array containing the name and description.
|
| 594 |
*/
|
| 595 |
function _password_policy_constraint_description($name) {
|
| 596 |
return call_user_func('password_policy_constraint_'. $name .'_description');
|
| 597 |
}
|
| 598 |
|
| 599 |
/**
|
| 600 |
* Gets the constraint's error message.
|
| 601 |
*
|
| 602 |
* @param $name
|
| 603 |
* Name of the constraint.
|
| 604 |
* @param $constraint
|
| 605 |
* Constraint value.
|
| 606 |
*
|
| 607 |
* @return
|
| 608 |
* Error message.
|
| 609 |
*/
|
| 610 |
function _password_policy_constraint_error($name, $constraint) {
|
| 611 |
return call_user_func('password_policy_constraint_'. $name .'_error', $constraint);
|
| 612 |
}
|
| 613 |
|
| 614 |
/**
|
| 615 |
* Gets the javascript code from the constraint to be added to the password validation.
|
| 616 |
*
|
| 617 |
* @param $name
|
| 618 |
* Name of the constraint.
|
| 619 |
* @param $constraint
|
| 620 |
* Constraint value.
|
| 621 |
* @param $uid
|
| 622 |
* User's id.
|
| 623 |
*
|
| 624 |
* @return
|
| 625 |
* Javascript code snippet for the constraint.
|
| 626 |
*/
|
| 627 |
function _password_policy_constraint_js($name, $constraint, $uid) {
|
| 628 |
if (function_exists('password_policy_constraint_'. $name .'_js')) {
|
| 629 |
return call_user_func('password_policy_constraint_'. $name .'_js', $constraint, $uid);
|
| 630 |
}
|
| 631 |
}
|
| 632 |
|
| 633 |
//////////////////////////////////////////////////////////////////////////////
|
| 634 |
// Auxiliary functions
|
| 635 |
|
| 636 |
/**
|
| 637 |
* Loads the policy with the specified id.
|
| 638 |
*
|
| 639 |
* @param $pid
|
| 640 |
* The policy id.
|
| 641 |
*
|
| 642 |
* @return
|
| 643 |
* A policy array, or NULL if no policy was found.
|
| 644 |
*/
|
| 645 |
function _password_policy_load_policy_by_pid($pid) {
|
| 646 |
$result = db_query('SELECT * FROM {password_policy} WHERE pid = %d', $pid);
|
| 647 |
$row = db_fetch_array($result);
|
| 648 |
if (is_array($row)) {
|
| 649 |
// fetch and unserialize the serialized policy
|
| 650 |
$row['policy'] = unserialize($row['policy']);
|
| 651 |
return $row;
|
| 652 |
}
|
| 653 |
return NULL;
|
| 654 |
}
|
| 655 |
|
| 656 |
/**
|
| 657 |
* Loads the default (enabled and active) policy.
|
| 658 |
*
|
| 659 |
* @return
|
| 660 |
* A policy array, or NULL if no active policy exists.
|
| 661 |
*/
|
| 662 |
function _password_policy_load_active_policy() {
|
| 663 |
$result = db_query('SELECT * FROM {password_policy} p WHERE p.enabled = 1');
|
| 664 |
$row = db_fetch_array($result);
|
| 665 |
if (is_array($row)) {
|
| 666 |
// fetch and unserialize the serialized policy
|
| 667 |
$row['policy'] = unserialize($row['policy']);
|
| 668 |
return $row;
|
| 669 |
}
|
| 670 |
return NULL;
|
| 671 |
}
|
| 672 |
|
| 673 |
/**
|
| 674 |
* Returns a timestamp when the policy was enabled.
|
| 675 |
* It might be pushed back by the expiration time for the retroactive behaviour
|
| 676 |
* based on the configuration options.
|
| 677 |
*
|
| 678 |
* @param $expiration
|
| 679 |
* Expiration time in seconds.
|
| 680 |
*
|
| 681 |
* @return
|
| 682 |
* A timestamp when a policy was enabled
|
| 683 |
*/
|
| 684 |
function _password_policy_start($expiration = 0) {
|
| 685 |
$result = db_query_range("SELECT created FROM {password_policy} WHERE enabled = 1 ORDER BY enabled DESC", 0, 1);
|
| 686 |
if ($row = db_fetch_object($result)) {
|
| 687 |
$policy_start = $row->created;
|
| 688 |
}
|
| 689 |
if (PASSWORD_POLICY_BEGIN == 1) {
|
| 690 |
// Password older than expiration time expires starting from setting the policy.
|
| 691 |
$policy_start -= $expiration;
|
| 692 |
}
|
| 693 |
return $policy_start;
|
| 694 |
}
|
| 695 |
|
| 696 |
/**
|
| 697 |
* Stores user password hash.
|
| 698 |
*
|
| 699 |
* @param $uid
|
| 700 |
* User id.
|
| 701 |
* @param $pass
|
| 702 |
* Clear text password.
|
| 703 |
*/
|
| 704 |
function _password_policy_store_password($uid, $pass) {
|
| 705 |
db_query("INSERT INTO {password_policy_history} (uid, pass, created) VALUES (%d, '%s', %d)", $uid, md5($pass), time());
|
| 706 |
}
|
| 707 |
|
| 708 |
/**
|
| 709 |
* Unblocks the expired account.
|
| 710 |
*
|
| 711 |
* @param $uccount
|
| 712 |
* User pbject.
|
| 713 |
*/
|
| 714 |
function _password_policy_unblock($account) {
|
| 715 |
// Unblock the user
|
| 716 |
db_query("UPDATE {users} SET status = '1' WHERE uid = %d", $account->uid);
|
| 717 |
db_query("UPDATE {password_policy_expiration} SET unblocked = %d WHERE uid = %d", time(), $account->uid);
|
| 718 |
|
| 719 |
$message = drupal_mail('user', 'password_reset', $account->mail, user_preferred_language($account), array('account' => $account, 'login_url' => user_pass_reset_url($account)));
|
| 720 |
if ($message['result']) {
|
| 721 |
watchdog('password_policy', 'Password reset instructions mailed to %name at %email.', array('%name' => $account->name, '%email' => $account->mail));
|
| 722 |
}
|
| 723 |
drupal_set_message(t('The user %name has been unblocked.', array('%name' => $account->name)));
|
| 724 |
}
|
| 725 |
|
| 726 |
/**
|
| 727 |
* Add password policy JS
|
| 728 |
*
|
| 729 |
* @param $policy
|
| 730 |
* A policy array.
|
| 731 |
* @param $uid
|
| 732 |
* A user ID for which the policy is applied.
|
| 733 |
*/
|
| 734 |
function password_policy_add_policy_js($policy, $uid) {
|
| 735 |
|
| 736 |
// Print out the javascript which checks the strength of the password.
|
| 737 |
// It overwrites the defaut core javascript function.
|
| 738 |
drupal_add_js(drupal_get_path('module', 'password_policy') .'/password_policy.js', 'module');
|
| 739 |
$s = "/**\n";
|
| 740 |
$s .= " * Evaluate the strength of a user's password.\n";
|
| 741 |
$s .= " *\n";
|
| 742 |
$s .= " * Returns the estimated strength and the relevant output message.\n";
|
| 743 |
$s .= " */\n";
|
| 744 |
$s .= "Drupal.evaluatePasswordStrength = function(value) {\n";
|
| 745 |
$s .= " var strength = \"high\", msg = [], translate = Drupal.settings.password_policy;\n";
|
| 746 |
// Print out each constraint's javascript password strength evaluation.
|
| 747 |
foreach ($policy['policy'] as $key => $value) {
|
| 748 |
$s .= _password_policy_constraint_js($key, $value, $uid);
|
| 749 |
// Constraints' error messages are used in javascript.
|
| 750 |
$translate['constraint_'. $key] = _password_policy_constraint_error($key, $value);
|
| 751 |
}
|
| 752 |
$s .= " msg = msg.length > 0 ? translate.needsMoreVariation +\"<ul><li>\"+ msg.join(\"</li><li>\") +\"</li></ul>\" : \"\";\n";
|
| 753 |
$s .= " return { strength: strength, message: msg };\n";
|
| 754 |
$s .= "};\n";
|
| 755 |
drupal_add_js($s, 'inline');
|
| 756 |
|
| 757 |
drupal_add_js(array(
|
| 758 |
'password_policy' => array_merge(array(
|
| 759 |
'strengthTitle' => t('Password quality:'),
|
| 760 |
'lowStrength' => t('Bad'),
|
| 761 |
'mediumStrength' => t('Medium'),
|
| 762 |
'highStrength' => t('Good'),
|
| 763 |
'needsMoreVariation' => t('The password does not include enough variation to be secure.'),
|
| 764 |
'confirmSuccess' => t('Yes'),
|
| 765 |
'confirmFailure' => t('No'),
|
| 766 |
'confirmTitle' => t('Passwords match:')), $translate)),
|
| 767 |
'setting');
|
| 768 |
}
|
| 769 |
|