You are here

force_password_change.module in Force Password Change 2.0.x

File

force_password_change.module
View source
<?php

/**
 * @file
 */
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Implements hook_form_alter().
 */
function force_password_change_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Alter the role edit page for a single role.
  if ($form_id == 'user_role_form') {

    // Set a message depending on whether pending password changes are
    // checked on every page, or only login.
    if (\Drupal::config('force_password_change.settings')
      ->get('check_login_only')) {
      $description = t('Users will be required to change their password upon their next login.');
    }
    else {
      $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.');
    }
    $form['force_password_change'] = [
      '#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.'),
    ];

    // Add a custom submit function that forces all users in
    // the role to change their password.
    $form['actions']['submit']['#submit'][] = 'force_password_change_edit_role';
  }
  elseif ($form_id == 'user_role_delete_form') {
    $role = \Drupal::routeMatch()
      ->getParameter('user_role');
    $form['rid'] = [
      '#type' => 'value',
      '#value' => $role
        ->id(),
    ];

    // Add a custom submit function that removes the role
    // from the {force_password_change_roles} table.
    $form['actions']['submit']['#submit'][] = 'force_password_change_delete_role';
  }
  elseif ($form_id == 'user_form') {
    $account = \Drupal::routeMatch()
      ->getParameter('user');
    $current_user = \Drupal::currentUser();

    // Only alter the form if the user has the correct permission.
    if ($current_user
      ->hasPermission('administer force password change')) {

      // 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'] = [
        '#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 (\Drupal::config('force_password_change.settings')
        ->get('check_login_only')) {
        $description = t('If this box is checked, the user will be forced to change their password upon their next login.');
      }
      else {
        $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.');
      }
      $use_form['password']['force_password_change'] = [
        '#type' => 'checkbox',
        '#title' => t('Force this user to change their password'),
        '#access' => $current_user
          ->hasPermission('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.
      $last_force_time = \Drupal::service('user.data')
        ->get('force_password_change', $account
        ->id(), 'last_force');
      if ($last_force_time) {
        $last_force = \Drupal::service('date.formatter')
          ->format($last_force_time, 'short');
      }
      elseif (\Drupal::config('force_password_change.settings')
        ->get('first_time_login_password_change') && $account
        ->getCreatedTime() > \Drupal::config('force_password_change.settings')
        ->get('installation_date')) {
        $last_force = t('Their first login');
      }
      else {
        $forced_uids = \Drupal::service('force_password_change.service')
          ->getFirstTimeLoginUids();
        if (count($forced_uids) && isset($forced_uids[$account
          ->id()])) {
          $last_force = t('First login');
        }
        else {
          $last_force = t('Never');
        }
      }
      $items[] = t('User has a pending forced password change: @pending', [
        '@pending' => \Drupal::service('user.data')
          ->get('force_password_change', $account
          ->id(), 'pending_force') ? t('Yes') : t('No'),
      ]);
      $items[] = t('User was last forced to change their password on: @last_force', [
        '@last_force' => $last_force,
      ]);
      $last_change_time = \Drupal::service('user.data')
        ->get('force_password_change', $account
        ->id(), 'last_change');
      $items[] = t("User's password was last changed on: @last_change", [
        '@last_change' => $last_change_time ? \Drupal::service('date.formatter')
          ->format($last_change_time, 'short') : t('Never'),
      ]);

      // Display the user's password change stats for the administrator.
      $use_form['password']['password_stats'] = [
        'header' => [
          '#prefix' => '<p><strong>',
          '#suffix' => '</strong></p>',
          '#markup' => t('Password Stats:'),
        ],
        'list' => [
          '#theme' => 'item_list',
          '#items' => $items,
        ],
      ];
    }
    $pass_reset = isset($_SESSION['pass_reset_' . $account
      ->id()]) && isset($_GET['pass-reset-token']) && $_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account
      ->id()];
    $protected_values = [];

    // The user may only change their own password without their current
    // password if they logged in via a one-time login link.
    if (!$pass_reset) {
      $protected_values['mail'] = $form['account']['mail']['#title'];
      $protected_values['pass'] = t('Password');
      $url = Url::fromRoute('user.pass', [], [
        'attributes' => [
          'title' => t('Request new password via e-mail.'),
        ],
      ]);
      $request_link = Link::fromTextAndUrl(t('request a new password'), $url);
      $request_new = new FormattableMarkup('@link', [
        '@link' => $request_link
          ->toString(),
      ]);
      $form['account']['current_pass']['#description'] = t('Enter your current password to change the %mail or %pass. If you do not know your current password, you can @request_new.', [
        '%mail' => $protected_values['mail'],
        '%pass' => $protected_values['pass'],
        '@request_new' => $request_new,
      ]);
    }

    // Set a flag indicating whether the user is being
    // force to change their password.
    $form['pending_force_password_change'] = [
      '#type' => 'value',
      '#value' => \Drupal::service('user.data')
        ->get('force_password_change', $account
        ->id(), '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';
    $form['actions']['submit']['#submit'][] = 'force_password_change_update_user';
  }
  elseif ($form_id == 'user_register_form' && \Drupal::currentUser()
    ->id()) {

    // 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 (!\Drupal::config('force_password_change.settings')
      ->get('first_time_login_password_change')) {

      // 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'] = [
        '#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' => \Drupal::currentUser()
          ->hasPermission('administer force password change'),
      ];
      $form['actions']['submit']['#submit'][] = 'force_password_change_insert_user';
    }
  }
}

/**
 * Implements hook_user_login().
 */
function force_password_change_user_login($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 (\Drupal::config('force_password_change.settings')
    ->get('check_login_only')) {

    // Check to see if the user has a pending password change.
    if (\Drupal::service('force_password_change.service')
      ->checkForForce()) {
      $middleware = \Drupal::service('force_password_change.on_only_login');
      \Drupal::messenger()
        ->addStatus(t('An administrator has required that you change your password. Please change your password to proceed.'));
      $response = new RedirectResponse(Url::fromRoute('entity.user.edit_form', [
        'user' => $account
          ->id(),
      ])
        ->toString());
      $middleware
        ->setRedirectResponse($response);
    }
  }
}

/**
 * Implements hook_user_insert().
 */
function force_password_change_user_insert($account) {

  // 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 (\Drupal::config('force_password_change.settings')
    ->get('first_time_login_password_change')) {
    $force_password_change_service = \Drupal::service('force_password_change.service');
    $force_password_change_service
      ->forceUserPasswordChange($account
      ->id());
    $force_password_change_service
      ->addFirstTimeLogin($account
      ->id());
  }
}

/**
 * Implements 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 = \Drupal::database()
    ->delete('force_password_change_uids')
    ->condition('uid', $account
    ->id())
    ->execute();
}

/**
 * This function is called after a user's account page is updated.
 */
function force_password_change_validate_user(array &$form, FormStateInterface $form_state) {
  $account = \Drupal::routeMatch()
    ->getParameter('user');
  $current_user = \Drupal::currentUser();

  // Check to see if the user's account has been flagged to change
  // their password, and if so, have they changed it?
  if ($account
    ->id() == $current_user
    ->id() && \Drupal::service('user.data')
    ->get('force_password_change', $account
    ->id(), 'force_password_change')) {
    if (!strlen($form_state
      ->getValue('pass'))) {
      $form_state
        ->setErrorByName('pass', t('You must choose a new password'));
    }
  }

  // Check to see if the new password is different from the old password.
  if (\Drupal::service('user.auth')
    ->authenticate($account
    ->getAccountName(), $form_state
    ->getValue('pass'))) {
    $form_state
      ->setErrorByName('pass', t('You cannot use your current password. Please choose a different password.'));
  }
}

/**
 *
 */
function force_password_change_update_user(array &$form, FormStateInterface $form_state) {
  $uid = $form_state
    ->getValue('uid');
  $current_user = \Drupal::currentUser();
  $user_data_service = \Drupal::service('user.data');
  $db = \Drupal::database();
  $current_pass = $form_state
    ->getValue('current_pass');
  if ($current_pass && $current_pass != $form_state
    ->getValue('pass')) {

    // If a user's password has changed their password, the time of their
    // password change is saved to the database.
    \Drupal::service('force_password_change.service')
      ->setChangedTimeForUser($uid);

    // This next conditional is entered when a user is changing
    // their own password.
    if ($current_user
      ->id() == $uid && $user_data_service
      ->get('force_password_change', $uid, 'pending_force')) {
      $force_password_change_service = \Drupal::service('force_password_change.service');

      // Remove the flag from the users account.
      $force_password_change_service
        ->removePendingForce($uid);

      // Remove first time force.
      $force_password_change_service
        ->removeFirstTimeLogin($uid);
    }
  }

  // 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
  // sets that flag.
  if ($form_state
    ->getValue('force_password_change')) {
    Drupal::service('force_password_change.service')
      ->forceUsersPasswordChange([
      $uid,
    ]);
  }
}

/**
 *
 */
function force_password_change_insert_user(array $form, FormStateInterface $form_state) {
  if ($form_state
    ->getValue('force_password_change')) {
    $uid = $form_state
      ->getValue('uid');
    $force_password_change_service = \Drupal::service('force_password_change.service');
    $force_password_change_service
      ->forceUserPasswordChange($uid);
    $force_password_change_service
      ->addFirstTimeLogin($uid);
    \Drupal::messenger()
      ->addMessage(t('%name will be required to change their password upon their first login', [
      '%name' => $form_state
        ->getValue('name'),
    ]));
  }
}

/**
 * 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(array $form, FormStateInterface $form_state) {
  \Drupal::database()
    ->delete('force_password_change_roles')
    ->condition('rid', $form_state
    ->getValue('rid'))
    ->execute();
  \Drupal::database()
    ->delete('force_password_change_expiry')
    ->condition('rid', $form_state
    ->getValue('rid'))
    ->execute();
}

/**
 * Callback #submit function called on the role edit page when the user clicks the save button.
 */
function force_password_change_edit_role(array $form, FormStateInterface $form_state) {
  $db = \Drupal::database();

  // Add role to the {force_password_change_roles} form if it doesn't exist.
  $exists = $db
    ->query('SELECT 1 FROM {force_password_change_roles} WHERE rid = :rid', [
    ':rid' => $form_state
      ->getValue('id'),
  ])
    ->fetchField();
  if (!$exists) {
    $db
      ->insert('force_password_change_roles')
      ->fields([
      'rid' => $form_state
        ->getValue('id'),
      'last_force' => 0,
    ])
      ->execute();
  }

  // Only flag user's accounts to be changed if the checkbox
  // was selected.
  if ($form_state
    ->getValue('force_password_change')) {

    // Get the UIDs for all users in the role.
    if ($form_state
      ->getValue('id') == 'authenticated') {
      $sql = 'SELECT uid FROM {users} WHERE uid > 0';
      $values = [];
    }
    else {
      $sql = 'SELECT entity_id FROM {user__roles} WHERE roles_target_id = :rid ';
      $values[':rid'] = $form_state
        ->getValue('id');
    }
    $uids = $db
      ->query($sql, $values)
      ->fetchCol();
    if (count($uids)) {

      // Flag the users accounts.
      \Drupal::service('force_password_change.service')
        ->forceUsersPasswordChange($uids);
    }

    // Set the last force time for the role for statistics sake.
    $request_time = \Drupal::time()
      ->getRequestTime();
    $query = $db
      ->update('force_password_change_roles')
      ->fields([
      'last_force' => $request_time,
    ])
      ->condition('rid', $form_state
      ->getValue('id'))
      ->execute();
    if (\Drupal::config('force_password_change.settings')
      ->get('check_login_only')) {
      $description = t('User with the %role_name role will be required to change their password upon their next login.', [
        '%role_name' => $form_state
          ->getValue('label'),
      ]);
    }
    else {
      $description = t('Users in the %role_name role will be required to immediately change their password', [
        '%role_name' => $form_state
          ->getValue('label'),
      ]);
    }
    \Drupal::messenger()
      ->addMessage($description);
  }
}

/**
 * Implements hook_entity_type_alter().
 *
 * Changes the callback class for the role list builder class. The new class
 * overrides the original class, and adds the link to the force password change
 * settings on the role listing page.
 */
function force_password_change_entity_type_alter(array &$entity_types) {
  $entity_types['user_role']
    ->setListBuilderClass('\\Drupal\\force_password_change\\Entity\\User\\ForcePasswordChangeRoleListBuilder');
}

Functions

Namesort descending Description
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_entity_type_alter Implements hook_entity_type_alter().
force_password_change_form_alter Implements hook_form_alter().
force_password_change_insert_user
force_password_change_update_user
force_password_change_user_delete Implements hook_user_delete().
force_password_change_user_insert Implements hook_user_insert().
force_password_change_user_login Implements hook_user_login().
force_password_change_validate_user This function is called after a user's account page is updated.