diff --git a/modules/user/user.module b/modules/user/user.module index 37b5a039ac4b7732f956c78a716c1bf5f27930b9..e34c9c9ebd033a274e1bfceea24befeaa40f9523 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -934,6 +934,7 @@ function user_account_form(&$form, &$form_state) { '#attributes' => array('class' => array('username')), '#default_value' => (!$register ? $account->name : ''), '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin), + '#weight' => -10, ); $form['account']['mail'] = array( @@ -953,6 +954,35 @@ function user_account_form(&$form, &$form_state) { '#size' => 25, '#description' => t('To change the current user password, enter the new password in both fields.'), ); + // To skip the current password field, the user must have logged in via a + // one-time link and have the token in the URL. + $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]); + $protected_values = array(); + $current_pass_description = ''; + // 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'); + $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.')))); + $current_pass_description = t('Enter your current password to change the %mail or %pass. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new)); + } + // The user must enter their current password to change to a new one. + if ($user->uid == $account->uid) { + $form['account']['current_pass_required_values'] = array( + '#type' => 'value', + '#value' => $protected_values, + ); + $form['account']['current_pass'] = array( + '#type' => 'password', + '#title' => t('Current password'), + '#size' => 25, + '#access' => !empty($protected_values), + '#description' => $current_pass_description, + '#weight' => -5, + ); + $form['#validate'][] = 'user_validate_current_pass'; + } } elseif (!variable_get('user_email_verification', TRUE) || $admin) { $form['account']['pass'] = array( @@ -1048,6 +1078,30 @@ function user_account_form(&$form, &$form_state) { $form['#validate'][] = 'user_validate_picture'; } +/** + * Form validation handler for the current password on the user_account_form(). + */ +function user_validate_current_pass(&$form, &$form_state) { + global $user; + + $account = $form['#user']; + foreach ($form_state['values']['current_pass_required_values'] as $key => $name) { + // This validation only works for required textfields (like mail) or + // form values like password_confirm that have their own validation + // that prevent them from being empty if they are changed. + if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) { + require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); + $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $user); + if ($current_pass_failed) { + form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name))); + form_set_error($key); + } + // We only need to check the password once. + break; + } + } +} + /** * Form validation handler for user_account_form(). */ @@ -1439,7 +1493,7 @@ function user_menu() { 'title' => 'Request new password', 'page callback' => 'drupal_get_form', 'page arguments' => array('user_pass'), - 'access callback' => 'user_is_anonymous', + 'access callback' => TRUE, 'type' => MENU_LOCAL_TASK, 'file' => 'user.pages.inc', ); @@ -3204,7 +3258,6 @@ function user_block_user_action(&$object, $context = array()) { * @ingroup forms * @see user_account_form() * @see user_account_form_validate() - * @see user_account_form_submit() * @see user_register_submit() */ function user_register_form($form, &$form_state) { diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index 063ea100e19f8c711cca7bde45036aa00eb92bac..3b04278c9f37cefb6efa6cb74886b29c342cfa03 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -29,6 +29,8 @@ function user_autocomplete($string = '') { * @see user_pass_submit() */ function user_pass() { + global $user; + $form['name'] = array( '#type' => 'textfield', '#title' => t('Username or e-mail address'), @@ -36,6 +38,16 @@ function user_pass() { '#maxlength' => max(USERNAME_MAX_LENGTH, EMAIL_MAX_LENGTH), '#required' => TRUE, ); + // Allow logged in users to request this also. + if ($user->uid > 0) { + $form['name']['#type'] = 'value'; + $form['name']['#value'] = $user->mail; + $form['mail'] = array( + '#prefix' => '

', + '#markup' => t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the e-mail.', array('%email' => $user->mail)), + '#suffix' => '

', + ); + } $form['submit'] = array('#type' => 'submit', '#value' => t('E-mail new password')); return $form; @@ -120,7 +132,10 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a // user, which invalidates further use of the one-time login link. user_login_finalize(); drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.')); - drupal_goto('user/' . $user->uid . '/edit'); + // Let the user's password be changed without the current password check. + $token = md5(drupal_random_bytes(55)); + $_SESSION['pass_reset_' . $user->uid] = $token; + drupal_goto('user/' . $user->uid . '/edit', array('query' => array('pass-reset-token' => $token))); } else { $form['message'] = array('#markup' => t('

This is a one-time login for %user_name and will expire on %expiration_date.

Click on this button to log in to the site and change your password.

', array('%user_name' => $account->name, '%expiration_date' => format_date($timestamp + $timeout)))); @@ -219,7 +234,6 @@ function template_preprocess_user_profile_category(&$variables) { * @ingroup forms * @see user_account_form() * @see user_account_form_validate() - * @see user_account_form_submit() * @see user_profile_form_validate() * @see user_profile_form_submit() * @see user_cancel_confirm_form_submit() @@ -283,6 +297,10 @@ function user_profile_form_submit($form, &$form_state) { user_save($account, $edit, $category); $form_state['values']['uid'] = $account->uid; + if ($category == 'account' && !empty($edit['pass'])) { + // Remove the password reset tag since a new password was saved. + unset($_SESSION['pass_reset_'. $account->uid]); + } // Clear the page cache because pages can contain usernames and/or profile information: cache_clear_all(); diff --git a/modules/user/user.test b/modules/user/user.test index 374a7fced236a0e27c4163be65fe836954a255c2..f3387069daf9eff4d8c6b20301b051e887b4eec1 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -1396,5 +1396,34 @@ class UserEditTestCase extends DrupalWebTestCase { variable_set('user_pictures', 1); $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name']))); + + // Test that the error message appears when attempting to change the mail or + // pass without the current password. + $edit = array(); + $edit['mail'] = $this->randomName() . '@new.example.com'; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('E-mail address')))); + + $edit['current_pass'] = $user1->pass_raw; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + + // Test that the user must enter current password before changing passwords. + $edit = array(); + $edit['pass[pass1]'] = $new_pass = $this->randomName(); + $edit['pass[pass2]'] = $new_pass; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('Password')))); + + // Try again with the current password. + $edit['current_pass'] = $user1->pass_raw; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + + // Make sure the user can log in with their new password. + $this->drupalLogout(); + $user1->pass_raw = $new_pass; + $this->drupalLogin($user1); + $this->drupalLogout(); } }