Problem/Motivation

Currently, when a new user registers for a Drupal account the details are sent to the user's supplied email address. This provides a basic mechanism that confirms the user is at that email address. However, once registered, users are permitted to change their email address without further confirming that the user is in fact at that email address.

Possible implications

  • A user can change their email address to be that of an unsuspecting third party as no confirmation of change is required. Using a second Drupal account (with it's email address also faked using the same method) the first user is then able to send anonymous malicious messages to the unsuspecting third party
  • A slow method for sending spam but exploitable none the less

Proposed resolution

Add a mechanism (similar to reset password) that:

  1. Sends an E-mail to the new address requiring the verification of the new address (similar to register confirmation).
  2. Sends a notification E-mail to the old address.
  3. Allow the site builder to customise both messages at admin/config/people/accounts
  4. Provides an update path to set the default behaviour and messages content.
  5. Write tests.

This new mechanism is bypassed if the e-mail address is changed by an administrator.

Remaining tasks

  1. #279.2: Seems to be fixed as well as possible right now given limitations of other issues. Raise a follow-on issue to tidy up once other issues have been fixed.
  2. #279.6: Add a test - see further explanation in #284
  3. Release note snippet for IS.
  4. #345 test #2 outstanding - Not easy to test as involves email send failure - code seems OK
  5. #347 unaddressed - Addressed around #356, with new test included
  6. Security review.
  7. Follow-up questions from #270: (Potentially follow up change as there are other potential improvements in #358 that would resolve a lot of these and go further to improve UX)
  8. See #360

    1. Should there be some indication that additional form fields are available once checked? (NOTE: Same issue in Account Canceled email form) See #360
    2. Should the suggestions exist prior to checking the box? (NOTE: Same issue in Account Canceled email form)See #360
    3. Should we make the request visible as pending to user and/or admin until completion?See #360
    4. Should there be visible history (related question, are users revisionable)?See #360
  9. Follow-up questions from #272: See #360
    1. Should there be an exception for admin users changing their own email? - In #312 noted admin users are generally able to do a lot of things without checks, including remove their own rights, so already trustworthy to set a new email correctly without needing to verify it
  10. Check that the unresolved comments on MR 437 are fixed on MR 5774
  11. #334 Create a followup for JSONAPI
  12. Don't re-use user_mail_notify or mutate the email on a cloned version of the user in case someone calls ::save() on it
  13. Add flood control to the change email controller

User interface changes

New UI additions to admin/config/people/accounts:

New confirmation message (warning) when user changes e-mail address:

Your updated email address needs to be validated. Further instructions have been sent to your new email address.

Default text of the generated e-mail (some elements will vary):

alice,

A request to change your email address has been made at Drupal Usability. You
need to verify the change by clicking on the link below or copying and
pasting it in your browser:

http://drupalux.lndo.site/user/mail-change/2/alice%40example.org/1542687...

This is a one-time URL, so it can be used only once. It expires after one
day. If not used, your email address at Drupal Usability will not change.

After using the one-time link, the user is redirected to the site's home page, with the message (info)

Your email address has been changed to alice@example.org.

API changes

New controller used for mail changing: \Drupal\user\Controller\MailChangeController

Data model changes

New schema for configs user.settings and user.mail.

Release notes snippet

When users wish to change their email, they must now verify the email belongs to them using a link sent to that address. This behavior is enabled by default on new installations but disabled by default on existing installations. Review the change record for more information.

CommentFileSizeAuthor
#452 85494-451-drupal9511.patch24.02 KBlawxen
#447 85494_447_10_2_3.patch43.11 KBInaW
#440 85494-440.rebase-conflict.txt937 bytesdww
#425 interdiff_423-425.txt1.37 KBvsujeetkumar
#425 85494-425.patch41.56 KBvsujeetkumar
#423 interdiff_422-423.txt8.77 KBvsujeetkumar
#423 85494-423.patch41.53 KBvsujeetkumar
#422 interdiff_421-422.txt5 KBkarishmaamin
#422 85494-422.patch41.42 KBkarishmaamin
#421 drupal.org_files_issues_2023-03-28_85494-421.patch41.62 KBmrinalini9
#420 85494-nr-bot.txt84 bytesneeds-review-queue-bot
#417 85494-417.patch41.45 KBsarvjeetsingh
#416 85494-416.patch44.6 KBfranksj
#409 85494-409.patch44.83 KBkarishmaamin
#398 Screen Shot 2022-07-18 at 10.09.27 AM.png75.41 KBsmustgrave
#397 Screen Shot 2022-07-18 at 1.13.33 pm.png222.5 KBdarvanen
#395 85494-395.patch44.59 KBdarvanen
#394 85494-394.patch21.89 KBdarvanen
#376 85494-376-9.3.x.patch42.28 KBpfrenssen
#351 interdiff-344-350.txt4.98 KBdarvanen
#343 email-change-notification-settings.png174.66 KBdarvanen
#312 email-change.85494-interdiff-310-312.txt1.83 KBAdamPS
#312 email-change.85494-312.patch37.22 KBAdamPS
#310 interdiff_301-310.txt1.83 KBsharma.amitt16
#310 85494-310.patch38.27 KBsharma.amitt16
#308 interdiff_298-301.txt899 bytessharma.amitt16
#308 85494_301.patch37.1 KBsharma.amitt16
#302 85494-301.patch37.1 KBnarendra.rajwar27
#302 interdiff_298-301.txt627 bytesnarendra.rajwar27
#298 interdiff-85494-296-298.txt488 bytesmrinalini9
#298 85494_298.patch37.1 KBmrinalini9
#296 interdiff.txt1.67 KBLal_
#296 85494_296.patch37.09 KBLal_
#294 85494-293-9.1.x.patch37.06 KBpfrenssen
#294 85494-293-8.9.x.patch37.07 KBpfrenssen
#287 85494-287-8.8.x.patch37.05 KBclaudiu.cristea
#282 85494-282.interdiff.txt15.85 KBclaudiu.cristea
#272 edit-confirmation.png155.69 KBbenjifisher
#264 85494-261.interdiff.txt806 bytesclaudiu.cristea
#264 85494-261.patch36.98 KBclaudiu.cristea
#257 85494-257.patch36.93 KBclaudiu.cristea
#257 85494-257.interdiff.txt2.69 KBclaudiu.cristea
#256 85494-256-test-omly-for-252.patch36.88 KBclaudiu.cristea
#256 85494-256-test-omly-for-252.interdiff.txt1.63 KBclaudiu.cristea
#253 email_verification-85494-253.patch36.53 KBsandzel
#248 85494-248.patch35.79 KBclaudiu.cristea
#248 85494-248.interdiff.txt3.5 KBclaudiu.cristea
#246 85494-246.interdiff.txt1.13 KBclaudiu.cristea
#246 85494-246.patch36 KBclaudiu.cristea
#242 85494-242.interdiff.txt2.44 KBclaudiu.cristea
#242 85494-242.patch35.4 KBclaudiu.cristea
#241 85494-241.interdiff.txt928 bytesclaudiu.cristea
#241 85494-241.patch35.32 KBclaudiu.cristea
#234 85494-234.interdiff.txt2.68 KBclaudiu.cristea
#234 85494-234.patch35.17 KBclaudiu.cristea
#233 85494-233.interdiff.txt5.28 KBclaudiu.cristea
#233 85494-233.patch34.43 KBclaudiu.cristea
#231 85494-231.interdiff.txt5.54 KBclaudiu.cristea
#231 85494-231.patch34.39 KBclaudiu.cristea
#228 85494-228.patch34.37 KBclaudiu.cristea
#206 interdiff.txt3.49 KBclaudiu.cristea
#206 user_email_verification-85494-206.patch34.37 KBclaudiu.cristea
#204 interdiff.txt350 bytesclaudiu.cristea
#204 use_email_verification-85494-204.patch33.76 KBclaudiu.cristea
#203 interdiff.txt16.41 KBclaudiu.cristea
#203 use_email_verification-85494-203.patch33.8 KBclaudiu.cristea
#200 interdiff.txt977 bytesclaudiu.cristea
#200 user_email_verification-85494-200.patch33.17 KBclaudiu.cristea
#198 interdiff.txt1.75 KBclaudiu.cristea
#198 user_email_verification-85494-198.patch33.12 KBclaudiu.cristea
#192 interdiff.txt1.38 KBclaudiu.cristea
#192 85494-192.patch33.29 KBclaudiu.cristea
#190 interdiff.txt8.1 KBclaudiu.cristea
#190 85494-189.patch33.28 KBclaudiu.cristea
#186 interdiff.txt2.32 KBclaudiu.cristea
#186 85494-187.patch31.71 KBclaudiu.cristea
#185 85494-185.patch30.91 KBclaudiu.cristea
#185 interdiff.txt4.61 KBclaudiu.cristea
#182 interdiff.txt1.35 KBclaudiu.cristea
#182 85494-182.patch30.93 KBclaudiu.cristea
#180 u.png348.92 KBclaudiu.cristea
#180 interdiff.txt44.16 KBclaudiu.cristea
#180 85494-180.patch30.95 KBclaudiu.cristea
#174 verify_changing_user-85494-174.patch25.83 KBLOBsTerr
#174 interdiff-85494-171-174.txt881 bytesLOBsTerr
#171 verify_changing_user-85494-171.patch25.82 KBAnonymous (not verified)
#171 interdiff-168-171.txt1 KBAnonymous (not verified)
#168 interdiff-85494-166-168.txt5.87 KBLOBsTerr
#168 verify_changing_user-85494-168.patch25.79 KBLOBsTerr
#166 verify_changing_user-85494-166.patch25.82 KBLOBsTerr
#166 interdiff-85494-160-166.txt11.91 KBLOBsTerr
#161 verify_changing_user-85494-160.patch31.91 KBLOBsTerr
#161 interdiff-85494-158-160.txt26.45 KBLOBsTerr
#158 interdiff-85494-148-158.txt19.23 KBLOBsTerr
#158 verify_changing_user-85494-158.patch25.77 KBLOBsTerr
#148 verify_changing_user-85494-148.patch23.22 KBLOBsTerr
#148 interdiff-85494-140-148.txt17.17 KBLOBsTerr
#146 interdiff-85494-140-145.txt17.15 KBLOBsTerr
#146 verify_changing_user-85494-145.patch23.21 KBLOBsTerr
#144 interdiff-85494-140-144.txt17.16 KBLOBsTerr
#144 verify_changing_user-85494-144.patch23.21 KBLOBsTerr
#140 interdiff-85494-129-140.txt10.58 KBLOBsTerr
#140 verify_changing_user-85494-140.patch19.83 KBLOBsTerr
#129 interdiff.124.129.txt3.55 KBk4v
#129 verify_changing_user-85494-129.patch19.83 KBk4v
#124 verify_changing_user-85494-124.patch18.31 KBLOBsTerr
#118 interdiff-85494-103-117.txt4.89 KBLOBsTerr
#117 verify_changing_user-85494-117.patch18.79 KBLOBsTerr
#114 interdiff-85494-103-114-do-not-test.diff3.1 KBLOBsTerr
#110 interdiff-85494-103-110.diff3.1 KBLOBsTerr
#107 interdiff-85494-103-107.patch3.1 KBLOBsTerr
#103 verify_changing_user-85494-103.patch19.21 KBLOBsTerr
#98 verify_changing_user-85494-98.patch18.13 KBStryKaizer
#97 interdiff.txt9.41 KBStryKaizer
#97 verify_changing_user-85494-97.patch9.41 KBStryKaizer
#90 verify_changing_user-85494-90.patch17.83 KBAnonymous (not verified)
#90 interdiff-89-90.txt7.67 KBAnonymous (not verified)
#89 verify_changing_user-85494-89.patch13.47 KBAnonymous (not verified)
#89 interdiff-86-89.txt3.18 KBAnonymous (not verified)
#86 verify_changing_user-85494-86.patch12.08 KBAnonymous (not verified)
#86 interdiff-84-86.txt604 bytesAnonymous (not verified)
#84 verify_changing_user-85494-84.patch11.49 KBAnonymous (not verified)
#83 verify_changing_user-85494-83.do-not-test.patch6.25 KBAnonymous (not verified)
#73 85494-73.patch14.27 KBjibran
#37 user-mail-change.patch7.83 KBZen
#34 email_confirm_5.patch8.04 KBhunmonk
#30 email_confirm_4.patch8.04 KBhunmonk
#26 email_confirm_3.patch7.62 KBhunmonk
#22 email_confirm_2.patch6.95 KBhunmonk
#20 email_confirm_1.patch6.95 KBhunmonk
#18 email_confirm_0.patch6.93 KBhunmonk
#17 85494_user.module_r1.687_patch7.49 KBAjK
#14 email_confirm.patch5.84 KBhunmonk
#10 user_mail_change_confirm.patch6.09 KBhunmonk
#7 85494_user.module_r1.684_patch_16.21 KBAjK
user.module_r1.682_patch6.2 KBAjK

Issue fork drupal-85494

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

beginner’s picture

LOL! Why single me out :) I'm innocent, I didn't do anything, I promise!

Can the code be extended for deal with another situation where the user's email is not confirmed: when new user accounts must be reviewed by the admin before being set active. I have had this happen on a Drupal site. If the settings is that the admin must authorized new accounts, the admin may waste time reviewing new accounts created by robots or by a human spammer with fake emails. It is not always obvious which account is bona-fide, which is fake. The admin shouldn't have to make a decision on an account with an invalid email (user error is also possible).

The workflow should be:
1) user creates account
2) user confirms email is valid (where part of your code is relevant)
3) admin authorizes (or not) the account.

The following ancient proposal is not necessarily good, but it may give some ideas for a better systematic approach: http://drupal.org/node/64861.

My point is that your new function user_change_mail() should be user_confirm_mail() so that it is general enough, and reusable in the different scenario I've exposed.

I hope you don't regret that I DID follow up with "the not so easy bit" ;)

AjK’s picture

Not at all, your proposal sounds good. However, as drumm has pointed out, let's keep core patches nice, simple and byte sized ;)

This issue addresses one simple potential exploit which I would class as a bug. What you want is, imho, is a feature. A good one, but none the less, a feature. Introducing that on this issue will hamper it's chances of a commit.

The current issue is plain, simple and hopefully committable (review will tell).

I'd be happy to look at what you want to do, just lets get it on another issue and thrash it out there so this cuurent issue doesn't get bogged down.

Sound reasonable?

Thanks for the feedback.

regards,
-AjK

(p.s. I didn't single you in particular, just a gentle prod to get a review! How sneaky of me ;)

beginner’s picture

Your 'sneakiness' worked (and I don't mind it, so don't worry) :)

I know different issues must be kept separate, so I perfectly agree with your point.

My question still stand, though: is there a way to make your new function more general so it can be used for this bug report and reused for other separate issues later?
How difficult would it be to reuse existing code and make it more general to reuse in every situation when we want a mail check?

AjK’s picture

The answer to your question is "probably fairly easy". The only reason I'm holding back on this is that we are in code freeze right now.

I believe this issue is regards a "critical bug" and labelled as such stands a chance of being committed.

I believe what you want is a "feature" and if this issue thread goes down that road I'm 100% certain this issue won't be addressed in the code freeze before Drupal5 is released.

I would suggest you start a new issue for what you want and in link back to this issue and say "could have gone in that issue but missed the boat".

I'm all up for doing what you want, it's a good solution, just not on this thread (we can develope what you want on a new thread right now, it doesn't have to be postponed, just I'd like it on it's own issue, pref as a patch after this patch gets commited, to modify the function in core, not modify a patch with a patch).

seem reasonable? (if/when you start a new issue, contact me via my contact form so I can get subscribed to it)

regards,
--AjK

drumm’s picture

Title: Prevent "contact form" email abuse » Verify changing user email addresses
AjK’s picture

Status: Needs review » Needs work

Found a bug in this patch.

When user updates their "my account" but leaves the password fields empty (i.e. they don't want to change their password but are changing something else) the email still gets sent informing them of their "intention to change their password".

Obviously, if the password fields are empty the "change email" workflow should not kick in and the email should not get sent.

I'll sort this out shortly but for now changing status to "code needs work"

regards,
--AjK

AjK’s picture

Status: Needs work » Needs review
FileSize
6.21 KB

Corrected bug in #6

webchick’s picture

going to test this later.

Heine’s picture

Status: Needs review » Needs work

Some t calls in #7 need work:

watchdog('user', t('User %name used one-time email change link at time %timestamp.', array('%name' => "<em>$account->name</em>", '%timestamp' => $timestamp)));

Afaik, % calls theme_placeholder, additional em-tags are not necessary. In the mail t with %-variables will get you em-tags in the plaintext mail.

hunmonk’s picture

updated patch attached. this addresses many issues:

  1. user admins should not trigger a confirmation email
  2. if user changes their password, the new password must be used in the hash
  3. error messages missing 'error' status
  4. unnecessary elements in $variables array
  5. email string substitutions should use !, not %
  6. drupal_goto after confirmation needed to be more intelligent
  7. cleaned up wording of the confirmation email

patch has been pretty thoroughly tested on a clean HEAD site, and seems to work perfectly.

chx’s picture

Status: Needs work » Reviewed & tested by the community

Excellent work, Chad!

Dries’s picture

We shouldn't set 'X-Mailer' => 'Drupal'. AFAIK, drupal_mail already does that ...

I'll provide a more in-depth review shortly.

webchick’s picture

adding to queue...

hunmonk’s picture

Assigned: Unassigned » hunmonk
FileSize
5.84 KB

@Dries: looks to me like we don't need that header array at all there.

attached patch removes it. anything else we need to do here?

Dries’s picture

Status: Reviewed & tested by the community » Needs work

This patch needs work. Punctuation is not consistent; some sentences end with a point, other don't. Also, the URL scheme is awkard.

Restting a password happens at ?q=user/reset. Restting an e-mail adress happens at ?q=user/change/mail. Maybe that should be ?q=user/reset-password and q?=user/reset-mail.

I haven't tested the patch yet but it is not clear what happens as long the new e-mail adress hasn't been confirmed. What are the implications?

AjK’s picture

I haven't tested the patch yet but it is not clear what happens as long the new e-mail adress hasn't been confirmed. What are the implications?

The "reset email" that is sent out is time limited (24hours). If the reset link is not activated within this time, the email address is not altered and remains the same. In fact, it doesn't change to the new address until activation by the one time, time limited email link.

As for the implications, it means that if the Drupal generated link in the email sent out isn't activated within it's given timeout value, the email address is not altered.

Did that make sense? It's late!

AjK’s picture

Status: Needs work » Needs review
FileSize
7.49 KB

Maybe that should be ?q=user/reset-password and q?=user/reset-mail.

Patch attached addresses / corrects this.

Punctuation is not consistent; some sentences end with a point

Hmm, I may be English but languages is "low" on my "best of" list. So, I failed to spot the missing full stop!

hunmonk’s picture

FileSize
6.93 KB

okay, i believe i've fixed up the last of the problems:

  • corrected bad/inconsistent punctuation, particularlry missing periods and inconsistency in the use of the word e-mail
  • changed the callback name to user/change-mail. user/reset-mail is not really an accurate name
  • changed the mail key to user_change_mail. mail_change_mail_send seemed awfully confusing

tested the mail change and password reset functionalities on a clean HEAD site, and both are working perfectly AFAICT. it would be great if we could get one more person to test them both...

edmund.kwok’s picture

Tested on fresh head:

1) Email changed only when the activation link is clicked. Did not wait to see if the link is disabled after the timeout though
2) Reset password works as it normally does

I noticed that in the confirmation mail that was sent when the email is changed, the From field is the user's old email address. Is this intentional? Or should the mail come from the site's email address?

hunmonk’s picture

FileSize
6.95 KB

per chx's consult, changed $from to the site mail.

chx’s picture

Status: Needs review » Reviewed & tested by the community

Just before the beta. To sum up: what's the point in the initial email confirmation if I can change it to foo whenever?

hunmonk’s picture

FileSize
6.95 KB

patch broken by the capitalization commit. updated patch attached. no changes in code functionality whatsoever--just fixed up the conflict and updated the capitalization.

Dries’s picture

chx: note that we store the initial e-mail address, even if you chose to change it later. $user->init should always be valid, and we should have some sort of audit trail.

(We've never had this kind of functionality, and all of a sudden it is critical? The critical status is debatable, IMO.)

chx’s picture

Note that this is the child of the contact spam issue which is indeed a very serious one -- hence the critical.

Dries’s picture

Status: Reviewed & tested by the community » Needs work

I think there is room for improvement:

1. I wonder what my mother will think when she reads: "The account identification numbers did not match.
2. "There was a problem with this one time link. Please try again.'" Try what again? And what difference could it make?

hunmonk’s picture

FileSize
7.62 KB

per Dries' request, i have reworked the user messages in this patch.

hunmonk’s picture

Status: Needs work » Needs review
drumm’s picture

Status: Needs review » Needs work

Is there any particular need to change the password reset url? I think it is fine at the old place until a separate patch decided it should happen.

"If it is not used, your e-mail address change will not be processed." might want to say what will happen instead of what won't happen- "If not used, your email address at [site] will not change."

Many sites send a non-essential email to the old email address as a notification of the change and what to do if the change wasn't intended. Should we have that?

AjK’s picture

Is there any particular need to change the password reset url? I think it is fine at the old place until a separate patch decided it should happen.

see #15

Many sites send a non-essential email to the old email address as a notification of the change and what to do if the change wasn't intended. Should we have that?

Good point, I would say so (if we're copying the functionality often found on other sites, might as well make it a deep copy ;)

hunmonk’s picture

Status: Needs work » Needs review
FileSize
8.04 KB

s there any particular need to change the password reset url? I think it is fine at the old place until a separate patch decided it should happen.

per dries' comment in #15 i'm leaving this change in.

If not used, your email address at [site] will not change.

fixed.

Many sites send a non-essential email to the old email address as a notification of the change and what to do if the change wasn't intended. Should we have that?

yeah, seems like a good idea. added.

moshe weitzman’s picture

i question the need for this. it is trivially easy to forge the from address of any email, not just ones sent from drupal. if proper authentication is our goal, i suggest we add a header like X-Drupal-UID and send the uid there. Or send the $user->init email address. Those are both authoritative/verified.

I don't really object to this going in, but it doesn't buy a whole lot and the critical status is shaky at best.

moshe weitzman’s picture

actually, contact.module already states the UID of the sender so we are covered there. thanks for illustrating that, boggieman.

moshe weitzman’s picture

Priority: Critical » Normal
hunmonk’s picture

FileSize
8.04 KB

patch updated. functionality is the same.

AjK’s picture

Version: x.y.z » 5.x-dev
drumm’s picture

Version: 5.x-dev » 6.x-dev
Zen’s picture

FileSize
7.83 KB

Patch no longer applies; re-rolled with updates to hook_menu; untested.

-K

hunmonk’s picture

Assigned: hunmonk » Unassigned

no time to put into this anymore. any takers?

hickory’s picture

Priority: Normal » Critical

I'd say this is critical. We need to be sure that a user owns the email address registered for their account, otherwise what's the point confirming it in the first place?

hickory’s picture

Also, without this confirmation, a user could change their registered email address to someone else's, causing all kinds of problems (content associated with a person, unable to create a new account, etc).

igorzh’s picture

Version: 6.x-dev » 5.x-dev

Hello everyone, has this problem been solved in drupal 5.x versions?

hickory’s picture

Version: 5.x-dev » 6.x-dev

Changing back to 6.x-dev. I doubt this will be changed for 5.x.

chx’s picture

Version: 6.x-dev » 7.x-dev
Category: bug » feature
Priority: Critical » Normal

This can happily live in a contrib. Viva la form_alter!

birdmanx35’s picture

Status: Needs review » Needs work

No surprise, but this fails against HEAD:

$ patch -p0 < user-mail-change.patch
(Stripping trailing CRs from patch.)
patching file modules/user/user.module
Hunk #1 FAILED at 801.
Hunk #2 FAILED at 1256.
Hunk #3 succeeded at 1381 (offset 106 lines).
Hunk #4 succeeded at 1536 with fuzz 1 (offset -14 lines).
Hunk #5 FAILED at 1763.
3 out of 5 hunks FAILED -- saving rejects to file modules/user/user.module.rej

sun’s picture

Re: #40: A user is not able to change her email address to one that is already used by another user.

jaydub’s picture

As chx said: "This can happily live in a contrib" and here is the proof:

http://drupal.org/project/email_confirm

feedback is welcome

moshe weitzman’s picture

Priority: Normal » Critical

I withdraw my objection earlier. This reallty does make perfect sense. Considering where we are in the release cycle, critical is warranted here.

moshe weitzman’s picture

I should elaborate on my use case. My client is is borrowing an idea from Facebook about authorization. Specifically, if you have a verified @harvard.edu email address then you belong to harvard group and are allowed to see certain premium nodes and so on. Naturally, we need to verify email address changes so that people don't just switch into harvard group.

So, I hope the need has been proven and someone can move on to polishing and committing the patch.

Anonymous’s picture

subscribing. Currently I'm drawn toward leaving this feature as a module add-on. Workflow is different for different sites and what one might do another will do something else. If the feature is added to core it needs to be an option that the admin can use or not use. So, this might as well as just be a contrib package.

mikeryan’s picture

Should this patch get re-rolled for D7, please have it call hook_user('update') - for Moshe's use case, other modules may need to know when the email address has been changed and take action based on the new address.

Taxoman’s picture

FYI - Decscription / Feature request for the email_confirm module mentioned in #46:

"Account hijack prevention management (prerequisites)"
http://drupal.org/node/475264

robby.smith’s picture

+1 subscribing

jrabeemer’s picture

This seems like a good core feature to have for D7. I hope it doesn't die here.

+1 subscribe

AaronBauman’s picture

Priority: Critical » Normal

"feature request" and "critical" are mutually exclusive.
maybe priority-major is warranted.

Damien Tournoud’s picture

Version: 7.x-dev » 8.x-dev

7.x is feature frozen anyway.

marcingy’s picture

Priority: Normal » Major

Changing to major as per tag.

MustangGB’s picture

Tag update

amc’s picture

subscribing

geerlingguy’s picture

Subscribing. Some of this functionality should, IMHO, live in core. I've form altered the registration and account forms too many times to get this/similar functionality :-/

webchick’s picture

Status: Needs work » Fixed

Unless I'm totally mistaken, this feature is already in D7, so this is fixed.

Current password field on user profile

geerlingguy’s picture

Wait... I thought the issue was about actually confirming the email address itself, not the user's *intention* of changing the address. E.g. If a user changes his email, then the user would get a confirmation email sent to the new email address, and the new email address wouldn't be used for $user->mail until its confirmed.

Right? That's kind of what I read in most of the posts here... I think.

webchick’s picture

Status: Fixed » Needs work

Oops! You are right, sorry. :)

shadysamir’s picture

Totally the wrong place to post this. That's what you get when you post while asleep. Sorry folks

I use LT along with http://drupal.org/project/email_confirm which deals with this issue.

My problem is when a user changes their email address BEFORE verifying the old one. This function is needed as a second chance for users to use a different email address and activate their account. But even though email_confirm is one form of email confirmation, LT is nor aware of it or that it already verified the address. I think I will write something for email_confirm to integrate with LT.

How do force account verification from PHP?

ralf.strobel’s picture

I support this request, as not having a valid user email address at all times is also a potential legal issue.
Users could post illegal content and there would be no ways of persecuting them.

It's not hugely problematic, as Drupal retains the original, validated address in the "init" field. But that address might be outdated and no longer functional.

andypost’s picture

Status: Needs work » Postponed

This functionality should stay in contrib, email could be changed by custom auth provider and it's not really useful for now. Let's close this issue

Taxoman’s picture

Status: Postponed » Needs work

IMO, this is important to have in core. Absolutely needed.

amc’s picture

Agreed. Some even thought to make it critical -- while that's debatable, it's definitely a hole that needs to be fixed and should be part of core's default system.

rogical’s picture

There's a potential security issue here, user password may somehow happened exposed -- simple password, same password with other low secure sites etc.

Then people get the password can have full control of the account -- change password, email etc. And you'll never can get back your account!

So, we should at least let the hackers not able to change email easily. User needs to have email change confirm in the old email address and this features is very common in websites.

This feature should be able to port to D7, as it's a potential security issue!

arnoldbird’s picture

Is there a contrib module that provides this functionality for Drupal 7? Thanks

jaydub’s picture

http://drupal.org/project/email_confirm - have a beta release for testing on d7.

greggles’s picture

Issue tags: +Security improvements

@rogical - since Drupal doesn't store the password in plaintext I don't see how a password change could expose the user's current password. Further, I don't see how someone can be tricked into setting their email to that of an attacker.

Tagging to increase awareness.

ralf.strobel’s picture

I rather see a legal problem here and not a security problem. As a web site owner, you usually wish to maintain a valid email address for each of your users, in case they do anything illegal. So users shouldn't be allowed to change their address to an unconfirmed one.

You could also defend it from a usability point, since the verification protects the users from misspelled addresses which would prevent them from receiving updates.

jibran’s picture

Status: Needs work » Needs review
FileSize
14.27 KB

This is the re-roll against 8.x from #37 and a working patch.

Status: Needs review » Needs work

The last submitted patch, 85494-73.patch, failed testing.

Homotechsual’s picture

This bug has been in existence since D5.x (2006) it's managed to exist before, during and after code freeze on D6, D7 and D8. Whilst the need for debate is understood this is a pretty basic and low level feature. The use case for it has been iterated and re-iterated many times on this issue queue and whilst a contrib module does exist the case is STILL there for this to appear in core.

There are, from what I can see, no blockers to this becoming a core feature so the time has come to ask why, after 7 years, this is not already a feature?

Is this ever likely to appear in core?

kifuzzy’s picture

#69 Posted by arnoldbird on February 23, 2012 at 6:55pm

Is there a contrib module that provides this functionality for Drupal 7? Thanks

Hi, may out of topic but may more around questioned by you one of my bookmarks

knowledge-base/contributed-modules-securing-your-drupal-site
(Submitted by Ben Jeavons on Thu, 09/08/2011 - 15:53)
[Among the thousands of modules on drupal.org there are over 100 in the security category. Unfortunately some of those are abandoned or inaccurately tagged. We've looked at every module and compiled this resource to help you understand the security-related community modules available. Not all modules provide security exactly, some are about hardening your site against weaknesses and others are about monitoring and reporting abuses (....)]

.. or link drupal-7-user-login-and-registration

Homotechsual’s picture

There is, at least in part. The module is: https://drupal.org/project/email_confirm

dawehner’s picture

Issue summary: View changes

Related to #2014185: Check usernames that are email addresses more rigidly, only allow if matches email

This sounds like a feature request contrib should provide and probably can.

joachim’s picture

I think this is an omission in core, for the reasons outlined in the issue summary. I don't think it should be left to contrib.

kaare’s picture

Category: Feature request » Bug report

Don't see why this is a feature request. Smells like a bug to me. Today, at least. Maybe not 9 years ago when Drupal was a hobbyist project.

In addition to be a slow-spam method, it has UX-implications and/or potential security risk: If you by mistake enter a non-functional email, you won't receive reset password info if you forget it. Your account is effectively lost and you need to contact support. And who is support to verify that whoever calls is the rightful owner? It's just a random email nobody owns.

andypost’s picture

kaare’s picture

The email address is more and more used as the primary ID of user accounts. The concept of user name is a fading thing. That Drupal 8 will come out with a ID-mechanism this flawed is, uhm, not good. Yes, contrib can do this, but having this functionality in core will assert its quality and assure that the user is who it claims to be.

Anonymous’s picture

Assigned: Unassigned »
FileSize
6.25 KB

I'm working on this. I wasn't able to rebase the patch in #73, so I started rewriting it. I finished the first part for now. This patch contains the WIP.

Anonymous’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll +Needs tests
FileSize
11.49 KB

I rewrote the rest of the patch, but didn't test this manually yet. I'm just curious if we have failing tests now, so triggering testbot.

Also this will need tests, so tagging accordingly.

Status: Needs review » Needs work

The last submitted patch, 84: verify_changing_user-85494-84.patch, failed testing.

Anonymous’s picture

Anonymous’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 86: verify_changing_user-85494-86.patch, failed testing.

Anonymous’s picture

Status: Needs work » Needs review
FileSize
3.18 KB
13.47 KB
Anonymous’s picture

Added some minor changes, and added some coverage.

Some remarks/questions:
- I think we might need a follow up for migration from D6/D7.
- The current permissions don't specify changing an email address, only 'cancel own user account', 'change own username' and 'view user information'. Should we add one specifically for this use case, e.g. 'change own email address'?
- I can't find any unit test coverage for UserController. Shouldn't we do that here? Specifically for UserController::changeEmail().

donutdan4114’s picture

+1 this should be fixed. It is clearly a security concern, especially for sites that integrate with 3rd party services that rely on a verified email address.
No idea why Drupal would verify a new user email address, but not reverify when they change it.

deanflory’s picture

Just a reminder to other users that the patch in #90 is for D8 and not D7.

ptmkenny’s picture

@pjonckiere: Regarding the need for a follow-up, it would be great to get this fixed in D7/D6 as well. I have actually had a couple users change their emails to harass people on my site, so it would be awesome to fix this.

Anonymous’s picture

Assigned: » Unassigned
Issue tags: +Needs backport to D7

Re #92: Indeed. The policy is to first fix this in D8 and then backport it to previous versions.

Re #93: With a follow-up I meant a new issue to tackle migration issue that might occur due to this fix.

Anybody’s picture

Patch works great for me.
+1 and absolutely important to also fix in D7 :)

legolasbo’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/user/config/install/user.mail.yml
    @@ -4,6 +4,12 @@ cancel_confirm:
    +  body: "[user: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."
    ...
    +  body: "[user:name],\n\nA request to change your e-mail address has been made at [site:name]. You need to verif 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."
    

    shouldn't "one day" be "24 hours" to be more precise?

    Also shouldn't the mail_change_notification contain the new e-mail address? If it was a legitimate change, the user already knows their own e-mail address. Else they have some clue as to who tried to change their email address.

  2. +++ b/core/modules/user/config/schema/user.schema.yml
    @@ -20,6 +20,12 @@ user.settings:
    +          label: 'Verify user of an email address change'
    

    How about: Require email verification when a user changes their email address

  3. +++ b/core/modules/user/src/AccountForm.php
    @@ -388,13 +388,31 @@ public function validate(array $form, FormStateInterface $form_state) {
    +    $user = \Drupal::currentUser();
    ...
    +    if(!$account->isNew() && $account->getEmail() !== $new_email && !$user->hasPermission('administer users')) {
    

    Let's just do \Drupal::currentUser()->hasPermission();

  4. +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -312,6 +312,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#description' => t('Edit the e-mail messages sent to users old mail address who change mail address.') . ' ' . $email_token_help,
    

    Edit the e-mail message sent to a user's old e-mail address when the e-mail address is changed.

  5. +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -312,6 +312,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#description' => t('Edit the e-mail messages sent to users new mail address who change mail address.') . ' ' . $email_token_help,
    

    Edit the e-mail message sent to a user's new e-mail address when the e-mail address is changed.

  6. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -138,6 +138,55 @@ public function resetPass($uid, $timestamp, $hash) {
    +   * Returens the user change email page.
    

    Returns

  7. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -138,6 +138,55 @@ public function resetPass($uid, $timestamp, $hash) {
    +        drupal_set_message(t('You have tried to use a one-time e-mail change link for %account that has expired--your change of e-mail request was not completed. Please visit your account edit page if you wish to attempt the change again.', array('%account' => $account->name)), 'error');
    

    expired -- your

  8. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -138,6 +138,55 @@ public function resetPass($uid, $timestamp, $hash) {
    +        drupal_set_message(t('There was a problem verifying your change of e-mail request. Please visit your account edit page and attempt the change again'), 'error');
    

    Let's inform the user a little more about the actual problem.

  9. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -138,6 +138,55 @@ public function resetPass($uid, $timestamp, $hash) {
    +        drupal_set_message(t('Your e-mail address is now %mail.', array('%mail' => $new_email)));
    

    Your e-mail address has been changed to %email.

  10. +++ b/core/modules/user/user.module
    @@ -931,12 +957,14 @@ function user_mail($key, &$message, $params) {
    + *   - email: The email address of the user.
    

    The user's email address.

StryKaizer’s picture

Updated patch from #90 to current head.

Remarks from #96:
1. To stay consistent with the other user emails, (being cancel_confirm and password_reset), I thinks its better to keep “one day” instead of 24 hours.
2. Ok
3. Ok
4. Changed it to "Edit the email messages sent to users' old email address when the email address is changed.” to stay consistent with the other descriptions.
5. Changed to "Edit the email messages sent to users' new email address when the email address is changed.” for consistency.
6. Ok
7. Ok
8. Ok
9. Ok
10. Ok

I added an extra warning message when an email is changed with validation, to ensure the user knows an action is required.

Bugs which still needs to be addressed:
- Provide a token to show the new email address in the mail_change_notification email. Since we are not storing the old emailadres at this moment, there's no quick fix
- When a user is not logged in while clicking the email validation change link, an error occurs.

StryKaizer’s picture

Correct patch now, remarks see #97

borisson_’s picture

Status: Needs work » Needs review

Setting to needs review so the testbot can have a look at #98.

The last submitted patch, 97: verify_changing_user-85494-97.patch, failed testing.

StryKaizer’s picture

Status: Needs review » Needs work

Bugs which still needs to be addressed:
- Provide a token to show the new email address in the mail_change_notification email. Since we are not storing the old emailadres at this moment, there's no quick fix
- When a user is not logged in while clicking the email validation change link, an error occurs.

David_Rothstein’s picture

One thing to be careful of here is that if Drupal actually verified the email addresses of all accounts, the privacy problems discussed in the issues I'm linking to would become significantly more severe.

LOBsTerr’s picture

I rerolled the patch. It is my first rerroll. So, if I did something wrong, please let me know. I will try to fix asap. Thank you!

LOBsTerr’s picture

Status: Needs work » Needs review
cilefen’s picture

Status: Needs review » Needs work

@LOBsTerr Well done! Achievement unlocked! I noticed only one thing, looking at this side-by-side with the prior patch:

+++ b/core/modules/user/config/install/user.mail.yml
@@ -2,8 +2,14 @@ cancel_confirm:
-  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]'
+  body: "[user: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 to 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:name] at [site:name]'

This should not be in the patch (should have taken HEAD's version). Generally, when resolving conflicts, HEAD is "correct" and you have to carefully consider what the prior patch was doing in that place. Dreditor helps. You can easily sort out the thing above with a commit on your reroll branch then submit a new patch.

LOBsTerr’s picture

@cilefen - Thank you for information. I will fix it.

LOBsTerr’s picture

@cilefen - I have created an interdiff. I hope now it will be ok! I'm sorry the next time I will be more careful!

LOBsTerr’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 107: interdiff-85494-103-107.patch, failed testing.

LOBsTerr’s picture

Renamed interdiff to be ignored by the Testbot

LOBsTerr’s picture

LOBsTerr’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 110: interdiff-85494-103-110.diff, failed testing.

LOBsTerr’s picture

Rename interdiff one more. I missed "do-not-test" part :(

LOBsTerr’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 110: interdiff-85494-103-110.diff, failed testing.

LOBsTerr’s picture

LOBsTerr’s picture

FileSize
4.89 KB
LOBsTerr’s picture

Status: Needs work » Needs review

The last submitted patch, 110: interdiff-85494-103-110.diff, failed testing.

valthebald’s picture

Status: Needs review » Reviewed & tested by the community

I dare mark it RTBC

The last submitted patch, 110: interdiff-85494-103-110.diff, failed testing.

cilefen’s picture

+++ b/core/modules/user/config/install/user.mail.yml
@@ -2,8 +2,15 @@ cancel_confirm:
 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"

There is a random, unrelated newline here.

LOBsTerr’s picture

Status: Reviewed & tested by the community » Needs work
FileSize
18.31 KB

@cilefen - You are right. Nice catch. Missed. Fixed it in a new patch

LOBsTerr’s picture

Status: Needs work » Needs review

The last submitted patch, 110: interdiff-85494-103-110.diff, failed testing.

cilefen’s picture

Status: Needs review » Needs work
+++ b/core/modules/user/config/install/user.mail.yml
@@ -4,6 +4,12 @@ cancel_confirm:
+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]'
+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]'

A hook_update_N() is needed to set the body and subject for these message for existing sites.

+++ b/core/modules/user/config/schema/user.schema.yml
@@ -20,6 +20,12 @@ user.settings:
+        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'

There does not seem to be a form element for a site admin to set these booleans, as there is with the "Account blocked" notification, for example.

k4v’s picture

Assigned: Unassigned » k4v

Working on this @Drupalcamp Vienna

k4v’s picture

Fixed a few minor things, works for me now. I also added the update hook.

k4v’s picture

Assigned: k4v » Unassigned
Status: Needs work » Needs review

The last submitted patch, 110: interdiff-85494-103-110.diff, failed testing.

LOBsTerr’s picture

Status: Needs review » Reviewed & tested by the community

For me everything looks good: Updated logic to get user, to set an e-mail and update it only when it is updated, return result of redirect function.
Checked the update hook everything was set properly.

The last submitted patch, 110: interdiff-85494-103-110.diff, failed testing.

catch’s picture

Version: 8.0.x-dev » 8.1.x-dev

Since this contains form and string changes, moving to 8.1.x

I didn't do a full review, so leaving at RTBC isn't an indication either way of whether I think it's actually RTBC.

claudiu.cristea’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Security improvements, -Needs beta evaluation
  1. +++ b/core/modules/user/config/install/user.mail.yml
    @@ -4,6 +4,12 @@ cancel_confirm:
    +  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."
    ...
    +  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."
    
    +++ b/core/modules/user/config/schema/user.schema.yml
    @@ -20,6 +20,12 @@ user.settings:
    +          label: 'Notify user when email changes'
    ...
    +          label: 'Require email verification when a user changes their email address'
    
    @@ -61,6 +67,12 @@ user.mail:
    +    label: 'Change mail notification'
    ...
    +    label: 'Change mail verification'
    
    +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +      drupal_set_message($this->t('Your email address needs to be validated. Further instructions have been sent to your new email address.'), 'warning');
    
    +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -303,6 +303,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#title' => t('Email change notification'),
    ...
    +      '#description' => t("Edit the email messages sent to users' old email address when the email address is changed.") . ' ' . $email_token_help,
    ...
    +      '#title' => t('Email change verification'),
    ...
    +      '#description' => t("Edit the email messages sent to users' new email address when the email address is changed.") . ' ' . $email_token_help,
    
    +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +        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');
    ...
    +        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');
    ...
    +        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');
    ...
    +        drupal_set_message(t('Your email address has been changed to %mail.', array('%mail' => $new_email)));
    
    +++ b/core/modules/user/src/Tests/UserEditedOwnAccountTest.php
    @@ -43,5 +47,20 @@ function testUserEditedOwnAccount() {
    +    // Set a new email address for the user account.
    ...
    +    $this->assertTrue(count($user_mail), 'A verification email was sent to the user.');
    
    +++ b/core/modules/user/user.module
    @@ -593,6 +593,32 @@ function user_pass_reset_url($account, $options = array()) {
    + * Generates a unique URL for a one time email change confirmation.
    ...
    + *   A unique URL that provides a one-time email change confirmation for the
    
    @@ -940,6 +966,7 @@ function user_mail($key, &$message, $params) {
    + *   - email: The user's email address.
    
    @@ -1176,6 +1204,8 @@ function user_role_revoke_permissions($rid, array $permissions = array()) {
    + *   - 'mail_change_notification': Email change notification.
    + *   - 'mail_change_verification': Email change verification.
    
    +++ b/core/modules/user/user.routing.yml
    @@ -150,3 +150,14 @@ user.reset:
    +    _title: 'Change email address'
    

    We currently use "E-mail", not "e-mail", "email", or "mail", when referring the E-mail in human-readable strings.

  2. +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +    if(!$account->isNew() && $account->getEmail() !== $new_email && !\Drupal::currentUser()->hasPermission('administer users')) {
    +      $old_email = $account->getEmail();
    

    Move $old_email before if() and use it in if() condition. Also consider to name it $old_mail, $new_mail (we typically use "mail" as machine-readable name).

    Also you can replace \Drupal::currentUser() with $this->currentUser()

  3. +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -303,6 +303,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#title' => t('Subject'),
    ...
    +      '#title' => t('Body'),
    ...
    +      '#title' => t('Subject'),
    ...
    +      '#title' => t('Body'),
    

    For all t(...) inside this function consider replacing them with $this->t(...).

  4. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +   * Returns the user change email page.
    

    Maybe "Provides a callback for user E-mail change page"?

  5. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +   * @param string new_email
    

    Missed the '$' prefix.

  6. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +  public function changeEmail($uid, $timestamp, $new_email, $hash) {
    

    Use $this->t(...) instead of t() for all translated strings inside this function.

  7. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +    $user = \Drupal::currentUser();
    ...
    +      else if ($user->id() && $user->id() != $account->id()) {
    ...
    +      else if ($hash != user_pass_rehash($account, $timestamp)) {
    

    You don't need $user variable. Just use $this->currentUser or, better, $this->currentUser().

  8. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +    // 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.
    

    Lines should wrap at 80 characters.

  9. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +    $current = $_SERVER['REQUEST_TIME'];
    

    We are not using directly this variable. Use the REQUEST_TIME constant instead.

  10. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +      if($current - $timestamp > $timeout) {
    

    Space after "if".

  11. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,58 @@ public function resetPass($uid, $timestamp, $hash) {
    +      else if ($hash != user_pass_rehash($account, $timestamp)) {
    

    We are using the compact form "elseif" w/o space between.

  12. +++ b/core/modules/user/user.install
    @@ -85,3 +85,16 @@ function user_install() {
    + * Update config for change mail notifications.
    

    "Updates", maybe?

  13. +++ b/core/modules/user/user.install
    @@ -85,3 +85,16 @@ function user_install() {
    +  $mail_config->set('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.");
    +  $mail_config->set('mail_change_notification.subject','E-mail change information for [user:display-name] at [site:name]');
    +  $mail_config->set('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.");
    +  $mail_config->set('mail_change_verification.subject','E-mail change information for [user:display-name] at [site:name]');
    

    This I don't like. Is it possible to read to parse directly the YAML file and get the values instead hardcoding them?

  14. +++ b/core/modules/user/user.module
    @@ -593,6 +593,32 @@ function user_pass_reset_url($account, $options = array()) {
    +function user_change_mail_url($account, $options = array()) {
    

    Use modern [] instead of array().

    I wonder if we cannot place this as static method in an external class. It's used rarely, why loading user.module?

LOBsTerr’s picture

Assigned: Unassigned » LOBsTerr
David_Rothstein’s picture

We currently use "E-mail", not "e-mail", "email", or "mail", when referring the E-mail in human-readable strings.

Nope, "email" is correct - see https://www.drupal.org/drupalorg/style-guide/content#relatedwords

claudiu.cristea’s picture

Ouch, sorry. I missed that.

k4v’s picture

The test could actually call the URL from the notification mail and make shure the email address is the new one afterwards.

LOBsTerr’s picture

Assigned: LOBsTerr » Unassigned
Status: Needs work » Needs review
FileSize
19.83 KB
10.58 KB
claudiu.cristea’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/user/config/install/user.mail.yml
    @@ -4,6 +4,12 @@ cancel_confirm:
    +  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 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]'
    

    Per #137, let's revert to "email", "Email" form. Sorry again for confusing.

  2. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,57 @@ public function resetPass($uid, $timestamp, $hash) {
    +   * Returns the user change email page.
    

    Maybe swap "change" with "email"? For me "Returns the user email change page" sounds better, but I'm not a native English speaker.

  3. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,57 @@ public function resetPass($uid, $timestamp, $hash) {
    +    $current = REQUEST_TIME;
    ...
    +    if ($timestamp < $current) {
    +      if ($current - $timestamp > $timeout) {
    ...
    +      elseif ($timestamp > $account->getLastLoginTime() && $timestamp < $current) {
    

    Now, let's drop $current and use directly the constant.

  4. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -139,6 +139,57 @@ public function resetPass($uid, $timestamp, $hash) {
    +        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');
    ...
    +        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');
    ...
    +        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');
    ...
    +        drupal_set_message(t('Your email address has been changed to %mail.', array('%mail' => $new_mail)));
    

    s/t()/$this->t().

    Also, use [] instead of array().

  5. +++ b/core/modules/user/user.module
    @@ -593,6 +593,32 @@ function user_pass_reset_url($account, $options = array()) {
    +  $timestamp = $_SERVER['REQUEST_TIME'];
    

    Drop $timestamp, use REQUEST_TIME constant.

LOBsTerr’s picture

Assigned: Unassigned » LOBsTerr
claudiu.cristea’s picture

+++ b/core/modules/user/src/Controller/UserController.php
@@ -139,6 +139,57 @@ public function resetPass($uid, $timestamp, $hash) {
+  public function changeEmail($uid, $timestamp, $new_mail, $hash) {
...
+  }

+++ b/core/modules/user/user.module
@@ -593,6 +593,32 @@ function user_pass_reset_url($account, $options = array()) {
+function user_change_mail_url($account, $options = []) {
...
+}

@@ -947,6 +974,7 @@ function user_mail($key, &$message, $params) {
+    $replacements['[user:mail-change-login-url]'] = user_change_mail_url($data['user'], $options);

+++ b/core/modules/user/user.routing.yml
@@ -150,3 +150,14 @@ user.reset:
+user.change_email:
+  path: 'user/change-mail/{uid}/{timestamp}/{new_email}/{hash}'
+  defaults:
+    _controller: '\Drupal\user\Controller\UserController::changeEmail'

I wonder if we cannot create a new Drupal\user\Controller\ChangeEmailController. Then we put there the changeEmail() (maybe as changeEmailPage()?) page and also we move there the user_change_mail_url() as a static method called changeEmailUrl(). I don't like adding new procedural functions to *.module files that are parsed on every request.

EDIT: Also we can open a follow-up to do the same with reset password, in the future.

LOBsTerr’s picture

Assigned: LOBsTerr » Unassigned
Status: Needs work » Needs review
FileSize
23.21 KB
17.16 KB

Status: Needs review » Needs work

The last submitted patch, 144: verify_changing_user-85494-144.patch, failed testing.

LOBsTerr’s picture

Status: Needs work » Needs review
FileSize
23.21 KB
17.15 KB

Status: Needs review » Needs work

The last submitted patch, 146: verify_changing_user-85494-145.patch, failed testing.

LOBsTerr’s picture

Status: Needs work » Needs review
FileSize
17.17 KB
23.22 KB
claudiu.cristea’s picture

Because the interdiff is against #140, I cannot see the cause of test failures in #146, #148. And I'm so curious :)

LOBsTerr’s picture

@claudiu.cristea.

1) in comment 144 - As, you asked I change the new_email to new_mail

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_email' => $account->getEmail(), // THIS LINE!!!
      'hash' => user_pass_rehash($account, REQUEST_TIME),
    ], $url_options);
  }

2) in comment 146. I used the wrong namespace

use use Drupal\user\ChangeEmailController;

instead of

use Drupal\user\Controller\ChangeEmailController;

If you need I can create interdiffs. Was a little bit in hurry didn't check it properly :(

claudiu.cristea’s picture

Ah, OK. Thank you for explanations. Minor changes. However, It's a recommended practice to create interdiffs on each patch agains the last patch, so that the reviewer understands exactly what was changed since the last patch.

Some more reviews:

  1. +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -303,6 +303,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#description' => $this->t("Edit the email messages sent to users' old email address when the email address is changed.") . ' ' . $email_token_help,
    

    s/users'/user's

  2. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    + * Contains \Drupal\user\Controller\UserController.
    

    ChangeEmailController?

  3. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +use Drupal\Core\Controller\ControllerBase;
    

    Missed:

    use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
    
  4. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +    $account = \Drupal::entityManager()->getStorage('user')->load($uid);
    

    Entity manager is deprecated but the parent ControllerBase already provides $this->entityTypeManager(). Let's use that. Also consider to a add a type hint comment above, telling to IDE that $account is UserInterface.

    /** @var \Drupal\user\UserInterface $account */
    
  5. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +    // 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.
    

    The wrapping is still incorrect. "which" can go up 1 line. Also words from the 3rd line can go up.

  6. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +        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');
    ...
    +        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');
    ...
    +        drupal_set_message($this->t('Your email address has been changed to %mail.', array('%mail' => $new_mail)));
    

    Forgot to convert array() to [] :)

  7. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +      // Deny access if the timestamp passed is in the future.
    +      throw new AccessDeniedHttpException();
    

    We need to test this.

  8. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +   * @param object $account
    

    This is wrong. It should be type-hinted to \Drupal\user\UserInterface, not object.

  9. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +   *    URLs. If langcode is NULL the users preferred language is used.
    

    This line indent is wrong "URLs" should left-align with "langcode".

  10. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +   * @return
    

    Missed the type-hint.

  11. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +  public static function changeEmailUrl($account, $options = []) {
    

    $account & $options must be type-hinted.

  12. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +    $url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode));
    

    Let's use the modern [] instead of array(). Also we have the service already in, let's use it: replace \Drupal::languageManager() with $this->languageManager().

  13. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +    return \Drupal::url('user.change_email', [
    

    \Drupal::url() is deprecated. Use Url::fromRoute() instead.

  14. +++ b/core/modules/user/src/Controller/UserController.php
    @@ -71,7 +71,7 @@ public static function create(ContainerInterface $container) {
    -   * Returns the user password reset page.
    +   * Provides a callback for user email change page.
    
    @@ -112,7 +112,6 @@ public function resetPass($uid, $timestamp, $hash) {
    -    $current = REQUEST_TIME;
    
    @@ -120,11 +119,11 @@ public function resetPass($uid, $timestamp, $hash) {
    -      if ($user->getLastLoginTime() && $current - $timestamp > $timeout) {
    +      if ($user->getLastLoginTime() && REQUEST_TIME - $timestamp > $timeout) {
    ...
    -      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))) {
    
    @@ -210,7 +209,7 @@ public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass
    -        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');
    

    All changes to UserController are unrelated. Remove them from this patch.

claudiu.cristea’s picture

Status: Needs review » Needs work
LOBsTerr’s picture

Assigned: Unassigned » LOBsTerr

I'm sorry, I didn't about the patches. I thought, the last successful patch must have been used.

claudiu.cristea’s picture

No problem :)

And more...

  1. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +        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');
    ...
    +        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');
    

    There's no $account->name but only $account->getAccountName(). Also, there's no $user object.

  2. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +        drupal_set_message($this->t('Your email address has been changed to %mail.', array('%mail' => $new_mail)));
    

    array() -> [].

  3. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,92 @@
    +      if (REQUEST_TIME - $timestamp > $timeout) {
    ...
    +      elseif ($this->currentUser()->id() && $this->currentUser()->id() != $account->id()) {
    ...
    +      elseif ($hash != user_pass_rehash($account, $timestamp)) {
    ...
    +      elseif ($timestamp > $account->getLastLoginTime() && $timestamp < REQUEST_TIME) {
    

    We need to test each of these cases.

LOBsTerr’s picture

@claudiu.cristea

ChangeEmailController? - what should be done here ?

claudiu.cristea’s picture

That is about the line before

/**
 * @file
 * Contains \Drupal\user\Controller\UserController.
 */

You forgot the class name when copied the file :)

LOBsTerr’s picture

@claudiu.cristea Thanks for review, back to work :)

LOBsTerr’s picture

Assigned: LOBsTerr » Unassigned
Status: Needs work » Needs review
FileSize
25.77 KB
19.23 KB

I have added simple tests to test the change email functionality.

claudiu.cristea’s picture

Status: Needs review » Needs work
Issue tags: +Needs upgrade path tests

Thank you for the great work.

  1. +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +    $account = $this->getEntity($form_state);
    

    This method takes no argument. Just remove $form_state. Also it would be nice to type-hint the $account var with /** @var ... */ above, like you did in other place.

  2. +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +    if (!$account->isNew() && $old_mail !== $new_mail && !$this->currentUser()->hasPermission('administer users')) {
    

    I would wrap $old_mail !== $new_mail in parenthesis.

  3. +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -303,6 +303,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#description' => $this->t("Edit the email message sent to the user old email address when the email address is changed.") . ' ' . $email_token_help,
    

    Isn't it "...sent to user's old email..."? I'm not a native English speaker, I might be wrong.

  4. +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -303,6 +303,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#description' => $this->t("Edit the email messages sent to users' new email address when the email address is changed.") . ' ' . $email_token_help,
    

    s/users'/user's

  5. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,95 @@
    +   *   UID of user requesting reset.
    

    Let's change it to "The ID of the user requesting reset."

  6. +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +      $account->setEmail($new_mail);
    ...
    +        // Send notification email to the old email address.
    +        $account->setEmail($old_mail);
    +        _user_mail_notify('mail_change_notification', $account);
    +      }
    

    I don't like that we are playing here with the $account object by changing its email 2 times. Also if the 1st notification fail, the $account will have the $new_mail as email and that's a lie. I know that this seems to not affect the current flow, but we cannot predict how contrib or custom modules will add their submit callbacks and they will not find there the correct email. I would prefer to use a fake cloned account just to send that email.

    $cloned_account = clone $account;
    $cloned_account->setEmail($new_mail);
    if (_user_mail_notify('mail_change_verification', $cloned_account)) {
      // Send notification email to the old email address.
      _user_mail_notify('mail_change_notification', $account);
    }
    

    Maybe $cloned_account is not a very beautiful name?

  7. +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +      drupal_set_message($this->t('Your email address needs to be validated. Further instructions have been sent to your new email address.'), 'warning');
    

    Maybe "Your new email address..."?

  8. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,95 @@
    +      elseif ($this->currentUser()->id() && $this->currentUser()->id() != $account->id()) {
    

    Let's use !$this->currentUser()->isAnonymous() instead of the 1st condition. Also let's wrap the 2nd condition in parenthesis.

  9. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,95 @@
    +      elseif ($hash != user_pass_rehash($account, $timestamp)) {
    

    Well, this is very problematic. I don't think we can rely on user_pass_rehash() to check the integrity of the $hash. I see that hash generated by that function vary on: $timestamp, $account->getLastLoginTime(), $account->id(), $account->getEmail() and $account->getPassword(). Let's take this scenario:

    1. The user changes its email
    2. A link with the hash is sent to the new email. Remember, the hash is dependent on the last login time
    3. The user ignores the mail.
    4. The user logs in again.
    5. After that, the user remembers that he needs to confirm the new email. He clicks on the link from notification.
    6. When he arrives here, Buuum! The user_pass_rehash() will generate a different hash because in the meantime the last login time has changed.

    I see this solved in other way. Let's add a new public method in this controller called changeEmailAccess. Also add the access check to the route. Add a new protected static method in the controller called getHash(). We will use that method to generate the hash in both places —­ url and validation. gatHash() might be similar with user_pass_rehash() but depend only on non-alterable variables: $timestamp, $account->id(), $account->getEmail(), $account->getCreatedTime(), $account->getInitialEmail(). In this way we can remove this check because the URL will not be accessible for wrong hashes.

  10. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,95 @@
    +      elseif ($timestamp > $account->getLastLoginTime() && $timestamp < REQUEST_TIME) {
    

    The 2nd condition is redundant, we tested that in the wrapping if (). I don't see the reason of the first condition. I think we can replace the elseif with else.

  11. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,95 @@
    +      // Deny access if the timestamp passed is in the future.
    

    Well, you added the ->isActive() condition. I think is correct, but we should reflect this also in this comment.

  12. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,95 @@
    +  public static function changeEmailUrl(\Drupal\user\Entity\User $account, array $options = []) {
    

    Move the namespaced name to user Drupal\user\UserInterface;. use UserInterface instead of User for type-hinting.

  13. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +  /**
    +   * The profile to install as a basis for testing.
    +   *
    +   * @var string
    +   */
    +  protected $profile = 'standard';
    

    Why we need standard? If 'user' module is not installed just add it to $modules. But I think is installed by default.

  14. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +  public static $modules = [];
    

    Remove it.

  15. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    $token_service = \Drupal::token();
    

    In tests, the container is available $this->container->get('token'); You can also add a type-hint declaration for $token_service.

  16. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    $account = $this->drupalCreateUser(['change own username']);
    +    $this->drupalLogin($account);
    

    Why that permission?

    Also I see that we are doing this in each test. Then we need to reuse that by implementing setUp() method. We declare a protected $account; in class and in setUp() we assign the account and login. Then in each test, we only use $this->account if case.

  17. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    $edit = array();
    +    $edit['mail'] = $new_mail;
    +    $edit['current_pass'] = $account->pass_raw;
    
    $edit = [
      'mail' => ...,
      'current_pass' => ...,
    ];
    
  18. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    // Take email settings from user.mail.yml.
    +    $mail_settings = Yaml::parse(file_get_contents(__DIR__ . '/../../config/install/user.mail.yml'));
    

    No. We use the Drupal config system to retrieve configurations. YAML files are only a way to import configs into Drupal when a module gets enabled.

    $mail_settings = $this->config('user.mail');

  19. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    // Verify that the user was sent a notification email.
    ...
    +    // Verify that the user was sent a verification email.
    ...
    +    // Ensure the change email URL is not cached.
    

    "... the user has sent..." ?

    and generally we use a comment for assertions like:

    "Check that the user...." —­ let's standardise all assertion comments like that.

  20. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    $this->assertMail('to', $account->getEmail(), 'Notification email with link to change the email has sent to user.');
    

    We can omit assertion messages.

  21. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +  /**
    +   * Retrieves the change email and extracts the link.
    +   */
    

    Missing @param and @return.

  22. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +  public function getChangeEmailURLFromEmail($mail_id) {
    

    protected? Are we using it outside?

  23. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +   * Generates random email.
    

    "Generates a random..."?

  24. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +  public function getRandomEmail() {
    

    protected. Are we using the function outside the test?

  25. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    return $this->randomMachineName() . '@example.com';
    

    Maybe it would be nicer to have only lowercase email user. Then Unicode::strtolower($this->randomMachineName()).

  26. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +   *   Unique hash.
    

    If a parameter is optional you should tell that and also provide the default. See https://www.drupal.org/coding-standards/docs#param

  27. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +  public function getChangeEmailURL(\Drupal\user\UserInterface $account, $timestamp, $hash = NULL) {
    

    Based on our coding standards we need to use lowerCamel naming for methods. See https://www.drupal.org/node/608152#naming. So, getChangeEmailUrl(). Also use use Drupal\user\UserInterface; to avoid the full qualified name in method signature.

  28. +++ b/core/modules/user/src/Tests/UserChangeEmailTest.php
    @@ -0,0 +1,182 @@
    +    return '/user/change-mail/' . $account->id() . "/$timestamp/" . $this->getRandomEmail() . '/' . $hash;
    

    Hmmm. Why not using the already existing controller url generator?

  29. +++ b/core/modules/user/user.install
    @@ -85,3 +87,18 @@ function user_install() {
    +function user_update_8002() {
    

    We need to test that. See how other modules are testing upgrades.

I need to review more on tests but it's difficult right now because we need to implement first the changes.

LOBsTerr’s picture

Assigned: Unassigned » LOBsTerr

@claudiu.cristea - as usual, thank you very much for you great review!

LOBsTerr’s picture

Assigned: LOBsTerr » Unassigned
Status: Needs work » Needs review
FileSize
26.45 KB
31.91 KB

@claudiu.cristea - It is not necessary, but if you don't mind I have fixed the codding standards for AccountForm.php, AccountSettingsForm.php, because there are only minors changes.

I forgot to add the test case for update hook :( I will do it tomorrow.

LOBsTerr’s picture

@claudiu.cristea Unfortunately, I cannot find examples in the core modules. I'm not sure how to properly check the update hook, I just need to check the current configuration with values in YML files?

claudiu.cristea’s picture

Status: Needs review » Needs work

This is only a brief, bird-eye, formal review. More to come. I didn't had time to review the test scenarios and to think more on hashing and security.

An update test: \Drupal\field\Tests\Update\FieldUpdateTest. Create a similar one in core/modules/user/src/Tests/Update

  1. +++ b/core/modules/user/src/AccountForm.php
    @@ -98,7 +98,8 @@ public function form(array $form, FormStateInterface $form_state) {
    -    // Only show name field on registration form or user can change own username.
    +    // Only show name field on registration form or user can change own
    +    // username.
    
    @@ -313,10 +314,9 @@ public function syncUserLangcode($entity_type_id, UserInterface $user, array &$f
    -    //   set on the field, which throws an exception as the list requires
    -    //   numeric keys. Allow to override this per field. As this function is
    -    //   called twice, we have to prevent it from getting the array keys twice.
    -
    +    // set on the field, which throws an exception as the list requires
    +    // numeric keys. Allow to override this per field. As this function is
    +    // called twice, we have to prevent it from getting the array keys twice.
    
    @@ -355,7 +355,7 @@ protected function getEditedFieldNames(FormStateInterface $form_state) {
    -      'preferred_admin_langcode'
    +      'preferred_admin_langcode',
    
    @@ -373,7 +373,7 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    -      'preferred_admin_langcode'
    +      'preferred_admin_langcode',
    
    +++ b/core/modules/user/src/AccountSettingsForm.php
    @@ -109,8 +109,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    -    // Do not allow users to set the anonymous or authenticated user roles as the
    -    // administrator role.
    +    // Do not allow users to set the anonymous or authenticated user roles as
    +    // the administrator role.
    
    @@ -157,13 +157,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    -      '#description' => $this->t('New users will be required to validate their email address prior to logging into the site, and will be assigned a system-generated password. With this setting disabled, users will be logged in immediately upon registering, and may select their own passwords during registration.')
    +      '#description' => $this->t('New users will be required to validate their email address prior to logging into the site, and will be assigned a system-generated password. With this setting disabled, users will be logged in immediately upon registering, and may select their own passwords during registration.'),
    
    @@ -174,7 +174,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    -      '#description' => $this->t('Users with the %select-cancel-method or %administer-users <a href=":permissions-url">permissions</a> can override this default method.', array('%select-cancel-method' => $this->t('Select method for cancelling account'), '%administer-users' => $this->t('Administer users'), ':permissions-url' => $this->url('user.admin_permissions'))),
    +      '#description' => $this->t('Users with the %select-cancel-method or %administer-users <a href=":permissions-url">permissions</a> can override this default method.',
    +        array(
    +          '%select-cancel-method' => $this->t('Select method for cancelling account'),
    +          '%administer-users' => $this->t('Administer users'),
    +          ':permissions-url' => $this->url('user.admin_permissions'),
    +        )
    +      ),
    
    @@ -219,7 +225,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    -      '#default_value' =>  $mail_config->get('register_admin_created.body'),
    +      '#default_value' => $mail_config->get('register_admin_created.body'),
    
    +++ b/core/modules/user/user.module
    @@ -577,13 +578,12 @@ function user_user_logout($account) {
    -  $timestamp = REQUEST_TIME;
    ...
    -      'timestamp' => $timestamp,
    -      'hash' => user_pass_rehash($account, $timestamp),
    +      'timestamp' => REQUEST_TIME,
    +      'hash' => user_pass_rehash($account, REQUEST_TIME),
    

    These changes are unrelated. Please remove them.

  2. +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,35 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +      $account_cloned = clone $account;
    +      $account_cloned->setEmail($new_mail);
    

    I think $account_copy sounds better. What you think?

  3. +++ b/core/modules/user/src/AccountForm.php
    @@ -386,13 +386,35 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +      if (_user_mail_notify('mail_change_verification', $account_cloned, NULL)) {
    

    Don't send NULL, that argument always defaults to NULL.

  4. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,139 @@
    +  public function changeEmailPage($uid, $timestamp, $new_mail) {
    ...
    +  public static function changeEmailUrl(UserInterface $account, array $options = [], $timestamp = REQUEST_TIME, $hash = NULL) {
    ...
    +  public function changeEmailAccess($uid, $timestamp, $hash, $new_mail) {
    

    Now that we have a dedicated controller only for E-mail change, we can simplify the name changing. Let's:

    • changeEmailPage() > page()
    • changeEmailUrl() > url()
    • changeEmailAccess() > access()
  5. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,139 @@
    +  public static function getHash(UserInterface $account, $timestamp) {
    

    We should add other per-user variables? I mentioned earlier the getCreatedTime() and getInitEmail().

  6. +++ b/core/modules/user/src/Controller/ChangeEmailController.php
    @@ -0,0 +1,139 @@
    +    return Crypt::hmacBase64($data, Settings::getHashSalt() . $account->getPassword());
    

    I'm not sure. What if the user changes its pass after issuing the E-mail change but before verifying? Anyway this is open for discussion. I don't see very clear right now how to deal with this. I agree that the passwords hash is a good value of randomness. Leave it as it is for now.

  7. +++ b/core/modules/user/user.install
    @@ -85,3 +87,18 @@ function user_install() {
    +
    

    Remove extra-line.

LOBsTerr’s picture

@claudiu.cristea - I took an example from other core modules. They use this naming $account_cloned and not $account_copy. About the first section, I know it is not related to this task, but I just wanted to fix them, because these changes are minor and it is easy to figure out what has been changed. If you insist I will remove them and will not touch other parts of the code anymore.

claudiu.cristea’s picture

I took an example from other core modules. They use this naming $account_cloned and not $account_copy

OK, let's go in this way.

I know it is not related to this task, but I just wanted to fix them, because these changes are minor and it is easy to figure out what has been changed. If you insist I will remove them and will not touch other parts of the code anymore.

Reviewing a patch is not easy. Unrelated changes make patches hard to review. also, each issue should fix only one thing. Yes, please remove them from patch.

EDIT: There are exceptions. For example you edit a line in the scope of the issue but the above comment, referring to the line you've just edited, doesn't respect the coding standards. Of course you can correct that comment. But it has to be in the area you already touched. But here is not the case.

LOBsTerr’s picture

Status: Needs work » Needs review
FileSize
11.91 KB
25.82 KB

I have created this test, but I cannot run it. There are some issues with update tests. I tested other simple tests which use UpdatePathTestBase. All their breaks with the same errors. So, I will try to find an issue for that and upload this test later.

Status: Needs review » Needs work

The last submitted patch, 166: verify_changing_user-85494-166.patch, failed testing.

LOBsTerr’s picture

Status: Needs work » Needs review
FileSize
25.79 KB
5.87 KB

Status: Needs review » Needs work

The last submitted patch, 168: verify_changing_user-85494-168.patch, failed testing.

LOBsTerr’s picture

@claudiu.cristea - It is strange, I tried to run UserChangeEmailTest before I uploaded the patch.

51 passes, 0 fails, 0 exceptions, 14 debug messages

Could it be some glitches with Drupal testing system?

Anonymous’s picture

Status: Needs work » Needs review
FileSize
1 KB
25.82 KB

Afaik, it should work. I ran the test several times locally and it always passes.

I did fix a typo while I was waiting for the test to run.

Status: Needs review » Needs work

The last submitted patch, 171: verify_changing_user-85494-171.patch, failed testing.

claudiu.cristea’s picture

@LOBsTerr, probably the HEAD is ahead and you need to update your codebase from repo.

LOBsTerr’s picture

Status: Needs work » Needs review
FileSize
881 bytes
25.83 KB

I always try to pull the latest changes. Anyway tried to pull changes again and added some minor fixes.

claudiu.cristea’s picture

Status: Needs review » Needs work

Green, thank you. Then let's add the Update test and I will review the whole patch. Also we need to figure out what to make with the hash.

LOBsTerr’s picture

@claudiu.cristea - I think it is ok to remove password as you suggested above, because we enough unique information to generate a secure hash.

LOBsTerr’s picture

@claudiu.cristea - I have been struggled with UpdateTest and I cannot make it work. Actually, I cannot make work any update test in the core. Usually, I have about 150 fails, but it happens UpdatePathTestBase. It is strange, because the same tests obviously run without any issue on the Drupal.org. Can you try to run any UpdateTest locally? Maybe I need to install something to make it work. I can send the patch with my UserUpdateTest class, but I cannot test it locally:(
As you see it is quite simple, but it fails, because something breaks in $this->runUpdates();

Thank you for helping me.

<?php

/**
 * @file
 * Contains \Drupal\user\Tests\Update\UserUpdateTest.
 */

namespace Drupal\user\Tests\Update;

use Drupal\system\Tests\Update\UpdatePathTestBase;
use Symfony\Component\Yaml\Yaml;

/**
 * Tests that user settings are properly updated during database updates.
 *
 * @group user
 */
class UserUpdateTest extends UpdatePathTestBase {

  /**
   * {@inheritdoc}
   */
  public function setDatabaseDumpFiles() {
    $this->databaseDumpFiles = [__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz'];
  }

  /**
   * Tests user_update_8002().
   *
   * @see user_update_8002()
   */
  public function testUserUpdate8002() {
    $mail_config = Yaml::parse(file_get_contents(__DIR__ . '/../../../config/install/user.mail.yml'));
    $this->runUpdates();
    $mail_settings = $this->config('user.mail');

    $this->assertEqual($mail_config['mail_change_notification']['subject'], $mail_settings->get('mail_change_notification.subject'));
    $this->assertEqual($mail_config['mail_change_notification']['body'], $mail_settings->get('mail_change_notification.body'));
    $this->assertEqual($mail_config['mail_change_verification']['subject'], $mail_settings->get('mail_change_verification.subject'));
    $this->assertEqual($mail_config['mail_change_verification']['body'], $mail_settings->get('mail_change_verification.body'));
  }

}
claudiu.cristea’s picture

I don't see the test in the patch. Please add the patch with the test to see the failures.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

claudiu.cristea’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update, -Needs tests, -Needs upgrade path tests +Needs security review, +Needs usability review
FileSize
30.95 KB
44.16 KB
348.92 KB

Here we go.

attiks’s picture

Code is looking good, same phrasing sounds a bit weird to me, so best that somebody else can review it as well

Didn't had a chance to test the patch, will try later today

  1. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,140 @@
    +      // Reflect the changes in the session if the user is user is logged in.
    

    Strange sentence

  2. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,140 @@
    +   *   (optional) The timestamp when hash is created. If missed, the current
    +   *   request time is used.
    ...
    +   *   (optional) Unique hash. If missed, the hash is computed based on the
    +   *   account data and timestamp.
    

    'If missed' => 'When not provided'

claudiu.cristea’s picture

FileSize
30.93 KB
1.35 KB

Thank you @attiks.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

swentel’s picture

  1. +++ b/core/modules/user/src/AccountForm.php
    @@ -382,13 +382,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +    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.
    +        _user_mail_notify('mail_change_notification', $account);
    +      }
    +
    +      // 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');
    +    }
    

    The logic seems off here. If you don't enable mail change verification, you will still see the message and your old mail will be stored, no ?

  2. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,140 @@
    +      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 <a href=":logout">log out</a> and try using the link again.', ['%user' => $current_user->getAccountName(), ':logout' => Url::fromRoute('user.logout')->toString()]), 'error');
    

    'for another account' instead of 'for other account'

claudiu.cristea’s picture

FileSize
4.61 KB
30.91 KB

@swentel, thank you for looking at this issue.

#184.1: (EDIT) mail_change_verification is not a flag but a pair of E-mail subject + body texts. So _user_mail_notify('mail_change_verification', $account_cloned) is sending out an E-mail. The fact that the mail was sent cannot be guaranteed. The logic there would be

Even the notification E-mails were not sent (because some emailing system error) you will still see the status message and the old email will be stored.

But note that we store the old E-mail because we want the account to be saved with the old E-mail. We don't allow the new E-mail to be stored till the user will confirm the new E-mail. We effectively change the E-mail when the user clicks the verification link, in \Drupal\user\Controller\MailChangeController::page().

#184.2: Fixed Thanks.

I also dropped MailChangeController::getHash() because a more simple way was to change user_pass_rehash() to accept a new optional parameter.

claudiu.cristea’s picture

FileSize
31.71 KB
2.32 KB

@swentel, you are right in #184. Forgot that we have also checkboxes for that notification.

swentel’s picture

Looks good to me now imo. Wondering how to trigger usability and security reviews on this one .. maybe just put to RTBC, that will at least set more eyes on this one.

The last submitted patch, 185: 85494-185.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 186: 85494-187.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
FileSize
33.28 KB
8.1 KB

@swentel, yes probably it's good to have it RTBC. This issue is 10 years old and has 72 followers :)

Fixed the test and converted the test to BrowserTestBase test. Also I added a new test testMailChangeNoVerification() that tests the case when mail change validation is switched off. Fixed also other nits.

swentel’s picture

Status: Needs review » Reviewed & tested by the community

Nit in the test.

+++ b/core/modules/user/tests/src/Functional/UserMailChangeTest.php
@@ -64,15 +70,41 @@ public function testMailChange() {
+    // Save a new email and verify that the user has sent an email.
+    $new_mail = $this->getRandomEmailAddress();

Hmm, should be something like '.. and verify that the no email was sent'.

Moving to RTBC, let's see what the core committers think of this.

claudiu.cristea’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +8.3.0 release notes
FileSize
33.29 KB
1.38 KB

Fixed the comments.

claudiu.cristea’s picture

Status: Needs review » Reviewed & tested by the community

Changed the status by mistake

xjm’s picture

Title: Verify changing user email addresses » Use email verification when changing user email addresses
swentel’s picture

+++ b/core/modules/user/user.post_update.php
@@ -0,0 +1,37 @@
+/**
+ * Update config for change mail notifications.
+ */
+function user_post_update_mail_change() {
+  $config_factory = \Drupal::service('config.factory');

So I've been thinking more about this update which enables the email change verification. While this update was handy in my case (because we require it for a project), I'm not sure if we should really run this by default when users are going to upgrade.

It's an extremely useful feature of course, which on new installations warrant to be enabled by default, but I'm not sure about upgrades, because maybe users don't necessarily see this as a bug ?

Not setting to needs work for this, because I'm fine if it gets committed as is as well, just wanted to express my thoughts.

dawehner’s picture

+++ b/core/modules/user/user.post_update.php
@@ -0,0 +1,37 @@
+  $config = Yaml::parse(file_get_contents(__DIR__ . '/config/install/user.settings.yml'));
+  $config_factory->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();

Personally I'm a huge fan of changing default values, but not update to those default values, but rather keep existing behaviours. You know, it just avoids unnecessary problems.

claudiu.cristea’s picture

I agree with #195 and #196. More clarifications:

  1. +++ b/core/modules/user/config/install/user.settings.yml
    @@ -9,8 +9,11 @@ notify:
    +  mail_change_notification: true
    +  mail_change_verification: true
    

    I think this should remain as it is, so new site installations will get advantage of this fix.

  2. +++ b/core/modules/user/user.post_update.php
    @@ -0,0 +1,37 @@
    +  $config_factory->getEditable('user.settings')
    +    ->set('notify.mail_change_notification', $config['notify']['mail_change_notification'])
    +    ->set('notify.mail_change_verification', $config['notify']['mail_change_verification'])
    

    Here we need these values to FALSE, to disable this feature for existing sites.

  3. +++ b/core/modules/user/user.post_update.php
    @@ -0,0 +1,37 @@
    +  $config_factory->getEditable('user.mail')
    +    ->set('mail_change_notification', $config['mail_change_notification'])
    +    ->set('mail_change_verification', $config['mail_change_verification'])
    

    This should remain even the feature will be disabled for the existing sites because here we set the default mail subject & body. Will not have effect but will be set in the case the admin manually enable them.

Are we OK with this plan?

claudiu.cristea’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
33.12 KB
1.75 KB

@swentel,

Implemented #195, #196, #197. I'm setting the issue to "Needs review" to get your feedback.

swentel’s picture

Status: Needs review » Reviewed & tested by the community

Sounds perfect to me.

+++ b/core/modules/user/src/Tests/Update/UpdateMailChangeTest.php
@@ -0,0 +1,59 @@
+    // Check that mail change notifications were set to TRUE.

Extreme nit, should be FALSE :)

Moving back to RTBC, if anyone wants to fix that nit, leave the status on RTBC :)

claudiu.cristea’s picture

Oh, yeah. Missed that :)

alexpott’s picture

+++ b/core/modules/user/user.module
@@ -638,15 +639,19 @@ function user_cancel_url(UserInterface $account, $options = array()) {
+function user_pass_rehash(UserInterface $account, $timestamp, $mail = NULL) {
+  $mail = $mail ?: $account->getEmail();
   $data = $timestamp;
   $data .= $account->getLastLoginTime();
   $data .= $account->id();
-  $data .= $account->getEmail();
+  $data .= $mail;
   return Crypt::hmacBase64($data, Settings::getHashSalt() . $account->getPassword());
 }

Initially looking at this change I wondered if this was a security issue - ie it is possible to generate a hash without an email address - but looking at the code it appears not.

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs product manager review
  1. +++ b/core/modules/user/src/AccountForm.php
    @@ -382,13 +382,30 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +        drupal_set_message($this->t('Your new email address needs to be validated. Further instructions have been sent to your new email address.'), 'warning');
    

    This should probably be 'updated' rather than new. The e-mail address is new to the site, but probably not to the user.

  2. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,111 @@
    +    $timeout = $this->config('user.settings')->get('mail_change_timeout');
    

    Is there a UI for this? If not why not put it in $settings?

  3. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,111 @@
    +      // Save the new email but refresh also the last login time so that this
    +      // mail change link get expired.
    ...
    +    }
    +    // 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 {
    

    Nit, should be 'gets expired' or 'is expired'.

    Should 'registered a new login' bit 'logged in'?

  4. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,111 @@
    +      // Reflect the changes in the session if the user is user is logged in.
    

    if the user is user is

  5. +++ b/core/modules/user/user.post_update.php
    @@ -0,0 +1,36 @@
    +  $config = Yaml::parse(file_get_contents(__DIR__ . '/config/install/user.mail.yml'));
    

    Shouldn't this duplicate the value from the configuration instead of reading it in? Otherwise the config might change again, and this update will have different results depending on which version is updated to.

Also this is still tagged usability review. I didn't review the whole issue to see whether that had been done, would be good to update the issue summary and remove the tag if it has, or ask someone to review it if not. Tagging for product review as well.

claudiu.cristea’s picture

Status: Needs work » Needs review
Issue tags: -Needs usability review
FileSize
33.8 KB
16.41 KB

Fixed #202.

Also removed t() from test according to https://www.drupal.org/node/2783189#t and replaced the deprecated REQUEST_TIME with the new service.

claudiu.cristea’s picture

Removed unused use statement

swentel’s picture

+++ b/core/modules/user/config/schema/user.schema.yml
@@ -53,9 +53,6 @@ user.settings:
     password_reset_timeout:
       type: integer
       label: 'Password reset timeout'
-    mail_change_timeout:
-      type: integer
-      label: 'Mail change timeout'
     password_strength:

This isn't really consistent. Password reset timeout doesn't have a UI either and it's in config. So I'd leave mail_change_timeout in config as well and create a follow up to expose both password and mail change timeout in the UI (if wanted).

Looks good to me other than that.

claudiu.cristea’s picture

Reverted mail_change_timeout to a config in order to keep the symmetry with password_reset_timeout. Opened #2823877: [PP-1] Expose user.settings:password_reset_timeout and mail_change_timeout in UI to provide UI for both, as a follow-up.

swentel’s picture

Status: Needs review » Reviewed & tested by the community

Ok, looks good to me. Let's give committers an eye on this again.

catch’s picture

re #205 I think really the password reset timeout should move to settings as well - this looks like something that got converted from variables to config all in one go.

claudiu.cristea’s picture

@catch, If somebody has overwritten that value in a custom module he will lose the setting as we cannot provide an update path. Or I'm missing something.

EDIT: Or a custom/contrib module just uses the config for some reasons. After conversion, this will crash: \Drupal::config('user.settings')->get('password_reset_timeout')

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

.

  1. Re mail_change_timeout

    Is there a UI for this? If not why not put it in $settings?

    Lack of UI does not mean something should be in settings. I think this belongs in config because it is precisely the sort of thing you want the same in all environments and to be able to be able to deploy via a config change.

    Also this still needs a product manager review. It definitely needs a change record too.

  2. +++ b/core/modules/user/src/AccountForm.php
    @@ -382,13 +382,30 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +    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) !== NULL) {
    +        // Send notification email to the old email address.
    +        _user_mail_notify('mail_change_notification', $account);
    +        // The user's mail address will be updated only after verification.
    +        $form_state->setValue('mail', $old_mail);
    +        drupal_set_message($this->t('Your updated email address needs to be validated. Further instructions have been sent to your new email address.'), 'warning');
    +      }
    +    }
    

    Doing this in the submitForm is wrong - it needs to be done in User::postSave() so the changes via the API have the same logic.

  3. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,114 @@
    +use Drupal\Core\Site\Settings;
    

    Not used.

  4. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,114 @@
    +      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');
    

    Will this actually work? Ah yes the mail is only updated after the verification is successful.

  5. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,114 @@
    +    return $this->redirect('<front>');
    

    Given that everything redirects I don't think we have the issue with hash exposure that we had with one time links. Nice! However I think there should be a similar comment in the function docblock...

       * In order to never disclose a reset link via a referrer header this
       * controller must always return a redirect response.
    
  6. +++ b/core/modules/user/user.routing.yml
    @@ -200,3 +200,16 @@ user.reset.form:
    +    _maintenance_access: TRUE
    

    Are we sure about this?

alexpott’s picture

Re #210.2 It actually might need to be done in preSave actually because you're resetting the value back to the old value.

claudiu.cristea’s picture

@alexpott, sending Email notifications from preSave()? Brrrr

claudiu.cristea’s picture

As an effect of #210.2, #211, we need to test the REST part too. Test in \Drupal\user\Tests\RestRegisterUserTest

k4v’s picture

I just took a quick look at User::preSave, where should we get the old value from? The new address is already set at this point.

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea

@k4v, I already started to work on this. Assigned.

webchick’s picture

Issue tags: -8.3.0 release notes

Removed the "8.3.0 release notes" for now, since this isn't actually committed to 8.3.0.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

rootwork’s picture

Assigned: claudiu.cristea » Unassigned

Unassigned given the length of time since the last code contribution. @claudiu.cristea if you have updates, please let us know!

manuel.adan’s picture

I've recently publish the email_confirmer module, that makes the hard work for any other module that needs email address confirmation. It provides a service for email confirmation as well as a content entity type for the confirmations control. This approach might be convenient to be in core, since confirming email addresses is a basic feature for any website that captures emails in some way.

shrop’s picture

Issue summary: View changes

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

webchick’s picture

Sounds like this more needs usability review than product manager review; changing the tag so we can review on a future UX meeting with a larger crew.

manuel.adan’s picture

For those who need email change confirmation in Drupal 8, email_confirmer now provides it. It has been added as a functionality of a new user-related submodule.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

claudiu.cristea’s picture

Assigning to fix #210.

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea
claudiu.cristea’s picture

Straight reroll of #206. Plus a WebTestBase was moved as Functional. More fixes to follow...

claudiu.cristea’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 228: 85494-228.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

claudiu.cristea’s picture

Status: Needs work » Needs review
FileSize
34.39 KB
5.54 KB

Fixing test failures. #210 not yet addressed.

Status: Needs review » Needs work

The last submitted patch, 231: 85494-231.patch, failed testing. View results

claudiu.cristea’s picture

Status: Needs work » Needs review
FileSize
34.43 KB
5.28 KB

More failures to fix.

claudiu.cristea’s picture

FileSize
35.17 KB
2.68 KB

Fixed #210.3, 5, 6.

Now, regarding #210.2:

@alexpott,

After investigating the request from #210.2 and trying to implement that, I found that moving the verification & notification logic from the form submit into the API (User::preSave()) will definitely not work. Here are the reasons:

  • This piece of code, written somewhere in an arbitrary module, will not work anymore: User::load($uid)->setEmail('new@example.com')->save(). This is because in User::preSave(), the old E-mail will be set back, waiting for the user verification.
  • It’s totally unusual and wrong to expect user interaction in an API operation such as User::load($uid)->setEmail('new@example.com')->save(). I read this snippet straight: after this, the account should have new@example.com as E-mail, not the old one. And the API should not depend on user interaction.
  • If the logic is moved into User::preSave() the E-mail change will never occur because when the user clicks the E-mail link to verify the new E-mail, this code is invoked $user->setEmail($new_mail)->setLastLoginTime($request_time)->save();, in MailChangeController::page() and, again, User::preSave() is called, which is switching back to the old E-mail. Of, course this could be handled in some way, but it would not be a nice solution.
  • On user registering (new users), the E-mail verification operation, is also performed in form submit (see \Drupal\user\RegisterForm::save()). This totally makes sense because it allows you to programatically create an unblocked user in this way User::create([...])->activate()->save(). If the registration E-mail verification logic would have been implemented in User::preSave() (or somewhere in the save API chain), that snippet would not have been created an active user but a blocked one, contrary on how this code reads.

Proposal

  • For UI: Keep the logic in form API for UI.
  • For REST: Override the REST resource entity:user by extending \Drupal\rest\Plugin\rest\resource\EntityResource::patch() and implementing the same logic. But, I think, due to the fact that this patch is already complex, we should do it in a follow-up.
mcdruid’s picture

Issue tags: +Security improvements
jonathanshaw’s picture

We can't RTBC as it still needs a change record

claudiu.cristea’s picture

Issue tags: -Needs change record

Draft change record added https://www.drupal.org/node/2994918

mcdruid’s picture

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

RTBC per #199 as #210 is addressed.

Let's see if comitter thinks security or usability reviews are still needed.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 234: 85494-234.patch, failed testing. View results

claudiu.cristea’s picture

Status: Needs work » Needs review
FileSize
35.32 KB
928 bytes

Luckily, the last failure from #234 has uncovered a bug in the patch. True, that it's a very, very edge case. I was running locally the UserMailChangeTest::testMailChange() test and I managed to get a failure rate of 50% (!!!) The problem occurs when testing the verification link expiry when accessing for the 2nd time the link. If the test runs the second request within 1 second (in the same server request timestamp) then the test fails because the link is not considered expired. I cannot explain why the high failure rate didn't make old patches to fail, I only guess that the infrastructure is now improved, we run now tests faster, on PHP7, and that made possible the edge case (making 2 GET requests within the same second).

The fix is simple. When comparing the link timestamp with the server request time, use the server request time as float, which adds milliseconds precision and is always greater than the timestamp send via the link.

claudiu.cristea’s picture

FileSize
35.4 KB
2.44 KB

An improved version that honours the setLastLoginTime() contract. Also has small improvements on docs.

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 242: 85494-242.patch, failed testing. View results

claudiu.cristea’s picture

Weird, the patch never fails when on posting but only when is RTBC :)

claudiu.cristea’s picture

Status: Needs work » Needs review
FileSize
36 KB
1.13 KB

Testing this is tricky. What exactly is happening with the random failures? Follow UserMailChangeTest and MailChangeController code to understand

  • The testing user logs in, UserMailChangeTest, line 49.
  • The last login time is set to a value. Let's say 1500000000 just to exemplify.
  • The user is changing its E-mail in UserMailChangeTest, line 67.
  • The request time is still 1500000000. This could not happen to a human, but a test can do it.
  • The user clicks the verification E-mail, in UserMailChangeTest, line 90.
  • The E-mail is verified and the user last login time is updated in order to expire the link.
  • But as the test is very fast, this happens again within the 1500000000.
  • The user clicks again on the expired link, in UserMailChangeTest, line 94.
  • But, because the link timestamp equals again the last user login time acts as if the link is not expired. Check MailChangeController, line 82, to understand why.

This cannot happen in the real world for a human. So, I added an one second sleep before the user changes its account E-mail just to ensure that the test is acting as a human. Note that if the user last login time were stored as a timestamp with millisecond precision this trick wouldn't had been needed.

jonathanshaw’s picture

Status: Needs review » Needs work

Seems like you need to walk back #241/242, or at least modify the comments? At the moment it seems like we've got different bits of code claiming they fix the same problem.

claudiu.cristea’s picture

Assigned: claudiu.cristea » Unassigned
Status: Needs work » Needs review
FileSize
3.5 KB
35.79 KB

Indeed. And #246 makes #241/#242 undeeded. I reverted #241/#242 and improved a little bit the docs in test. Unassigning.

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 248: 85494-248.patch, failed testing. View results

jonathanshaw’s picture

Status: Needs work » Reviewed & tested by the community

Bot results show no failure I can see.

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea
Status: Reviewed & tested by the community » Needs work

Luckily, we've been tested this patch also in production and we managed to hit an edge case: Drupal accepts accounts without E-mail when the account is created by an admin. When such a user is edited, on save, will throw an exception when building the user tokens.

We need also to prevent sending a notification to the old address, if the old address is empty. The other way around, changing an non-empty E-mail to an empty one, is not allowed, so we don't need to deal with that case. Provide test for this edge case.

Assigning to work on it.

sandzel’s picture

Version: 8.7.x-dev » 8.6.x-dev
FileSize
36.53 KB

That's great idea to have email verification functionality,
I have a suggestion for the step when user already passed all validation and email is changed. Will be great to have a hook that permits developers apply some custom functionality. For example add/remove a role, permissions, notify admins, etc.
I updated the patch 85494-248.patch and I added the hook_user_email_change($account).
By the way, I noticed that when user is not logged on the site and click on mail-change-url the email is changed but user is not logged automatically.
Is it an intentional behavior?

sandzel’s picture

Version: 8.6.x-dev » 8.7.x-dev
jonathanshaw’s picture

Re #253 I suggest opening a follow-up issue for that and postpone it on this issue, it doesn't need to block this issue.

claudiu.cristea’s picture

Test proving the bug described in #252.

claudiu.cristea’s picture

Fix the bug. The interdiff is against #248.

@sandzel, I don't think there's need for such a new hook. This can be easily handled in a normal hook (presave, update) by checking the mail against the original E-mail. However, while this is debatable, please open a followup, as @jonathanshaw suggested, if you still think that core needs such an API addition.

The last submitted patch, 256: 85494-256-test-omly-for-252.patch, failed testing. View results

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community
claudiu.cristea’s picture

Assigned: claudiu.cristea » Unassigned

Unassigning

The last submitted patch, 256: 85494-256-test-omly-for-252.patch, failed testing. View results

AaronMcHale’s picture

Thanks for all the work on this, looking forward to seeing it in core :)

Also file clean-up, cause I'm OCD like that

AaronMcHale’s picture

claudiu.cristea’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
36.98 KB
806 bytes

Let's take an extra precaution for the case when 3rd party code computes the tokens and the account has no E-mail.

andypost’s picture

AaronMcHale’s picture

Found another one, thanks to the issue @andypost linked

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community
larowlan’s picture

Pinged security team and UX team for reviews here

benjifisher’s picture

Issue summary: View changes

I plan to bring this up at the weekly Usability meeting in about 4 hours (3:30 PM EST). Join us on the #ux channel in the Drupal Slack account if you would like to be part of the discussion.

worldlinemine’s picture

At the Drupal Usability meeting we reviewed the suggested patches (participating Benji Fisher and Thomas Howell).

Change examined at "Configuration" -> "People" -> "Account email changing"
Note to testers: must check field to enable form for subject and body of potential emails.

Question:

  • Q: Should the help text appear at all if the checkbox is empty?
  • A: Yes, for now because this follows an existing pattern.

Follow up questions:

  1. Should there be some indication that additional form fields are available once checked?
  2. Should the suggestions exist prior to checking the box?
  3. Should we make the request visible as pending to user and/or admin until completion?
  4. Should there be visible history (related question, are users revisionable)?

Testing patch results:
Inconclusive technically because we were unable to test locally with outgoing emails. However, the page updated by patch matches the screenshots provided in this issue which is sufficient for the usability review.

Conclusion: We concur that this change is an excellent idea though if possible the follow up questions should be addressed. This was a usability review so the lack of confirmation on outgoing emails by our team should not be viewed as a blocker.

worldlinemine’s picture

Issue tags: -Needs usability review
benjifisher’s picture

Issue summary: View changes
FileSize
155.69 KB

As @worldlinemine said, we discussed this at the Drupal Usability Meeting 2018-11-13 (recording on Youtube).

Since then, I have made two changes before re-testing:

  1. Install mailhog on my local server, in order to test e-mails.
  2. Add a second user to the site.

I notice that the e-mail verification is not sent when I change the e-mail address of user 1 (admin). From the comments above, I guess that any user with the "administer users" permission can change an e-mail address without triggering the e-mail. I wonder if there should be an exception to the exception: when an admin user changes their own password, maybe we should still use the new system.

I added a note to the issue summary about such admin users. I also added another screenshot and the text of a confirmation e-mail.

claudiu.cristea’s picture

@benjifisher, @worldlinemine, thank you for the UX review.

I wonder if there should be an exception to the exception: when an admin user changes their own password, maybe we should still use the new system.

They can always set back or enter a correct E-mail address if they used a wrong one. No need for verification.

benjifisher’s picture

According to Murphy's Law, if I ever make a mistake changing my e-mail address, that will be the time that my password manager crashes. Then I will not be able to log in and fix it.

plach’s picture

Issue tags: +Needs usability review

Per #268 tagging as needing UX review as well (the IS has all the relevant info).

benjifisher’s picture

Issue tags: -Needs usability review

The usability review was in #270. I gave a link to the recording of the meeting in #272.

plach’s picture

Oops, missed that, sorry for the noise.

alexpott’s picture

Issue summary: View changes

There's been a process change whilst this is RTBC - we need to populate the issue summary with a Release note section. This is important for this issue as there is change record and this does affect site operation.

Not changing the status of the issue - going to review soon.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/user/src/AccountForm.php
    @@ -389,13 +389,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +    if (!$account->isNew() && ($old_mail !== $new_mail) && !$this->currentUser()->hasPermission('administer users')) {
    ...
    +        // The user's mail address will be updated only after verification.
    +        $form_state->setValue('mail', $old_mail);
    

    We've done quite a bit of work recently to make it easier to have users with a permission other than administer users be able to administer users because that permission allows you to change everything. See #2854252: User forms broken for admin without 'administer users' I think we need to account for that somehow.

  2. +++ b/core/modules/user/src/AccountForm.php
    @@ -389,13 +389,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +      if (_user_mail_notify('mail_change_verification', $account_cloned) !== NULL) {
    +        // Send notification email to the old email address, if it's set.
    +        if ($old_mail) {
    +          _user_mail_notify('mail_change_notification', $account);
    

    We need to enforce the logic of the form on the config level. I.e. if user.settings:notify.mail_change_verification is set to FALSE there is no world where user.settings:notify.mail_change_notifcation. I.e. we need a user version of \Drupal\system\SystemConfigSubscriber to enforce this logic.

  3. +++ b/core/modules/user/src/AccountForm.php
    @@ -389,13 +389,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    +        $this->messenger()->addWarning($this->t('Your updated email address needs to be validated. Further instructions have been sent to your new email address.'));
    

    It might not be you doing the updating. See comment above. I think we should consider this like admin approval - ie. we should only do this if the user is themselves.

  4. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,148 @@
    +   * @param \Drupal\user\UserInterface $account
    +   *   An object containing the user account.
    

    I think it is worth commenting that we expect the account to have the new email at this point.

  5. +++ b/core/modules/user/src/Controller/MailChangeController.php
    @@ -0,0 +1,148 @@
    +   * @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.
    

    This functionality appears to be only for testing. Given this method is being used as API I'm not sure about this. I think tests can generate their own URLs without this extra complexity.

  6. +++ b/core/modules/user/tests/src/Functional/UserMailChangeTest.php
    @@ -0,0 +1,246 @@
    +  /**
    +   * Tests change of email with the wrong hash.
    +   */
    +  public function testWrongHash() {
    +    $timestamp = $this->time->getRequestTime() - 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->assertSession()->responseContains('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.');
    +  }
    +
    

    Nice that this test is here. It would be great if there is also a test to ensure that if a user has a correct hash - but hacks the email address on the URL it still does not work. Afaics it won't but this all relies on the User object being stateful and passed to functions in the expected state - ie. having the new email but not being saved.

  7. +++ b/core/modules/user/user.module
    @@ -658,15 +659,19 @@ function user_cancel_url(UserInterface $account, $options = []) {
    +  $mail = $mail ?: $account->getEmail();
    ...
    +  $data .= $mail;
    

    $data .= $mail ?: $account->getEmail() will suffice. The additional local variable makes me wonder what it is for.

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea

Assigning to fix requests from #279.

claudiu.cristea’s picture

Assigned: claudiu.cristea » alexpott
Status: Needs work » Postponed (maintainer needs more info)

@alexpott, I need some clarifications on your request from #279:

#279.1:

We've done quite a bit of work recently to make it easier to have users with a permission other than administer users be able to administer users because that permission allows you to change everything. See #2854252: User forms broken for admin without 'administer users' I think we need to account for that somehow.

By evaluating $account->access('update') and checking if the account is the current user we cannot determine if the user is a sub-admin editing its account. In that case we don't want to verify the email. I see no way to avoid the verification in such case by not checking the administer users permission. This is because $account->access('update') returns TRUE also when a regular user is updating his own account. Or probably I don't get correctly your request, which is a little bit vague.

#279.2:

We need to enforce the logic of the form on the config level. I.e. if user.settings:notify.mail_change_verification is set to FALSE there is no world where user.settings:notify.mail_change_notifcation. I.e. we need a user version of \Drupal\system\SystemConfigSubscriber to enforce this logic.

This is tricky. Unfortunately there's no way to alter the config (user.settings) being saved, The actual ConfigEvents::SAVE event is fired after the config has been saved to the storage. For this reason the subscriber must save again the config that was just saved and this leads to a recursion problem (as the subscriber is recursively called). Of course, we can hack around by using a recurse flag. But that would look so ugly. I created an issue to add a pre-save event just before the config is stored in the backend: #3022725: Add ConfigEvents::PRE_SAVE event.

There is also a UX/UI aspect. We should do the same in UI by using FAPI #states so that the user visually understands this dependency. I.e. in UI, when he unchecks the notify.mail_change_verification, the notify.mail_change_notifcation should be automatically unchecked. But, unfortunately, #states are now buggy when it comes to, at least, the following aspects:

  • The #states (un)check currently don't work: #994360: #states cannot check/uncheck checkboxes elements.
  • Event if that is fixed, it doesn't help because of the way #states is designed now in core. Right now, uncheck is the negation of check. But we want to trigger the state only on "uncheck", not on "check". I.e 'unchecked' => ['input[name="mail_change_verification[enabled]"]' => ['unchecked' => TRUE]] is not the same as 'checked' => ['input[name="mail_change_verification[enabled]"]' => ['checked' => TRUE]]. But the actual implementation works in this way. Even I use 'unchecked' as host/remote condition is triggered also on check.
  • Even with the patch from #994360: #states cannot check/uncheck checkboxes elements, the states are not cascading. I.e. when notify.mail_change_verification is unchecked, the notify.mail_change_notifcation gets unchecked but it doesn't propagate the event by closing the details section (see the other implemented #states).

#279.3:

It might not be you doing the updating. See comment above. I think we should consider this like admin approval - ie. we should only do this if the user is themselves.

There's no way to land there w/o being a user editing its own account. We don't trigger verification for (sub)admins editing the account of other user. At least this is the initial plan. Am I missing something?

EDIT (added later):

#279.6:

Sorry, I don't understand the scenario you are proposing.

claudiu.cristea’s picture

FileSize
15.85 KB

I'm posting also the incomplete progress I made while analysing #279. It covers #279.4, 5, 7 and a try for #279.2.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

AdamPS’s picture

Status: Postponed (maintainer needs more info) » Needs work

Background: I was the main author of #2854252: User forms broken for admin without 'administer users'

Re #279.1: We could write the test something like this

$own_account = $this->currentUser()->id() == $account->id();
$skip_verification = !$own_account || $this->currentUser()->hasPermission('administer users');
if (!$account->isNew() && ($old_mail !== $new_mail) && !$skip_verification)

Or if we want admins to have to verify their own change (as #274) then instead

$own_account = $this->currentUser()->id() == $account->id();
if (!$account->isNew() && ($old_mail !== $new_mail) && $own_account)

#279.3:

There's no way to land there w/o being a user editing its own account. We don't trigger verification for (sub)admins editing the account of other user. At least this is the initial plan. Am I missing something?

I think @AlexPott is saying that in the previous patch #264 it could be another "sub-admin user" as per comment #279.1. However you will be right it must be own account if you change the code as per #279.1. Hence solve #279.1 and this problem goes away too.

#279.6 I would interpret this comment as a request to test this scenario:

  • User changes email address to E1
  • User receives confirmation email to E1.
  • User submits an altered confirmation URL which contains a different address E2 for parameter {new_mail}
  • Assert that hash verification fails
AdamPS’s picture

Assigned: alexpott » Unassigned

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

claudiu.cristea’s picture

Reroll for 8.8.x.

AaronMcHale’s picture

The backport to D7 tag doesn't really feel relevant anymore.

Could we also get an issue summary update listing the remaining tasks to move this from NW to NR/RTBC?

AkashKumar07’s picture

1. The remaining failure is now due to a $defaultTheme property required on your test class.
Example :-
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
2. Drupal\Component\Utility\Crypt::hashEquals() is deprecated in drupal:8.8.0.
Use PHP's built-in hash_equals() function instead.
Example :-
hashEquals($hash, user_pass_rehash($user, $timestamp, $new_mail)) instead of
use Drupal\Component\Utility\Crypt;
Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp, $new_mail))

Hope this will help.
Thanks

AdamPS’s picture

Issue summary: View changes
Issue tags: -Needs backport to D7

I updated the IS as far as I can understand it based on reading the recent comments.

#289 accidentally reverted the tag change of #287 - fixing that.

AaronMcHale’s picture

Issue summary: View changes
Issue tags: +Needs usability review

Great, sounds like we're near the home stretch for this issue, yay!

Tagging this for usability review, looking back it looks like one might have been done a few years ago but it would be good to get an up to date one, I'll demo this at a future UX Team meeting.

rwohleb’s picture

Ideally we'd have tokens available in the email templates where we have access to both the old and new email addresses. Right now [user:email] is either the old or new email address depending on if it's the notification or validation template. This isn't exactly intuitive nor explained in the help text.

The token module adds support for [user:original], but unfortunately the source of that ($entity->original) is only set in EntityStorageBase::doPreSave(). We could set $account->original in AccountForm::submitForm() which would be nice when that module is enabled, but we obviously aren't going to add that as a dependency. That seems to leave us with the current situation. I suppose we could at least set $account_cloned->original so that'd be available in the verification template.

Thoughts?

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

pfrenssen’s picture

jonathanshaw’s picture

Issue tags: +Novice

#289 explains clearly how to fix the remaining test failures.

Lal_’s picture

Status: Needs work » Needs review
FileSize
37.09 KB
1.67 KB

interdiff wrt #294 added the changes mentioned in #289

jonathanshaw’s picture

Issue tags: -Novice

Thanks @Lal_

mrinalini9’s picture

Found one more deprecation still exits in #296 (Declaring ::setUp without a void return typehint) that I have updated here in the patch, please review.

The last submitted patch, 296: 85494_296.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 298: 85494_298.patch, failed testing. View results

narendra.rajwar27’s picture

Assigned: Unassigned » narendra.rajwar27
narendra.rajwar27’s picture

Status: Needs work » Needs review
FileSize
627 bytes
37.1 KB
narendra.rajwar27’s picture

Assigned: narendra.rajwar27 » Unassigned
AaronMcHale’s picture

I just want to be sure that the IS is up to date, specifically the remaining tasks? As they seem to reference comment numbers which might be old now, so I suspect some of the remaining tasks are now done. If it is already up to date then please feel free to remove the tag.

Also, is this at the stage where we can do a usability review (as per the remaining tasks) or are there still likely to be UI/UX changes?

Thanks

jonathanshaw’s picture

Nothing related to IS has happened since @AdamPS updated the IS 6 months ago; he's a careful guy so the IS is likely good. The more recent work fixed deprecations only.

The UI is final, the slight implementation question mark around #279.2 only involves a detail of the admin UI, is believed to be fixed as much as @claudiu and @Adam can see, and the end user UX is likely far more important to review anyway.

AdamPS’s picture

Issue summary: View changes
Status: Needs review » Needs work

The tests now pass, good work, so I remove that item from the remaining tasks list. However unfortunately this has to go back to "needs work" as there are still some remaining tasks..

1.

avoid hard-coded reference to 'administer users' permissions

This is definitely not fixed - the hard-coded permission is still present at line 87 of patch 301.

3.

Add a test...

From a quick glance I don't think this has been done either.

I propose that these need to be done before a security review. The usability review could perhaps go ahead as the UI seems fixed.

AaronMcHale’s picture

@jonathanshaw @AdamPS
Great, thanks for confirming :)

sharma.amitt16’s picture

Status: Needs work » Needs review
FileSize
37.1 KB
899 bytes

Fix for test failure.

sharma.amitt16’s picture

Status: Needs review » Needs work

Apologies, I checked the first page only. Please ignore the previous patch.
REverting back to needs work.

sharma.amitt16’s picture

Status: Needs work » Needs review
FileSize
38.27 KB
1.83 KB

Uploading a patch for change raised in #306.1
Working for #306.3, it will take some time. I will upload that after completion.

Please review.

AdamPS’s picture

Issue summary: View changes
Status: Needs review » Needs work

Great thanks @sharma.amitt16

Here are my comments based on looking at the interdiff.

1.The latest patch appears to have resorted the use statements at the top of AccountForm.php. I understand that this is the preference of the Drupal coding standards, but I don't think we should fix it here - it's not the job of this issue to make unrelated minor changes.

2. In #284 I offered two options for the code to use. You have chosen the one that means that admins have to verify their own change. Is this definitely the consensus of the community? - I'm not sure but I think maybe not - we should get confirmation from an appropriate expert. At very least it doesn't match the issue summary which says:

This new mechanism is bypassed if the e-mail address is changed by an administrator.

AdamPS’s picture

Status: Needs work » Needs review
Issue tags: -Needs usability review
FileSize
37.22 KB
1.83 KB

I thought some more and it seems there is a clear precedent that the Admin role is trustworthy and competent. The could easily remove the admin role from their account with no way to get back. They could delete all site content. They could assign the admin role to an untrusted user. Drupal makes no attempt to prevent any of these. Therefore I propose that a user with 'administer users' permission should not need to perform verification. Here is a new patch that does that and fixes the comments from #311.

The usability review was done in #270 and #272 so removing that tag.

jonathanshaw’s picture

Status: Needs review » Needs work

#312 makes sense to me.
NW per #306

benjifisher’s picture

After the usability review in #270 and #272, a new one was requested in #291. So I am adding back the tag. Please make sure that the usability issues are clearly described in the issue summary.

I see that this issue still needs a release notes snippet. It may also need updates to the proposed resolution and remaining tasks, so I am adding a tag for that.

nimoatwoodway’s picture

Tested patch #312 with Drupal 9.0.3.

Tested
- NOTIFICATION OF OLD EMAIL
- VERIFICATION OF NEW EMAIL

Works like a charm! Thanks for your efforts!

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

voleger made their first commit to this issue’s fork.

voleger’s picture

Replaced deprecated method call in the test. Added review comments.

ptmkenny’s picture

Status: Needs work » Needs review
AdamPS’s picture

Issue summary: View changes
Status: Needs review » Needs work
Issue tags: -Needs issue summary update

Thanks @voleger, the new patch looks good. However it is still "needs work" for adding a test requested by AlexPott - see the issue summary for an explanation. I updated the issue summary so I removed that tag.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Darvanen made their first commit to this issue’s fork.

darvanen’s picture

Status: Needs work » Needs review

Wanted a rebased version but wasn't sure of the etiquette around rebasing a MR someone else created.... so I made a new one. Easier to pull my work into the first MR than to extract it in the case it was unwanted :)

Sorry if that was the wrong thing to do ¯\_(ツ)_/¯

New MR

  1. adds \Drupal\Tests\user\Functional\UserMailChangeTest::testWrongEmail
  2. addresses the code feedback from MR 437
jonathanshaw’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

The remaining tasks from the IS are now complete.

A usability review has been conducted; a new one was offered but never happened so I'm unsure it's needed.

A security review would probably be a good idea.

I'm tagging this RTBC to get a committer verdict on what further reviews are needed.

AaronMcHale’s picture

Thanks @jonathanshaw.

Yeah that was me and I completely forgot to follow-up on this, since its now RTBC I'm queuing this for review at #3218550: Drupal Usability Meeting 2021-06-18, thanks.

AaronMcHale’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Needs usability review

Hi everyone.

At #3218550: Drupal Usability Meeting 2021-06-18 we reviewed this issue based on the screenshots in the issue summary.

First of all, we wanted to highlight how much effort and work has been put into this so far, thanks to everyone involved; This is a really important issue and getting this in will be a really valuable change for Drupal.

The bulk of our review focused on the settings screen, specifically the "old email" template and the tokens.

Relating to the "old email", the overall question that we had is, why are we sending the old email if it's not actionable?

We considered how sites like GitHub would deal with this and what their standard messaging might be; It was recommended that the "old email" template for we should provide some kind of call to action, in the event this change was made maliciously or in error.

For example: If the old email address is still correct, but someone managed to compromise the user's account, chances are they will finish the process (clicking confirm, updating account information, change password, etc.) before the user has a chance to undo it. At that point any cancel/one-time URLs we provide will have been invalidated, so contacting site administrator would be the right course of action here. If a site has more specific business logic they can of course then update the template to meet their needs, but we felt that starting from a sensible default is important for account security.

With that in mind, for the "old email" template, we recommend discouraging the use of cancel-url token. The one-time-login-url token being available in that email template was also a concern; If the old email was compromised, we definitely don't want to provide a one-time login URL in it.

To summarise that, more work is needed on the "old email" template, providing something actionable. We specifically recommended providing the site email address. For example, this could say "If you did not intend to make this change contact [site-email]."

We also looked more generally at the list of tokens: It was not clear what some of them are, for example the [user:cancel-url] token is ambiguous; We assumed it was the URL to cancel the change, but it coudd also be the actual account cancelation URL. We recomended "mail-cancel-url" as an alternative name. We also noted that the tokens could use better descriptions in general, we did note that this should be done in a follow-up as it is probably a wider issue covering the other templates on that form, and it is a minor thing so shouldn't block this getting in.

Following on from that, and as a more general recommendation, we wanted to highlight that there should be as little user information as possible in the "old email". So for example, in the event that the user was changing their email address because their old email address was compromised, we wouldn't want to give the malicious actor a direct route to stop the change, so that's why providing a contact email address felt most appropriate.

We also looked at the subject line of the email notifications, the word "information" seemed redundant, "Email change ..." seemed enough or "Email change notification ..."

We then moved over to the user account screen and look at the status message wording. The first sentence in the message "Your updated email address needs to be validated" didn't work for us, we advise using an active voice and changing it to "You must confirm your email address."

So to summarise:

  1. More work is needed on the "old email" template, providing something actionable;
  2. For account security and good practice, we rrecommend reviewing whether the cancel-url and one-time-login should be available at all in the "old email" template;
  3. Email subject lines need updated;
  4. Status message text needs updating;
  5. Follow-up issue needs to be created (or an existing one found) to work on better display/descriptions of the tokens (probably for all of the message templates on the account settings page), maybe part of that could even be bringing token_ui into Core.

Most of these are minor tweaks, and once these are addressed this could go back to RTBC.

Again, thanks everyone for all the hard work on this, it's looking great overall.

-Aaron and everyone on the UX Call (Benji, Thomas, Ofer, and Cristina).

skyriter’s picture

Issue summary: View changes
rootwork’s picture

Apologies for pushing changes twice. This is actually my first time working in the new MR system (I know, behind) so I'm sorry if I did anything wrong.

The commits include a rebase, and address:

#328.1: Adds the line "If you did not intend to make this change, contact [site-email]."
#328.3: Changes the subject to "'Email change for [user:display-name] at [site:name]"
#328.4: Changes the first line of the status message to "You must confirm your email address."

I also added to the descriptions of the new parameters in _user_mail_notify() to clarify that mail_change_notification goes to the old address, and mail_change_verification goes to the new one, because at first I wasn't sure which was which.

As for #328.2, I didn't actually see any use of a cancel/one-time URL in the notification email (just in the verification email) but maybe that was changed in the meantime.

Leaving #328.5 for someone who has more history on this issue/area to articulate.

Out of scope for this issue, but I noticed that many of the email templates reference "clicking" a link rather than "using" or "following" (or some other term that would encompass both a click and a tap). Maybe also worth a follow-up?

DiDebru’s picture

Thanks for the patch it works like a charm.
Maybe some improvement is an event or hook to react on after the user has verified,
to also update e.g. NL providers.

Potential follow-up force setting:
After the user changes his email address he will be logged out and blocked.
After he verfies himself he will be unblocked and logged in with updated email.

jonathanshaw’s picture

Status: Needs work » Reviewed & tested by the community

I think this looks ready now.

#330 seems good.

It's not always easy to follow #328, it's possible they were using an outdated version of the patch or misunderstanding some things. The main point is that found no significant usability issues that are relevant to the latest patch.

Regarding tokens, the default notification email to the old address uses no tokens of questionable safety, but they are available if a sitebuilder wanted to use them. It's not clear whether #328 is for or against them being available, but I believe it's a good compromise and very much an edge case.

For improving the token UI we have #514990: Add a UI for browsing tokens.

AaronMcHale’s picture

Regarding tokens, the default notification email to the old address uses no tokens of questionable safety, but they are available if a sitebuilder wanted to use them. It's not clear whether #328 is for or against them being available, but I believe it's a good compromise and very much an edge case.

Casting my mind back to the UX meeting, I think it was just a concern around them being available and the potential for a site builder to unknowingly use them in a way that might result in a security issue for an account being compromised. As in, site builder provides the cancel URL as the only way to take action (not providing contact information) and how quickly that could become invalidated. It wasn't a particularly strong concern from memory, and if the default "old email" template has the site contact email address as the call to action then I think that's broadly fine.

Strictly speaking though it is not in the remit of the UX group to form opinions/recommendations on matters relating to site security, so we would tend to defer to the security team on this specific matter. Therefor, if we could get input on this point from the security team as to whether there is any potential real-world risks relating to these tokens being available in the "old email" template, then I think that would satisfy.

Thanks,
-Aaron

larowlan’s picture

Hiding patches because there's an MR here

Left some comments in the MR.

It looks like all of @alexpott's points from #279 are yet to be resolved.

Do we have a follow-up to add this functionality for non form-based interactions (e.g. JSONAPI etc)

darvanen’s picture

Issue tags: +DrupalSouth

Might take a crack at this tomorrow.

darvanen’s picture

Consolidated merge requests, favouring !437 as the original.
Cherry-picked work by @rootwork and @darvanen and pushed.

That should now address #319 within the MR.

What should I do with MR!776? Delete it?

larowlan’s picture

@Darvanen @quietone and I discussed the need for MailChangeController::getUrl at DrupalSouth.

We decided that instead of introducing new APIs it would be best inline those 7 lines into user.module and file a follow up to create a user url generator service.

This service could be the home of user_pass_reset_url and user_cancel_url too.

We'd then move the method from the controller to one of the two test classes (the one that uses it the most) and use that in tests, with a todo to remove it in favour of the service from the above follow up when that is done.

darvanen’s picture

Assigned: Unassigned » darvanen

Follow-up created: #3229038: Consolidate public APIs for generating user action URLs into a service

Assigning while I attempt to address feedback.

darvanen’s picture

Assigned: darvanen » Unassigned
Status: Needs review » Needs work


Re #279:

  1. Not sure what can be done here without introducing new permissions.
  2. A bit out of my depth here, don't quite understand the ask.
  3. Addressed by prior patch.
  4. I don't think the email is stored on the user prior to successful validation, so this method *only* works in its current state in the middle of ::submitFrom, I'm surprised the tests are passing.
  5. Inlined per #337
  6. Agree that test would be good, however, I don't think the email is stored on the user Entity until it is successfully changed, so we might have a vulnerability here.
  7. Pushed a commit.

Additional question:

The contributed Email Change Verification requires a user to log in to confirm their email change, are we sure we want to allow anonymous users to confirm the change?

darvanen’s picture

Re #279.6 I tested this flow manually and I've since realised that this issue patches user_pass_rehash to allow injection of the new email address in order to verify the requested email address. I'm thinking we should inline that API change and address it in #3229038: Consolidate public APIs for generating user action URLs into a service.

shalini_jha’s picture

Assigned: Unassigned » shalini_jha
darvanen’s picture

Feedback on #340 in slack from @larowlan:

looks like there's only one place its being used with the mail argument, so I think that's a good idea too, limit the API until its something we know we want in the follow up

darvanen’s picture

Re #279.2, the account settings form allows mail_change_verification and mail_change_notification to be set separately, which makes more sense to me than making the notification dependent upon verification:

Suggest instead of enforcing the logic in AccountForm we alter the submit function to send the emails individually instead of the current dependent behaviour?

darvanen’s picture

Assigned: darvanen » Unassigned
darvanen’s picture

Got some direction from @alexpott on Slack: "343 makes sense - if the system obeys the flags then I think we’re good. The code I highlighted in 279.2 does not. This will all need testing."

I think the reason the notification email was nested in a check on the verification email was to make sure it wasn't sent if the verification email was attempted and failed. So I've adjusted the code logic to keep that check in place, while allowing the notification email to still happen if we're skipping verification.

Flagging needs tests for the various email send scenarios as I understand them:

  1. Verification on / Notification on - verification succeeds: send notification
  2. Verification on / Notification on - verification fails: don't send notification
  3. Verification off / Notification on
  4. Verification on / Notification off
  5. Verification off / Notification off
darvanen’s picture

Issue tags: +Needs tests
andyrigby’s picture

Hi, we're having success with this patch, but unfortunately have hit an issue. If the user attempts to change their password and email address at the same time, they cannot verify their email address using the verify email link.

I believe it's down to the password hash generation, as this occurs only on the User entity save.

e.g.

  • User's password hash is "abc"
  • User changes their password and email address at the same time, on the same form.
  • The code clones the user's account, and sets the new email address
  • A verify password link is produced and sent in the email. Part of the URL token is based in the "abc" hash (see user_pass_rehash())
  • The parent::submitForm() continues, and a new password is set on the User
  • The User entity is saved and a new hash is created e.g. "xyz"
  • User clicks the link in the email
  • The tokens are compared, but the email link token contains "abc" and the generated one uses the current password, hash "xyz", so comparison fails.
  • User receives the message "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."

I had some success calling parent::submitForm() earlier, and calling User->save() in a couple of places in AccountForm, but it wasn't pretty.

We have a fair amount of custom code on this site - I was wondering if anyone was seeing the same issue? #10 alludes to the issue, so it might have been addressed and we're just seeing a conflict from custom / other contrib code.

darvanen’s picture

Yeah.... the password is used to form the comparison hash, and the hash is being generated and sent to the user before the new password is saved, just as you've described.

I've got some thoughts on that tangle in a follow-up #3229038: Consolidate public APIs for generating user action URLs into a service though they were based on loginTime changing rather than the password. I'll add a link to your comment there for discussion.

Not sure what to do about it for this issue. My first instinct is to pull that follow-up into this issue to make it available here but as @larowlan has pointed out, that will delay this issue considerably. But if we change the contents of the inlined code to use a different hash we're going to generate a heap more debate (rightly so) on this thread anyway.

I hate to say it but perhaps we need to flip the dependency and postpone this issue on #3229038: Consolidate public APIs for generating user action URLs into a service.

Unless... is there any way we can ensure Drupal only ever generates the email *after* all account saving activity in a request has been completed? I doubt it.

Custom code is one thing, pretty or not (I've made some pretty ugly class overrides myself) but I wouldn't want to ship this code with this obvious flaw. So thank you for trying it out!

darvanen’s picture

Ahhhhhhahahahaha oh dear.

The checks for whether the email should be sent or not based on the checkboxes shown in #343 are done *inside* _user_mail_notify. So that function returns null if either:

  1. The verification email is disabled, or
  2. the verification email fails

Hi, welcome to the party, my name's Max, yes I just got here too... /facepalm

I think we need to repeat the test for the setting in the submitForm method so that a null result from _user_mail_notify('mail_change_verification', ...) definitely means failure. I'll give that a try.

darvanen’s picture

Issue summary: View changes

Obviously this took me a few tries, and in the end I didn't change much. Here's an interdiff against #344 which is just before I started trying to alter the logic regarding the notification.

In short: I consolidated all of the tests required to determine if verification is required into a more readable format (happy to be told that's not how we should do it), and I moved the notification email outside of the verification logic with a flag to disable it if the verification email is attempted and fails.

I've taken a stab at the tests outlined in #345. I have no clue how to cover the event of an email failing to send (test #2 on that comment).

#347 remains outstanding, I've suggested an approach in #348 but I'm not convinced that's the right approach.

Leaving as NW on the above but this needs a few eyeballs other than mine now.

darvanen’s picture

FileSize
4.98 KB

Now with 100% more attachment.

Driskell’s picture

@darvanen Regarding #347, I think this is due to the parent::submitForm happening after sending the email? As it's parent::submitForm that will copy the new values to the entity and save.

Perhaps the fix here is to only send email once parent::submitForm is called. So it does mean keeping some logic at top to work out, "Do we need to verify?" and if that's the case we need to restore original email, then call parent::submitForm, then trigger the necessary emails.

I haven't tried this yet but hope to allocate some time on Monday (9am BST) to try it. But on the face of things it looks like it would ensure the emails use the latest password hash, with very minor changes. I'll check some of the other remaining tasks too it'll be good if this made it to a security review and eventual release :)

Driskell’s picture

Status: Needs work » Needs review

@darvanen I added one commit to add the test for the issue in #347. Sorry if I was meant to push to a separate MR it seemed to me the best approach.

I also added a potential fix for #347. It moves the email send to a separate submit handler. Which makes sense to me to keep the submit processing, save processing, and eventual notifications separate. It also means when the notifications are sent the account is fully saved so any new password is now hashed (which happens in the field plugin on preSave), fixing #347.

Probably one for later but I did wonder about the addition of $mail to user_pass_rehash. I wonder if in a separate issue it should be recommended to instead improve _user_mail_notify. If someone adds a $to parameter to override who receives the email it'll mean the user_pass_rehash does not need modifying. It also has the benefit that inside the "verify change email" notification and the "notify change email" the "[user:mail]" tokens are the same and consistent. It wasn't expected to me that they'd be different as the account wasn't updated yet... and perhaps at the same time a new "$params" would be worthwhile to provide more tokens to the emails, such as the new email in the case of the email change ones.

Furthermore, and this is probably an edge case, if the emails contain any other URLs added by a module using user_pass_rehash, they currently break - using one-time-login-url as a really bad example the URL will fail in the verification notification sent to the new email due to it using the new email for the hash that isn't saved against the account yet.

darvanen’s picture

Status: Needs review » Needs work

Of course! I was too deep in the problem to see we could solve it by running the parent submit first. Thanks @Driskell for giving it a go, and yes putting it on the same MR is fine, we can always rollback if need be (I wasn't sure about this either) - it's better than opening a new one, unfortunately you've revived the very MR I made that mistake with, I should delete that one.

Can you try your changes again on branch 85494-use-email-verification instead of 85494-use-email-verification-9.3.x? The latter has fallen well behind the current work which left a bunch of logic errors in the code you submitted.

I didn't realise all of this until I was through reviewing the logic, I'm going to submit that just so you can see where to watch out.

darvanen’s picture

As for the arguments around the emails and how they're used, I think that's valuable commentary for the follow-up: #3229038: Consolidate public APIs for generating user action URLs into a service

Driskell’s picture

@darvanen I've pushed to the correct branch now, sorry about that!

I did find that after my change the testMailChangeNoVerification was not working. It seemed before my changed it was sending a notification for any email change even if verification was disabled. This seemed a usability issue to me - especially as the default notification text contains "In order to complete the change you will need to follow the instructions sent to your new email address". I also found it was sending it even if the email DID NOT change.

So I added a test to ensure if email is not changed it does not send anything. I also adjusted the test for testMailChangeNoVerification to not expect any emails.

If we want to send a notification to old email when old email is changed I would suggest that would be a different template so a default can be provided without the "in order to complete the change" text. This template is then only used if the verification is disabled. Something I would suggest occurs in a different issue as it is unrelated to verification of email addresses.

darvanen’s picture

I agree the template could be changed, I didn't notice that. See #279.2 and #345 for a bit of backstory on that one... what's the default setting though? If a site admin deliberately turns off verification and leaves notification on, I say it's on them to ensure their email fits the bill.

I personally think since the notification email can be turned on completely independently of the verification email that if it's on, it should be sent regardless of whether the verification is required/requested - EXCEPT if the verification email is attempted and fails.

You're right that I left a hole in the logic where the notification gets sent even if the email doesn't change.

Driskell’s picture

@darvanen I'm on Slack (Jason.woods) if it helps to chat things over to push through.

Thinking about the email change notifications I am thinking the following:

* The mail_change_notification is labeled "Notify user when email changes" but actually we're not doing that - we're emailing them to let them know a REQUEST was made to change it (when verification is enabled). When the email finally is verified and the change happens there is no notification it was done. Possibly this is a further argument for separate "requested" notifications and "email changed" notification - where the "requested" ones are only used is verification is enabled. This to me is extremely useful from a user perspective as it both notifies them someone is trying to change the email, and notifies them if it was successful. It would also mean admins don't need to think about changing email templates when adjusting configuration and ending up getting an email forwarded from a confused person down the line. Further it improves on usability review in #328 as the benefit of this notification is made available even if verification is not desired.

* I also am unsure about the checkbox for verification being inside the emails. All current checkboxes in there are about enabling and disabling notifications. The checkbox for enabling notifying of old email a change happened is fine as that's just on/off. However, the enable of verification email does more than just enable the email - it enables verification preventing email change until action - so it's more wide reaching. The configuration is also called "notify.mail_change_verification" - I think it might be unexpected to some developers that turning off the notification turns off verification. For example, turning off "notify.cancel_confirm" won't stop the process of confirming it would just stop the email. Perhaps it should be top level like the registration approval settings? I appreciate this isn't something raised in the usability reviews though.

* Regarding verification email send failing - I don't think this can be acted upon. So my last push removed some code on that as it was unreachable except in case of failure to send email. Just because the verification failed to send doesn't mean we shouldn't send the other email (which might fail too?). Core only seems to check this in order to adjust messaging to the user to say whether sent or not, or to raise an error that send failed. Not sure either of those cases apply except perhaps the error message as you wouldn't be able to confirm the email change.

What are your thoughts? I've already done some changes locally to split the template and move the verification option to top level and happy to contribute bits of it if we think it's a good approach and improves upon what's here already.

Driskell’s picture

@darvanen Just a thought - does the REST API need to be taken into account? It's an area I'm not really familiar with so unsure if a regular user could update their account via REST API.

darvanen’s picture

@Driskell considering the age of the issue and the fact that it has gone to the UX review team at least once I wasn't game to change up the form. However, if I was designing this from scratch I would:

  1. Create a fieldset under 'Registration and cancellation' on the account settings page called 'Email change configuration' or similar
  2. Have settings in there for:
    1. Require confirmation of email change from old address
    2. Require verification of new email address
    3. Notify old address of [all/successful] (as a switch) email change requests
    4. Notify new address of successful email change requests
  3. Put the emails where they currently are and add the missing templates from the list above
  4. Allow site-builders to change the redirect destination upon successful (or not) email verification, a bit like #3228864: Add events for setRedirect and validationSuccess but with more UI.
  5. Hook into a preSave or Update function if possible instead of the AccountForm so as to cover all manner of API requests like REST that you brought up and graphQL that I want covered.

All of the settings would be independent of each other, e.g. one could turn on notifications to the old address and no other emails in the case that they don't want verification but do want to let the old address know what has happened and how to deal with it if it's an issue (seems a fairly common pattern in SaaS these days).

I doubt I would ever use all of the options but Drupal's strength is its flexibility, so I would want site builders to be able to create the flow they want out of the options available to them there.

Some of these things mirror what you've written.

As we discussed on Slack, this is opening a rather large can of worms. I'd rather not take this line of thought much further without some direction from the UX team. I think we can arrange that by requesting to present the issue at the weekly meeting.

There's a module

Just a note to anyone despairing that this will ever get off the ground... I've found https://www.drupal.org/project/email_change_verification does a good job, if in a slightly different fashion to how this patch currently aims to operate. I'm honestly surprised it has such a low uptake.

AaronMcHale’s picture

Re discussion in #358-360: This issue is getting quite long and hard to follow, so I'm inclined to recommend that we try and get it over the line with the UI as is and then make incremental improvements in one or more follow-up issues, especially given that the current UI has already been signed off. Splitting the improvements in this way will be much easier to manage and much easier to review. An example of this approach is #3199972: Improve user interface text on the account cancellation screen and its various follow-up issues.

It is important to keep in mind that the email templates we provide are just what we consider sensible defaults, the responsibility is left up to the site builder to ensure they make sense for their specific configuration, it would be impossible for us to cover all the various use cases out of the box. We had a lengthy discussion to this effect during the last UX review of this issue.

Overall this is a very positive improvement to Drupal, even if it's not perfect, it's still better than the current state.

Driskell’s picture

@AaronMcHale @darvanen Thank you both. I agree entirely. I've now moved to Needs review. It is entirely a huge benefit as is!

I'll hold on raising additional bits so I can save time to help push this forwards first.

Driskell’s picture

Status: Needs work » Needs review
voleger’s picture

Related contrib module list:
- https://www.drupal.org/project/email_confirm - D7 only
- https://www.drupal.org/project/email_field_confirm - D7 only
- https://www.drupal.org/project/email_confirmer - D9
- https://www.drupal.org/project/email_change_verification - D9

It is worth tracking such modules as this change will require follow-up feature support issues for the related version of the modules.

jonathanshaw’s picture

It would really help if someone could document what points are still outstanding for work or discussion ... it's not easy to follow the recent discussions.

darvanen’s picture

@jonathanshaw I feel you, my apologies for my role in that.

#350 was my attempt at a summary at the last point that I did any work. If your request is still outstanding tomorrow (it's late here) I'll review @Driskell's work against the status at #350.

Driskell’s picture

Issue summary: View changes
Issue tags: -Needs tests

The remaining tasks in the OP are the same with exception that #347 is fixed now, and #342.2 wasn't implemented as it's not something easily tested, and is a rare edge case which I have checked and behaves as described (if fail to send verification email to new email, don't send the notice to the old email either).

Everything else I believe is addressed so just needs a fresh code review, and a security review, and some testing feedback. The usability review was completed and changes requested done so I don't believe needs that again. I will remove needs tests flag as those are all done.

Latest discussions involved me raising a few things that can be improved but @AaronMcHale, @darvanen and I believe that although there are things that could be improved, after 15 years and with the positive improvements this will make to Drupal Core it would not make sense to revisit everything here, so I'll raise separate issues later on if needed to tweak. The code, as is, provides significant benefit and does as described and UX review has already concluded it would be OK with the final requested copy changes made. Of course, maintainers needs to make that final judgement.

Hope this helps move things! I have updated OP with what I think is the current status.

Driskell’s picture

Issue summary: View changes

Just added another note to OP for the #270 follow up questions - as the first two are generally apply also to Account cancel email form thus why I am thinking follow up issue for that.
The last 2 questions seem like separate issues too to improve things and will need a lot of thought (history of changes / revisioning, making the request visible in the admin / storing the new email which is currently not stored we just generate a link containing hashed token against new email and the new email)

darvanen’s picture

By making this notification contingent on verification #279.2 is reopened.

Considering this, and the age of this issue, may I suggest we take the path of least resistance (notify the old email address *every time*) and open a follow-up to make it configurable whether a notification is sent based on verification?

larowlan’s picture

Considering this, and the age of this issue, may I suggest we take the path of least resistance (notify the old email address *every time*) and open a follow-up to make it configurable whether a notification is sent based on verification?

That seems like a reasonable approach - can we get a follow-up issue opened (this issue is already at 370 comments) to add this as a configurable feature?

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

oknate made their first commit to this issue’s fork.

darvanen’s picture

@voleger could you change the target branch to 9.4.x again? I can do the rebase if you like.

larowlan’s picture

I've changed the target to 9.4.x @Darvanen

pfrenssen’s picture

FileSize
42.28 KB

Now that we have moved to 9.4.x the patch doesn't apply any more to 9.3.x. Here is a backport to 9.3.x for whoever needs it.

Drupal's JavaScript development dependencies are not installed. Run 'yarn install' inside the core directory.

This failure is reported by the test bot, but the underlying failure that is logged is this:

error An unexpected error occurred: "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz: Request failed \"503 Service Unavailable\"".

I wasn't able to find any drupal.org issues related to this, but according to #7996: registry.yarnpkg.com responds with 503 error code quite frequently it looks like it could be a temporary availability issue.

darvanen’s picture

The error will likely be because I wasn't able to complete the rebase yet, that patch will help a lot, thanks. Late here, will do tomorrow

dww made their first commit to this issue’s fork.

dww’s picture

Issue tags: +Bug Smash Initiative

This issue was mentioned in the #ux channel. Seemed blocked on a trivial phpcs fix, so I just re-rebased to the 9.4.x branch and fixed AccountForm.php (actually, phpcbf fixed it, I just committed and pushed). 😉 Haven't looked at any code yet or read the issue, but hopefully the bot will have valid results now. I'll try to more closely review "soon", but if there's anything else that needs fixing in the meanwhile, lemme know.

Thanks,
-Derek

jonathanshaw’s picture

Needs followups creating per #370 and the IS.

Needs security review for #333.

dww’s picture

I believe the test failures are from the user.mail.yml changes still being 1 giant newline-tastic string, not the nice multiline config that the rest of core uses now that #2844452: Export configuration YAML strings as multiline is done. At least core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php is passing for me again locally. Also re-rebased. Let's see what the bot says now.

benjifisher’s picture

I am adding some related issues based on earlier comments:

I am not sure I described #3265076 well. I plan to do some manual testing of this issue, and I will update the issue description then.

benjifisher’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record updates

I did some manual testing. I enabled both e-mails, then tried to change the address for a non-admin account. There are two problems, but they should both be easy to fix.

First, the e-mail sent to my old address contains the literal text "[site-email]". The correct token is "[site:mail]". Make sure to fix it in both the config file and the update function.

The second problem is that the one-time link in the verification e-mail does not work. The problem is that mail_change_timeout is not set in user.settings. We have to update the config in the Standard profile with the new settings, and we also have to fix other profiles:

find core -name user.settings.yml
core/profiles/standard/config/install/user.settings.yml
core/profiles/minimal/config/install/user.settings.yml
core/profiles/demo_umami/config/install/user.settings.yml
core/modules/user/config/install/user.settings.yml

I am adding the tag for a change record update, since that same change will have to be made in non-core profiles, if they override user.settings.

dww’s picture

Status: Needs work » Needs review

Thanks for the manual testing, @benjifisher, great finds! Pushed a few commits to address both points. The relevant tests are all still passing locally, I assume the bot will be happy.

But does this point to a gap in our test coverage? How come we didn't notice that the link wasn't valid until manual testing? I still haven't gotten through the whole diff yet, so I haven't looked closely at the tests, but core/modules/user/tests/src/Functional/UserMailChangeTest.php sure looks pretty thorough on a brief skim. However, do we need to add coverage for this before we can merge?

benjifisher’s picture

I think "need to" is too strong, but it is good practice to add test coverage for any bug fix.

I care more about updating the change record. I already added "Distribution developers" to the audience of the change record, but I did not add any text.

dww’s picture

Issue summary: View changes

Thanks to #2852557: Config export key order is not predictable, use config schema to order keys for maps we can get the ordering right in user.schema.yml and have deterministic results for exported config. 🎉 Drupal\KernelTests\Config\DefaultConfigTest passes locally once again, so I'm hoping the bot is happy, too. Sorry for all the noise in here.

Also, very minor tweak to the release note snippet.

I still haven't finished reading the full diff relative to 9.4.x. I also notice there are some unresolved threads in the MR, but I haven't looked into those closely yet, either.

Agreed on the need for CR updates, but I won't be working on that until I'm at least done with reviewing the diff and keeping the bot happy. 😉

Thanks,
-Derek

p.s. I also just noticed that the 9.4.x version of this has an upgrade path using drupal-8.8.0.bare.standard.php.gz. Sadly, that fixture is already gone from 10.0.x. So when a core committer wants to commit this to 10.0.x first, we're going to need a separate patch. Trying to figure out the right way to handle that from an MR / patch perspective. My thought is to wait on this until the 9.4.x patch is RTBCed and then push a new branch to the issue fork, rebase to 10.0.x, change the path to the fixture only in that branch, and open a 2nd MR here targeting 10.0.x. But to avoid complication, let's not do that until we have to.

benjifisher’s picture

Status: Needs review » Needs work

Usability review

We discussed this issue at #3263897: Drupal Usability Meeting 2022-02-18. That issue will have a link to a recording of the meeting.

Unlike the last time we discussed this issue, we applied the patch and tested functionality instead of relying on screenshots.

Here are some suggestions:

  1. Indicate on the user-edit form when an address change is pending. I think this has already been suggested. Perhaps it can be put off to a follow-up issue. When things work well, the pending state will not last long, so it will not make much difference. But things do not always work smoothly.
  2. Add a warning message to the site status report if the e-mail to the old address is not enabled.
  3. Most of the other messages now end with "-- [site:name] team". Add that to be consistent.
  4. Instead of "one day", use "24 hours" in the message. Perhaps the default should be shorter for security reasons, in which case we will not use "one day" anyway.
  5. Follow-up issue: add a token for the expiration timestamp.
  6. The default e-mails use the "[site:mail]" token. This should be added to the list in the help text.
  7. Add an admin UI to manage the setting for how long the verification link is valid. #3265346: [PP-1] Manage user account timeouts in the admin UI

There are a lot of improvements that should be made to the help text. Earlier comments on this issue mention some, and those changes are out of scope for this issue. I want to add one more: the closed tabs should have an indication of whether the email is enabled or disabled.

For the record, the meeting attendees were

benjifisher, Antoniya, beatrizrodrigues, victoria-marina, shaal, rkoller, tmaiochi, worldlinemine, Johnny Santos, kimberlly_amaral

dww’s picture

Status: Needs work » Needs review

Re: #387: Thanks for the review!

  1. Moved to #3265315: [PP-1] Indicate on the user-edit form when an address change is pending.
  2. Moved to #3265316: [PP-1] Add a warning message to the site status report if the e-mail to the old address is not enabled..
  3. Fixed.
  4. Fixed.
  5. Moved to #3265317: [PP-1] Add a token for the email verification expiration timestamp.
  6. Fixed.

Re: #386.p.s.: Yay, we landed #3263886: Copy drupal-9.3.0.bare.standard.php.gz and drupal-9.3.0.filled.standard.php.gz from the Drupal 10 branch so now we've got a drupal-9.3.0.bare.standard.php.gz fixture we can use in all relevant branches. 🎉 Rebased and updated UpdateMailChangeTest to use that. Passes locally. So that mostly helps. However, sadly the patch won't apply to 10.0.x user.post_update.php. 😢 So we're still gonna need separate patches / branches once this is actually RTBC...

benjifisher’s picture

@dww, thanks for the updates!

I just added another child issue: #3265346: [PP-1] Manage user account timeouts in the admin UI. I will edit my earlier comment, since this was suggested at today's meeting.

We might get some push-back from postponing the status indication (#387.1) to a follow-up issue. I think we will get less push-back if we change the default from 24 hours (86400s) to something shorter (part of #387.4).

AaronMcHale’s picture

Status: Needs review » Needs work
Issue tags: +Needs follow-up

Catching up on the recording from #3263897: Drupal Usability Meeting 2022-02-18, unfortunately I was unable to attend the meeting.

The first part of the meeting looked at some of the remaining tasks:

Should there be some indication that additional form fields are available once checked? (NOTE: Same issue in Account Canceled email form)
Should the suggestions exist prior to checking the box? (NOTE: Same issue in Account Canceled email form)

Potentially, but that should be looked at in a follow-up issue, because it impacts the other fields on the form, and what we do here should behave consistent with the other fields. So for this issue, no that's not something we should worry about, but in a follow-up issue it should be discussed for all of the email templates.

One concern I have is that, by using progressive discovery, in order to change e.g. one of the body fields, if you have one of the checkboxes unchecked, you have to check the box, change the field, and then uncheck the box again before saving. If you don't know that the field exists you wouldn't know where to go to change this. Why this is a problem is because it creates inequality between changing something via configuration and changing something via the UI, when you change the same field via configuration, it doesn't matter whether the box is checked or not, you can still find and change the key. Now, that may or may not be a problem, but I think a separate issue to discuss this is appropriate.

Tagging needs follow-up for that.

Should we make the request visible as pending to user and/or admin until completion?

Would be nice to have, happy for this to be dealt with in a follow-up, as it would be good to have the space to refine what that looks like. Glad to see #3265315: [PP-1] Indicate on the user-edit form when an address change is pending has been created.

I would add that there should also be a link in that message to resend the verification email, the verification link in the email might have expired when the user finally gets to it or maybe the email didn't arrive. That is also quite a common pattern with online services these days. I will also add this to the follow-up issue.

The next part of the meeting looked at more general improvements/recommendations:

Add a warning message to the site status report if the e-mail to the old address is not enabled.

Agree with this, and good to see the follow-up issue has been created #3265316: [PP-1] Add a warning message to the site status report if the e-mail to the old address is not enabled..

I would also like to see this warning appear on the account settings form itself in the same part of the form as the email settings. I will also add this comment to the follow-up issue.

[From the recording, referring to the account settings screen] Benji: One thing I don't like is that there's no indication on the closed tabs whether or not the email is enabled.

I think this was missed from the review comment #387, but I think this would be a nice improvement to add, so we also need a follow-up for this.

So, 2 more follow-up issues required.

AaronMcHale’s picture

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

darvanen’s picture

FileSize
21.89 KB

Rerolled MR437 as a patch for 9.5.x

As far as I can tell, all of the feedback so far has been addressed.

darvanen’s picture

FileSize
44.59 KB

Oops, here's one without the patch rejections inside the patch file....

smustgrave’s picture

Status: Needs review » Needs work

Tested by applying patch

Running drush updb and enabling the 2 new forms under account settings
Creating a content editor user with email something@test.com
Verified in mailhog I got an email about a new account being created
Changed the email with my admin user account and received no email
Change the email with the logged in content editor I just created and received no email.

Please advise if I missed a step

darvanen’s picture

That's odd, I just followed the same steps and it worked fine. Just checking these are the two options you turned on?

I believe it's by design that no email is sent when the admin user changes an email (see IS) in case you were indicating that as a failure.

smustgrave’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
75.41 KB

Okay I tested against this morning and it appears to be working. My test account may not have been fully "verified" first after creation.

If anyone feels it shouldn't be marked RTBC please move back.

quietone’s picture

Status: Reviewed & tested by the community » Needs work

I have not read the 398 comments in this issue. :-) But I see that there are quite a few remaining tasks still open. Are they complete or is there work to do?

Can someone familiar with this issue check the open remaining tasks and update the Issue Summary accordingly? Thanks.

Also to do is to get a security review and the change record needs updates.

Thanks.

brad.bulger’s picture

I imagine there has been some back and forth about this, but "require confirmation of changes to email" and related settings seem like a fairly major config setting, that belongs up with "require email verification when a visitor creates an account", not hidden down in the template. I couldn't figure out why the patch was having no effect...

brad.bulger’s picture

I tried applying the 9.4 diff of the fork as a patch before installing, and mail_change_timeout did not end up being defined.

If I installed Drupal on the normal core code first, and then applied the patch, then running updatedb ran user_post_update_mail_change and mail_change_timeout was defined.

Also, it seems like there are no log entries caused by any of the steps of changing an email address. Probably there should be.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

brad.bulger’s picture

It ought to at least be a configurable choice to require being logged in to the account that requested the change for the confirmation link to work. Allowing an anonymous user to immediately confirm the change by using the link is not something we would prefer, at least, I imagine we're not alone. It would need to redirect anonymous users through login, and give users logged in to a different account (including admins) some kind of error message.

brad.bulger’s picture

I am using the patch for 9.4.x from the issue fork, and the problem described in #159.9 above doesn't seem to have been fixed.

1) Log in to user account
2) Change email address
3) Log out
4) Log back in to same account
5) Load mail-change link from mail

This fails with a bad hash match.

DiDebru’s picture

Patch doesn't apply for 9.5.2

DiDebru’s picture

Issue tags: +Needs reroll

Patch doesn't apply for 9.5.2

No Sssweat’s picture

Patch doesn't apply for 9.5.2

Why bother? It has been 16 years...

Clearly this tells me that this just a "nice to have" feature that most people are living happily without.

Plus, it seems there are a lot of hoops, walls, and gates to jump through to get this into core.

This should've been moved into contrib or gotten scrapped all together a long time ago.

AaronMcHale’s picture

Re-rolls should be done for the latest Core branch, so 10.1.x, not 9.5.x.

Just because an issue has been around for a long time, and still has some work to do, doesn't make it any less valuable or worthy of inclusion in Core. If this functionality is needed for a site there are already contrib modules that will do this.

karishmaamin’s picture

Status: Needs work » Needs review
FileSize
44.83 KB

Re-rolled patch at #395 against 10.1.x. Please review

Status: Needs review » Needs work

The last submitted patch, 409: 85494-409.patch, failed testing. View results

mxr576’s picture

Updated the version information in the related CR and also triggered a new build on #409 because it might have failed for transient errors before.

mxr576’s picture

Issue summary: View changes

Unfortunately, it also seems to me that the latest patch still solves this issue on the form level only and does not cover other presentation layers, like APIs. I searched for "JSONAPI" in this thread and I have only found that @larowlan was asked about that in #334 only... even though it is a fundamental building block of Drupal core now and there was an API first initiative.

Added #344 to remaining tasks to have a clear answer for that.

mxr576’s picture

Issue summary: View changes
mxr576’s picture

Why bother? It has been 16 years...

Clearly this tells me that this just a "nice to have" feature that most people are living happily without.

Let me gently disagree with this statement in #407. This is very much a requirement by enterprise companies and the fact that this is not OOTB in Drupal makes Drupal less appealing to them. Selling this as custom development does not sound good to them.

Being that said, we have also developed our custom solution once or twice, it is not perfect either, therefore it does not worth sharing either. Plus the customer owns the code.

luenemann’s picture

Issue summary: View changes
  • fix #334 reference
  • convert comment references to links with dreditors help
franksj’s picture

There is a new level of indent in user.schema.yml so I had to adjust the spacing for the user.mail changes to make this apply to 9.5.4.

sarvjeetsingh’s picture

Updated patch for 9.5 support

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

J-Lee’s picture

Status: Needs work » Needs review
needs-review-queue-bot’s picture

Status: Needs review » Needs work
FileSize
84 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

mrinalini9’s picture

Status: Needs work » Needs review
FileSize
41.62 KB

Rerolled patch #417 for 11.x branch, please review it.

Thanks!

karishmaamin’s picture

Fixed custom command failure #421. Please review

vsujeetkumar’s picture

Fixed CCF issues, Please have a look.

Status: Needs review » Needs work

The last submitted patch, 423: 85494-423.patch, failed testing. View results

vsujeetkumar’s picture

Fixed fail test cases, Please have a look.

larowlan changed the visibility of the branch 85494-use-email-verification-9.3.x to hidden.

larowlan changed the visibility of the branch 85494-use-email-verification-9.4.x to hidden.

larowlan changed the visibility of the branch force-rebase to hidden.

larowlan’s picture

quietone’s picture

Locally, I apply the diff to 11.x and fixed the patch rejections from 5 files,

./core/modules/user/user.post_update.php.rej
./core/modules/user/src/AccountForm.php.rej
./core/modules/user/src/AccountSettingsForm.php.rej
./core/modules/user/config/schema/user.schema.yml.rej
./core/modules/user/user.module.rej

They were all straightforward except for AccountSettings form. That needed extra work due to #3382510: Introduce a new #config_target Form API property to make it super simple to use validation constraints on simple config forms, and adopt it in several core config forms. The it failed spell check and I changed usages of E-mail and Please. I also learned that the AccountSettings form does not have a test and I did not look for one.

To do because of the changes I made to rebase.

  • Is there an issue for adding a test of AccountSettings Form? if not, make one.
  • Review all comments
  • Review all user facing text to make sure it aligns with the Usability review.
quietone’s picture

Issue summary: View changes

There is a test of the Account settings form and it is failing.

Lendude made their first commit to this issue’s fork.

Lendude’s picture

Issue tags: -Needs reroll

Account settings form has a test, and that was failing due to the changes here, so updated that test with the new settings. Change to Account settings form for two fields that needed to use #config_target

Fixed the autowire test and added some type hints and constructor property promotion while I was at it

larowlan changed the visibility of the branch 85494-use-email-verification to hidden.

larowlan’s picture

Issue summary: View changes
Issue tags: -Needs security review

Pushed some minor cleanups.

Left a review from a security POV

Added new tasks to the issue summary as follows:

  1. I don't think we should be cloning the user and setting the email on it, it takes one contrib hook to call $user->save() and the email change is permanent. This means not reusing user_mail_notify for sending the verification email, and a custom case in user_mail for this case. We should keep the account as is and pass an additional param for the new email.
  2. The controller needs flood control/protection to prevent brute force. This is the same as \Drupal\user\Controller\UserController::resetPassLogin
quietone’s picture

Made what I hope are improvements to the comments and some cleanup. Then did the low hanging fruit from @larowlan review.

Edit: s/handing/hanging/

darvanen’s picture

I've made an initial attempt to add flood control. It passed a local test of one test class but then my rig broke and I need to stop for now so pushed as-is.

dww’s picture

Rebased to 11.x, removed a few stray "revert" commits that had ended up in this branch.

Pushed a commit that fixes a new bug in MailChangeController. UserMailChangeTest was failing (in GitLab CI and locally) because it was triggering this fatal error:

Error: Call to a member function get() on null in Drupal\user\Controller\MailChangeController->page() (line 49 of core/modules/user/src/Controller/MailChangeController.php).

That gets the test passing locally. Let's see what GitLab CI thinks.

Leaving NW since there are still a lot of un-addressed MR threads I haven't gotten to, yet. Only trying to get back to green tests for now.

dww’s picture

FileSize
937 bytes

The MR branch had conflicts when rebasing to 11.x due to #3277003: Harden user_pass_rehash() against attack. Since this is a security hardening issue, and these details are so important, I'm attaching a file showing the rebase conflict, and how I decided to merge the two versions. Would be wise for someone else to review this and confirm I've done it right, such that both this issue and 3277003 are happy.

darvanen’s picture

#440: I've checked the change, and searched for any similar areas in the full diff, looks good to me.

catch’s picture

Double checked #440 as well and logic looks the same to me too.

larowlan’s picture

Copying comms from slack about the fail in #440

I think this might be a manifestation of the remaining point in my review regarding overloading of user_mail_notify
because the hash is being generated with the new email (because user_pass_rehash considers email now)
see line 145 of MailChangeController, we don't pass an email there, so it now uses $account->getEmail and because account is a cloned version of the user with the new email (which I said was dangerous) this is happening
pretty sure that validates my reservations about using user_mail_notify with a cloned version of the account
the risk of the temp email getting into places it shouldn't was a genuine fear it seems
but in the controller it uses the old email so the hash doesn't match

quietone’s picture

Issue summary: View changes

I checked the old MR and ported the three unresolved comments/question to the current MR. My apologies if that is repeating answered questions. I figured it was better to do that instead of me reading all the comments to get up to speed.

quietone’s picture

Status: Needs work » Needs review

I did a bit of work on this. This is new stuff for me so I just did what made sense to me today. There are still unresolved comments to address but it would be good to review the changes I made.

InaW changed the visibility of the branch 85494-use-email-verification to active.

InaW’s picture

FileSize
43.11 KB

I rerolled the patch from #409 for 10.2.3

larowlan’s picture

Status: Needs review » Needs work

Left a review, there are also some aspects of #279 that are yet to be addressed

larowlan’s picture

Category: Bug report » Feature request

We discussed this again in the monthly bugsmash meeting and resolved to move this to a feature request because:

a) it is adding new features
b) there is a contrib module for this

Involved in the discussion were catch, griffynh, Amber Hines Matz, quietone, longwave and myself.

lawxen’s picture

Reroll #417 for drupal 9.5.11 without test code

lawxen’s picture

FileSize
24.02 KB