? forced_password_change_1.patch Index: password_policy.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.admin.inc,v retrieving revision 1.12 diff -u -p -r1.12 password_policy.admin.inc --- password_policy.admin.inc 15 Mar 2010 17:24:08 -0000 1.12 +++ password_policy.admin.inc 30 Mar 2010 17:08:53 -0000 @@ -222,7 +222,7 @@ function password_policy_admin_form($for $form['roles']['roles'] = array( '#type' => 'checkboxes', '#title' => t('Roles'), - '#options' => user_roles(), + '#options' => user_roles(TRUE), '#default_value' => isset($policy['roles']) ? $policy['roles'] : array(), '#description' => t('Select the roles that this policy will apply to.'), ); @@ -309,9 +309,11 @@ function password_policy_admin_form_subm $policy = array(); foreach ($form_state['values'] as $key => $value) { // If we have no form value, then we have no constraint to set. - $value = trim($value); - if ($value != "" && preg_match("/^constraint_/", $key)) { - $policy[substr($key, 11)] = $value; + if (!is_array($value)) { //dodge issues with roles array + $value = trim($value); + if ($value != "" && preg_match("/^constraint_/", $key)) { + $policy[substr($key, 11)] = $value; + } } } @@ -367,3 +369,75 @@ function password_policy_admin_delete_su drupal_goto('admin/settings/password_policy/list'); } +/** + * Forced password change form. + */ +function password_policy_password_change_settings() { + $form['password_policy_new_login_change'] = array( + '#type' => 'checkbox', + '#title' => t('Force password change on first-time login'), + '#default_value' => variable_get('password_policy_new_login_change', 0), + ); + $roles = user_roles(TRUE); + $form['password_policy_force_change_roles'] = array( + '#type' => 'checkboxes', + '#options' => $roles, + '#title' => t('Force users in the following roles to change their password'), + '#description' => t('Users who are not signed in will be required to change their password immediately upon sign in. Users who are currently signed in will be required to change their password upon their next page click, but after changing their password will be redirected back to the page they were attempting to access.'), + ); + $form['change_password_url'] = array( + '#type' => 'textfield', + '#title' => t('Path to password change URL'), + '#description' => t('Only change this if you have implemented a module that changes the user password URL from user/[UID]/edit to something else. Use !uid in place of the user id') . '
' . t('WARNING: if you set this URL to an invalid URL, you could break your site, so backup your files first, and double check the path before setting it.'), + '#default_value' => 'user/!uid/edit', + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Submit'), + ); + return $form; +} + +/** + * Submit hook for forced password change form. + */ +function password_policy_password_change_settings_submit($form, &$form_state) { + global $user; + $selected_roles = array(); + variable_set('password_policy_change_password_url', $form_state['values']['change_password_url']); + variable_set('password_policy_new_login_change', $form_state['values']['password_policy_new_login_change']); + if ($form_state['values']['password_policy_new_login_change'] == 1) { + watchdog('password policy', t('New user accounts must change password on new login enabled by !admin', array('!admin' => $user->name)), array(), WATCHDOG_NOTICE); + } + ($form_state['values']['password_policy_new_login_change']) ? drupal_set_message(t('New users will be required to change their password on first-time login.')) : drupal_set_message(t('New users will not be required to change their pasword on first-time login.')); + foreach ($form_state['values']['password_policy_force_change_roles'] as $role) { + if ($role > 2) { + $uids = array(); + $db_uids = db_query('SELECT uid FROM {users_roles} WHERE rid = %d', $role); + while ($uid = db_fetch_array($db_uids)) { + $uids[] = $uid['uid']; + } + db_query("UPDATE {password_policy_force_change} SET force_change = 1 WHERE uid IN ('%s')", implode(', ', $uids)); + $selected_roles[] = $role; + } + elseif ($role == 2) { + db_query('UPDATE {password_policy_force_change} SET force_change = 1'); + $selected_roles[] = $role; + break; + } + } + if (count($selected_roles)) { + $roles = user_roles(TRUE); + $list = array(); + foreach ($selected_roles as $sr) { + $list[] = $roles[$sr]; + } + $list = implode(', ', $list); + drupal_set_message(t('Users in the following roles will be required to immediately change their password: %list', array('%list' => $list)), 'status'); + watchdog('password policy', t('!admin has flagged users in the following roles to immediately change their password: %list', array('%list' => $list, '!admin' => $user->name)), array(), WATCHDOG_NOTICE); + } + else { + drupal_set_message(t('No roles were selected.')); + } +} + Index: password_policy.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.install,v retrieving revision 1.8 diff -u -p -r1.8 password_policy.install --- password_policy.install 15 Mar 2010 17:24:08 -0000 1.8 +++ password_policy.install 30 Mar 2010 17:08:53 -0000 @@ -20,6 +20,11 @@ function password_policy_enable() { */ function password_policy_install() { drupal_install_schema('password_policy'); + // We want to avoid an avalanche of inserts + // so instead we grab the current uid list at install + // then update when users are added/deleted. + // Ignore user 0 since anon. user can't log in + db_query('INSERT INTO {password_policy_force_change} (uid) SELECT DISTINCT uid FROM {users} WHERE uid > 0'); } /** @@ -33,6 +38,7 @@ function password_policy_uninstall() { variable_del('password_policy_show_restrictions'); variable_del('password_policy_warning_subject'); variable_del('password_policy_warning_body'); + // TODO: add force password change vars } function password_policy_update_6000() { @@ -75,6 +81,21 @@ function password_policy_update_6000() { return $ret; } +function password_policy_update_6001() { + $ret = array(); + + db_create_table($ret, 'password_policy_force_change', array( + 'description' => t('Forced password reset status.'), + 'fields' => array( + 'uid' => array('type' => 'int', 'not null' => TRUE), + 'force_change' => array('type' => 'int', 'default' => 0), + ), + 'indexes' => array('uid' => array('uid')), + )); + $ret[] = update_sql('INSERT INTO {password_policy_force_change} (uid) SELECT DISTINCT uid FROM {users} WHERE uid > 0'); + return $ret; +} + ////////////////////////////////////////////////////////////////////////////// // Schema API hooks @@ -200,6 +221,14 @@ function password_policy_schema() { 'primary key' => array('pid'), 'indexes' => array('uid' => array('uid')), ), + 'password_policy_force_change' => array( + 'description' => t('Forced password reset status.'), + 'fields' => array( + 'uid' => array('type' => 'int', 'not null' => TRUE), + 'force_change' => array('type' => 'int', 'default' => 0), + ), + 'indexes' => array('uid' => array('uid')), + ), 'password_policy_role' => array( 'description' => t("Links policies with roles."), 'fields' => array( Index: password_policy.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.module,v retrieving revision 1.26 diff -u -p -r1.26 password_policy.module --- password_policy.module 15 Mar 2010 17:24:08 -0000 1.26 +++ password_policy.module 30 Mar 2010 17:08:54 -0000 @@ -33,6 +33,10 @@ function password_policy_help($path, $ar */ function password_policy_init() { global $_password_policy; + global $user; + // Timing issues require reloading the user object + // to get the password_change property set. + $user = user_load($user->uid); // Save all available constrains in a global variable. $dir = drupal_get_path('module', 'password_policy') .'/constraints'; @@ -43,6 +47,16 @@ function password_policy_init() { $_password_policy[] = drupal_substr($file->name, 11); } } + + // Check password reset status and force a reset if needed. + $change_password_url = str_replace('!uid', $user->uid, variable_get('change_password_url', 'user/!uid/edit')); + if ($user->force_password_change && $_GET['q'] != $change_password_url) { + // let users log out + if (arg(0) != 'logout') { + drupal_set_message(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), 'error', FALSE); + drupal_goto($change_password_url, 'destination=' . $_GET['q']); + } + } } /** @@ -62,7 +76,7 @@ function password_policy_theme() { * Implementation of hook_perm(). */ function password_policy_perm() { - return array('unblock expired accounts'); + return array('unblock expired accounts', 'force password change'); } /** @@ -129,6 +143,16 @@ function password_policy_menu() { 'access arguments' => array('administer site configuration'), 'file' => 'password_policy.admin.inc', ); + $items['admin/settings/password_policy/password_change'] = array( + 'title' => 'Force password change', + 'description' => 'Force users to change their password', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('password_policy_password_change_settings'), + 'access arguments' => array('force password change'), + 'file' => 'password_policy.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + ); $items['admin/user/expired'] = array( 'title' => 'Expired accounts', 'description' => 'Lists all expired accounts.', @@ -210,7 +234,29 @@ function password_policy_format_title($p */ function password_policy_user($op, &$edit, &$account, $category = NULL) { switch ($op) { + case "load": + $account->force_password_change = db_result(db_query('SELECT force_change FROM {password_policy_force_change} WHERE uid=%d', $account->uid)); + break; + case "validate": + global $user; + if (isset($account->uid) && $account->force_password_change == 1) { + // we want admins to be able to edit accounts without having to + // reset passwords. + if ($edit['pass'] == '' && $user->name == $account->name) { + form_set_error('password', t('You must choose a new password.')); + } + else { + $password_same = db_result(db_query('SELECT 1 FROM {users} WHERE uid = %d AND pass ="%s"', $account->uid, md5($edit['pass']))); + if ($password_same) { + form_set_error('password', t('You cannot use your current password. Please choose something different.')); + } + } + } + break; + case "insert": + $force = variable_get('password_policy_new_login_change', 0); + db_query('INSERT INTO {password_policy_force_change} VALUES(%d, %d)', $account->uid, $force); if (!empty($edit['pass'])) { // New users do not yet have an uid during the validation step, but they do have at this insert step. // Store their first password in the system for use with the history constraint (if used). @@ -219,6 +265,11 @@ function password_policy_user($op, &$edi } } break; + case "update": + if ($account->force_password_change) { + db_query('UPDATE {password_policy_force_change} SET force_change = 0 WHERE uid = %d', $account->uid); + } + break; case "login": $roles = is_array($account->roles) ? array_keys($account->roles) : array(); $policy = _password_policy_load_active_policy($roles); @@ -285,6 +336,7 @@ function password_policy_user($op, &$edi case "delete": db_query("DELETE FROM {password_policy_history} WHERE uid = %d", $account->uid); db_query("DELETE FROM {password_policy_expiration} WHERE uid = %d", $account->uid); + db_query("DELETE FROM {password_policy_force_change} WHERE uid = %d", $account->uid); break; case "submit": if ($account->status == 0 && isset($edit['status']) && $edit['status'] == 1 && user_access('unblock expired accounts')) { @@ -307,7 +359,6 @@ function password_policy_form_alter(&$fo //if ($uid == 1 && !PASSWORD_POLICY_ADMIN) { break; } $roles = isset($form['_account']['#value']) ? array_keys($form['_account']['#value']->roles) : array(); $policy = _password_policy_load_active_policy($roles); - $translate = array(); if (!empty($policy['policy'])) { // Some policy constraints are active. @@ -327,9 +378,46 @@ function password_policy_form_alter(&$fo $form['#submit'][] = 'password_policy_password_submit'; break; } + if (($form_id == 'user_register' || $form_id == 'user_profile_form') && user_access('force password change')) { + $form['password'] = array( + '#type' => 'fieldset', + '#title' => t('Password settings'), + ); + $form['password']['force_password_change'] = array( + '#type' => 'checkbox', + '#title' => t('Force password change on next login'), + ); + $form['#submit'][] = 'password_policy_password_change_user_submit'; + } } /** + * Submit handler for force password change field + */ +function password_policy_password_change_user_submit($form, $form_state) { + global $user; + $force_change = $form_state['values']['force_password_change']; + $name = $form_state['values']['name']; + $uid = $form['#uid']; + if ($force_change && $user->name != $name) { + drupal_set_message(t('!user will be required to change their password the next time they log in.', array('!user' => $name))); + } + if (is_numeric($uid) && $force_change == 1) { + // existing account + db_query('UPDATE {password_policy_force_change} SET force_change=%d WHERE uid=%d', $force_change, $form['#uid']); + } + else { + // new account + if (variable_get('password_policy_new_login_change', 0)) { + $force_change = 1; + } + db_query("UPDATE {password_policy_force_change} AS f INNER JOIN {users} u ON u.uid = f.uid SET force_change=%d WHERE name='%s'", $force_change, $name); + } + if ($force_change == 1) { + watchdog('password policy', t('!user flagged to change password on next login by !admin', array('!user' => $name, '!admin' => $user->name)), array(), WATCHDOG_NOTICE); + } +} +/** * Implementation of hook_cron(). */ function password_policy_cron() { Index: tests/password_policy.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/tests/password_policy.test,v retrieving revision 1.4 diff -u -p -r1.4 password_policy.test --- tests/password_policy.test 4 Jun 2009 21:49:08 -0000 1.4 +++ tests/password_policy.test 30 Mar 2010 17:08:54 -0000 @@ -14,6 +14,10 @@ class PasswordPolicyTest extends DrupalW ); } + public function setUp() { + parent::setUp('password_policy'); + } + function testUppercaseConstraint() { $constraint = 2; $uid = 0; @@ -143,62 +147,55 @@ class PasswordPolicyTest extends DrupalW $policy_name = $this->randomName(); $edit = array( 'name' => $policy_name, - 'constraint_history' => '2', + 'constraint_history' => t('2'), + 'roles[2]' => '2', ); $this->drupalPost('admin/settings/password_policy/add', $edit, t('Create')); $this->assertText('Policy '. $policy_name .' has been created.', 'Policy '. $policy_name .' has been created'); - // Saving default policy - $source = $this->drupalGet('admin/settings/password_policy/list'); - $source = str_replace("\n", '', $source); - $default = preg_replace('/(.*)value="(\d+)" checked="checked"(.*)/', '$2', $source); - $default = ctype_digit($default) ? $default : ''; - // Finding the policy's pid - $pid = preg_replace('/(.*)'. $policy_name .'[^\d]+(\d+)"(.*)/', '$2', $source); - // Setting default policy + // Enable newly created policy + $pid = db_result(db_query('SELECT pid FROM {password_policy} WHERE name="%s"', $policy_name)); + $constraints = unserialize(db_result(db_query('SELECT policy FROM {password_policy} WHERE pid=%d', $pid))); + $this->assertTrue($constraints['history'] == 2, t('Verified history constraint set.')); $edit = array( - 'default' => $pid, + "enabled[$pid]" => $pid, ); - $this->drupalPost('admin/settings/password_policy/list', $edit, t('Set default policy')); - $this->assertText($policy_name .' has been set as the default password policy.', $policy_name .' has been set as the default password policy'); + $this->drupalPost('admin/settings/password_policy/list', $edit, t('Save changes')); + $this->assertText(t('The changes have been saved.'), t('Form submitted successfully.')); + $enabled = db_result(db_query('SELECT enabled FROM {password_policy} WHERE pid=%d', $pid)); + $this->assertTrue($enabled == 1, t('Policy enabled.')); + // Change password $pass1 = "aaaaaa"; $edit = array( 'pass[pass1]' => $pass1, 'pass[pass2]' => $pass1, ); - $this->drupalPost('user/'. $user->uid .'/edit', $edit, t('Save')); - $this->assertText('The changes have been saved.', 'The password has been changed'); + $this->drupalPost("user/$user->uid/edit", $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), t("1st password change: $pass1")); // Change password second time $pass2 = "bbbbbb"; $edit = array( - 'pass[pass1]' => $pass2, - 'pass[pass2]' => $pass2, + 'pass[pass1]' => $pass1, + 'pass[pass2]' => $pass1, ); - $this->drupalPost('user/'. $user->uid .'/edit', $edit, t('Save')); - $this->assertText('The changes have been saved.', 'The password has been changed'); + $this->drupalPost("user/$user->uid/edit", $edit, t('Save')); + $this->assertText(t('Your password has not met the following requirement(s):'), t("2nd password change should fail: $pass1")); // Try changing password with the first one $edit = array( - 'pass[pass1]' => $pass1, - 'pass[pass2]' => $pass1, + 'pass[pass1]' => $pass2, + 'pass[pass2]' => $pass2, ); - $this->drupalPost('user/'. $user->uid .'/edit', $edit, t('Save')); - $this->assertText('Password must not match 2 last passwords.', 'The password has not been changed'); + $this->drupalPost("user/$user->uid/edit", $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), t("3rd password change should pass: $pass2")); // Change password again $pass3 = "cccccc"; $edit = array( 'pass[pass1]' => $pass3, 'pass[pass2]' => $pass3, ); - $this->drupalPost('user/'. $user->uid .'/edit', $edit, t('Save')); - $this->assertText('The changes have been saved.', 'The password has been changed'); - // Setting back default policy - if ($default) { - $edit = array( - 'default' => $default, - ); - $this->drupalPost('admin/settings/password_policy/list', $edit, t('Set default policy')); - $this->assertText(' has been set as the default password policy.', 'Default password policy was restored'); - } + $this->drupalPost("user/$user->uid/edit", $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), t("4th password change should pass: $pass3")); + // Delete test policy $this->drupalPost('admin/settings/password_policy/delete/'. $pid, array(), t('Delete')); $this->assertText('Password policy '. $policy_name .' was deleted.', 'Default password policy '. $policy_name .'was deleted'); @@ -239,3 +236,186 @@ class PasswordPolicyTest extends DrupalW } } +class PasswordPolicyForcePasswordChangeTestCase extends DrupalWebTestCase { + protected $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Force password change tests', + 'description' => 'Tests forced password reset for single user, role and all new users.', + 'group' => 'Password Policy', + ); + } + + public function setUp() { + parent::setUp('password_policy'); + } + + // test permissions + public function testPerms() { + // no perms + $user = $this->drupalCreateUser(); + $this->drupalLogin($user); + $this->drupalGet('admin/settings/password_policy/password_change'); + $this->assertResponse('403', t('Acces should be denied.')); + $this->drupalLogout(); + + // with perms + $user = $this->drupalCreateUser(array('force password change')); + $this->drupalLogin($user); + $this->drupalGet('admin/settings/password_policy/password_change'); + $this->assertResponse('200', t('Access should be granted.')); + $this->drupalLogout(); + } + + // test form elements + public function testForms() { + // test admin form + $user = $this->drupalCreateUser(array('force password change', 'administer users')); + $this->drupalLogin($user); + $this->drupalGet('admin/settings/password_policy/password_change'); + $this->assertFieldByName('password_policy_new_login_change', '', t('Found first time login change checkbox.')); + $this->assertFieldByName('password_policy_force_change_roles[2]', '', t('Found roles checkboxes.')); + $this->assertFieldByName('change_password_url', $value='user/!uid/edit', t('Found change password url textfield.')); + $this->assertFieldById('edit-submit', '', t('Found submit button.')); + + // test user edit form with perms + $this->drupalGet("user/$user->uid/edit"); + $this->assertFieldByName('force_password_change', '', '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', '', 'Force password change checkbox is hidden for normal users.'); + $this->drupalLogout(); + } + + // test 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' => 1, + ); + $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)), t('User flagged for password change.')); + $force_change = db_result(db_query('SELECT force_change FROM {password_policy_force_change} WHERE uid=%d', $user->uid)); + $this->assertTRUE($force_change == 1, t('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, + 'mail' => $user->mail, + 'force_password_change' => 1, + ); + $this->drupalPost("user/$user->uid/edit", $edit, t('Save')); + $force_change = db_result(db_query('SELECT force_change FROM {password_policy_force_change} WHERE uid=%d', $user->uid)); + $this->assertTrue($force_change == 1, t('User force change flag set in database:%s.', array('%s' => $force_change))); + $this->assertNoRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('Admin can edit user account without changing password.')); + $this->drupalLogout(); + + // verify user is forced to change password + $this->drupalLogin($user); + $this->assertFieldByName('mail', '', t('User redirected correctly.')); // marginal + $this->assertRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('User presented with error instructing them to change their password.')); + + // attempt to submit form with null password fields + $edit = array( + 'mail' => $user->mail, + ); + $this->drupalPost('user/$user->uid/edit', $edit, t('Save')); + $this->assertRaw(t('You must choose a new password.'), t('Null password not permitted.')); + + // attempt to submit form with current password + $edit = array( + 'mail' => $user->mail, + 'pass[pass1]' => $user->pass_raw, + 'pass[pass2]' => $user->pass_raw, + ); + $this->drupalPost('user/$user->uid/edit', $edit, t('Save')); + $this->assertRaw(t('You cannot use your current password. Please choose something different.'), t('Old password not permitted.')); + + // attempt to change password + $edit = array( + 'mail' => $user->mail, + 'pass[pass1]' => 'random_string', + 'pass[pass2]' => 'random_string', + ); + $this->drupalPost('user/$user->uid/edit', $edit, t('Save')); + $this->assertRaw(t('The changes have been saved.'), t('Password change successful.')); + + // verify user not prompted to change password a 2nd time + $this->drupalGet('node'); + $this->assertNoFieldByName('mail', '', t('User not forced to change password a 2nd time.')); + $this->drupalLogout(); + } + + // test 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/settings/password_policy/password_change', $edit, t('Submit')); + $this->assertText(t('Users in the following roles will be required to immediately change their password: authenticated user'), t('Authenticated users role selected.')); + $this->assertRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('Admin forced to change password due to role selection.')); + $this->drupalLogout(); + + //test individual users + $this->drupalLogin($user1); + $this->assertRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('First test user forced to change password.')); + $this->drupalLogout(); + + //test 2nd user + $this->drupalLogin($user2); + $this->assertRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('Second test user forced to change password.')); + $this->drupalLogout(); + + } + + //test 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' => 1, + ); + $this->drupalPost('admin/settings/password_policy/password_change', $edit, t('Submit')); + $this->assertRaw(t('New users will be required to change their password on first-time login.'), t('New users required to change password on 1st login.')); + $this->drupalLogout(); + $user = $this->drupalCreateUser(); + $this->drupalLogin($user); + $this->drupalGet('node'); + $this->assertRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('New user forced to change password.')); + } + + //test 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('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('Admin should not be prompted to change password yet.')); + $edit = array( + 'force_password_change' => 1, + ); + $this->drupalPost("user/$admin->uid/edit", $edit, t('Save')); + $this->assertRaw(t('The changes have been saved.'), t('Admin has queued account for password change.')); + $this->assertNoRaw(t('An administrator has required that you change your pasword. You must change your password to proceed on the site.'), t('Admin not initially prompted to change password.')); + $this->drupalGet('node'); + $this->assertRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('Admin forced to change password once they try to leave account page.')); + $edit = array( + 'mail' => $admin->mail, + 'pass[pass1]' => 'fpcR@nd0m!', + 'pass[pass2]' => 'fpcR@nd0m!', + ); + $this->drupalPost("user/$admin->uid/edit", $edit, t('Save')); + $this->assertRaw(t('The changes have been saved.'), t('Admin changed password.')); + $this->drupalGet('node'); + $this->assertNoRaw(t('An administrator has required that you change your password. You must change your password to proceed on the site.'), t('Not prompted to change password a 2nd time.')); + } +}