diff --git a/core/modules/user/config/install/user.mail.yml b/core/modules/user/config/install/user.mail.yml index cc5a728..6b215b5 100644 --- a/core/modules/user/config/install/user.mail.yml +++ b/core/modules/user/config/install/user.mail.yml @@ -5,11 +5,11 @@ password_reset: body: "[user:display-name],\n\nA request to reset the password for your account has been made at [site:name].\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.\n\n-- [site:name] team" subject: 'Replacement login information for [user:display-name] at [site:name]' mail_change_notification: - body: "[user:display-name],\n\nA request to change your e-mail address has been made at [site:name]. In order to complete the change you will need to follow the instructions sent to your new e-mail address within one day." - subject: 'E-mail change information for [user:display-name] at [site:name]' + body: "[user:display-name],\n\nA request to change your email address has been made at [site:name]. In order to complete the change you will need to follow the instructions sent to your new email address within one day." + subject: 'Email change information for [user:display-name] at [site:name]' mail_change_verification: - body: "[user:display-name],\n\nA request to change your e-mail address has been made at [site:name]. You need to verify the change by clicking on the link below or copying and pasting it in your browser:\n\n[user:mail-change-login-url]\n\nThis is a one-time URL, so it can be used only once. It expires after one day. If not used, your e-mail address at [site:name] will not change." - subject: 'E-mail change information for [user:display-name] at [site:name]' + body: "[user:display-name],\n\nA request to change your email address has been made at [site:name]. You need to verify the change by clicking on the link below or copying and pasting it in your browser:\n\n[user:mail-change-login-url]\n\nThis is a one-time URL, so it can be used only once. It expires after one day. If not used, your email address at [site:name] will not change." + subject: 'Email change information for [user:display-name] at [site:name]' register_admin_created: body: "[user:display-name],\n\nA site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team" subject: 'An administrator created an account for you at [site:name]' diff --git a/core/modules/user/src/Controller/ChangeEmailController.php b/core/modules/user/src/Controller/ChangeEmailController.php new file mode 100644 index 0000000..3984b90 --- /dev/null +++ b/core/modules/user/src/Controller/ChangeEmailController.php @@ -0,0 +1,92 @@ +getStorage('user')->load($uid); + + // We need to set the new email here to validate the hash correctly, + // which is created using the new mail adress. We only save the account + // if the hash matches. + $account->setEmail($new_mail); + $timeout = 24 * 60 * 60; + + if ($timestamp < REQUEST_TIME) { + if (REQUEST_TIME - $timestamp > $timeout) { + drupal_set_message($this->t('You have tried to use a one-time email address change link for %account that has expired -- your change of email address was not completed. Please visit your account edit page if you wish to attempt the change again.', array('%account' => $account->name)), 'error'); + } + elseif ($this->currentUser()->id() && $this->currentUser()->id() != $account->id()) { + drupal_set_message($this->t('You are currently logged in as %user, and are attempting to confirm an email address change for %account, which is not allowed. Please log in as %account and initiate a new change of email request.', array('%user' => $user->name, '%account' => $account->name)), 'error'); + } + elseif ($hash != user_pass_rehash($account, $timestamp)) { + drupal_set_message($this->t('There was a problem validating the used link. Please visit your account edit page and retry changing your email address.'), 'error'); + } + elseif ($timestamp > $account->getLastLoginTime() && $timestamp < REQUEST_TIME) { + $account->save(); + drupal_set_message($this->t('Your email address has been changed to %mail.', array('%mail' => $new_mail))); + } + } + else { + // Deny access if the timestamp passed is in the future. + throw new AccessDeniedHttpException(); + } + + return $this->redirect('user.page'); + } + + /** + * Generates a unique URL for a one time email change confirmation. + * + * @param object $account + * An object containing the user account. + * @param array $options + * (optional) A keyed array of settings. Supported options are: + * - langcode: A language code to be used when generating locale-sensitive + * URLs. If langcode is NULL the users preferred language is used. + * + * @return + * A unique URL that provides a one-time email change confirmation for the + * user. + */ + public static function changeEmailUrl($account, $options = []) { + $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode(); + $url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode)); + return \Drupal::url('user.change_email', [ + 'uid' => $account->id(), + 'timestamp' => REQUEST_TIME, + 'new_mail' => $account->getEmail(), + 'hash' => user_pass_rehash($account, REQUEST_TIME), + ], $url_options); + } + +} diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php index 9c4a667..d3d18f2 100644 --- a/core/modules/user/src/Controller/UserController.php +++ b/core/modules/user/src/Controller/UserController.php @@ -71,7 +71,7 @@ public static function create(ContainerInterface $container) { } /** - * Provides a callback for user E-mail change page. + * Provides a callback for user email change page. * * @param int $uid * UID of user requesting reset. @@ -112,7 +112,6 @@ public function resetPass($uid, $timestamp, $hash) { // The current user is not logged in, so check the parameters. // Time out, in seconds, until login URL expires. $timeout = $config->get('password_reset_timeout'); - $current = REQUEST_TIME; /* @var \Drupal\user\UserInterface $user */ $user = $this->userStorage->load($uid); @@ -120,11 +119,11 @@ public function resetPass($uid, $timestamp, $hash) { // Verify that the user exists and is active. if ($user && $user->isActive()) { // No time out for first time login. - if ($user->getLastLoginTime() && $current - $timestamp > $timeout) { + if ($user->getLastLoginTime() && REQUEST_TIME - $timestamp > $timeout) { drupal_set_message($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error'); return $this->redirect('user.pass'); } - elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) { + elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= REQUEST_TIME) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) { $expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL; return $this->formBuilder()->getForm('Drupal\user\Form\UserPasswordResetForm', $user, $expiration_date, $timestamp, $hash); } @@ -139,57 +138,6 @@ public function resetPass($uid, $timestamp, $hash) { } /** - * Returns the user change email page. - * - * @param int $uid - * UID of user requesting reset. - * @param int $timestamp - * The current timestamp. - * @param string $new_mail - * The user's new email address. - * @param string $hash - * Login link hash. - * - * @return array|\Symfony\Component\HttpFoundation\RedirectResponse - * The form structure or a redirect response. - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * If the timestamp passed is in the future. - */ - public function changeEmail($uid, $timestamp, $new_mail, $hash) { - $account = $this->userStorage->load($uid); - - // We need to set the new email here to validate the hash correctly, - // which is created using the new mail adress. We only save the account - // if the hash matches. - $account->setEmail($new_mail); - $timeout = 24 * 60 * 60; - $current = REQUEST_TIME; - - if ($timestamp < $current) { - if ($current - $timestamp > $timeout) { - drupal_set_message(t('You have tried to use a one-time email address change link for %account that has expired -- your change of email address was not completed. Please visit your account edit page if you wish to attempt the change again.', array('%account' => $account->name)), 'error'); - } - elseif ($this->currentUser()->id() && $this->currentUser()->id() != $account->id()) { - drupal_set_message(t('You are currently logged in as %user, and are attempting to confirm an email address change for %account, which is not allowed. Please log in as %account and initiate a new change of email request.', array('%user' => $user->name, '%account' => $account->name)), 'error'); - } - elseif ($hash != user_pass_rehash($account, $timestamp)) { - drupal_set_message(t('There was a problem validating the used link. Please visit your account edit page and retry changing your email address.'), 'error'); - } - elseif ($timestamp > $account->getLastLoginTime() && $timestamp < $current) { - $account->save(); - drupal_set_message(t('Your email address has been changed to %mail.', array('%mail' => $new_mail))); - } - } - else { - // Deny access if the timestamp passed is in the future. - throw new AccessDeniedHttpException(); - } - - return $this->redirect('user.page'); - } - - /** * Redirects users to their profile page. * * This controller assumes that it is only invoked for authenticated users. @@ -261,7 +209,7 @@ public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass return batch_process(''); } else { - drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error'); + drupal_set_message($this->t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error'); return $this->redirect('entity.user.cancel_form', ['user' => $user->id()], ['absolute' => TRUE]); } } diff --git a/core/modules/user/src/Tests/UserEditedOwnAccountTest.php b/core/modules/user/src/Tests/UserEditedOwnAccountTest.php index b7fb095..df05bec 100644 --- a/core/modules/user/src/Tests/UserEditedOwnAccountTest.php +++ b/core/modules/user/src/Tests/UserEditedOwnAccountTest.php @@ -59,7 +59,7 @@ function testUserEditedOwnAccount() { $site_name = $this->config('system.site')->get('name'); $user_mail = $this->drupalGetMails(array( 'to' => $new_email, - 'subject' => "E-mail change information for $name at $site_name", + 'subject' => "Email change information for $name at $site_name", )); $this->assertTrue(count($user_mail), 'A verification email was sent to the user.'); } diff --git a/core/modules/user/src/Tests/UserTokenReplaceTest.php b/core/modules/user/src/Tests/UserTokenReplaceTest.php index c35677a..92008fb 100644 --- a/core/modules/user/src/Tests/UserTokenReplaceTest.php +++ b/core/modules/user/src/Tests/UserTokenReplaceTest.php @@ -12,6 +12,7 @@ use Drupal\language\Entity\ConfigurableLanguage; use Drupal\simpletest\WebTestBase; use Drupal\user\Entity\User; +use Drupal\user\ChangeEmailController; /** * Generates text using placeholders for dummy content to check user token @@ -134,7 +135,7 @@ function testUserTokenReplacement() { // Generate login and cancel link. $tests = array(); $tests['[user:one-time-login-url]'] = user_pass_reset_url($account); - $tests['[user:mail-change-login-url]'] = user_change_mail_url($account); + $tests['[user:mail-change-login-url]'] = ChangeEmailController::changeEmailUrl($account); $tests['[user:cancel-url]'] = user_cancel_url($account); // Generate tokens with interface language. diff --git a/core/modules/user/user.module b/core/modules/user/user.module index cf58e5b..0877422 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -17,6 +17,7 @@ use Drupal\user\Entity\User; use Drupal\user\RoleInterface; use Drupal\user\UserInterface; +use Drupal\user\Controller\ChangeEmailController; /** * @file @@ -577,13 +578,12 @@ function user_user_logout($account) { * they can change their password. */ function user_pass_reset_url($account, $options = array()) { - $timestamp = REQUEST_TIME; $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode(); return \Drupal::url('user.reset', array( 'uid' => $account->id(), - 'timestamp' => $timestamp, - 'hash' => user_pass_rehash($account, $timestamp), + 'timestamp' => REQUEST_TIME, + 'hash' => user_pass_rehash($account, REQUEST_TIME), ), array( 'absolute' => TRUE, @@ -593,32 +593,6 @@ function user_pass_reset_url($account, $options = array()) { } /** - * Generates a unique URL for a one time email change confirmation. - * - * @param object $account - * An object containing the user account. - * @param array $options - * (optional) A keyed array of settings. Supported options are: - * - langcode: A language code to be used when generating locale-sensitive - * URLs. If langcode is NULL the users preferred language is used. - * - * @return - * A unique URL that provides a one-time email change confirmation for the - * user. - */ -function user_change_mail_url($account, $options = []) { - $timestamp = $_SERVER['REQUEST_TIME']; - $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode(); - $url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode)); - return \Drupal::url('user.change_email', [ - 'uid' => $account->id(), - 'timestamp' => $timestamp, - 'new_email' => $account->getEmail(), - 'hash' => user_pass_rehash($account, $timestamp), - ], $url_options); -} - -/** * Generates a URL to confirm an account cancellation request. * * @param \Drupal\user\UserInterface $account @@ -974,7 +948,7 @@ function user_mail($key, &$message, $params) { function user_mail_tokens(&$replacements, $data, $options) { if (isset($data['user'])) { $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options); - $replacements['[user:mail-change-login-url]'] = user_change_mail_url($data['user'], $options); + $replacements['[user:mail-change-login-url]'] = ChangeEmailController::changeEmailUrl($data['user'], $options); $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options); } } diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index 2886adf..47677fd 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -152,9 +152,9 @@ user.reset: no_cache: TRUE user.change_email: - path: 'user/change-mail/{uid}/{timestamp}/{new_email}/{hash}' + path: 'user/change-mail/{uid}/{timestamp}/{new_mail}/{hash}' defaults: - _controller: '\Drupal\user\Controller\UserController::changeEmail' + _controller: '\Drupal\user\Controller\ChangeEmailController::changeEmailPage' _title: 'Change email address' requirements: _access: 'TRUE'