mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	change-email: Implement confirmation flow.
This adds to Zulip support for a user changing their own email address. It's backed by a huge amount of work by Steve Howell on making email changes actually work from a UI perspective. Fixes #734.
This commit is contained in:
		
							
								
								
									
										24
									
								
								confirmation/migrations/0003_emailchangeconfirmation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								confirmation/migrations/0003_emailchangeconfirmation.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					# Generated by Django 1.10.4 on 2017-01-17 09:16
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('confirmation', '0002_realmcreationkey'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='EmailChangeConfirmation',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            options={
 | 
				
			||||||
 | 
					                'proxy': True,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            bases=('confirmation.confirmation',),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -19,7 +19,7 @@ from django.utils.timezone import now
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from confirmation.util import get_status_field
 | 
					from confirmation.util import get_status_field
 | 
				
			||||||
from zerver.lib.utils import generate_random_token
 | 
					from zerver.lib.utils import generate_random_token
 | 
				
			||||||
from zerver.models import PreregistrationUser
 | 
					from zerver.models import PreregistrationUser, EmailChangeStatus
 | 
				
			||||||
from typing import Optional, Union, Any, Text
 | 
					from typing import Optional, Union, Any, Text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
B16_RE = re.compile('^[a-f0-9]{40}$')
 | 
					B16_RE = re.compile('^[a-f0-9]{40}$')
 | 
				
			||||||
@@ -59,7 +59,7 @@ def generate_realm_creation_url():
 | 
				
			|||||||
class ConfirmationManager(models.Manager):
 | 
					class ConfirmationManager(models.Manager):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def confirm(self, confirmation_key):
 | 
					    def confirm(self, confirmation_key):
 | 
				
			||||||
        # type: (str) -> Union[bool, PreregistrationUser]
 | 
					        # type: (str) -> Union[bool, PreregistrationUser, EmailChangeStatus]
 | 
				
			||||||
        if B16_RE.search(confirmation_key):
 | 
					        if B16_RE.search(confirmation_key):
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                confirmation = self.get(confirmation_key=confirmation_key)
 | 
					                confirmation = self.get(confirmation_key=confirmation_key)
 | 
				
			||||||
@@ -140,6 +140,20 @@ class ConfirmationManager(models.Manager):
 | 
				
			|||||||
        send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [email_address], html_message=html_content)
 | 
					        send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [email_address], html_message=html_content)
 | 
				
			||||||
        return self.create(content_object=obj, date_sent=now(), confirmation_key=confirmation_key)
 | 
					        return self.create(content_object=obj, date_sent=now(), confirmation_key=confirmation_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EmailChangeConfirmationManager(ConfirmationManager):
 | 
				
			||||||
 | 
					    def get_activation_url(self, key, host=None):
 | 
				
			||||||
 | 
					        # type: (Text, Optional[str]) -> Text
 | 
				
			||||||
 | 
					        if host is None:
 | 
				
			||||||
 | 
					            # This will raise exception if the key doesn't exist.
 | 
				
			||||||
 | 
					            host = self.get(confirmation_key=key).content_object.realm.host
 | 
				
			||||||
 | 
					        return u'%s%s%s' % (settings.EXTERNAL_URI_SCHEME,
 | 
				
			||||||
 | 
					                            host,
 | 
				
			||||||
 | 
					                            reverse('zerver.views.user_settings.confirm_email_change',
 | 
				
			||||||
 | 
					                                    kwargs={'confirmation_key': key}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_link_validity_in_days(self):
 | 
				
			||||||
 | 
					        # type: () -> int
 | 
				
			||||||
 | 
					        return settings.EMAIL_CHANGE_CONFIRMATION_DAYS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Confirmation(models.Model):
 | 
					class Confirmation(models.Model):
 | 
				
			||||||
    content_type = models.ForeignKey(ContentType)
 | 
					    content_type = models.ForeignKey(ContentType)
 | 
				
			||||||
@@ -158,6 +172,12 @@ class Confirmation(models.Model):
 | 
				
			|||||||
        # type: () -> Text
 | 
					        # type: () -> Text
 | 
				
			||||||
        return _('confirmation email for %s') % (self.content_object,)
 | 
					        return _('confirmation email for %s') % (self.content_object,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EmailChangeConfirmation(Confirmation):
 | 
				
			||||||
 | 
					    class Meta(object):
 | 
				
			||||||
 | 
					        proxy = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    objects = EmailChangeConfirmationManager()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RealmCreationKey(models.Model):
 | 
					class RealmCreationKey(models.Model):
 | 
				
			||||||
    creation_key = models.CharField(_('activation key'), max_length=40)
 | 
					    creation_key = models.CharField(_('activation key'), max_length=40)
 | 
				
			||||||
    date_created = models.DateTimeField(_('created'), default=now)
 | 
					    date_created = models.DateTimeField(_('created'), default=now)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -517,12 +517,44 @@ function _setup_page() {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#change_email_button').on('click', function (e) {
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					        e.stopPropagation();
 | 
				
			||||||
 | 
					        $('#change_email_modal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var data = {};
 | 
				
			||||||
 | 
					        data.email = $('.email_change_container').find("input[name='email']").val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        channel.patch({
 | 
				
			||||||
 | 
					            url: '/json/settings/change',
 | 
				
			||||||
 | 
					            data: data,
 | 
				
			||||||
 | 
					            success: function (data) {
 | 
				
			||||||
 | 
					                if ('account_email' in data) {
 | 
				
			||||||
 | 
					                    settings_change_success(data.account_email);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    settings_change_error(i18n.t("Error changing settings: No new data supplied."));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            error: function (xhr) {
 | 
				
			||||||
 | 
					                settings_change_error("Error changing settings", xhr);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $('#default_language').on('click', function (e) {
 | 
					    $('#default_language').on('click', function (e) {
 | 
				
			||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
        e.stopPropagation();
 | 
					        e.stopPropagation();
 | 
				
			||||||
        $('#default_language_modal').show().attr('aria-hidden', false);
 | 
					        $('#default_language_modal').show().attr('aria-hidden', false);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#change_email').on('click', function (e) {
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					        e.stopPropagation();
 | 
				
			||||||
 | 
					        $('#change_email_modal').modal('show');
 | 
				
			||||||
 | 
					        var email = $('#email_value').text();
 | 
				
			||||||
 | 
					        $('.email_change_container').find("input[name='email']").val(email);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $("#user_deactivate_account_button").on('click', function (e) {
 | 
					    $("#user_deactivate_account_button").on('click', function (e) {
 | 
				
			||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
        e.stopPropagation();
 | 
					        e.stopPropagation();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,31 @@
 | 
				
			|||||||
    {{t "Your account" }}
 | 
					    {{t "Your account" }}
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div class="account-settings-form">
 | 
					  <div class="account-settings-form">
 | 
				
			||||||
 | 
					    <form class="email-change-form">
 | 
				
			||||||
 | 
					      <p for="change_email" class="inline-block title">
 | 
				
			||||||
 | 
					        {{t "Email" }}: <span id='email_value'>{{page_params.email}}</span>
 | 
				
			||||||
 | 
					        <a id="change_email" href="#change_email" title="{{t 'Change email' }}">[Change]</a>
 | 
				
			||||||
 | 
					      </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div id="change_email_modal" class="modal hide" tabindex="-1" role="dialog"
 | 
				
			||||||
 | 
					            aria-labelledby="change_email_modal_label" aria-hidden="true">
 | 
				
			||||||
 | 
					        <div class="modal-header">
 | 
				
			||||||
 | 
					          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
 | 
				
			||||||
 | 
					          <h3 id="change_email_modal_label">{{t "Change email" }}</h3>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="modal-body">
 | 
				
			||||||
 | 
					          <div class="input-group email_change_container">
 | 
				
			||||||
 | 
					            <label for="email">{{t "Email" }}</label>
 | 
				
			||||||
 | 
					            <input type="text" name="email" value="{{ page_params.email }}" />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="modal-footer">
 | 
				
			||||||
 | 
					          <button id='change_email_button' class="btn btn-success" data-dismiss="modal" aria-hidden="true">{{t "Change" }}</button>
 | 
				
			||||||
 | 
					          <button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">{{t "Close" }}</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form action="/json/settings/change" method="post"
 | 
					    <form action="/json/settings/change" method="post"
 | 
				
			||||||
          class="form-horizontal your-account-settings">
 | 
					          class="form-horizontal your-account-settings">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										34
									
								
								templates/confirmation/confirm_email_change.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								templates/confirmation/confirm_email_change.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					{% extends "zerver/portico.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block portico_content %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="pitch">
 | 
				
			||||||
 | 
					    <hr/>
 | 
				
			||||||
 | 
					    {% if confirmed %}
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					    This confirms that the email address for your Zulip account has changed
 | 
				
			||||||
 | 
					    from {{old_email}} to {{ new_email }}.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					    {% else %}
 | 
				
			||||||
 | 
					    <p class="lead">Whoops, something's not right. We couldn't find your confirmation ID!</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {% if verbose_support_offers %}
 | 
				
			||||||
 | 
					        <p>Make sure you copied the link correctly in to your browser. If you're
 | 
				
			||||||
 | 
					        still encountering this page, its probably our fault. We're sorry.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <p>Anyway, shoot us a line at
 | 
				
			||||||
 | 
					            <a href="mailto:{{ support_email }}">{{ support_email }}</a>
 | 
				
			||||||
 | 
					            and we'll get this resolved shortly.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					        <p>Make sure you copied the link correctly in to your browser.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <p>If you're still having problems, please contact your Zulip administrator at
 | 
				
			||||||
 | 
					            <a href="mailto:{{ support_email }}">{{ support_email }}</a>.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 | 
				
			||||||
 | 
					    <title>Zulip</title>
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <table width="80%" style="align:center; max-width:800px" align="center">
 | 
				
			||||||
 | 
					      <tr>
 | 
				
			||||||
 | 
					        <td style="font-size:16px; font-family:Helvetica;">
 | 
				
			||||||
 | 
					          <p>Hi!
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <p>
 | 
				
			||||||
 | 
					          We received a request to change the email address for the Zulip
 | 
				
			||||||
 | 
					          account on {{ realm.uri }} from {{ old_email }} to {{ new_email }}.
 | 
				
			||||||
 | 
					          If you would like to confirm this change, please click this link:
 | 
				
			||||||
 | 
					          <br />
 | 
				
			||||||
 | 
					          <a href="{{ activate_url }}" style="color:#08c">{{ activate_url }}</a>
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <p>
 | 
				
			||||||
 | 
					          {% if verbose_support_offers %}
 | 
				
			||||||
 | 
					          Feel free to give us a shout at
 | 
				
			||||||
 | 
					          <a href="mailto:{{ support_email }}" style="color:#08c">{{ support_email }}</a>
 | 
				
			||||||
 | 
					          if you have any questions or you did not request this change.
 | 
				
			||||||
 | 
					          {% else %}
 | 
				
			||||||
 | 
					          If you did not request this change, please contact the administrator
 | 
				
			||||||
 | 
					          of this Zulip server at
 | 
				
			||||||
 | 
					          <a href="mailto:{{ support_email }}" style="color:#08c">{{ support_email }}</a>.
 | 
				
			||||||
 | 
					          {% endif %}
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <p>
 | 
				
			||||||
 | 
					          Cheers,<br />
 | 
				
			||||||
 | 
					          The Zulip Team
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					      </tr>
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					[Zulip] Confirm your new email address for {{ realm.name }}
 | 
				
			||||||
@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					Hi!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We received a request to change the email address for the Zulip account on
 | 
				
			||||||
 | 
					{{ realm.uri }} from {{ old_email }} to {{ new_email }}. If you would like
 | 
				
			||||||
 | 
					to confirm this change, please click this link:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{ activate_url }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% if verbose_support_offers %}
 | 
				
			||||||
 | 
					Feel free to give us a shout at <{{ support_email }}> if you have any
 | 
				
			||||||
 | 
					questions or you did not request this change.
 | 
				
			||||||
 | 
					{% else %}
 | 
				
			||||||
 | 
					If you did not request this change, please contact the administrator
 | 
				
			||||||
 | 
					of this Zulip server at <{{ support_email }}>.
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Cheers,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Zulip Team
 | 
				
			||||||
							
								
								
									
										9
									
								
								templates/confirmation/notify_change_in_email_body.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								templates/confirmation/notify_change_in_email_body.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					Hi,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We just wanted to let you know that the email associated with your Zulip account
 | 
				
			||||||
 | 
					was recently changed to {{ new_email }}. If you did not request this change,
 | 
				
			||||||
 | 
					please contact us immediately at <{{ support_email }}>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Best,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Zulip Team
 | 
				
			||||||
@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					[Zulip] Email address changed for {{ realm.name }}
 | 
				
			||||||
@@ -375,6 +375,8 @@ def build_custom_checkers(by_lang):
 | 
				
			|||||||
              'return json_error(data=error_data, msg=ret_error)'),
 | 
					              'return json_error(data=error_data, msg=ret_error)'),
 | 
				
			||||||
             ('zerver/views/streams.py', 'return json_error(property_conversion)'),
 | 
					             ('zerver/views/streams.py', 'return json_error(property_conversion)'),
 | 
				
			||||||
             ('zerver/views/streams.py', 'return json_error(e.error, data=result, status=404)'),
 | 
					             ('zerver/views/streams.py', 'return json_error(e.error, data=result, status=404)'),
 | 
				
			||||||
 | 
					             # error and skipped are already internationalized
 | 
				
			||||||
 | 
					             ('zerver/views/user_settings.py', 'return json_error(error or skipped)'),
 | 
				
			||||||
             # We can't do anything about this.
 | 
					             # We can't do anything about this.
 | 
				
			||||||
             ('zerver/views/realm_filters.py', 'return json_error(e.messages[0], data={"errors": dict(e)})'),
 | 
					             ('zerver/views/realm_filters.py', 'return json_error(e.messages[0], data={"errors": dict(e)})'),
 | 
				
			||||||
         ]),
 | 
					         ]),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity,
 | 
				
			|||||||
    realm_filters_for_realm, RealmFilter, receives_offline_notifications, \
 | 
					    realm_filters_for_realm, RealmFilter, receives_offline_notifications, \
 | 
				
			||||||
    ScheduledJob, get_owned_bot_dicts, \
 | 
					    ScheduledJob, get_owned_bot_dicts, \
 | 
				
			||||||
    get_old_unclaimed_attachments, get_cross_realm_emails, receives_online_notifications, \
 | 
					    get_old_unclaimed_attachments, get_cross_realm_emails, receives_online_notifications, \
 | 
				
			||||||
    Reaction
 | 
					    Reaction, EmailChangeStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.alert_words import alert_words_in_realm
 | 
					from zerver.lib.alert_words import alert_words_in_realm
 | 
				
			||||||
from zerver.lib.avatar import avatar_url
 | 
					from zerver.lib.avatar import avatar_url
 | 
				
			||||||
@@ -50,7 +50,7 @@ from importlib import import_module
 | 
				
			|||||||
from django.core.mail import EmailMessage
 | 
					from django.core.mail import EmailMessage
 | 
				
			||||||
from django.utils.timezone import now
 | 
					from django.utils.timezone import now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from confirmation.models import Confirmation
 | 
					from confirmation.models import Confirmation, EmailChangeConfirmation
 | 
				
			||||||
import six
 | 
					import six
 | 
				
			||||||
from six.moves import filter
 | 
					from six.moves import filter
 | 
				
			||||||
from six.moves import map
 | 
					from six.moves import map
 | 
				
			||||||
@@ -708,6 +708,30 @@ def do_change_user_email(user_profile, new_email):
 | 
				
			|||||||
               'old_email': old_email,
 | 
					               'old_email': old_email,
 | 
				
			||||||
               'new_email': new_email})
 | 
					               'new_email': new_email})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def do_start_email_change_process(user_profile, new_email):
 | 
				
			||||||
 | 
					    # type: (UserProfile, Text) -> None
 | 
				
			||||||
 | 
					    old_email = user_profile.email
 | 
				
			||||||
 | 
					    user_profile.email = new_email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context = {'support_email': settings.ZULIP_ADMINISTRATOR,
 | 
				
			||||||
 | 
					               'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS,
 | 
				
			||||||
 | 
					               'realm': user_profile.realm,
 | 
				
			||||||
 | 
					               'old_email': old_email,
 | 
				
			||||||
 | 
					               'new_email': new_email,
 | 
				
			||||||
 | 
					               }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with transaction.atomic():
 | 
				
			||||||
 | 
					        obj = EmailChangeStatus.objects.create(new_email=new_email,
 | 
				
			||||||
 | 
					                                               old_email=old_email,
 | 
				
			||||||
 | 
					                                               user_profile=user_profile,
 | 
				
			||||||
 | 
					                                               realm=user_profile.realm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        EmailChangeConfirmation.objects.send_confirmation(
 | 
				
			||||||
 | 
					            obj, new_email,
 | 
				
			||||||
 | 
					            additional_context=context,
 | 
				
			||||||
 | 
					            host=user_profile.realm.host,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def compute_irc_user_fullname(email):
 | 
					def compute_irc_user_fullname(email):
 | 
				
			||||||
    # type: (NonBinaryStr) -> NonBinaryStr
 | 
					    # type: (NonBinaryStr) -> NonBinaryStr
 | 
				
			||||||
    return email.split("@")[0] + " (IRC)"
 | 
					    return email.split("@")[0] + " (IRC)"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								zerver/migrations/0053_emailchangestatus.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								zerver/migrations/0053_emailchangestatus.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					# Generated by Django 1.10.5 on 2017-02-23 05:37
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.db.models.deletion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('zerver', '0052_auto_fix_realmalias_realm_nullable'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='EmailChangeStatus',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('new_email', models.EmailField(max_length=254)),
 | 
				
			||||||
 | 
					                ('old_email', models.EmailField(max_length=254)),
 | 
				
			||||||
 | 
					                ('updated_at', models.DateTimeField(auto_now=True)),
 | 
				
			||||||
 | 
					                ('status', models.IntegerField(default=0)),
 | 
				
			||||||
 | 
					                ('realm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='zerver.Realm')),
 | 
				
			||||||
 | 
					                ('user_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -678,6 +678,18 @@ class PreregistrationUser(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    realm = models.ForeignKey(Realm, null=True) # type: Optional[Realm]
 | 
					    realm = models.ForeignKey(Realm, null=True) # type: Optional[Realm]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EmailChangeStatus(models.Model):
 | 
				
			||||||
 | 
					    new_email = models.EmailField() # type: Text
 | 
				
			||||||
 | 
					    old_email = models.EmailField() # type: Text
 | 
				
			||||||
 | 
					    updated_at = models.DateTimeField(auto_now=True) # type: datetime.datetime
 | 
				
			||||||
 | 
					    user_profile = models.ForeignKey(UserProfile) # type: UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # status: whether an object has been confirmed.
 | 
				
			||||||
 | 
					    #   if confirmed, set to confirmation.settings.STATUS_ACTIVE
 | 
				
			||||||
 | 
					    status = models.IntegerField(default=0) # type: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    realm = models.ForeignKey(Realm) # type: Realm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PushDeviceToken(models.Model):
 | 
					class PushDeviceToken(models.Model):
 | 
				
			||||||
    APNS = 1
 | 
					    APNS = 1
 | 
				
			||||||
    GCM = 2
 | 
					    GCM = 2
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										140
									
								
								zerver/tests/test_email_change.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								zerver/tests/test_email_change.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					from __future__ import absolute_import
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import django
 | 
				
			||||||
 | 
					import mock
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.core import mail
 | 
				
			||||||
 | 
					from django.http import HttpResponse
 | 
				
			||||||
 | 
					from django.urls import reverse
 | 
				
			||||||
 | 
					from django.utils.timezone import now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from confirmation.models import EmailChangeConfirmation, generate_key
 | 
				
			||||||
 | 
					from zerver.lib.actions import do_start_email_change_process
 | 
				
			||||||
 | 
					from zerver.lib.test_classes import (
 | 
				
			||||||
 | 
					    ZulipTestCase,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from zerver.models import get_user_profile_by_email, EmailChangeStatus, Realm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EmailChangeTestCase(ZulipTestCase):
 | 
				
			||||||
 | 
					    def test_confirm_email_change_with_non_existent_key(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        self.login('hamlet@zulip.com')
 | 
				
			||||||
 | 
					        key = generate_key()
 | 
				
			||||||
 | 
					        with self.assertRaises(EmailChangeConfirmation.DoesNotExist):
 | 
				
			||||||
 | 
					            url = EmailChangeConfirmation.objects.get_activation_url(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        url = EmailChangeConfirmation.objects.get_activation_url(
 | 
				
			||||||
 | 
					            key, 'testserver')
 | 
				
			||||||
 | 
					        response = self.client_get(url)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					        self.assertIn("Whoops", response.content.decode('utf8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_confirm_email_change_with_invalid_key(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        self.login('hamlet@zulip.com')
 | 
				
			||||||
 | 
					        key = 'invalid key'
 | 
				
			||||||
 | 
					        with self.assertRaises(EmailChangeConfirmation.DoesNotExist):
 | 
				
			||||||
 | 
					            url = EmailChangeConfirmation.objects.get_activation_url(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        url = EmailChangeConfirmation.objects.get_activation_url(
 | 
				
			||||||
 | 
					            key, 'testserver')
 | 
				
			||||||
 | 
					        response = self.client_get(url)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					        self.assertIn("Whoops", response.content.decode('utf8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_confirm_email_change_when_time_exceeded(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        old_email = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        new_email = 'hamlet-new@zulip.com'
 | 
				
			||||||
 | 
					        user_profile = get_user_profile_by_email(old_email)
 | 
				
			||||||
 | 
					        obj = EmailChangeStatus.objects.create(new_email=new_email,
 | 
				
			||||||
 | 
					                                               old_email=old_email,
 | 
				
			||||||
 | 
					                                               user_profile=user_profile,
 | 
				
			||||||
 | 
					                                               realm=user_profile.realm)
 | 
				
			||||||
 | 
					        key = generate_key()
 | 
				
			||||||
 | 
					        date_sent = now() - datetime.timedelta(days=2)
 | 
				
			||||||
 | 
					        EmailChangeConfirmation.objects.create(content_object=obj,
 | 
				
			||||||
 | 
					                                               date_sent=date_sent,
 | 
				
			||||||
 | 
					                                               confirmation_key=key)
 | 
				
			||||||
 | 
					        url = EmailChangeConfirmation.objects.get_activation_url(key)
 | 
				
			||||||
 | 
					        response = self.client_get(url)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					        self.assertIn("Whoops", response.content.decode('utf8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_confirm_email_change(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        old_email = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        new_email = 'hamlet-new@zulip.com'
 | 
				
			||||||
 | 
					        user_profile = get_user_profile_by_email(old_email)
 | 
				
			||||||
 | 
					        obj = EmailChangeStatus.objects.create(new_email=new_email,
 | 
				
			||||||
 | 
					                                               old_email=old_email,
 | 
				
			||||||
 | 
					                                               user_profile=user_profile,
 | 
				
			||||||
 | 
					                                               realm=user_profile.realm)
 | 
				
			||||||
 | 
					        key = generate_key()
 | 
				
			||||||
 | 
					        EmailChangeConfirmation.objects.create(content_object=obj,
 | 
				
			||||||
 | 
					                                               date_sent=now(),
 | 
				
			||||||
 | 
					                                               confirmation_key=key)
 | 
				
			||||||
 | 
					        url = EmailChangeConfirmation.objects.get_activation_url(key)
 | 
				
			||||||
 | 
					        response = self.client_get(url)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					        self.assertIn("This confirms that the email address for your Zulip",
 | 
				
			||||||
 | 
					                      response.content.decode('utf8'))
 | 
				
			||||||
 | 
					        user_profile = get_user_profile_by_email(new_email)
 | 
				
			||||||
 | 
					        self.assertTrue(bool(user_profile))
 | 
				
			||||||
 | 
					        obj.refresh_from_db()
 | 
				
			||||||
 | 
					        self.assertEqual(obj.status, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_start_email_change_process(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        user_profile = get_user_profile_by_email('hamlet@zulip.com')
 | 
				
			||||||
 | 
					        do_start_email_change_process(user_profile, 'hamlet-new@zulip.com')
 | 
				
			||||||
 | 
					        self.assertEqual(EmailChangeStatus.objects.count(), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_end_to_end_flow(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        data = {'email': 'hamlet-new@zulip.com'}
 | 
				
			||||||
 | 
					        email = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        self.login(email)
 | 
				
			||||||
 | 
					        url = '/json/settings/change'
 | 
				
			||||||
 | 
					        self.assertEqual(len(mail.outbox), 0)
 | 
				
			||||||
 | 
					        result = self.client_post(url, data)
 | 
				
			||||||
 | 
					        self.assertEqual(len(mail.outbox), 1)
 | 
				
			||||||
 | 
					        self.assertIn('We have sent you an email', result.content.decode('utf8'))
 | 
				
			||||||
 | 
					        email_message = mail.outbox[0]
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            email_message.subject,
 | 
				
			||||||
 | 
					            '[Zulip] Confirm your new email address for Zulip Dev'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        body = email_message.body
 | 
				
			||||||
 | 
					        self.assertIn('We received a request to change the email', body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        activation_url = [s for s in body.split('\n') if s][4]
 | 
				
			||||||
 | 
					        response = self.client_get(activation_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					        self.assertIn("This confirms that the email address",
 | 
				
			||||||
 | 
					                      response.content.decode('utf8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_post_invalid_email(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        data = {'email': 'hamlet-new'}
 | 
				
			||||||
 | 
					        email = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        self.login(email)
 | 
				
			||||||
 | 
					        url = '/json/settings/change'
 | 
				
			||||||
 | 
					        result = self.client_post(url, data)
 | 
				
			||||||
 | 
					        self.assertIn('Invalid address', result.content.decode('utf8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_post_same_email(self):
 | 
				
			||||||
 | 
					        # type: () -> None
 | 
				
			||||||
 | 
					        data = {'email': 'hamlet@zulip.com'}
 | 
				
			||||||
 | 
					        email = 'hamlet@zulip.com'
 | 
				
			||||||
 | 
					        self.login(email)
 | 
				
			||||||
 | 
					        url = '/json/settings/change'
 | 
				
			||||||
 | 
					        result = self.client_post(url, data)
 | 
				
			||||||
 | 
					        self.assertEqual('success', result.json()['result'])
 | 
				
			||||||
 | 
					        self.assertEqual('', result.json()['msg'])
 | 
				
			||||||
@@ -84,6 +84,10 @@ class TemplateTestCase(ZulipTestCase):
 | 
				
			|||||||
            'confirmation/mituser_confirmation_email_subject.txt',
 | 
					            'confirmation/mituser_confirmation_email_subject.txt',
 | 
				
			||||||
            'confirmation/mituser_invite_email_body.txt',
 | 
					            'confirmation/mituser_invite_email_body.txt',
 | 
				
			||||||
            'confirmation/mituser_invite_email_subject.txt',
 | 
					            'confirmation/mituser_invite_email_subject.txt',
 | 
				
			||||||
 | 
					            'confirmation/emailchangestatus_confirmation_email.subject',
 | 
				
			||||||
 | 
					            'confirmation/emailchangestatus_confirmation_email.html',
 | 
				
			||||||
 | 
					            'confirmation/emailchangestatus_confirmation_email.txt',
 | 
				
			||||||
 | 
					            'confirmation/notify_change_in_email_subject.txt',
 | 
				
			||||||
            'corporate/mit.html',
 | 
					            'corporate/mit.html',
 | 
				
			||||||
            'corporate/privacy.html',
 | 
					            'corporate/privacy.html',
 | 
				
			||||||
            'corporate/zephyr.html',
 | 
					            'corporate/zephyr.html',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,11 @@ from typing import Text
 | 
				
			|||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.contrib.auth import authenticate, update_session_auth_hash
 | 
					from django.contrib.auth import authenticate, update_session_auth_hash
 | 
				
			||||||
 | 
					from django.core.mail import send_mail
 | 
				
			||||||
from django.http import HttpRequest, HttpResponse
 | 
					from django.http import HttpRequest, HttpResponse
 | 
				
			||||||
 | 
					from django.shortcuts import redirect, render
 | 
				
			||||||
 | 
					from django.template.loader import render_to_string
 | 
				
			||||||
 | 
					from django.urls import reverse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.decorator import authenticated_json_post_view, has_request_variables, REQ
 | 
					from zerver.decorator import authenticated_json_post_view, has_request_variables, REQ
 | 
				
			||||||
from zerver.lib.actions import do_change_password, \
 | 
					from zerver.lib.actions import do_change_password, \
 | 
				
			||||||
@@ -17,7 +21,8 @@ from zerver.lib.actions import do_change_password, \
 | 
				
			|||||||
    do_change_enable_stream_desktop_notifications, do_change_enable_stream_sounds, \
 | 
					    do_change_enable_stream_desktop_notifications, do_change_enable_stream_sounds, \
 | 
				
			||||||
    do_regenerate_api_key, do_change_avatar_fields, do_change_twenty_four_hour_time, \
 | 
					    do_regenerate_api_key, do_change_avatar_fields, do_change_twenty_four_hour_time, \
 | 
				
			||||||
    do_change_left_side_userlist, do_change_default_language, \
 | 
					    do_change_left_side_userlist, do_change_default_language, \
 | 
				
			||||||
    do_change_pm_content_in_desktop_notifications
 | 
					    do_change_pm_content_in_desktop_notifications, validate_email, \
 | 
				
			||||||
 | 
					    do_change_user_email, do_start_email_change_process
 | 
				
			||||||
from zerver.lib.avatar import avatar_url
 | 
					from zerver.lib.avatar import avatar_url
 | 
				
			||||||
from zerver.lib.i18n import get_available_language_codes
 | 
					from zerver.lib.i18n import get_available_language_codes
 | 
				
			||||||
from zerver.lib.response import json_success, json_error
 | 
					from zerver.lib.response import json_success, json_error
 | 
				
			||||||
@@ -25,7 +30,43 @@ from zerver.lib.upload import upload_avatar_image
 | 
				
			|||||||
from zerver.lib.validator import check_bool, check_string
 | 
					from zerver.lib.validator import check_bool, check_string
 | 
				
			||||||
from zerver.lib.request import JsonableError
 | 
					from zerver.lib.request import JsonableError
 | 
				
			||||||
from zerver.lib.users import check_change_full_name
 | 
					from zerver.lib.users import check_change_full_name
 | 
				
			||||||
from zerver.models import UserProfile, Realm, name_changes_disabled
 | 
					from zerver.models import UserProfile, Realm, name_changes_disabled, \
 | 
				
			||||||
 | 
					    EmailChangeStatus
 | 
				
			||||||
 | 
					from confirmation.models import EmailChangeConfirmation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def confirm_email_change(request, confirmation_key):
 | 
				
			||||||
 | 
					    # type: (HttpRequest, str) -> HttpResponse
 | 
				
			||||||
 | 
					    confirmation_key = confirmation_key.lower()
 | 
				
			||||||
 | 
					    obj = EmailChangeConfirmation.objects.confirm(confirmation_key)
 | 
				
			||||||
 | 
					    confirmed = False
 | 
				
			||||||
 | 
					    new_email = old_email = None  # type: Text
 | 
				
			||||||
 | 
					    if obj:
 | 
				
			||||||
 | 
					        confirmed = True
 | 
				
			||||||
 | 
					        assert isinstance(obj, EmailChangeStatus)
 | 
				
			||||||
 | 
					        new_email = obj.new_email
 | 
				
			||||||
 | 
					        old_email = obj.old_email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        do_change_user_email(obj.user_profile, obj.new_email)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = {'support_email': settings.ZULIP_ADMINISTRATOR,
 | 
				
			||||||
 | 
					                   'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS,
 | 
				
			||||||
 | 
					                   'realm': obj.realm,
 | 
				
			||||||
 | 
					                   'new_email': new_email,
 | 
				
			||||||
 | 
					                   }
 | 
				
			||||||
 | 
					        subject = render_to_string(
 | 
				
			||||||
 | 
					            'confirmation/notify_change_in_email_subject.txt', context)
 | 
				
			||||||
 | 
					        body = render_to_string(
 | 
				
			||||||
 | 
					            'confirmation/notify_change_in_email_body.txt', context)
 | 
				
			||||||
 | 
					        send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [old_email])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx = {
 | 
				
			||||||
 | 
					        'confirmed': confirmed,
 | 
				
			||||||
 | 
					        'support_email': settings.ZULIP_ADMINISTRATOR,
 | 
				
			||||||
 | 
					        'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS,
 | 
				
			||||||
 | 
					        'new_email': new_email,
 | 
				
			||||||
 | 
					        'old_email': old_email,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return render(request, 'confirmation/confirm_email_change.html', context=ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@has_request_variables
 | 
					@has_request_variables
 | 
				
			||||||
def json_change_ui_settings(request, user_profile,
 | 
					def json_change_ui_settings(request, user_profile,
 | 
				
			||||||
@@ -53,11 +94,12 @@ def json_change_ui_settings(request, user_profile,
 | 
				
			|||||||
@has_request_variables
 | 
					@has_request_variables
 | 
				
			||||||
def json_change_settings(request, user_profile,
 | 
					def json_change_settings(request, user_profile,
 | 
				
			||||||
                         full_name=REQ(default=""),
 | 
					                         full_name=REQ(default=""),
 | 
				
			||||||
 | 
					                         email=REQ(default=""),
 | 
				
			||||||
                         old_password=REQ(default=""),
 | 
					                         old_password=REQ(default=""),
 | 
				
			||||||
                         new_password=REQ(default=""),
 | 
					                         new_password=REQ(default=""),
 | 
				
			||||||
                         confirm_password=REQ(default="")):
 | 
					                         confirm_password=REQ(default="")):
 | 
				
			||||||
    # type: (HttpRequest, UserProfile, Text, Text, Text, Text) -> HttpResponse
 | 
					    # type: (HttpRequest, UserProfile, Text, Text, Text, Text, Text) -> HttpResponse
 | 
				
			||||||
    if not (full_name or new_password):
 | 
					    if not (full_name or new_password or email):
 | 
				
			||||||
        return json_error(_("No new data supplied"))
 | 
					        return json_error(_("No new data supplied"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if new_password != "" or confirm_password != "":
 | 
					    if new_password != "" or confirm_password != "":
 | 
				
			||||||
@@ -82,6 +124,16 @@ def json_change_settings(request, user_profile,
 | 
				
			|||||||
        request.session.save()
 | 
					        request.session.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    result = {}
 | 
					    result = {}
 | 
				
			||||||
 | 
					    new_email = email.strip()
 | 
				
			||||||
 | 
					    if user_profile.email != email and new_email != '':
 | 
				
			||||||
 | 
					        error, skipped = validate_email(user_profile, new_email)
 | 
				
			||||||
 | 
					        if error or skipped:
 | 
				
			||||||
 | 
					            return json_error(error or skipped)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        do_start_email_change_process(user_profile, new_email)
 | 
				
			||||||
 | 
					        result['account_email'] = _('We have sent you an email on your '
 | 
				
			||||||
 | 
					                                    'new email address for confirmation.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if user_profile.full_name != full_name and full_name.strip() != "":
 | 
					    if user_profile.full_name != full_name and full_name.strip() != "":
 | 
				
			||||||
        if name_changes_disabled(user_profile.realm):
 | 
					        if name_changes_disabled(user_profile.realm):
 | 
				
			||||||
            # Failingly silently is fine -- they can't do it through the UI, so
 | 
					            # Failingly silently is fine -- they can't do it through the UI, so
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -99,6 +99,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
 | 
				
			|||||||
                    'TWITTER_CONSUMER_SECRET': '',
 | 
					                    'TWITTER_CONSUMER_SECRET': '',
 | 
				
			||||||
                    'TWITTER_ACCESS_TOKEN_KEY': '',
 | 
					                    'TWITTER_ACCESS_TOKEN_KEY': '',
 | 
				
			||||||
                    'TWITTER_ACCESS_TOKEN_SECRET': '',
 | 
					                    'TWITTER_ACCESS_TOKEN_SECRET': '',
 | 
				
			||||||
 | 
					                    'EMAIL_CHANGE_CONFIRMATION_DAYS': 1,
 | 
				
			||||||
                    'EMAIL_GATEWAY_PATTERN': '',
 | 
					                    'EMAIL_GATEWAY_PATTERN': '',
 | 
				
			||||||
                    'EMAIL_GATEWAY_EXAMPLE': '',
 | 
					                    'EMAIL_GATEWAY_EXAMPLE': '',
 | 
				
			||||||
                    'EMAIL_GATEWAY_BOT': None,
 | 
					                    'EMAIL_GATEWAY_BOT': None,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ import zerver.views.zephyr
 | 
				
			|||||||
import zerver.views.users
 | 
					import zerver.views.users
 | 
				
			||||||
import zerver.views.unsubscribe
 | 
					import zerver.views.unsubscribe
 | 
				
			||||||
import zerver.views.integrations
 | 
					import zerver.views.integrations
 | 
				
			||||||
 | 
					import zerver.views.user_settings
 | 
				
			||||||
import confirmation.views
 | 
					import confirmation.views
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.rest import rest_dispatch
 | 
					from zerver.lib.rest import rest_dispatch
 | 
				
			||||||
@@ -103,6 +104,10 @@ i18n_urls = [
 | 
				
			|||||||
        name='zerver.views.registration.accounts_register'),
 | 
					        name='zerver.views.registration.accounts_register'),
 | 
				
			||||||
    url(r'^accounts/do_confirm/(?P<confirmation_key>[\w]+)', confirmation.views.confirm, name='confirmation.views.confirm'),
 | 
					    url(r'^accounts/do_confirm/(?P<confirmation_key>[\w]+)', confirmation.views.confirm, name='confirmation.views.confirm'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    url(r'^accounts/confirm_new_email/(?P<confirmation_key>[\w]+)',
 | 
				
			||||||
 | 
					        zerver.views.user_settings.confirm_email_change,
 | 
				
			||||||
 | 
					        name='zerver.views.user_settings.confirm_email_change'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Email unsubscription endpoint. Allows for unsubscribing from various types of emails,
 | 
					    # Email unsubscription endpoint. Allows for unsubscribing from various types of emails,
 | 
				
			||||||
    # including the welcome emails (day 1 & 2), missed PMs, etc.
 | 
					    # including the welcome emails (day 1 & 2), missed PMs, etc.
 | 
				
			||||||
    url(r'^accounts/unsubscribe/(?P<type>[\w]+)/(?P<token>[\w]+)',
 | 
					    url(r'^accounts/unsubscribe/(?P<type>[\w]+)/(?P<token>[\w]+)',
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user