You are here

password_policy.test in Password Policy 7

Same filename and directory in other branches
  1. 5 tests/password_policy.test
  2. 6 tests/password_policy.test

Functional tests for Password policy module.

File

tests/password_policy.test
View source
<?php

/**
 * @file
 * Functional tests for Password policy module.
 */

/**
 * Tests of basic Password Policy constraints.
 */
class PasswordPolicyTestCase extends DrupalWebTestCase {

  /**
   * Get info about the test case.
   */
  public static function getInfo() {
    return array(
      'name' => 'Password constraints',
      'description' => 'Test password constraints.',
      'group' => 'Password Policy',
    );
  }

  /**
   * Set up the test.
   */
  public function setUp() {
    parent::setUp('password_policy');
  }

  /**
   * Test uppercase constraint.
   */
  public function testUppercaseConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_uppercase');
    $constraint = 2;
    $user = $this
      ->drupalCreateUser();
    $pass = 'Abc';
    $result = password_policy_constraint_uppercase_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'One uppercase letter in the uppercase constraint with param 2');
    $pass = 'ABc';
    $result = password_policy_constraint_uppercase_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Two uppercase letters in the uppercase constraint with param 2');
    $pass = 'ABC';
    $result = password_policy_constraint_uppercase_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Three uppercase letters in the uppercase constraint with param 2');
  }

  /**
   * Test lowercase constraint.
   */
  public function testLowercaseConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_lowercase');
    $constraint = 2;
    $user = $this
      ->drupalCreateUser();
    $pass = 'Abc';
    $result = password_policy_constraint_lowercase_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One uppercase letter in the lowercase constraint with param 2');
    $pass = 'ABc';
    $result = password_policy_constraint_lowercase_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'Two uppercase letters in the lowercase constraint with param 2');
    $pass = 'ABC';
    $result = password_policy_constraint_lowercase_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'Three uppercase letters in the lowercase constraint with param 2');
  }

  /**
   * Test letter constraint.
   */
  public function testLetterConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_letter');
    $constraint = 2;
    $user = $this
      ->drupalCreateUser();
    $pass = 'a12';
    $result = password_policy_constraint_letter_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'One lowercase letter in the letter constraint with param 2');
    $pass = 'aB1';
    $result = password_policy_constraint_letter_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One lowercase and one uppercase letter in the letter constraint with param 2');
    $pass = 'abC';
    $result = password_policy_constraint_letter_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Two lowercase and one uppercase letter in the letter constraint with param 2');
  }

  /**
   * Test digit constraint.
   */
  public function testDigitConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_digit');
    $constraint = 2;
    $user = $this
      ->drupalCreateUser();
    $pass = '1ab';
    $result = password_policy_constraint_digit_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'One digit in the digit constraint with param 2');
    $pass = '12a';
    $result = password_policy_constraint_digit_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Two digits in the digit constraint with param 2');
    $pass = '123';
    $result = password_policy_constraint_digit_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Three digits in the digit constraint with param 2');
  }

  /**
   * Test length constraint.
   */
  public function testLengthConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_length');
    $constraint = 6;
    $user = $this
      ->drupalCreateUser();
    $pass = 'abcde';
    $result = password_policy_constraint_length_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'Five characters password in the length constraint with param 6');
    $pass = 'abcdef';
    $result = password_policy_constraint_length_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Six characters password in the length constraint with param 6');
    $pass = 'abcdefg';
    $result = password_policy_constraint_length_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Seven characters password in the length constraint with param 6');
  }

  /**
   * Test alphanumeric constraint.
   */
  public function testAlphanumericConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_alphanumeric');
    $constraint = 2;
    $user = $this
      ->drupalCreateUser();
    $pass = '1$%';
    $result = password_policy_constraint_alphanumeric_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'One digit and no letter in the alphanumeric constraint with param 2');
    $pass = '1a#';
    $result = password_policy_constraint_alphanumeric_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One digit and one letter in the alphanumeric constraint with param 2');
    $pass = '1ab';
    $result = password_policy_constraint_alphanumeric_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One digit ant two letters in the alphanumeric constraint with param 2');
  }

  /**
   * Test punctuation constraint.
   */
  public function testPunctuationConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_punctuation');
    $constraint = 2;
    $user = $this
      ->drupalCreateUser();
    $pass = '%1a';
    $result = password_policy_constraint_punctuation_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'One punctuation character in the punctuation constraint with param 2');
    $pass = '%^a';
    $result = password_policy_constraint_punctuation_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Two punctuation character in the punctuation constraint with param 2');
    $pass = '%^&';
    $result = password_policy_constraint_punctuation_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Three punctuation character in the punctuation constraint with param 2');
  }

  /**
   * Test character types constraint.
   */
  public function testCharacterTypesConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_character_types');
    $constraint = 2;
    $user = $this
      ->drupalCreateUser();
    $pass = 'abc';
    $result = password_policy_constraint_character_types_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'Three letters in the type constraint with param 2');
    $pass = 'a1c';
    $result = password_policy_constraint_character_types_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'Two letters and one digit in the type constraint with param 2');
    $pass = 'a1&';
    $result = password_policy_constraint_character_types_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One letter, one digit and one punctuation in the type constraint with param 2');
  }

  /**
   * Test username constraint.
   */
  public function testUsernameConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_username');
    $user = $this
      ->drupalCreateUser();
    $name = $this
      ->randomName();
    $result = password_policy_constraint_username_validate($name, '', $user);
    $this
      ->assertTrue($result, 'Random string in the username constraint');
    $result = password_policy_constraint_username_validate($user->name, '', $user);
    $this
      ->assertFalse($result, 'Username in the username constraint');
    $result = password_policy_constraint_username_validate('foo' . $user->name . 'bar', '', $user);
    $this
      ->assertFalse($result, 'String containing username in the username constraint');
  }

  /**
   * Test delay constraint.
   */
  public function testDelayConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_delay');

    // Log in.
    $user = $this
      ->drupalCreateUser(array(
      'administer password policies',
    ));
    $this
      ->drupalLogin($user);

    // Create a policy.
    $policy_name = $this
      ->randomName();
    $edit = array(
      'name' => $policy_name,
      'constraint_delay' => t('1'),
      'roles[2]' => '2',
    );
    $this
      ->drupalPost('admin/config/people/password_policy/add', $edit, t('Create'));
    $this
      ->assertText('Policy ' . $policy_name . ' has been created.', 'Policy ' . $policy_name . ' has been created');

    // Enable newly created policy.
    $pid = db_query('SELECT pid FROM {password_policy} WHERE name = :name', array(
      ':name' => $policy_name,
    ))
      ->fetchField();
    $constraints = unserialize(db_query('SELECT constraints FROM {password_policy} WHERE pid = :pid', array(
      ':pid' => $pid,
    ))
      ->fetchField());
    $this
      ->assertTrue($constraints['delay'] == 1, 'Verified delay constraint set.');
    $edit = array(
      "policies[{$pid}][enabled]" => $pid,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/list', $edit, t('Save changes'));
    $this
      ->assertText(t('The changes have been saved.'), 'Form submitted successfully.');
    $enabled = db_query('SELECT enabled FROM {password_policy} WHERE pid = :pid', array(
      ':pid' => $pid,
    ))
      ->fetchField();
    $this
      ->assertTrue($enabled == 1, 'Policy enabled.');
    $two_hours = 60 * 60 * 2;
    _password_policy_advance_test_clock($two_hours);

    // Change password.
    $pass1 = 'aaaaaa';
    $edit = array(
      'current_pass' => $user->pass_raw,
      'pass[pass1]' => $pass1,
      'pass[pass2]' => $pass1,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), format_string('1st password change: !pass1', array(
      '!pass1' => $pass1,
    )));

    // Change password second time.
    $pass2 = 'bbbbbb';
    $edit = array(
      'current_pass' => $pass1,
      'pass[pass1]' => $pass2,
      'pass[pass2]' => $pass2,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertText(t('Your password has not met the following requirement(s):'), format_string('2nd password change should fail: !pass1', array(
      '!pass1' => $pass1,
    )));

    // Delete test policy.
    $this
      ->drupalPost('admin/config/people/password_policy/' . $pid . '/delete', array(), t('Delete'));
    $this
      ->assertText('Password policy ' . $policy_name . ' was deleted.', 'Default password policy ' . $policy_name . 'was deleted');
  }

  /**
   * Test history constraint.
   */
  public function testHistoryConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_history');

    // Log in.
    $user = $this
      ->drupalCreateUser(array(
      'administer password policies',
    ));
    $this
      ->drupalLogin($user);

    // Create a policy.
    $policy_name = $this
      ->randomName();
    $edit = array(
      'name' => $policy_name,
      'constraint_history' => t('2'),
      'roles[2]' => '2',
    );
    $this
      ->drupalPost('admin/config/people/password_policy/add', $edit, t('Create'));
    $this
      ->assertText('Policy ' . $policy_name . ' has been created.', 'Policy ' . $policy_name . ' has been created');

    // Enable newly created policy.
    $pid = db_query('SELECT pid FROM {password_policy} WHERE name = :name', array(
      ':name' => $policy_name,
    ))
      ->fetchField();
    $constraints = unserialize(db_query('SELECT constraints FROM {password_policy} WHERE pid = :pid', array(
      ':pid' => $pid,
    ))
      ->fetchField());
    $this
      ->assertTrue($constraints['history'] == 2, 'Verified history constraint set.');
    $edit = array(
      "policies[{$pid}][enabled]" => $pid,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/list', $edit, t('Save changes'));
    $this
      ->assertText(t('The changes have been saved.'), 'Form submitted successfully.');
    $enabled = db_query('SELECT enabled FROM {password_policy} WHERE pid = :pid', array(
      ':pid' => $pid,
    ))
      ->fetchField();
    $this
      ->assertTrue($enabled == 1, 'Policy enabled.');

    // Change password.
    $pass1 = 'aaaaaa';
    $edit = array(
      'current_pass' => $user->pass_raw,
      'pass[pass1]' => $pass1,
      'pass[pass2]' => $pass1,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), format_string('1st password change: !pass1', array(
      '!pass1' => $pass1,
    )));

    // Change password second time.
    $pass2 = 'bbbbbb';
    $edit = array(
      'current_pass' => $pass1,
      'pass[pass1]' => $pass1,
      'pass[pass2]' => $pass1,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertText(t('Your password has not met the following requirement(s):'), format_string('2nd password change should fail: !pass1', array(
      '!pass1' => $pass1,
    )));

    // Try changing password with the first one.
    $edit = array(
      'current_pass' => $pass1,
      'pass[pass1]' => $pass2,
      'pass[pass2]' => $pass2,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), format_string('3rd password change should pass: !pass2', array(
      '!pass2' => $pass2,
    )));

    // Change password again.
    $pass3 = 'cccccc';
    $edit = array(
      'current_pass' => $pass2,
      'pass[pass1]' => $pass3,
      'pass[pass2]' => $pass3,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), format_string('4th password change should pass: !pass3', array(
      '!pass3' => $pass3,
    )));

    // Delete test policy.
    $this
      ->drupalPost('admin/config/people/password_policy/' . $pid . '/delete', array(), t('Delete'));
    $this
      ->assertText('Password policy ' . $policy_name . ' was deleted.', 'Default password policy ' . $policy_name . 'was deleted');
  }

  /**
   * Test digit placement constraint.
   */
  public function testDigitPlacementConstraint() {
    module_load_include('inc', 'password_policy', 'constraints/constraint_digit_placement');
    $constraint = 0;
    $user = $this
      ->drupalCreateUser();
    $pass = 'ILove2Password';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One numeric character in the digit placement constraint with param 0');
    $pass = 'ILovePassword2';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One numeric character in the digit placement constraint with param 0');
    $constraint = 1;
    $pass = 'ILove2Password';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One numeric character in the digit placement constraint with param 1');
    $pass = 'ILovePassword2';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One numeric character in the digit placement constraint with param 1');
    $constraint = 2;
    $pass = 'ILove2Password';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One numeric character in the digit placement constraint with param 2');
    $pass = 'ILovePassword2';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'One numeric character in the digit placement constraint with param 2');
    $pass = '1LovePassword';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertFalse($result, 'One numeric character in the digit placement constraint with param 2');
    $pass = '1LovePassword2';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One numeric character in the digit placement constraint with param 2');
    $pass = 'ILove2Password3';
    $result = password_policy_constraint_digit_placement_validate($pass, $constraint, $user);
    $this
      ->assertTrue($result, 'One numeric character in the digit placement constraint with param 2');
  }

}

/**
 * Tests of forcing password changes.
 */
class PasswordPolicyForcePasswordChangeTestCase extends DrupalWebTestCase {

  /**
   * Gets info about the test case.
   */
  public static function getInfo() {
    return array(
      'name' => 'Forced password changes',
      'description' => 'Test forced password changes for single user, role, and all new users.',
      'group' => 'Password Policy',
    );
  }

  /**
   * Sets up the test.
   */
  public function setUp() {
    parent::setUp('password_policy', 'password_policy_test');
  }

  /**
   * Tests permissions.
   */
  public function testPerms() {

    // No perms.
    $user = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($user);
    $this
      ->drupalGet('admin/config/people/password_policy/password_change');
    $this
      ->assertResponse('403', 'Access should be denied.');
    $this
      ->drupalLogout();

    // With perms.
    $user = $this
      ->drupalCreateUser(array(
      'force password change',
    ));
    $this
      ->drupalLogin($user);
    $this
      ->drupalGet('admin/config/people/password_policy/password_change');
    $this
      ->assertResponse('200', 'Access should be granted.');
    $this
      ->drupalLogout();
  }

  /**
   * Tests form elements.
   */
  public function testForms() {

    // Test admin form.
    $user = $this
      ->drupalCreateUser(array(
      'force password change',
      'administer users',
    ));
    $this
      ->drupalLogin($user);
    $this
      ->drupalGet('admin/config/people/password_policy/password_change');
    $this
      ->assertFieldByName('password_policy_new_login_change', NULL, 'Found first time login change checkbox.');
    $this
      ->assertFieldByName('password_policy_force_change_roles[2]', NULL, 'Found roles checkboxes.');
    $this
      ->assertFieldById('edit-submit', NULL, 'Found submit button.');

    // Test user edit form with perms.
    $this
      ->drupalGet("user/{$user->uid}/edit");
    $this
      ->assertFieldByName('force_password_change', NULL, 'Force password change checkbox is visible to admin.');
    $this
      ->drupalLogout();

    // Test user edit form without perms.
    $user = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($user);
    $this
      ->drupalGet("user/{$user->uid}/edit");
    $this
      ->assertNoFieldByName('force_password_change', NULL, 'Force password change checkbox is hidden for normal users.');
    $this
      ->drupalLogout();
  }

  /**
   * Tests single user password change.
   */
  public function testSingleUser() {
    $admin = $this
      ->drupalCreateUser(array(
      'force password change',
      'administer users',
    ));
    $user = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($admin);
    $edit = array(
      'force_password_change' => TRUE,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertRaw(t('!user will be required to change their password the next time they log in.', array(
      '!user' => $user->name,
    )), 'User flagged for password change.');
    $force_change = db_query('SELECT force_change FROM {password_policy_force_change} WHERE uid = :uid', array(
      ':uid' => $user->uid,
    ))
      ->fetchField();
    $this
      ->assertTrue($force_change == 1, format_string('Force change flag set to %d for %s.', array(
      '%d' => $force_change,
      '%s' => $user->name,
    )));

    // Confirm admin can edit user account without changing password.
    $edit = array(
      'name' => $user->name,
      'force_password_change' => TRUE,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $force_change = db_query('SELECT force_change FROM {password_policy_force_change} WHERE uid = :uid', array(
      ':uid' => $user->uid,
    ))
      ->fetchField();
    $this
      ->assertTrue($force_change == 1, format_string('User force change flag set in database:%s.', array(
      '%s' => $force_change,
    )));
    $this
      ->assertNoRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'Admin can edit user account without changing password.');
    $this
      ->drupalLogout();

    // Verify user is forced to change password.
    $this
      ->drupalLogin($user);
    $this
      ->assertFieldByName('current_pass', NULL, 'User redirected correctly.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User presented with error instructing them to change their password.');

    // Attempt to change password.
    $edit = array(
      'current_pass' => $user->pass_raw,
      'pass[pass1]' => 'random_string',
      'pass[pass2]' => 'random_string',
    );
    $this
      ->drupalPost(NULL, $edit, t('Save'));
    $this
      ->assertRaw(t('The changes have been saved.'), 'Password change successful.');

    // Verify user not prompted to change password a 2nd time.
    $this
      ->drupalGet('node');
    $this
      ->assertNoFieldByName('current_pass', NULL, 'User not forced to change password a 2nd time.');
    $this
      ->drupalLogout();
  }

  /**
   * Tests role-based password change.
   */
  public function testRoleChange() {
    $admin = $this
      ->drupalCreateUser(array(
      'administer users',
      'force password change',
    ));
    $user1 = $this
      ->drupalCreateUser();
    $user2 = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($admin);
    $edit = array(
      'password_policy_force_change_roles[2]' => 2,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/password_change', $edit, t('Save changes'));
    $this
      ->assertText(t('Users in the following roles will be required to immediately change their password: authenticated user'), 'Authenticated users role selected.');
    $this
      ->assertTrue($admin->uid != 1, format_string('Admin uid not 1: !admin_uid', array(
      'admin_uid' => $admin->uid,
    )));
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'Admin (not uid 1) correctly forced to change password.');

    // Test db flags for individual users.
    $entry_1 = db_query('SELECT uid FROM {password_policy_force_change} WHERE uid = :uid', array(
      ':uid' => $user1->uid,
    ))
      ->fetchField();
    $this
      ->assertTrue($entry_1 == $user1->uid, format_string('Entry created in password_policy_force_change for user !uid.', array(
      '!uid' => $user1->uid,
    )));
    $flag_1 = db_query('SELECT force_change FROM {password_policy_force_change} WHERE uid = :uid', array(
      ':uid' => $user1->uid,
    ))
      ->fetchField();
    $this
      ->assertTrue($flag_1 == 1, format_string("User !uid flagged: !flag.", array(
      '!uid' => $user1->uid,
      '!flag' => $flag_1,
    )));
    $this
      ->drupalLogout();

    // Test individual users.
    $this
      ->drupalLogin($user1);
    $this
      ->drupalGet('node');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'First test user forced to change password.');
    $this
      ->drupalLogout();

    // Test 2nd user.
    $this
      ->drupalLogin($user2);
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'Second test user forced to change password.');
    $this
      ->drupalLogout();
  }

  /**
   * Tests new user change.
   */
  public function testNewUserChange() {
    $admin = $this
      ->drupalCreateUser(array(
      'administer users',
      'force password change',
    ));
    $this
      ->drupalLogin($admin);
    $edit = array(
      'password_policy_new_login_change' => TRUE,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/password_change', $edit, t('Save changes'));
    $this
      ->assertRaw(t('New users will be required to change their password on first-time login.'), 'New users required to change password on 1st login.');
    $this
      ->drupalLogout();
    $user = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($user);
    $this
      ->drupalGet('node');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'New user forced to change password.');
  }

  /**
   * Tests admin forcing their own account to reset.
   */
  public function testSelfChange() {
    $admin = $this
      ->drupalCreateUser(array(
      'administer users',
      'force password change',
    ));
    $this
      ->drupalLogin($admin);
    $this
      ->assertNoRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'Admin should not be prompted to change password yet.');
    $edit = array(
      'force_password_change' => TRUE,
    );
    $this
      ->drupalPost("user/{$admin->uid}/edit/account", $edit, t('Save'));
    $this
      ->assertRaw(t('The changes have been saved.'), 'Admin has queued account for password change.');
    $this
      ->assertNoRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'Admin not initially prompted to change password.');
    $this
      ->drupalGet('node');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'Admin forced to change password once they try to leave account page.');
    $edit = array(
      'current_pass' => $admin->pass_raw,
      'pass[pass1]' => 'fpcR@nd0m!',
      'pass[pass2]' => 'fpcR@nd0m!',
    );
    $this
      ->drupalPost("user/{$admin->uid}/edit/account", $edit, t('Save'));
    $this
      ->assertRaw(t('The changes have been saved.'), 'Admin changed password.');
    $this
      ->drupalGet('node');
    $this
      ->assertNoRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'Not prompted to change password a 2nd time.');
  }

  /**
   * Tests unforcing a password change.
   */
  public function testUnforceChange() {
    $admin = $this
      ->drupalCreateUser(array(
      'force password change',
      'administer users',
    ));
    $user = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($admin);

    // Force a password change.
    $edit = array(
      'force_password_change' => TRUE,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->assertRaw(t('!user will be required to change their password the next time they log in.', array(
      '!user' => $user->name,
    )), 'User flagged for password change.');

    // Unforce the password change.
    $edit = array(
      'force_password_change' => FALSE,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $force_change = db_query('SELECT force_change FROM {password_policy_force_change} WHERE uid = :uid', array(
      ':uid' => $user->uid,
    ))
      ->fetchField();
    $this
      ->assertTrue($force_change == 0, format_string('Force change flag set to %d for %s.', array(
      '%d' => $force_change,
      '%s' => $user->name,
    )));
  }

  /**
   * Tests "Force password change on reset" setting.
   *
   * Some code copied from UserPasswordResetTestCase::testUserPasswordReset().
   */
  public function testForceChangeOnReset() {

    // Create a user.
    $user = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($user);
    $this
      ->drupalLogout();

    // Check that user is not forced to change password on reset by default.
    // Attempt to reset password.
    $edit = array(
      'name' => $user->name,
    );
    $this
      ->drupalPost('user/password', $edit, t('E-mail new password'));

    // Visit reset URL.
    $reset_url = $this
      ->getPasswordResetUrlFromMail();
    $this
      ->drupalGet($reset_url);
    $this
      ->drupalPost(NULL, array(), t('Log in'));

    // Try to visit another page without changing password.
    $this
      ->drupalGet('node');

    // Verify user not redirected to change password.
    $this
      ->assertNoFieldByName('mail', NULL, 'User not redirected back to user-edit page.');
    $this
      ->assertNoRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User not forced to change password.');
    $this
      ->drupalLogout();

    // Enable force change on reset.
    $admin = $this
      ->drupalCreateUser(array(
      'administer password policies',
    ));
    $this
      ->drupalLogin($admin);
    $edit = array(
      'password_policy_force_change_reset' => TRUE,
    );
    $this
      ->drupalPost('admin/config/people/password_policy', $edit, t('Save configuration'));
    $this
      ->assertRaw(t('The configuration options have been saved.'), 'Enabled "Force password change on reset".');
    $this
      ->drupalLogout();

    // Check user is forced to change password if they try to skip doing so.
    // Attempt to reset password.
    $edit = array(
      'name' => $user->name,
    );
    $this
      ->drupalPost('user/password', $edit, t('E-mail new password'));

    // Visit reset URL.
    $reset_url = $this
      ->getPasswordResetUrlFromMail();
    $this
      ->drupalGet($reset_url);
    $this
      ->drupalPost(NULL, array(), t('Log in'));

    // Try to visit another page without changing password.
    $this
      ->drupalGet('node');

    // Verify user redirected to change password.
    $this
      ->assertFieldByName('mail', NULL, 'User redirected back to user-edit page.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User forced to change password.');

    // Change password.
    $edit = array(
      'pass[pass1]' => 'fpcR@nd0m!',
      'pass[pass2]' => 'fpcR@nd0m!',
    );
    $this
      ->drupalPost(NULL, $edit, t('Save'));
    $this
      ->assertRaw(t('The changes have been saved.'), 'User changed password.');

    // Try to visit another page without changing password.
    $this
      ->drupalGet('node');

    // Verify user not again redirected to change password.
    $this
      ->assertNoFieldByName('mail', NULL, 'User not redirected back to user-edit page.');
    $this
      ->assertNoRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User not forced to change password.');
    $this
      ->drupalLogout();

    // Check that user is not forced to change password twice if they
    // immediately change their password.
    // Attempt to reset password.
    $edit = array(
      'name' => $user->name,
    );
    $this
      ->drupalPost('user/password', $edit, t('E-mail new password'));

    // Visit reset URL.
    $reset_url = $this
      ->getPasswordResetUrlFromMail();
    $this
      ->drupalGet($reset_url);
    $this
      ->drupalPost(NULL, array(), t('Log in'));

    // Change password.
    $edit = array(
      'pass[pass1]' => 'fpcR@nd0m!',
      'pass[pass2]' => 'fpcR@nd0m!',
    );
    $this
      ->drupalPost(NULL, $edit, t('Save'));
    $this
      ->assertRaw(t('The changes have been saved.'), 'User changed password.');

    // Try to visit another page.
    $this
      ->drupalGet('node');

    // Verify user not redirected to change password.
    $this
      ->assertNoFieldByName('mail', NULL, 'User not redirected back to user-edit page.');
    $this
      ->assertNoRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User not forced to change password.');
  }

  /**
   * Tests "Extra allowed paths" setting.
   */
  public function testExtraAllowedPaths() {
    $admin = $this
      ->drupalCreateUser(array(
      'force password change',
      'administer password policies',
      'administer users',
    ));
    $user = $this
      ->drupalCreateUser();

    // Force user to change their password.
    $this
      ->drupalLogin($admin);
    $edit = array(
      'force_password_change' => TRUE,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->drupalLogout();

    // Verify user is forced to change password.
    $this
      ->drupalLogin($user);
    $this
      ->assertFieldByName('current_pass', NULL, 'User redirected correctly.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User presented with error instructing them to change their password.');

    // Try to visit disallowed paths.
    $this
      ->drupalGet('node');
    $this
      ->assertFieldByName('mail', NULL, 'User redirected back to user-edit page.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User forced to change password.');
    $this
      ->drupalGet('node/add/page');
    $this
      ->assertFieldByName('mail', NULL, 'User redirected back to user-edit page.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User forced to change password.');
    $this
      ->drupalLogout();

    // Add extra allowed paths.
    // One path tests using a wildcard and the other does not.
    $this
      ->drupalLogin($admin);
    $edit = array(
      'password_policy_force_change_extra_allowed_paths' => "node\nnode/add/*",
    );
    $this
      ->drupalPost('admin/config/people/password_policy', $edit, t('Save configuration'));
    $this
      ->assertRaw(t('The configuration options have been saved.'), 'Enabled "Force password change by e-mail".');
    $this
      ->drupalLogout();

    // Verify user is still forced to change password.
    $this
      ->drupalLogin($user);
    $this
      ->assertFieldByName('current_pass', NULL, 'User redirected correctly.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User presented with error instructing them to change their password.');

    // Try to visit paths that are now allowed.
    // Note that "Access denied" is expected for node/add/page.
    $this
      ->drupalGet('node');
    $this
      ->assertNoFieldByName('mail', NULL, 'User not redirected back to user-edit page.');
    $this
      ->drupalGet('node/add/page');
    $this
      ->assertNoFieldByName('mail', NULL, 'User not redirected back to user-edit page.');

    // Try to visit a path that is still disallowed.
    $this
      ->drupalGet("user/{$user->uid}/view");
    $this
      ->assertFieldByName('mail', NULL, 'User redirected back to user-edit page.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User forced to change password.');
    $this
      ->drupalLogout();
  }

  /**
   * Tests altering allowed paths from another module.
   */
  public function testAllowedPathsAlter() {
    $admin = $this
      ->drupalCreateUser(array(
      'force password change',
      'administer password policies',
      'administer users',
    ));
    $user = $this
      ->drupalCreateUser();

    // Force user to change their password.
    $this
      ->drupalLogin($admin);
    $edit = array(
      'force_password_change' => TRUE,
    );
    $this
      ->drupalPost("user/{$user->uid}/edit", $edit, t('Save'));
    $this
      ->drupalLogout();

    // Verify user is forced to change password.
    $this
      ->drupalLogin($user);
    $this
      ->assertFieldByName('current_pass', NULL, 'User redirected correctly.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User presented with error instructing them to change their password.');

    // Try to visit disallowed paths.
    $this
      ->drupalGet('node');
    $this
      ->assertFieldByName('mail', NULL, 'User redirected back to user-edit page.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User forced to change password.');
    $this
      ->drupalGet('node/add/page');
    $this
      ->assertFieldByName('mail', NULL, 'User redirected back to user-edit page.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User forced to change password.');
    $this
      ->drupalLogout();

    // Add allowed path using
    // hook_password_policy_force_change_allowed_path_alter().
    variable_set('password_policy_test_force_change_allowed_paths_alter', TRUE);

    // Try to visit paths that are now allowed.
    // Note that "Access denied" is expected for node/add/page.
    $this
      ->drupalLogin($user);
    $this
      ->drupalGet('node');
    $this
      ->assertNoFieldByName('mail', NULL, 'User not redirected back to user-edit page.');
    $this
      ->drupalGet('node/add/page');
    $this
      ->assertNoFieldByName('mail', NULL, 'User not redirected back to user-edit page.');

    // Try to visit a path that is still disallowed.
    $this
      ->drupalGet("user/{$user->uid}/view");
    $this
      ->assertFieldByName('mail', NULL, 'User redirected back to user-edit page.');
    $this
      ->assertRaw(t('Your password has expired. You must change your password to proceed on the site.'), 'User forced to change password.');
    $this
      ->drupalLogout();
  }

  /**
   * Parses the last sent e-mail and returns the one-time login link URL.
   *
   * Copy of OpenIDWebTestCase::getPasswordResetURLFromMail().
   */
  protected function getPasswordResetUrlFromMail() {
    $mails = $this
      ->drupalGetMails();
    $mail = end($mails);
    preg_match('@.+user/reset/.+@', $mail['body'], $matches);
    return $matches[0];
  }

}

/**
 * Tests administrator changing password of another user.
 */
class PasswordPolicyAdministratorPasswordChangeTestCase extends DrupalWebTestCase {
  protected $admin;
  protected $nonadmin;
  protected $policyRid;

  /**
   * Get info about the test case.
   */
  public static function getInfo() {
    return array(
      'name' => 'Administrator password changes',
      'description' => 'Test administrator changing password of another user.',
      'group' => 'Password Policy',
    );
  }

  /**
   * Set up the test.
   */
  public function setUp() {
    parent::setUp('password_policy');
    $this
      ->createAdmin();
    $this
      ->createNonAdmin();
  }

  /**
   * Creates a usable admin (UID=1) user.
   *
   * SimpleTest creates an admin user, but it cannot log in since it has no
   * password set. So, we give it a password.
   */
  protected function createAdmin() {
    global $user;
    $pass = user_password();
    $edit = array(
      'pass' => $pass,
    );
    user_save($user, $edit);
    $user->pass_raw = $pass;
    $this->admin = $user;
  }

  /**
   * Creates non-administrator user for testing.
   *
   * The non-administrator is given a dummy role so password policies can be
   * applied exclusively to the non-administrator.
   */
  protected function createNonAdmin() {
    $this->nonadmin = $this
      ->drupalCreateUser();
  }

  /**
   * Tests administrator changing password of another user.
   */
  public function testAdministratorPasswordChange() {
    $admin = $this->admin;
    $this
      ->drupalLogin($admin);
    $this
      ->setPolicyThatAppliesToNewRole();
    $this
      ->attemptNonAdminPasswordChanges();
    $this
      ->attemptAdminPasswordChanges();
  }

  /**
   * Set a password policy that applies just to the non-administrator.
   *
   * A password policy is created that applies to the role only the
   * non-administrator has. Then, it is enabled.
   */
  protected function setPolicyThatAppliesToNewRole() {
    $rid = $this
      ->drupalCreateRole(array());
    $policy_name = $this
      ->randomName();
    $edit = array(
      'name' => $policy_name,
      "roles[{$rid}]" => $rid,
      'constraint_length' => 4,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/add', $edit, t('Create'));
    $created_text = "Policy {$policy_name} has been created.";
    $this
      ->assertText($created_text, $created_text);
    $this
      ->enablePolicy($policy_name);
    $this->policyRid = $rid;
  }

  /**
   * Attempts to change password for non-administrator.
   *
   * This is intended to check that the password policy of the
   * non-administrator is applied and not the password policy of the
   * administrator.
   */
  protected function attemptNonAdminPasswordChanges() {
    $nonadmin_uid = $this->nonadmin->uid;
    $nonadmin_user_edit_path = 'user/' . $nonadmin_uid . '/edit';
    $policy_rid = $this->policyRid;
    $pass = 'foo';
    $edit = array(
      'pass[pass1]' => $pass,
      'pass[pass2]' => $pass,
    );
    $this
      ->drupalPost($nonadmin_user_edit_path, $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), 'Change password without policy applied.');
    $pass = 'foo';
    $edit = array(
      "roles[{$policy_rid}]" => $policy_rid,
      'pass[pass1]' => $pass,
      'pass[pass2]' => $pass,
    );
    $this
      ->drupalPost($nonadmin_user_edit_path, $edit, t('Save'));
    $this
      ->assertText(t('Password must be at least 4 characters in length.'), 'Password not accepted that is invalid per policy that applies to administrator-selected role.');
    $pass = 'foobar';
    $edit = array(
      "roles[{$policy_rid}]" => $policy_rid,
      'pass[pass1]' => $pass,
      'pass[pass2]' => $pass,
    );
    $this
      ->drupalPost($nonadmin_user_edit_path, $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), 'Password accepted that is valid per policy that applies to administrator-selected role.');
    $pass = 'foo';
    $edit = array(
      'pass[pass1]' => $pass,
      'pass[pass2]' => $pass,
    );
    $this
      ->drupalPost($nonadmin_user_edit_path, $edit, t('Save'));
    $this
      ->assertText(t('Password must be at least 4 characters in length.'), 'Password not accepted that is invalid per policy that applies to role of user.');
    $pass = 'foobar';
    $edit = array(
      'pass[pass1]' => $pass,
      'pass[pass2]' => $pass,
    );
    $this
      ->drupalPost($nonadmin_user_edit_path, $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), 'Password accepted that is valid per policy that applies to role of user.');
  }

  /**
   * Attempts to change password for administrator.
   *
   * This checks that the password policy that is supposed to apply only to
   * non-administrators does not also apply to administrators.
   */
  protected function attemptAdminPasswordChanges() {
    $admin_uid = $this->admin->uid;
    $current_pass = $this->admin->pass_raw;
    $new_pass = 'foo';
    $edit = array(
      // Set a new e-mail address because the default will not pass validation.
      'mail' => 'foo@example.com',
      'current_pass' => $current_pass,
      'pass[pass1]' => $new_pass,
      'pass[pass2]' => $new_pass,
    );
    $this
      ->drupalPost('user/' . $admin_uid . '/edit', $edit, t('Save'));
    $this
      ->assertText(t('The changes have been saved.'), 'Password accepted for administrator, who has no policy that applies to their roles.');
  }

  /**
   * Enables policy.
   */
  protected function enablePolicy($policy_name) {
    $pid = db_query('SELECT pid FROM {password_policy} WHERE name = :name', array(
      ':name' => $policy_name,
    ))
      ->fetchField();
    $edit = array(
      "policies[{$pid}][enabled]" => $pid,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/list', $edit, t('Save changes'));
    $this
      ->assertText(t('The changes have been saved.'), 'Form submitted successfully.');
    $this
      ->drupalGet('admin/config/people/password_policy');
    $enabled = db_query('SELECT enabled FROM {password_policy} WHERE pid = :pid', array(
      ':pid' => $pid,
    ))
      ->fetchField();
    $this
      ->assertTrue($enabled == 1, 'Policy enabled.');
  }

}

/**
 * Tests of restriction on password length.
 */
class PasswordPolicyPasswordLengthRestrictionTestCase extends DrupalWebTestCase {
  protected $admin;

  /**
   * Get info about the test case.
   */
  public static function getInfo() {
    return array(
      'name' => 'Password length restriction',
      'description' => 'Test that overlong passwords are disallowed.',
      'group' => 'Password Policy',
    );
  }

  /**
   * Set up the test.
   */
  public function setUp() {
    parent::setUp('password_policy');
    $this
      ->createAdmin();
  }

  /**
   * Creates a usable admin (UID=1) user.
   *
   * SimpleTest creates an admin user, but it cannot log in since it has no
   * password set. So, we give it a password. We also give it a valid email
   * address so its user edit form can be submitted.
   */
  protected function createAdmin() {
    global $user;
    $pass = user_password();
    $edit = array(
      'pass' => $pass,
      'mail' => 'foo@example.com',
    );
    user_save($user, $edit);
    $user->pass_raw = $pass;
    $this->admin = $user;
  }

  /**
   * Tests module response to submission of an overlong password.
   */
  public function testOverlongPasswordSubmission() {
    $admin = $this->admin;
    $this
      ->drupalLogin($admin);
    $this
      ->setPolicyThatAppliesToAuthenticatedUser();
    $this
      ->submitOverlongPassword();
    $this
      ->submitNotOverlongPassword();
  }

  /**
   * Sets a password policy that applies to the authenticated user role.
   *
   * This is just a minimal policy to apply to the admin (UID=1) user, which is
   * being used for this test.
   */
  protected function setPolicyThatAppliesToAuthenticatedUser() {
    $rid = DRUPAL_AUTHENTICATED_RID;
    $policy_name = $this
      ->randomName();
    $edit = array(
      'name' => $policy_name,
      "roles[{$rid}]" => $rid,
      'constraint_alphanumeric' => 1,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/add', $edit, t('Create'));
    $created_text = "Policy {$policy_name} has been created.";
    $this
      ->assertText($created_text, $created_text);
    $this
      ->enablePolicy($policy_name);
    $this->policyRid = $rid;
  }

  /**
   * Submits a password that is overlong.
   */
  protected function submitOverlongPassword() {
    $pass = str_repeat('a', 513);
    $edit = array(
      'current_pass' => $this->admin->pass_raw,
      'pass[pass1]' => $pass,
      'pass[pass2]' => $pass,
    );
    $this
      ->drupalPost('user/1/edit', $edit, t('Save'));
    $this
      ->assertText(t('Password exceeds maximum length.'), 'Overlong password causes form error.');
    $this
      ->assertNoText(t('The changes have been saved.'), 'Overlong password is not saved.');
  }

  /**
   * Submits a password that is not overlong.
   */
  protected function submitNotOverlongPassword() {
    $pass = str_repeat('a', 512);
    $edit = array(
      'current_pass' => $this->admin->pass_raw,
      'pass[pass1]' => $pass,
      'pass[pass2]' => $pass,
    );
    $this
      ->drupalPost('user/1/edit', $edit, t('Save'));
    $this
      ->assertNoText(t('Password exceeds maximum length.'), 'Not-overlong password does not cause form error.');
    $this
      ->assertText(t('The changes have been saved.'), 'Not-overlong password is saved.');
  }

  /**
   * Enables policy.
   */
  protected function enablePolicy($policy_name) {
    $pid = db_query('SELECT pid FROM {password_policy} WHERE name = :name', array(
      ':name' => $policy_name,
    ))
      ->fetchField();
    $edit = array(
      "policies[{$pid}][enabled]" => $pid,
    );
    $this
      ->drupalPost('admin/config/people/password_policy/list', $edit, t('Save changes'));
    $this
      ->assertText(t('The changes have been saved.'), 'Form submitted successfully.');
    $this
      ->drupalGet('admin/config/people/password_policy');
    $enabled = db_query('SELECT enabled FROM {password_policy} WHERE pid = :pid', array(
      ':pid' => $pid,
    ))
      ->fetchField();
    $this
      ->assertTrue($enabled == 1, 'Policy enabled.');
  }

}

Classes

Namesort descending Description
PasswordPolicyAdministratorPasswordChangeTestCase Tests administrator changing password of another user.
PasswordPolicyForcePasswordChangeTestCase Tests of forcing password changes.
PasswordPolicyPasswordLengthRestrictionTestCase Tests of restriction on password length.
PasswordPolicyTestCase Tests of basic Password Policy constraints.