You are here

force_password_change.module in Force Password Change 7

File

force_password_change.module
View source
<?php

/**
 * Implementation of hook_perm()
 */
function force_password_change_permission() {
  return array(
    'Administer force password change' => array(
      'title' => t('Force changing of passwords'),
      'description' => t('Gives users the ability to force users to change their password.'),
    ),
  );
}

/**
 * Implementation of hook_menu()
 */
function force_password_change_menu() {
  $menu['admin/config/people/force_password_change'] = array(
    'title' => 'Force password change',
    'description' => t('Force users to change their password either immediately or after a period of time.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'force_password_change_settings',
    ),
    'access arguments' => array(
      'Administer force password change',
    ),
    'file' => 'force_password_change.pages.inc',
  );
  $menu['admin/config/people/force_password_change/list/%'] = array(
    'title' => 'dummy title. Does not need translation',
    'page callback' => 'force_password_change_list',
    'page arguments' => array(
      5,
    ),
    'access arguments' => array(
      'Administer force password change',
    ),
    'file' => 'force_password_change.pages.inc',
    'type' => MENU_CALLBACK,
  );
  return $menu;
}

/**
 * Implementation of hook_init()
 *
 * This function checks two things:
 * 1) Whether the user's account has been flagged to change their password
 * 2) Whether their password has expired
 * 
 * If either of the two conditions above returns true, they are redirected to the change password page
 */
function force_password_change_init() {
  global $user;

  // Get the url for the change password screen as set in on the settings page. This is because
  // users may have aliased the change password page, or used the chpwd module which moves the
  // change password screen to another page altogether
  $change_password_url = preg_replace('/!uid/', $user->uid, variable_get('force_password_change_change_password_url', 'user/!uid/edit'));

  // Redirects should only happen if the user is logged in, not on the change password page, and not logging out
  if ($user->uid && current_path() != $change_password_url && current_path() != drupal_get_path_alias('user/logout')) {
    $redirect = FALSE;

    // Check whether testing is done on every page, or only on login
    if (variable_get('force_password_change_login_or_init', 0)) {

      // Enter here when testing is done only on login
      // Pending users is a list of UIDs for all users who have logged in
      // and been flagged to change their password
      $pending_users = variable_get('force_password_change_pending_login_users', array());
      if (isset($pending_users[$user->uid])) {

        // The user is required to change their password.
        // Force a redirect to the password change page.
        $value = $pending_users[$user->uid]['value'];
        $destination = array();
        $redirect = TRUE;
      }
    }
    else {

      // Enter here when testing is done on every page.
      // Check to see if a password change is pending.
      $pending_change = force_password_change_check();
      if ($pending_change) {

        // The user is required to change their password.
        // Force a redirect to the user password change page.
        $value = $pending_change;
        $destination = array(
          'query' => array(
            'destination' => $_GET['q'],
          ),
        );
        $redirect = TRUE;
      }
    }
    if ($redirect) {

      // The user is redirected. A message needs to be set informing them of the reason
      // for being redirected to the password change page.
      //
      // When value is 1, the user has been forced to change their password by an administrator
      // If value is not 1, it is a timestamp indicating the time period after which their password
      // is set to expire.
      if ($value == 1) {
        drupal_set_message(t('An administrator has required that you change your password. Please change your password to proceed.'), 'error', FALSE);
      }
      else {
        $time_period = force_password_change_get_text_date($value);
        drupal_set_message(t('This site requires that you change your password every !time_period. Please change your password to proceed.', array(
          '!time_period' => $time_period,
        )));
      }

      // Redirect the user to the change password page
      drupal_goto($change_password_url, $destination);
    }
  }
}

/**
 * Checks whether or not user has a pending password change
 */
function force_password_change_check() {
  global $user;

  // If the user's account has been flagged, a redirect is required
  if (isset($user->force_password_change) && $user->force_password_change) {
    return 1;
  }
  elseif (variable_get('force_password_change_expire_password', FALSE)) {

    // The user's account has not been flagged. Check to see
    // if their password has expired according to the rules of the module.
    //
    // First thing is to check the time of their last password change,
    // and the time of their account creation
    $query = db_select('force_password_change_users', 'fpcu');
    $alias = $query
      ->join('users', 'u', 'u.uid = fpcu.uid');
    $query
      ->fields('fpcu', array(
      'last_password_change',
    ))
      ->fields($alias, array(
      'created',
    ))
      ->condition($alias . '.uid', $user->uid);
    $user_data = $query
      ->execute()
      ->fetchObject();

    // Get the time period after which their password should expire
    // according to the rules laid out in the module settings page. Only the
    // role with the highest priority is retrieved
    $query = db_select('force_password_change_expiry', 'fpce');
    $expiry = $query
      ->fields('fpce', array(
      'expiry',
    ))
      ->condition('fpce.rid', array_keys($user->roles), 'IN')
      ->orderBy('fpce.weight')
      ->range(0, 1)
      ->addTag('force_password_change_expiry_check')
      ->execute()
      ->fetchField();

    // Test to see if their password has expired
    if ($expiry && ($user_data->last_password_change != '' && REQUEST_TIME - $expiry > $user_data->last_password_change) || $user_data->last_password_change == '' && REQUEST_TIME - $expiry > $user_data->created) {

      // Their password has expired, so their user account is flagged
      // and the expiration time period is returned, which will trigger the redirect
      // and be used to generate the message shown to the user
      $query = db_update('users')
        ->fields(array(
        'force_password_change' => 1,
      ))
        ->condition('uid', $user->uid)
        ->execute();
      return $expiry;
    }
  }
  return FALSE;
}

/**
 * Converts a time period of password expiry dates to the textual representation
 * of the number of years, months, days or hours that it represents
 */
function force_password_change_get_text_date($timestamp) {
  $year = 60 * 60 * 24 * 365;
  if ($timestamp % $year === 0) {
    $time_period = $timestamp / $year;
    $time_period = $time_period > 1 ? $time_period . ' ' . t('years') : t('year');
  }
  else {
    $week = 60 * 60 * 24 * 7;
    if ($timestamp % $week === 0) {
      $time_period = $timestamp / $week;
      $time_period = $time_period > 1 ? $time_period . ' ' . t('weeks') : t('week');
    }
    else {
      $day = 60 * 60 * 24;
      if ($timestamp % $day === 0) {
        $time_period = $timestamp / $day;
        $time_period = $time_period > 1 ? $time_period . ' ' . t('days') : t('day');
      }
      else {
        $hour = 60 * 60;
        if ($timestamp % $hour === 0) {
          $time_period = $timestamp / $hour;
          $time_period = $time_period > 1 ? $time_period . ' ' . t('hours') : t('hour');
        }
      }
    }
  }
  return $time_period;
}

/**
 * Implementation of hook_user_login()
 */
function force_password_change_user_login(&$edit, $account) {

  // Only test if the user's password should be checked when
  // the site's settings require it on login and not on
  // every page load.
  if (variable_get('force_password_change_login_or_init', 0)) {

    // Check to see if the user has a pending password change
    $pending_change = force_password_change_check();
    if ($pending_change) {

      // The user has been required to change their password, so their
      // password change data is added to the {variables} table.
      $pending_users = variable_get('force_password_change_pending_login_users', array());
      $pending_users[$account->uid] = array(
        // The current path is taken as a destination for the user so they can be
        // redirected back to it after changing their password
        'destination' => current_path(),
        'value' => $pending_change,
      );
      variable_set('force_password_change_pending_login_users', $pending_users);
    }
  }
}

/**
 * Implementation of hook_user_insert()
 */
function force_password_change_user_insert(&$edit, $account, $category) {

  // This module requires that their is a row in the {force_password_change_users} table
  // for every user. This query adds that row to the database.
  $query = db_insert('force_password_change_users')
    ->fields(array(
    'uid' => $account->uid,
  ))
    ->execute();

  // If the site settings require users to change their password on first time login,
  // the user's account is flagged to have the password changed.
  if (variable_get('force_password_change_first_time_login_password_change', 0)) {
    $query = db_update('users')
      ->fields(array(
      'force_password_change' => 1,
    ))
      ->condition('uid', $account->uid)
      ->execute();
  }
  elseif (isset($edit['force_password_change']) && $edit['force_password_change']) {
    $query = db_update('users')
      ->fields(array(
      'force_password_change' => 1,
    ))
      ->condition('uid', $account->uid)
      ->execute();
    $forced_uids = variable_get('force_password_change_first_time_uids', array());
    $forced_uids[$account->uid] = $account->uid;
    variable_set('force_password_change_first_time_uids', $forced_uids);
  }
  unset($edit['force_password_change']);
}

/**
 * Implementation of hook_user_update()
 */
function force_password_change_user_update(&$edit, $account, $category) {
  global $user;

  // If a user has changed their password, the time of their password change is
  // saved to the database.
  if ($account->pass != $account->original->pass) {
    $query = db_update('force_password_change_users')
      ->fields(array(
      'last_password_change' => REQUEST_TIME,
    ))
      ->condition('uid', $account->uid)
      ->execute();
  }

  // This next conditional is entered when a user is changing their own password
  if ($account->force_password_change && $user->uid == $account->uid && isset($edit['pending_force_password_change']) && $edit['pending_force_password_change']) {

    // Remove the flag from the users account
    $query = db_update('users')
      ->fields(array(
      'force_password_change' => 0,
    ))
      ->condition('uid', $account->uid)
      ->execute();
    $forced_uids = variable_get('force_password_change_first_time_uids', array());
    if (isset($forced_uids[$account->uid])) {
      unset($forced_uids[$account->uid]);
      variable_set('force_password_change_first_time_uids', $forced_uids);
    }
    $pending_users = variable_get('force_password_change_pending_login_users', array());
    if (isset($pending_users[$account->uid])) {
      $destination = $pending_users[$account->uid]['destination'];
      unset($pending_users[$account->uid]);
      variable_set('force_password_change_pending_login_users', $pending_users);
      $_REQUEST['destination'] = $destination;
    }
  }

  // An admin with the proper permissions is able to flag a user to change their password
  // on the user's account page. This next section setst that falg.
  if (isset($edit['force_password_change']) && $edit['force_password_change']) {
    $query = db_update('users')
      ->fields(array(
      'force_password_change' => 1,
    ))
      ->condition('uid', $account->uid)
      ->execute();

    // Flag the time of the forced password change for statistics sake.
    $query = db_update('force_password_change_users')
      ->fields(array(
      'last_force' => REQUEST_TIME,
    ))
      ->condition('uid', $account->uid)
      ->execute();
    unset($edit['force_password_change']);
  }
}

/**
 * Implementation of hook_user_delete()
 */
function force_password_change_user_delete($account) {

  // When a user's account is deleted, their row is removed from the
  // {force_password_change_users} table in the database
  $query = db_delete('force_password_change_users')
    ->condition('uid', $account->uid)
    ->execute();
}

/**
 * This function is called after a user's account page is updated
 */
function force_password_change_validate_user($form, &$form_state) {
  global $user;

  // Check to see if the user's account has been flagged to change their password, and if so,
  // have they changed it?
  if (isset($form['#user']->force_password_change) && $form['#user']->force_password_change && $form['#user']->uid == $user->uid) {
    if ($form_state['input']['pass']['pass1'] == '') {
      form_set_error('pass', t('You must choose a new password'));
    }
  }
  require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');

  // Check to see if the new password is different from the old password
  if (user_check_password($form_state['input']['pass']['pass1'], $form['#user'])) {
    form_set_error('pass', t('You cannot use your current password. Please choose a different password.'));
  }
}

/**
 * Implementation of hook_form_alter()
 */
function force_password_change_form_alter(&$form, &$form_state, $form_id) {

  // Alter the page that display's a list of roles on the site.
  if ($form_id == 'user_admin_roles') {

    // Override the default theme and add one from this moulde in order to add a new
    // 'force password change' selection to the list of options
    $form['#theme'] = array(
      'force_password_change_user_admin_roles_form',
    );

    // the following submit function will add a new row to {force_password_change_roles}
    // add a new submit function for any new roles created.
    $form['add']['#submit'][] = 'force_password_change_add_role';
  }
  elseif ($form_id == 'user_admin_role') {

    // Set a message depending on whether pending password changes are checked on
    // every page, or only login
    if (variable_get('force_password_change_login_or_init', 0)) {
      $description = t('Users who are not signed in will be required to change their password immediately upon login. Users who are currently signed in will be required to change their password upon their next page click, and after changing their password will be redirected back to the page they were attempting to access.');
    }
    else {
      $description = t('Users will be required to change their password upon their next login.');
    }
    $form['force_password_change'] = array(
      '#type' => 'checkbox',
      '#title' => t('Force users in this role to change their password'),
      '#description' => $description . '<br />' . t('Note: When you return to this page, this box will be unchecked. This is because this setting is a trigger, not a persistant state.'),
      '#weight' => -1,
    );
    $form['name']['#weight'] = -2;

    // Add a custom submit function that removes the role from the {force_password_change_roles} table
    $form['actions']['delete']['#submit'][] = 'force_password_change_delete_role';

    // Add a custom submit function that forces all users in the role to change their password
    $form['#submit'][] = 'force_password_change_edit_role';
  }
  elseif ($form_id == 'user_profile_form') {
    global $user;

    // Only alter the form if the user has the correct permission
    if (user_access('Administer force password change', $user)) {

      // Find out where the password area lies, in case
      // a module has moved it from its default location
      if (isset($form['account'])) {
        $use_form =& $form['account'];
      }
      else {
        $use_form =& $form;
      }

      // Set a weight on the name and email
      // fields to ensure they remain at the top
      $use_form['name']['#weight'] = -10;
      $use_form['mail']['#weight'] = -9;

      // Create a fieldset to contain all password information
      $use_form['password'] = array(
        '#type' => 'fieldset',
        '#title' => t('Password'),
        '#weight' => -1,
      );

      // Add the original password field to the fieldset
      $use_form['password']['pass'] = $use_form['pass'];
      if (isset($use_form['pass'])) {
        unset($use_form['pass']);
      }

      // Get the correct message to show depending on the site settings
      if (variable_get('force_password_change_login_or_init', 0)) {
        $description = t('If this box is checked, the user will be forced to change their password. If the user is signed in, they will be forced to change their password on their next page load. If they are not signed in, they will be forced to change their password the next time they log in.');
      }
      else {
        $description = t('If this box is checked, the user will be forced to change their password upon their next login.');
      }
      $use_form['password']['force_password_change'] = array(
        '#type' => 'checkbox',
        '#title' => t('Force this user to change their password'),
        '#access' => user_access('Administer force password change'),
        '#description' => $description . '<br />' . t('Note: This box will be unchecked each time the page is loaded, as it is a trigger, not a persistent state.'),
      );

      // Get the data regarding the users last password change and last force
      $force_password_data = db_query('SELECT last_password_change, last_force FROM {force_password_change_users} WHERE uid = :uid', array(
        ':uid' => $form['#user']->uid,
      ))
        ->fetchObject();
      if ($force_password_data->last_force != '') {
        $last_force = format_date($force_password_data->last_force, 'small');
      }
      elseif (variable_get('force_password_change_first_time_login_password_change', FALSE) && $form['#user']->created > variable_get('force_password_change_installation_date', 0)) {
        $last_force = t('Their first login');
      }
      else {
        $forced_uids = variable_get('force_password_change_first_login_change', array());
        if (count($forced_uids) && isset($forced_uids[$form['#user']->uid])) {
          $last_force = t('Their first login');
        }
        elseif ($force_password_data->last_password_change != '') {
          $last_force = t('Their first login');
        }
        else {
          $last_force = t('Never');
        }
      }
      $variables = array(
        'pending_change' => $form['#user']->force_password_change ? t('Yes') : t('No'),
        'last_force' => $last_force,
        'last_change' => $force_password_data->last_password_change != '' ? format_date($force_password_data->last_password_change, 'small') : t('Never'),
      );

      // Display the user's password change stats for the administrator
      $use_form['password']['password_stats'] = array(
        '#markup' => theme('force_password_change_stats', $variables),
      );
    }

    // Set a flag that will be used in hook_update_user()
    $form['pending_force_password_change'] = array(
      '#type' => 'value',
      '#value' => $form['#user']->force_password_change,
    );

    // Add a custom validation function to check that the user has both filled in a password, and that it
    // has been changed from the previous password
    $form['#validate'][] = 'force_password_change_validate_user';
  }
  elseif ($form_id == 'user_register_form' && $GLOBALS['user']->uid != 0) {

    // Add the option to force the user to change their password on
    // first time login only if it is not turned on site-wide
    if (variable_get('force_password_change_first_login_change', 0)) {

      // Get the location of the password field
      if ($form['account']) {
        $use_form =& $form['account'];
      }
      else {
        $use_form =& $form;
      }
      $use_form['name']['#weight'] = -10;
      $use_form['mail']['#weight'] = -9;
      $use_form['password']['#weight'] = -8;
      $use_form['password']['pass'] = $use_form['pass'];
      unset($use_form['pass']);
      $use_form['password']['force_password_change'] = array(
        '#type' => 'checkbox',
        '#title' => t('Force password change on first-time login'),
        '#description' => t('If this box is checked, the user will be forced to change their password on their first login.'),
        '#access' => user_access('Administer force password change'),
      );
    }
  }
}

/**
 * Implementation of hook_theme()
 */
function force_password_change_theme() {
  return array(
    'force_password_change_user_admin_roles_form' => array(
      'render element' => 'form',
      'file' => 'force_password_change.pages.inc',
    ),
    'force_password_change_settings' => array(
      'render element' => 'form',
      'file' => 'force_password_change.pages.inc',
    ),
    'force_password_change_expiry' => array(
      'render element' => 'form',
      'file' => 'force_password_change.pages.inc',
    ),
    'force_password_change_list' => array(
      'arguments' => array(
        'last_change' => NULL,
        'pending_users_table' => NULL,
        'non_pending_users_table' => NULL,
        'force_password_change_form' => NULL,
        'back_button' => NULL,
      ),
      'file' => 'force_password_change.pages.inc',
    ),
    'force_password_change_stats' => array(
      'arguments' => array(
        'variables' => array(
          'pending_change' => NULL,
          'last_force' => NULL,
          'last_change' => NULL,
        ),
      ),
      'file' => 'force_password_change.pages.inc',
    ),
  );
}

/**
 * This function is called after a new role has been added to the system.
 * It creates a new row in the {force_password_change_roles} table for the role that has been created
 */
function force_password_change_add_role($form, &$form_state) {
  $rid = db_query('SELECT rid FROM {role} WHERE name = :name', array(
    ':name' => $form_state['values']['name'],
  ))
    ->fetchCol();
  $query = db_insert('force_password_change_roles')
    ->fields(array(
    'rid' => $rid[0],
  ))
    ->execute();
}

/**
 * Callback #submit function called on the role edit page when the user clicks the delete button.
 * This function removes the fole from the {force_password_change_roles} table.
 */
function force_password_change_delete_role($form, &$form_state) {
  $query = db_delete('force_password_change_roles')
    ->condition('rid', $form_state['values']['rid'])
    ->execute();
}

/**
 * Callback #submit function called on the role edit page when the user clicks the save button
 */
function force_password_change_edit_role($form, &$form_state) {

  // Only flag user's accounts to be changed if the checkbox
  // was selected
  if ($form_state['values']['force_password_change']) {

    // Get the UIDs for all users in the role
    $db_uids = db_query('SELECT uid ' . 'FROM {users_roles} ' . 'WHERE rid = :rid', array(
      ':rid' => $form_state['values']['rid'],
    ));
    $uids = array();
    foreach ($db_uids as $uid) {
      $uids[] = $uid->uid;
    }
    if (isset($uids[0])) {

      // flag the users accounts
      force_password_change_force_users($uids);
    }

    // Set the last force time for the role for statistics sake
    $query = db_update('force_password_change_roles')
      ->fields(array(
      'last_force' => REQUEST_TIME,
    ))
      ->condition('rid', $form_state['values']['rid'])
      ->execute();
    if (variable_get('force_password_change_login_or_init', 0)) {
      $description = t('Users in this role will be required to immediately change their password');
    }
    else {
      $description = t('Users will be required to change their password upon their next login.');
    }
    drupal_set_message($description);
  }
}

/**
 * This function flags users accounts to change their passwords
 * It also logs the force time for statistics sake. If $uids is
 * an empty array, all users will have their stats updated
 */
function force_password_change_force_users($uids = array()) {
  $query = db_update('users')
    ->fields(array(
    'force_password_change' => 1,
  ));
  if (!empty($uids)) {
    $query
      ->condition('uid', $uids, 'IN');
  }
  $query
    ->execute();
  $query = db_update('force_password_change_users')
    ->fields(array(
    'last_force' => REQUEST_TIME,
  ));
  if (!empty($uids)) {
    $query
      ->condition('uid', $uids, 'IN');
  }
  $query
    ->execute();
}

Functions

Namesort descending Description
force_password_change_add_role This function is called after a new role has been added to the system. It creates a new row in the {force_password_change_roles} table for the role that has been created
force_password_change_check Checks whether or not user has a pending password change
force_password_change_delete_role Callback #submit function called on the role edit page when the user clicks the delete button. This function removes the fole from the {force_password_change_roles} table.
force_password_change_edit_role Callback #submit function called on the role edit page when the user clicks the save button
force_password_change_force_users This function flags users accounts to change their passwords It also logs the force time for statistics sake. If $uids is an empty array, all users will have their stats updated
force_password_change_form_alter Implementation of hook_form_alter()
force_password_change_get_text_date Converts a time period of password expiry dates to the textual representation of the number of years, months, days or hours that it represents
force_password_change_init Implementation of hook_init()
force_password_change_menu Implementation of hook_menu()
force_password_change_permission Implementation of hook_perm()
force_password_change_theme Implementation of hook_theme()
force_password_change_user_delete Implementation of hook_user_delete()
force_password_change_user_insert Implementation of hook_user_insert()
force_password_change_user_login Implementation of hook_user_login()
force_password_change_user_update Implementation of hook_user_update()
force_password_change_validate_user This function is called after a user's account page is updated