diff --git a/core/modules/user/config/install/user.mail.yml b/core/modules/user/config/install/user.mail.yml
index a1b9068..48d458b 100644
--- a/core/modules/user/config/install/user.mail.yml
+++ b/core/modules/user/config/install/user.mail.yml
@@ -8,7 +8,7 @@ mail_change_notification:
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 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."
+ 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-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"
diff --git a/core/modules/user/config/install/user.settings.yml b/core/modules/user/config/install/user.settings.yml
index 44e0ccf..ca46109 100644
--- a/core/modules/user/config/install/user.settings.yml
+++ b/core/modules/user/config/install/user.settings.yml
@@ -3,16 +3,17 @@ verify_mail: true
notify:
cancel_confirm: true
password_reset: true
- mail_change_notification: true
- mail_change_verification: true
status_activated: true
status_blocked: false
status_canceled: false
register_admin_created: true
register_no_approval_required: true
register_pending_approval: true
+ mail_change_notification: true
+ mail_change_verification: true
register: visitors
cancel_method: user_cancel_block
password_reset_timeout: 86400
+mail_change_timeout: 86400
password_strength: true
langcode: en
diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml
index d027c7b..206a853 100644
--- a/core/modules/user/config/schema/user.schema.yml
+++ b/core/modules/user/config/schema/user.schema.yml
@@ -20,12 +20,6 @@ user.settings:
password_reset:
type: boolean
label: 'Notify user when password reset'
- mail_change_notification:
- type: boolean
- label: 'Notify user when email changes'
- mail_change_verification:
- type: boolean
- label: 'Require email verification when a user changes their email address'
status_activated:
type: boolean
label: 'Notify user when account is activated'
@@ -44,6 +38,12 @@ user.settings:
register_pending_approval:
type: boolean
label: 'Welcome (awaiting approval)'
+ mail_change_notification:
+ type: boolean
+ label: 'Notify user when email changes'
+ mail_change_verification:
+ type: boolean
+ label: 'Require email verification when a user changes their email address'
register:
type: string
label: 'Who can register accounts?'
@@ -53,6 +53,9 @@ user.settings:
password_reset_timeout:
type: integer
label: 'Password reset timeout'
+ mail_change_timeout:
+ type: integer
+ label: 'Mail change timeout'
password_strength:
type: boolean
label: 'Enable password strength indicator'
diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php
index 4c97426..14272a2 100644
--- a/core/modules/user/src/AccountForm.php
+++ b/core/modules/user/src/AccountForm.php
@@ -388,17 +388,15 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$old_mail = $account->getEmail();
if (!$account->isNew() && ($old_mail !== $new_mail) && !$this->currentUser()->hasPermission('administer users')) {
-
// Send a verification to the new email address.
$account_cloned = clone $account;
$account_cloned->setEmail($new_mail);
if (_user_mail_notify('mail_change_verification', $account_cloned)) {
// Send notification email to the old email address.
- $account->setEmail($old_mail);
_user_mail_notify('mail_change_notification', $account);
}
- // The user's email address will be updated after verification.
+ // The user's mail address will be updated only after verification.
$form_state->setValue('mail', $old_mail);
drupal_set_message($this->t('Your new email address needs to be validated. Further instructions have been sent to your new email address.'), 'warning');
diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php
index b3c5821..f16c246 100644
--- a/core/modules/user/src/AccountSettingsForm.php
+++ b/core/modules/user/src/AccountSettingsForm.php
@@ -196,7 +196,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
);
// These email tokens are shared for all settings, so just define
// the list once to help ensure they stay in sync.
- $email_token_help = $this->t('Available variables are: [site:name], [site:url], [user:display-name], [user:account-name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:mail-change-login-url], [user:cancel-url].');
+ $email_token_help = $this->t('Available variables are: [site:name], [site:url], [user:display-name], [user:account-name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:mail-change-url], [user:cancel-url].');
$form['email_admin_created'] = array(
'#type' => 'details',
@@ -297,51 +297,76 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#default_value' => $mail_config->get('password_reset.body'),
'#rows' => 12,
);
-
- $form['email_email_change_notification'] = array(
+ $form['mail_change'] = [
'#type' => 'details',
- '#title' => $this->t('Email change notification'),
+ '#title' => $this->t('Account email changing'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
- '#description' => $this->t("Edit the email message sent to the user's old email address when the email address is changed.") . ' ' . $email_token_help,
'#group' => 'email',
'#weight' => 11,
- );
- $form['email_email_change_notification']['user_email_change_notification_subject'] = array(
+ ];
+ $form['mail_change']['mail_change_notification'] = [
+ '#type' => 'details',
+ '#tree' => TRUE,
+ '#title' => $this->t('Notification of old email'),
+ '#description' => $this->t("Edit the email message sent to the user's old email address when the email address is changed.") . ' ' . $email_token_help,
+ '#open' => TRUE,
+ ];
+ $form['mail_change']['mail_change_notification']['enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Notify user when email changes'),
+ '#default_value' => $config->get('notify.mail_change_notification'),
+ ];
+ $states = [
+ 'invisible' => [
+ 'input[name="mail_change_notification[enabled]"]' => ['checked' => FALSE],
+ ],
+ ];
+ $form['mail_change']['mail_change_notification']['subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('mail_change_notification.subject'),
'#maxlength' => 180,
- );
- $form['email_email_change_notification']['user_email_change_notification_body'] = array(
+ '#states' => $states,
+ ];
+ $form['mail_change']['mail_change_notification']['body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('mail_change_notification.body'),
'#rows' => 12,
- );
-
- $form['email_email_change_verification'] = array(
+ '#states' => $states,
+ ];
+ $form['mail_change']['mail_change_verification'] = [
'#type' => 'details',
- '#title' => $this->t('Email change verification'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
+ '#tree' => TRUE,
+ '#title' => $this->t('Verification of new email'),
'#description' => $this->t("Edit the email message sent to user's new email address when the email address is changed.") . ' ' . $email_token_help,
- '#group' => 'email',
- '#weight' => 13,
- );
- $form['email_email_change_verification']['user_email_change_verification_subject'] = array(
+ '#open' => TRUE,
+ ];
+ $form['mail_change']['mail_change_verification']['enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Require email verification when a user changes their email address'),
+ '#default_value' => $config->get('notify.mail_change_verification'),
+ ];
+ $states = [
+ 'invisible' => [
+ 'input[name="mail_change_verification[enabled]"]' => ['checked' => FALSE],
+ ],
+ ];
+ $form['mail_change']['mail_change_verification']['subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('mail_change_verification.subject'),
'#maxlength' => 180,
- );
- $form['email_email_change_verification']['user_email_change_verification_body'] = array(
+ '#states' => $states,
+ ];
+ $form['mail_change']['mail_change_verification']['body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('mail_change_verification.body'),
'#rows' => 12,
- );
-
+ '#states' => $states,
+ ];
$form['email_activated'] = array(
'#type' => 'details',
'#title' => $this->t('Account activation'),
@@ -478,16 +503,20 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
->set('notify.status_activated', $form_state->getValue('user_mail_status_activated_notify'))
->set('notify.status_blocked', $form_state->getValue('user_mail_status_blocked_notify'))
->set('notify.status_canceled', $form_state->getValue('user_mail_status_canceled_notify'))
+ ->set('notify.mail_change_notification', $form_state->getValue(['mail_change_notification', 'enabled']))
+ ->set('notify.mail_change_verification', $form_state->getValue(['mail_change_verification', 'enabled']))
->save();
+
+ $form_state->unsetValue(['mail_change_notification', 'enabled']);
+ $form_state->unsetValue(['mail_change_verification', 'enabled']);
+
$this->config('user.mail')
->set('cancel_confirm.body', $form_state->getValue('user_mail_cancel_confirm_body'))
->set('cancel_confirm.subject', $form_state->getValue('user_mail_cancel_confirm_subject'))
->set('password_reset.body', $form_state->getValue('user_mail_password_reset_body'))
->set('password_reset.subject', $form_state->getValue('user_mail_password_reset_subject'))
- ->set('mail_change_notification.subject', $form_state->getValue('user_email_change_notification_subject'))
- ->set('mail_change_notification.body', $form_state->getValue('user_email_change_notification_body'))
- ->set('mail_change_verification.subject', $form_state->getValue('user_email_change_verification_subject'))
- ->set('mail_change_verification.body', $form_state->getValue('user_email_change_verification_body'))
+ ->set('mail_change_notification', $form_state->getValue('mail_change_notification'))
+ ->set('mail_change_verification', $form_state->getValue('mail_change_verification'))
->set('register_admin_created.body', $form_state->getValue('user_mail_register_admin_created_body'))
->set('register_admin_created.subject', $form_state->getValue('user_mail_register_admin_created_subject'))
->set('register_no_approval_required.body', $form_state->getValue('user_mail_register_no_approval_required_body'))
diff --git a/core/modules/user/src/Controller/ChangeEmailController.php b/core/modules/user/src/Controller/ChangeEmailController.php
deleted file mode 100644
index 7c0e834..0000000
--- a/core/modules/user/src/Controller/ChangeEmailController.php
+++ /dev/null
@@ -1,141 +0,0 @@
-entityTypeManager()->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 and the user is active.
- $timeout = 24 * 60 * 60;
-
- if ($account->isActive() && $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.', ['%account' => $account->getAccountName()]), 'error');
- }
- elseif (!$this->currentUser()->isAnonymous() && ($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.', ['%user' => $this->currentUser()->getAccountName(), '%account' => $account->getAccountName()]), 'error');
- }
- else {
- $account->setEmail($new_mail);
- $account->save();
- drupal_set_message($this->t('Your email address has been changed to %mail.', ['%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 \Drupal\user\UserInterface $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.
- * @param int $timestamp
- * (optional) The timestamp when hash is created.
- * @param string $hash
- * (optional) Unique hash.
- *
- * @return \Drupal\Core\Url
- * A unique URL that provides a one-time email change confirmation for the
- * user.
- */
- public static function getUrl(UserInterface $account, array $options = [], $timestamp = REQUEST_TIME, $hash = NULL) {
- $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
- $hash = empty($hash) ? self::getHash($account, $timestamp) : $hash;
- $url_options = ['absolute' => TRUE, 'language' => \Drupal::getContainer()->get('language_manager')->getLanguage($langcode)];
- return Url::fromRoute('user.change_email', [
- 'uid' => $account->id(),
- 'timestamp' => $timestamp,
- 'new_mail' => $account->getEmail(),
- 'hash' => $hash,
- ], $url_options);
- }
-
- /**
- * Generates hash for change mail URL.
- *
- * @param \Drupal\user\UserInterface $account
- * An object containing the user account.
- * @param int $timestamp
- * The timestamp when hash is created.
- *
- * @return string
- * A new generated hash.
- */
- public static function getHash(UserInterface $account, $timestamp) {
- $data = $timestamp;
- $data .= $account->id();
- $data .= $account->getEmail();
- $data .= $account->getCreatedTime();
- $data .= $account->getInitialEmail();
- return Crypt::hmacBase64($data, Settings::getHashSalt() . $account->getPassword());
- }
-
- /**
- * Checks access to change email URL.
- *
- * @param int $uid
- * The ID of the user requesting reset.
- * @param int $timestamp
- * The timestamp when hash is created.
- * @param string $hash
- * Unique hash.
- * @param string $new_mail
- * A new user email.
- *
- * @return \Drupal\Core\Access\AccessResultInterface
- * An access result
- */
- public function access($uid, $timestamp, $hash, $new_mail) {
- /** @var \Drupal\user\UserInterface $account */
- $account = $this->entityTypeManager()->getStorage('user')->load($uid);
- $account->setEmail($new_mail);
- return AccessResult::allowedIf($hash == $this->getHash($account, $timestamp));
- }
-
-}
diff --git a/core/modules/user/src/Controller/MailChangeController.php b/core/modules/user/src/Controller/MailChangeController.php
new file mode 100644
index 0000000..3b3fa18
--- /dev/null
+++ b/core/modules/user/src/Controller/MailChangeController.php
@@ -0,0 +1,140 @@
+config('user.settings')->get('mail_change_timeout');
+ /** @var \Drupal\Core\Session\AccountProxyInterface $current_user */
+ $current_user = $this->currentUser();
+
+ // Other user is authenticated.
+ if ($current_user->isAuthenticated() && $current_user->id() != $user->id()) {
+ drupal_set_message($this->t('You are currently logged in as %user, and are attempting to confirm an email address change for other account. Please log out and try using the link again.', ['%user' => $current_user->getAccountName(), ':logout' => Url::fromRoute('user.logout')->toString()]), 'error');
+ }
+ // The link has expired.
+ elseif (REQUEST_TIME - $timestamp > $timeout) {
+ drupal_set_message($this->t('You have tried to use an email address change link that has expired. Please visit your account and change your email again.'), 'error');
+ }
+ // The link is valid.
+ elseif ($timestamp <= REQUEST_TIME && $timestamp >= $user->getLastLoginTime() && Crypt::hashEquals($hash, static::getHash($user, $timestamp, $new_mail))) {
+ // Save the new email but refresh also the last login time so that this
+ // mail change link get expired.
+ $user->setEmail($new_mail)->setLastLoginTime(REQUEST_TIME)->save();
+ /** @var \Drupal\user\UserStorageInterface $user_storage */
+ $user_storage = $this->entityTypeManager()->getStorage('user');
+ $user_storage->updateLastLoginTimestamp($user);
+ // Reflect the changes in the session if the user is user is logged in.
+ if ($current_user->isAuthenticated() && $current_user->id() == $user->id()) {
+ $current_user->setAccount($user);
+ }
+ drupal_set_message($this->t("Your email address has been changed to %mail.", ['%mail' => $new_mail]));
+ }
+ // Timestamp from the link is abnormal (in the future) or user registered a
+ // new login in the meantime or the hash is not valid.
+ else {
+ drupal_set_message($this->t('You have tried to use an email address change link that has either been used or is no longer valid. Please visit your account and change your email again.'), 'error');
+ }
+
+ return $this->redirect('');
+ }
+
+ /**
+ * Checks access to change email url.
+ *
+ * @param \Drupal\user\UserInterface $user
+ * The user account requesting Email change.
+ *
+ * @return \Drupal\Core\Access\AccessResultInterface
+ * An access result
+ */
+ public function access(UserInterface $user) {
+ return AccessResult::allowedIf($user->isActive());
+ }
+
+ /**
+ * Generates a unique url for a one time email change confirmation.
+ *
+ * @param \Drupal\user\UserInterface $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.
+ * @param int $timestamp
+ * (optional) The timestamp when hash is created. If missed, the current
+ * request time is used.
+ * @param string $hash
+ * (optional) Unique hash. If missed, the hash is computed based on the
+ * account data and timestamp.
+ *
+ * @return \Drupal\Core\Url
+ * A unique url that provides a one-time email change confirmation.
+ */
+ public static function getUrl(UserInterface $account, array $options = [], $timestamp = REQUEST_TIME, $hash = NULL) {
+ $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
+ $hash = empty($hash) ? static::getHash($account, $timestamp) : $hash;
+ $url_options = ['absolute' => TRUE, 'language' => \Drupal::service('language_manager')->getLanguage($langcode)];
+ return Url::fromRoute('user.mail_change', [
+ 'user' => $account->id(),
+ 'timestamp' => $timestamp,
+ 'new_mail' => $account->getEmail(),
+ 'hash' => $hash,
+ ], $url_options);
+ }
+
+ /**
+ * Generates hash for change mail url.
+ *
+ * This method is a wrapper around user_pass_rehash(). Optionally, it allows
+ * an alternative mail address to be used in hash computing.
+ *
+ * @param \Drupal\user\UserInterface $user
+ * An object containing the user account.
+ * @param int $timestamp
+ * The timestamp when hash is created.
+ * @param string $mail
+ * (optional) If passed, this value will be used to compute the hash instead
+ * that stored into $account.
+ *
+ * @return string
+ * A new generated hash.
+ */
+ protected static function getHash(UserInterface $user, $timestamp, $mail = NULL) {
+ if (!empty($mail)) {
+ // Clone to keep the original object unchanged.
+ $account = clone $user;
+ $account->setEmail($mail);
+ }
+ else {
+ $account = $user;
+ }
+ return user_pass_rehash($account, $timestamp);
+ }
+
+}
diff --git a/core/modules/user/src/Tests/Update/UpdateMailChangeTest.php b/core/modules/user/src/Tests/Update/UpdateMailChangeTest.php
new file mode 100644
index 0000000..8fde087
--- /dev/null
+++ b/core/modules/user/src/Tests/Update/UpdateMailChangeTest.php
@@ -0,0 +1,59 @@
+databaseDumpFiles = [
+ __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
+ ];
+ }
+
+ /**
+ * Tests user_post_update_mail_change().
+ *
+ * @see user_post_update_mail_change()
+ */
+ public function testMailChangeUpdate() {
+ $user_settings = $this->config('user.settings');
+ $user_mail = $this->config('user.mail');
+
+ // Check that mail change notifications settings are not set.
+ $this->assertNull($user_settings->get('notify.mail_change_notification'));
+ $this->assertNull($user_settings->get('notify.mail_change_verification'));
+ $this->assertNull($user_settings->get('mail_change_timeout'));
+
+ // Check that mail change configurations are not set.
+ $this->assertNull($user_mail->get('mail_change_notification'));
+ $this->assertNull($user_mail->get('mail_change_verification'));
+
+ $this->runUpdates();
+
+ $user_settings = $this->config('user.settings');
+ $user_mail = $this->config('user.mail');
+
+ // Check that mail change notifications were set to TRUE.
+ $this->assertTrue($user_settings->get('notify.mail_change_notification'));
+ $this->assertTrue($user_settings->get('notify.mail_change_verification'));
+ $this->assertIdentical($user_settings->get('mail_change_timeout'), 86400);
+
+ $config = Yaml::parse(file_get_contents(__DIR__ . '/../../../config/install/user.mail.yml'));
+
+ // Check that mail change configurations were set to default values.
+ $this->assertIdentical($user_mail->get('mail_change_notification'), $config['mail_change_notification']);
+ $this->assertIdentical($user_mail->get('mail_change_verification'), $config['mail_change_verification']);
+ }
+
+}
diff --git a/core/modules/user/src/Tests/UserChangeEmailTest.php b/core/modules/user/src/Tests/UserChangeEmailTest.php
deleted file mode 100644
index b54fd31..0000000
--- a/core/modules/user/src/Tests/UserChangeEmailTest.php
+++ /dev/null
@@ -1,166 +0,0 @@
-account = $this->drupalCreateUser();
- }
-
- /**
- * Tests email change functionality.
- */
- public function testEmailChange() {
- // Create a user.
- $this->drupalLogin($this->account);
-
- // Save a new email and verify that the user was sent an email.
- $new_mail = $this->getRandomEmailAddress();
- $edit = [
- 'mail' => $new_mail,
- 'current_pass' => $this->account->pass_raw,
- ];
- $this->drupalPostForm('user/' . $this->account->id() . '/edit', $edit, t('Save'));
-
- // Take email settings from user.mail.yml.
- $mail_settings = $this->config('user.mail');
-
- /** @var \Drupal\Core\Utility\Token $token_service */
- $token_service = $this->container->get('token');
- // Verify that the user was sent a notification email.
- $this->assertMail('to', $this->account->getEmail());
- $subject = $token_service->replace($mail_settings->get('mail_change_notification.subject'), ['user' => $this->account]);
- $this->assertMail('subject', $subject);
-
- // Verify that the user was sent a verification email.
- $this->assertMailString('to', $new_mail, 2);
- $subject = $token_service->replace($mail_settings->get('mail_change_verification.subject'), ['user' => $this->account]);
- $this->assertMailString('subject', $subject, 2);
-
- $change_mail_url = $this->gerUrlEmail('user_mail_change_verification');
-
- // Check that the email has been successfully updated.
- $this->drupalGet($change_mail_url);
- $this->assertRaw(t('Your email address has been changed to %mail.', ['%mail' => $new_mail]));
-
- $this->drupalGet($change_mail_url);
- $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
-
- // Ensure the change email URL is not cached.
- $this->drupalGet($change_mail_url);
- $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
- }
-
- /**
- * Tests change of email for blocked users.
- */
- public function testEmailChangeForBlockedUser() {
- $timestamp = REQUEST_TIME - 1;
- $account_cloned = clone $this->account;
- $account_cloned->block();
- $account_cloned->save();
- $this->drupalGet(ChangeEmailController::getUrl($account_cloned, [], $timestamp)->getInternalPath());
- $this->assertResponse(403);
- }
-
- /**
- * Tests change of email for expired timestamp.
- */
- public function testEmailChangeExpiredTimestamp() {
- $timestamp = REQUEST_TIME - 30 * 60 * 60 * 10;
- $this->drupalGet(ChangeEmailController::getUrl($this->account, [], $timestamp)->getInternalPath());
- $this->assertRaw(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.', ['%account' => $this->account->getAccountName()]));
- }
-
- /**
- * Tests change of email when other user is logged in.
- */
- public function testEmailImpersonation() {
- $timestamp = REQUEST_TIME - 1;
- // Create other account and login with it.
- $current_account = $this->drupalCreateUser();
- $this->drupalLogin($current_account);
- // Try to change the email for the first account when the other account is
- // logged in.
- $this->drupalGet(ChangeEmailController::getUrl($this->account, [], $timestamp)->getInternalPath());
- $this->assertRaw(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.', ['%user' => $current_account->getAccountName(), '%account' => $this->account->getAccountName()]));
- }
-
- /**
- * Tests change of email for timestamp in the future.
- */
- public function testEmailChangeForFutureTimestamp() {
- $timestamp = REQUEST_TIME + 30 * 60 * 60;
- $this->drupalGet(ChangeEmailController::getUrl($this->account, [], $timestamp)->getInternalPath());
- $this->assertResponse(403);
- }
-
- /**
- * Tests change of email with the wrong hash.
- */
- public function testEmailChangeWithWrongHash() {
- $timestamp = REQUEST_TIME - 1;
- // Generate the hash for other user.
- $other_account = $this->drupalCreateUser();
- $hash = ChangeEmailController::getHash($other_account, $timestamp);
- $this->drupalGet(ChangeEmailController::getUrl($this->account, [], $timestamp, $hash)->getInternalPath());
- $this->assertResponse(403);
- }
-
- /**
- * Retrieves the change email and extracts the link.
- *
- * @param string $mail_id
- * Unique mail ID.
- *
- * @return string
- * An URL.
- */
- protected function gerUrlEmail($mail_id) {
- // Assume the most recent email.
- $_emails = $this->drupalGetMails(['id' => $mail_id]);
- $email = end($_emails);
- $urls = [];
- preg_match('#.+user/change-mail/.+#', $email['body'], $urls);
- return $urls[0];
- }
-
- /**
- * Generates a random email address.
- *
- * @return string
- * A random email address.
- */
- protected function getRandomEmailAddress() {
- return Unicode::strtolower($this->randomMachineName()) . '@example.com';
- }
-
-}
diff --git a/core/modules/user/src/Tests/UserMailChangeTest.php b/core/modules/user/src/Tests/UserMailChangeTest.php
new file mode 100644
index 0000000..ef42e1e
--- /dev/null
+++ b/core/modules/user/src/Tests/UserMailChangeTest.php
@@ -0,0 +1,168 @@
+account = $this->drupalCreateUser();
+ }
+
+ /**
+ * Tests email change functionality.
+ */
+ public function testMailChange() {
+ $this->drupalLogin($this->account);
+
+ // Save a new email and verify that the user has sent an email.
+ $new_mail = $this->getRandomEmailAddress();
+ $edit = [
+ 'mail' => $new_mail,
+ 'current_pass' => $this->account->pass_raw,
+ ];
+ $this->drupalPostForm($this->account->toUrl('edit-form'), $edit, t('Save'));
+
+ $user_mail = $this->config('user.mail');
+
+ /** @var \Drupal\Core\Utility\Token $token_service */
+ $token_service = $this->container->get('token');
+
+ // Check that a notification mail has been sent.
+ $this->assertMail('to', $this->account->getEmail());
+ $subject = $token_service->replace($user_mail->get('mail_change_notification.subject'), ['user' => $this->account]);
+ $this->assertMail('subject', $subject);
+
+ // Check that a verification mail has been sent.
+ $this->assertMailString('to', $new_mail, 2);
+ $subject = $token_service->replace($user_mail->get('mail_change_verification.subject'), ['user' => $this->account]);
+ $this->assertMailString('subject', $subject, 2);
+
+ $sent_mail_change_url = $this->extractUrlFromMail('user_mail_change_verification');
+
+ // Check that the email has been successfully updated.
+ $this->drupalGet($sent_mail_change_url);
+ $this->assertRaw(t('Your email address has been changed to %mail.', ['%mail' => $new_mail]));
+
+ // Check the change email URL is not cached and expires after first use.
+ $this->drupalGet($sent_mail_change_url);
+ $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
+ $this->assertRaw(t('You have tried to use an email address change link that has either been used or is no longer valid. Please visit your account and change your email again.'));
+
+ // Check that the user mail has been changed.
+ $this->assertIdentical(User::load($this->account->id())->getEmail(), $new_mail);
+ }
+
+ /**
+ * Tests change of email for blocked users.
+ */
+ public function testBlockedUser() {
+ $timestamp = REQUEST_TIME - 1;
+ $account_cloned = clone $this->account;
+ $account_cloned->block()->save();
+ $this->drupalGet(MailChangeController::getUrl($account_cloned, [], $timestamp)->getInternalPath());
+ $this->assertResponse(403);
+ }
+
+ /**
+ * Tests change of email for expired timestamp.
+ */
+ public function testExpiredTimestamp() {
+ $timestamp = REQUEST_TIME - (24 * 60 * 60 + 1);
+ $this->drupalGet(MailChangeController::getUrl($this->account, [], $timestamp)->getInternalPath());
+ $this->assertRaw(t('You have tried to use an email address change link that has expired. Please visit your account and change your email again.'));
+ }
+
+ /**
+ * Tests change of email when other user is logged in.
+ */
+ public function testOtherUserLoggedIn() {
+ $timestamp = REQUEST_TIME - 1;
+ // Create other account and login with it.
+ $current_account = $this->drupalCreateUser();
+ $this->drupalLogin($current_account);
+ // Try to change the email for the first account when the other account is
+ // logged in.
+ $new_mail = $this->getRandomEmailAddress();
+ $this->account->setEmail($new_mail);
+ $path = MailChangeController::getUrl($this->account, [], $timestamp)->getInternalPath();
+ $this->drupalGet($path);
+ $this->assertRaw(t('You are currently logged in as %user, and are attempting to confirm an email address change for other account. Please log out and try using the link again.', ['%user' => $current_account->getAccountName(), ':logout' => Url::fromRoute('user.logout')->toString()]));
+
+ // Retry as anonymous.
+ $this->drupalLogout();
+ $this->drupalGet($path);
+ $this->assertRaw(t('Your email address has been changed to %mail.', ['%mail' => $new_mail]));
+ }
+
+ /**
+ * Tests change of email for timestamp in the future.
+ */
+ public function testFutureTimestamp() {
+ $timestamp = REQUEST_TIME + 60 * 60;
+ $this->drupalGet(MailChangeController::getUrl($this->account, [], $timestamp)->getInternalPath());
+ $this->assertRaw(t('You have tried to use an email address change link that has either been used or is no longer valid. Please visit your account and change your email again.'));
+ }
+
+ /**
+ * Tests change of email with the wrong hash.
+ */
+ public function testWrongHash() {
+ $timestamp = REQUEST_TIME - 1;
+ // Generate the hash for other user.
+ $other_account = $this->drupalCreateUser();
+ $hash = user_pass_rehash($other_account, $timestamp);
+ $this->drupalGet(MailChangeController::getUrl($this->account, [], $timestamp, $hash)->getInternalPath());
+ $this->assertRaw(t('You have tried to use an email address change link that has either been used or is no longer valid. Please visit your account and change your email again.'));
+ }
+
+ /**
+ * Retrieves the change email and extracts the link.
+ *
+ * @param string $mail_id
+ * Unique mail ID.
+ *
+ * @return string
+ * An URL.
+ */
+ protected function extractUrlFromMail($mail_id) {
+ // Assume the most recent email.
+ $email = $this->drupalGetMails(['id' => $mail_id]);
+ $email = end($email);
+ preg_match('#.+user\/mail\-change\/.+#', $email['body'], $urls);
+ return $urls[0];
+ }
+
+ /**
+ * Generates a random email address.
+ *
+ * @return string
+ * A random email address.
+ */
+ protected function getRandomEmailAddress() {
+ return Unicode::strtolower($this->randomMachineName()) . '@example.com';
+ }
+
+}
diff --git a/core/modules/user/src/Tests/UserTokenReplaceTest.php b/core/modules/user/src/Tests/UserTokenReplaceTest.php
index 0675269..7344eb3 100644
--- a/core/modules/user/src/Tests/UserTokenReplaceTest.php
+++ b/core/modules/user/src/Tests/UserTokenReplaceTest.php
@@ -7,7 +7,7 @@
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
use Drupal\user\Entity\User;
-use Drupal\user\Controller\ChangeEmailController;
+use Drupal\user\Controller\MailChangeController;
/**
* Generates text using placeholders for dummy content to check user token
@@ -130,7 +130,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]'] = ChangeEmailController::getUrl($account)->toString();
+ $tests['[user:mail-change-url]'] = MailChangeController::getUrl($account)->toString();
$tests['[user:cancel-url]'] = user_cancel_url($account);
// Generate tokens with interface language.
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index 2519918..7cc46ef 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -1,7 +1,5 @@
getEditable('user.mail');
- $mail_config->set('mail_change_notification.body', $mail_settings['mail_change_notification']['body']);
- $mail_config->set('mail_change_notification.subject', $mail_settings['mail_change_notification']['subject']);
- $mail_config->set('mail_change_verification.body', $mail_settings['mail_change_verification']['body']);
- $mail_config->set('mail_change_verification.subject', $mail_settings['mail_change_verification']['body']);
- $mail_config->save();
-}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index a1de45e..8d8f359 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -19,7 +19,7 @@
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\system\Entity\Action;
-use Drupal\user\Controller\ChangeEmailController;
+use Drupal\user\Controller\MailChangeController;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
@@ -951,7 +951,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]'] = ChangeEmailController::getUrl($data['user'], $options)->toString();
+ $replacements['[user:mail-change-url]'] = MailChangeController::getUrl($data['user'], $options)->toString();
$replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
}
}
diff --git a/core/modules/user/user.post_update.php b/core/modules/user/user.post_update.php
new file mode 100644
index 0000000..790e5dd
--- /dev/null
+++ b/core/modules/user/user.post_update.php
@@ -0,0 +1,37 @@
+getEditable('user.settings')
+ ->set('notify.mail_change_notification', $config['notify']['mail_change_notification'])
+ ->set('notify.mail_change_verification', $config['notify']['mail_change_verification'])
+ ->set('mail_change_timeout', 86400)
+ ->save();
+
+ $config = Yaml::parse(file_get_contents(__DIR__ . '/config/install/user.mail.yml'));
+ $config_factory->getEditable('user.mail')
+ ->set('mail_change_notification', $config['mail_change_notification'])
+ ->set('mail_change_verification', $config['mail_change_verification'])
+ ->save();
+}
+
+/**
+ * @} End of "addtogroup updates-8.2.x".
+ */
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index 25b27d2..1170912 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -151,13 +151,15 @@ user.reset:
_maintenance_access: TRUE
no_cache: TRUE
-user.change_email:
- path: '/user/change-mail/{uid}/{timestamp}/{new_mail}/{hash}'
+user.mail_change:
+ path: '/user/mail-change/{user}/{new_mail}/{timestamp}/{hash}'
defaults:
- _controller: '\Drupal\user\Controller\ChangeEmailController::page'
+ _controller: '\Drupal\user\Controller\MailChangeController::page'
_title: 'Change email address'
requirements:
- _custom_access: '\Drupal\user\Controller\ChangeEmailController::access'
+ _custom_access: '\Drupal\user\Controller\MailChangeController::access'
+ user: \d+
+ timestamp: \d+
options:
_maintenance_access: TRUE
no_cache: TRUE