1 user_password_reset.test public UserPasswordResetTest::testResetImpersonation()

Make sure that users cannot forge password reset URLs of other users.

File

core/modules/user/tests/user_password_reset.test, line 175
Tests for resetting the password.

Class

UserPasswordResetTest

Code

public function testResetImpersonation() {
  // Make sure user 1 has a valid password, so it does not interfere with the
  // test user accounts that are created below.
  $account = user_load(1);
  $account->pass = user_password();
  $account->save();

  // Create two user accounts with empty passwords. The only way to have an
  // empty password is to force it in the database, so do that and reload the
  // accounts afterwards.
  $user1 = $this->backdropCreateUser();
  $user2 = $this->backdropCreateUser();
  db_update('users')
    ->fields(array('pass' => ''))
    ->condition('uid', array($user1->uid, $user2->uid), 'IN')
    ->execute();
  $user1 = user_load($user1->uid);
  $user2 = user_load($user2->uid);

  // The password reset URL must not be valid for the second user when only
  // the user ID is changed in the URL.
  $reset_url = user_pass_reset_url($user1);
  $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
  $this->backdropGet($attack_reset_url);
  $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
  $this->assertText('You have tried to use a reset password link that has either been used or is no longer valid. Please request a new one using the form below.');

  // When legacy code calls user_pass_rehash() without providing the $uid
  // parameter, neither password reset URL should be valid since it is
  // impossible for the system to determine which user account the token was
  // intended for.
  $timestamp = REQUEST_TIME;
  // Pass an explicit NULL for the $uid parameter of user_pass_rehash()
  // rather than not passing it at all, to avoid triggering PHP warnings in
  // the test.
  $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
  $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
  $this->backdropGet($reset_url);
  $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
  $this->assertText('You have tried to use a reset password link that has either been used or is no longer valid. Please request a new one using the form below.');
  $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
  $this->backdropGet($attack_reset_url);
  $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
  $this->assertText('You have tried to use a reset password link that has either been used or is no longer valid. Please request a new one using the form below.');

  // To verify that user_pass_rehash() never returns a valid result in the
  // above situation (even if legacy code also called it to attempt to
  // validate the token, rather than just to generate the URL), check that a
  // second call with the same parameters produces a different result.
  $new_reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
  $this->assertNotEqual($reset_url_token, $new_reset_url_token);

  // However, when the duplicate account is removed, the password reset URL
  // should be valid.
  user_delete($user2->uid);
  $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
  $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
  $this->backdropGet($reset_url);
  $this->assertUrl($reset_url, array(), 'The user remains on the password reset login page.');
  $this->assertNoText('You have tried to use a reset password link that has either been used or is no longer valid. Please request a new one using the form below.');
}