Skip to content

Email confirmation for changing email in Settings#27277

Merged
PVince81 merged 11 commits intoowncloud:masterfrom
imujjwal96:master
Mar 22, 2017
Merged

Email confirmation for changing email in Settings#27277
PVince81 merged 11 commits intoowncloud:masterfrom
imujjwal96:master

Conversation

@imujjwal96
Copy link
Contributor

@imujjwal96 imujjwal96 commented Feb 28, 2017

Can be tested at: http://163.172.11.243:8080/core
Username: owncloud
Password: password

Description

When a user sets his/her email address for the first time, a confirmation mail is sent to the inputted address. From then onwards, every time a user tries to change/remove the email address, a confirmation mail is sent to the previous mail.
It also uses token validation (similar to reset password.)

Set Email / Change Email button added in front of the email input. Instead of changing email at every key delay, confirmation mail is sent when the user clicks on the button or when an Enter is pressed. Proper tests have been done.

Related Issue

Issue: #7326

Screenshots (if appropriate):

screenshot_20170228_164833

screenshot_20170228_165023

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@mention-bot
Copy link

@imujjwal96, thanks for your PR! By analyzing the history of the files in this pull request, we identified @DeepDiver1975, @phisch and @mmattel to be potential reviewers.

.htaccess Outdated
<IfModule pagespeed_module>
ModPagespeed Off
</IfModule>
#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Didn't intend to add them. Guess they were added automatically. Will delete them in the next push.

if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*12) ||
$user->getLastLogin() > $splittedToken[0]) {
$this->config->deleteUserValue($userId, 'owncloud', 'changemail');
throw new \Exception($this->l10n->t('Couldn\'t change the email address because the token is expired'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to always say "invalid", so an attacker cannot guess whether a token used to be valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This piece of code is inspired from the Reset password feature (tbh a copy+paste). Guess we'll have to correct it there too.

* @throws \Exception
*/
public function sendEmail($user, $mailAddress) {
$token = $this->secureRandom->generate(21,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some indent problems

<input type="email" name="email" id="email" value="<?php p($_['email']); ?>"
placeholder="<?php p($l->t('Your email address'));?>"
autocomplete="on" autocapitalize="off" autocorrect="off" />
<input id="emailbutton" type="button" value="<?php if(isset($_['email'][0])) { echo $l->t('Change email'); } else { echo $l->t('Set email'); }?>" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to display an indicator that tells whether the email address has been confirmed already or whether it's still pending ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into that.

[
'status' => 'success',
'data' => [
'message' => (string)$this->l10n->t('Email Sent.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also display a text like "An email has been sent to this address for confirmation".

Since the text is long, it might fit better in an orange temporary notification: OC.Notification.showTemporary(...) instead of inline.

Copy link
Contributor Author

@imujjwal96 imujjwal96 Mar 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PVince81 All notifications in the settings/personal route work similar to what i implemented. In fact the reason I did it was to maintain consistency. Changing the approach in one would require change in all of the forms in that page

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, thanks

@PVince81
Copy link
Contributor

PVince81 commented Mar 1, 2017

@imujjwal96 it seems you linked to the wrong issue ? #6633 is about limiting password reset.

I think there was another ticket about sending a confirmation email.

@PVince81 PVince81 mentioned this pull request Mar 1, 2017
9 tasks
@imujjwal96
Copy link
Contributor Author

@PVince81 A request for this feature was made in one of the comments of this issue so I referred it. I will search for the issue (if it is raised) and refer that instead.

@imujjwal96
Copy link
Contributor Author

@PVince81 All checks have passed. I guess this PR is ready for a merge. Please have a look.

Copy link
Contributor

@PVince81 PVince81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is a great addition!

Please see my comments for several minor issues in the code.

I'm going to test this now.

* @throws \Exception
*/
public function sendEmail($user, $mailAddress, $action='change') {
$token = $this->secureRandom->generate(21,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent is off

$userObject = $this->userManager->get($user);
$email = $userObject->getEMailAddress();

if ($action == 'change' || $action == 'add') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @param string $userId
* @param string $mailAddress
*/
public function setEmailAddress($userId, $mailAddress) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent is off

* @param $mailAddress
* @return TemplateResponse
*/
public function changemail($token, $userId, $mailAddress) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changeMail ? (camel case)

* @param allowEmptyValue if this is set to true the callback is also called when the value is empty
*/

jQuery.fn.Enter = function (callback, allowEmptyValue) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enter lowercase

<p class='hint'><?php print_unescaped($error['hint']) ?></p>
<?php endif;?>
</li>
<?php endforeach ?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could also be in a generic status template but only displayed if errors exist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If showing status on the notification bar in the settings url is to be implemented, then i guess this will also be shown as a notification

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Yes, it could make it easier as it would be the same code path than the success message.

.htaccess Outdated
<IfModule pagespeed_module>
ModPagespeed Off
</IfModule>
</IfModule> No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change is not needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no idea how this is messed.

* @param string $userId
* @throws \Exception
*/
private function checkEmailChangeToken($token, $userId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might need to check if $user is null and show an error/exception message. If the user was deleted in-between it could cause trouble.

);
}
$action = 'change';
$message = 'An email has been sent to the old address for confirmation';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use the t() translation function here directly instead of below.

the reason is because there's a bot that parses the code to find the texts to submit for translation and it can only find the texts if they are directly wrapped in t()


// delete user value if email address is empty
$user->setEMailAddress($mailAddress);
if ($mailAddress == '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use strict equality === (also check other places)

@PVince81
Copy link
Contributor

PVince81 commented Mar 8, 2017

Comments/tasks

  • TODO: don't send a confirmation email if the email was set by the administrator in the users page or provisioning API:

  • TODO: instead of showing a full page "Email changed successfully", please redirect to the settings page URL with the correct section and display a yellow notification at the top "Email changed successfully". This way the UX is better and the user is back in ownCloud instead of being on a plain page. (let me know if you need help, the notification thing can be a bit tricky)

  • I wonder what should happen if a user has two OC accounts and confirms their email address in both. I suppose this is an acceptable scenario and nothing needs to be done here.

  • BUG: email field in personal page still seems to auto-save without me having to click "Set email" (you might need to rebase onto master to see this happening)

  • BUG: with a non-admin user, if I open the link from the email I get "Logged in user must be an admin". Maybe you need to add @NoAdminRequired in your controller.

  • BUG: only accept the email link when logged in as the same user, not a different one

  • BUG: when I clear the email field it still sends an email. Is this intended ?

  • should we add a config.php switch to let admins disable email confirmation to go back to old behavior ? not sure if this is desirable. CC @pmaier1 @Peter-Prochaska

Test plan:

  • TEST: changing own email as admin works
  • TEST: changing own email as regular user works 🚫 FAIL, see above
  • TEST: changing email of other user as admin does not send email 🚫 FAIL, see above
  • TEST: changing email of other user through provisioning API does not send email
  • TEST: setting an empty email address does not send an email 🚫
  • TEST: opening email confirmation link but logging in as another user does nothing 🚫 FAIL, see above
  • TEST: opening email confirmation link after that user was deleted must show an error
  • TEST: opening the same link twice must show an error

@imujjwal96 it's a great start but there are still many cases to handle. Let me know if you need any assistance 😄

@imujjwal96
Copy link
Contributor Author

Bug1: the action was supposed to perform whenever a user presses Enter or when "Set email" is clicked. if it is happening otherwise, i will try to fix it
Bug4: the email is allowed to be empty as it was before.

@PVince81
Copy link
Contributor

PVince81 commented Mar 9, 2017

Bug4: the email is allowed to be empty as it was before.

Yes, it is allowed. But an email is still sent out. When emptying the field no email should be sent.

@imujjwal96
Copy link
Contributor Author

imujjwal96 commented Mar 9, 2017

Yes, it is allowed. But an email is still sent out. When emptying the field no email should be sent.

@PVince81 The email is always sent to the previous email address (one that was set before). This would prevent spamming mails to some extent

@PVince81
Copy link
Contributor

PVince81 commented Mar 9, 2017

The email is always sent to the previous email address (one that was set before). This would prevent spamming mails to some extent

But emptying should not send any email, there is no confirmation needed.

I think that the purpose of the email itself is to confirm the email address itself, not to confirm the change. (even if the change leads to an empty value)

Another issue if you send to the old email address is that the old email might not be valid / accessible any more. For example let's say a user has lost their email account / password (or left a university and was revoked access). Then they want to put their new email address in OC. They won't be able to because this PR sends to the old email address instead of the new one.

I think we should always send to the new email address.

Your concern about spamming is valid, not sure how other projects deal with that ? Maybe there needs to be a delay before sending email. For example don't let a user change their email more than once an hour or so.

@imujjwal96
Copy link
Contributor Author

@PVince81 I totally agree with you. I will make the necessary changes to it then.

PS: I did it according to a TODO in LukasReschke's comment in issue #6633.

@pmaier1
Copy link
Contributor

pmaier1 commented Mar 9, 2017

should we add a config.php switch to let admins disable email confirmation to go back to old behavior ? not sure if this is desirable.

I think confirmation mails are always desirable and can't imagine why an admin would want to disable this. A config.php switch should not be necessary therefore.

I think we should always send to the new email address.

Yes, good behavior would be to require the user password first and then send a confirmation link to the new address to confirm the change. Additionally it would be nice to send an email to the old address to inform about the change (this is how Dropbox handles it):

"Your Dropbox account's email address was recently changed.

Old email address: a@b.com
New email address: c@d.com

If you didn't make this change, please let us know "

Your concern about spamming is valid, not sure how other projects deal with that ? Maybe there needs to be a delay before sending email. For example don't let a user change their email more than once an hour or so.

Yep, a short delay would make sense, maybe around 5 minutes.

@imujjwal96
Copy link
Contributor Author

imujjwal96 commented Mar 9, 2017

@pmaier1 I've now made the change. It sends confirmation only to the new mail address.

t would be nice to send an email to the old address to inform about the change (this is how Dropbox handles it):

Can work on it.

@imujjwal96
Copy link
Contributor Author

imujjwal96 commented Mar 13, 2017

TODO: don't send a confirmation email if the email was set by the administrator

@PVince81 : Is this applicable to admin changing his own email address?
I was thinking of checking that if the user in session is an admin, then directly change the email address

TODO: don't send a confirmation email if the email was set by the administrator in the users page or provisioning API:

in apps/provisioning_api/lib/Users.php, the email address is changed by directly calling the function setEMailAddress on a User object. I guess it shouldn't send any confirmation email

@PVince81
Copy link
Contributor

I was thinking of checking that if the user in session is an admin, then directly change the email address

I'd say let's do it based on where it is changed.
If the email address is changed in the users page or provisioning API, do not send a confirmation.

If the email address is changed in the settings page of the user (personal section), then always send a confirmation regardless.

@imujjwal96
Copy link
Contributor Author

@PVince81 For Notifications, I was thinking to set the response message in the session and the get the message in the JS file and then use OC.Notif......

@PVince81
Copy link
Contributor

For Notifications, I was thinking to set the response message in the session and the get the message in the JS file and then use OC.Notif......

This won't work. Please don't store anything in the session. Also the JS code has no access to the session.

One way to pass the notification type is to pass an additional argument in the URL: "#email_saved_status=value" where value could be "success", "error", etc... Then you can read the URL hash from the JS code and decide what message to display based on it. Then you can also remove the hash part after processing.

@imujjwal96
Copy link
Contributor Author

@PVince81 There are some test files in the project which have in their setUp function

$this->container['UserManager'] = $this->getMockBuilder('\OCP\IUserManager')
		->disableOriginalConstructor()->getMock();

whereas other files have

$this->userManager = $this->getMockBuilder('\OCP\IUserManager')
		->disableOriginalConstructor()->getMock();

Does one need to be refactored to other?

@PVince81
Copy link
Contributor

Does one need to be refactored to other?

No need to refactor right now, thanks

@PVince81
Copy link
Contributor

PVince81 commented Mar 20, 2017

  • BUG: log out before opening the link from the email: getting an error that email could not be changed
  • BUG: don't log out before opening the link from the email: Internal Server Error
{"reqId":"\/tTSkuJ+Umwo+3bbtqX6","remoteAddr":"127.0.0.1","app":"PHP","message":"Undefined offset: 1 at \/srv\/www\/htdocs\/owncloud\/lib\/private\/Mail\/Message.php#59","level":3,"time":"2017-03-20T09:05:38+00:00","method":"GET","url":"\/owncloud\/index.php\/settings\/mailaddress\/change\/SmQUG6rKn9S4l485fPM94\/user2\/vincent@vvortex.ttv","user":"user2"}
{"reqId":"\/tTSkuJ+Umwo+3bbtqX6","remoteAddr":"127.0.0.1","app":"index","message":"Exception: {\"Exception\":\"Exception\",\"Message\":\"Couldn't send email address change notification mail. Please contact your administrator.\",\"Code\":0,\"Trace\":\"#0 [internal function]: OC\\\\Settings\\\\Controller\\\\UsersController->changeMail('SmQUG6rKn9S4l48...', 'user2', 'vincent@vvortex...')\\n#1 \\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/lib\\\/private\\\/AppFramework\\\/Http\\\/Dispatcher.php(159): call_user_func_array(Array, Array)\\n#2 \\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/lib\\\/private\\\/AppFramework\\\/Http\\\/Dispatcher.php(89): OC\\\\AppFramework\\\\Http\\\\Dispatcher->executeController(Object(OC\\\\Settings\\\\Controller\\\\UsersController), 'changeMail')\\n#3 \\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/lib\\\/private\\\/AppFramework\\\/App.php(98): OC\\\\AppFramework\\\\Http\\\\Dispatcher->dispatch(Object(OC\\\\Settings\\\\Controller\\\\UsersController), 'changeMail')\\n#4 \\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/lib\\\/private\\\/AppFramework\\\/Routing\\\/RouteActionHandler.php(46): OC\\\\AppFramework\\\\App::main('UsersController', 'changeMail', Object(OC\\\\AppFramework\\\\DependencyInjection\\\\DIContainer), Array)\\n#5 [internal function]: OC\\\\AppFramework\\\\Routing\\\\RouteActionHandler->__invoke(Array)\\n#6 \\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/lib\\\/private\\\/Route\\\/Router.php(299): call_user_func(Object(OC\\\\AppFramework\\\\Routing\\\\RouteActionHandler), Array)\\n#7 \\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/lib\\\/base.php(898): OC\\\\Route\\\\Router->match('\\\/settings\\\/maila...')\\n#8 \\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/index.php(49): OC::handleRequest()\\n#9 {main}\",\"File\":\"\\\/srv\\\/www\\\/htdocs\\\/owncloud\\\/settings\\\/Controller\\\/UsersController.php\",\"Line\":832}","level":3,"time":"2017-03-20T09:05:38+00:00","method":"GET","url":"\/owncloud\/index.php\/settings\/mailaddress\/change\/SmQUG6rKn9S4l485fPM94\/user2\/vincent@vvortex.ttv","user":"user2"}

In both cases I didn't have any email address set initially.
Is it trying to send an email to the empty previous email address ?

@imujjwal96
Copy link
Contributor Author

@PVince81 What should be expected when a logged out user changes his email address?
Changing it anyway?

@PVince81
Copy link
Contributor

What should be expected when a logged out user changes his email address?

If by "changes his email address" you mean "open the link from the email", then the following should happen:

  1. Login page appears with the correct redirect URL
  2. User logs in with correct credentials
  3. Login page redirects to the settings page (as per redirect URL parameter)
  4. Email address is changed

Now normally this should already work because the redirect logic already exists, but for some reason maybe some parameters are lost. I seem to remember advising you to use the hash part of the URL ? ("#something"). I think such parts might be lost on redirect, so maybe try using a real URL parameter instead like "&something"

@imujjwal96
Copy link
Contributor Author

BUG: log out before opening the link from the email: getting an error that email could not be changed

it was because there was some error in checking the validity of the email change token. This bug does not occur after correcting that.

@imujjwal96
Copy link
Contributor Author

imujjwal96 commented Mar 21, 2017

Test plan:

  • TEST: changing own email as admin works
  • TEST: changing own email as regular user works
  • TEST: changing email of other user as admin does not send email
  • TEST: changing email of other user through provisioning API does not send email
  • TEST: setting an empty email address does not send an emailn:
  • TEST: opening email confirmation link but logging in as another user does nothing
  • TEST: opening email confirmation link after that user was deleted must show an error
  • TEST: opening the same link twice must show an error
  • TEST: log out before opening the link from the email: Email should change successfully when user logs in
  • TEST: Setting a new address and confirming the mail should not show Internal Server error

@PVince81 : Will again test all these before pushing
Missing any?

@PVince81
Copy link
Contributor

  • TEST: change own email from empty to something else: after confirmation there are no errors nor attempts to send an email to the previous address

@imujjwal96 otherwise the other test cases look good.

@imujjwal96 side note: it seems there are conflicts that you'll need to resolve. Let me know if you need guidance for doing that as it can be tricky especially when working on your own fork.

@imujjwal96
Copy link
Contributor Author

I've tested all the cases and they're working fine on my server.

it seems there are conflicts that you'll need to resolve. Let me know if you need guidance for doing that as it can be tricky especially when working on your own fork.

What should I do? @PVince81

@PVince81
Copy link
Contributor

@imujjwal96 first make a backup of your branch:
git checkout -b backup
then go back to your master:
git checkout master

@imujjwal96 I see github has a button "Resolve conflicts" with a web editor, maybe you could first try with that ?

@imujjwal96
Copy link
Contributor Author

imujjwal96 commented Mar 21, 2017

Should I push the changes i made recently?

@PVince81
Copy link
Contributor

Should I push the changes i made recently?

sounds good

@imujjwal96
Copy link
Contributor Author

@PVince81 I have made some changes through Github in order to resolve the conflicts. Can you review them now or do I have to first commit them?

@PVince81
Copy link
Contributor

It seems the conflits still appear so maybe you need to commit them

@@ -0,0 +1,2 @@
<?php
echo str_replace('{mailAddress}', $_['mailAddress'], $l->t('Email address changed to {mailAddress} successfully.'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please delete this file it is not needed any more

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never mind, this is for the email contents

@PVince81
Copy link
Contributor

Code looks good otherwise, great job !

I'll redo a quick test and then I think this is good to go.

@PVince81
Copy link
Contributor

👍

@PVince81 PVince81 merged commit 82c529c into owncloud:master Mar 22, 2017
@PVince81 PVince81 mentioned this pull request Mar 22, 2017
@PVince81
Copy link
Contributor

@imujjwal96 congrats! Merged.

I think this PR doesn't fix the password reset limitation, right ? #6633

@imujjwal96
Copy link
Contributor Author

@PVince81 No it does not. Forgot to remove the reference to that issue.

@PVince81
Copy link
Contributor

Regression found for group admins (aka subadmins) who cannot change a group member's email any more: #28035

return new RedirectResponse($this->urlGenerator->linkToRoute('settings.SettingsPage.getPersonal', ['changestatus' => 'success']));
}

/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accident here (missing 2nd asterisk for docblock) that causes regression #31280 "subadmin cannot disable member of his group"

Copy link
Contributor

@mmattel mmattel May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check if there are other locations where there might be a docblock issue like that?

For reference, fix is in owncloud-archive/user_management#30

@lock
Copy link

lock bot commented Jul 31, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jul 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants