Merge lp:~matiasb/canonical-identity-provider/prevent-preferring-unvalidated-email into lp:canonical-identity-provider/release

Proposed by Matias Bordese
Status: Merged
Approved by: Natalia Bidart
Approved revision: no longer in the source branch.
Merged at revision: 631
Proposed branch: lp:~matiasb/canonical-identity-provider/prevent-preferring-unvalidated-email
Merge into: lp:canonical-identity-provider/release
Diff against target: 475 lines (+161/-90)
11 files modified
identityprovider/forms.py (+17/-17)
identityprovider/models/account.py (+4/-1)
identityprovider/models/emailaddress.py (+3/-0)
identityprovider/tests/unit/test_forms.py (+35/-0)
identityprovider/tests/unit/test_models_account.py (+9/-0)
identityprovider/tests/unit/test_models_emailaddress.py (+10/-0)
ubuntu_sso_saml/tests.py (+1/-1)
webui/templates/account/edit.html (+15/-6)
webui/tests/test_views_account.py (+2/-0)
webui/tests/test_views_ui.py (+32/-39)
webui/views/ui.py (+33/-26)
To merge this branch: bzr merge lp:~matiasb/canonical-identity-provider/prevent-preferring-unvalidated-email
Reviewer Review Type Date Requested Status
Natalia Bidart (community) Approve
Review via email: mp+145032@code.launchpad.net

Commit message

Updated checks and validation for users and (un)verified email addresses:
  - Prevent user changing preferred email address to an unverified email address.
  - Prevent password reset for users without a verified email address.

Description of the change

Updated checks and validation for users and (un)verified email addresses:
  - Prevent user changing preferred email address to an unverified email address.
  - Prevent password reset for users without a verified email address.

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'identityprovider/forms.py'
--- identityprovider/forms.py 2012-12-18 18:37:03 +0000
+++ identityprovider/forms.py 2013-01-25 22:16:21 +0000
@@ -154,12 +154,6 @@
154 displayname = fields.CharField(154 displayname = fields.CharField(
155 error_messages=default_errors,155 error_messages=default_errors,
156 widget=ROAwareTextInput(attrs={'class': 'textType', 'size': '20'}))156 widget=ROAwareTextInput(attrs={'class': 'textType', 'size': '20'}))
157 preferred_email = PreferredEmailField(
158 error_messages=email_errors,
159 queryset=EmailAddress.objects.none(),
160 widget=ROAwareSelect,
161 empty_label=None,
162 )
163 password = fields.CharField(157 password = fields.CharField(
164 required=False,158 required=False,
165 help_text=PASSWORD_POLICY_HELP_TEXT,159 help_text=PASSWORD_POLICY_HELP_TEXT,
@@ -193,16 +187,21 @@
193 else:187 else:
194 preferredemail_id = self.instance.preferredemail.id188 preferredemail_id = self.instance.preferredemail.id
195189
196 # Override the field to set initial value (current preferred email)190 # check for emails that can be preferred
197 emails = EmailAddress.objects.filter(account=self.instance)191 validated_emails = self.instance.verified_emails()
198 self.fields['preferred_email'] = PreferredEmailField(192
199 queryset=emails.exclude(status=EmailStatus.NEW).order_by('-status',193 if validated_emails.count() > 0:
200 'email'),194 # add and display a dropdown with the valid choices
201 initial=preferredemail_id,195 self.fields['preferred_email'] = PreferredEmailField(
202 widget=ROAwareSelect,196 queryset=validated_emails.order_by('email'),
203 error_messages=email_errors,197 initial=preferredemail_id,
204 empty_label=None198 widget=ROAwareSelect,
205 )199 error_messages=email_errors,
200 empty_label=None,
201 help_text=_(
202 'Only verified email addresses are listed. '
203 'You can add and verify emails through the link below.'),
204 )
206205
207 class Meta:206 class Meta:
208 fields = ('displayname', 'warn_about_backup_device',207 fields = ('displayname', 'warn_about_backup_device',
@@ -257,7 +256,8 @@
257 new_password = encrypt_launchpad_password(password)256 new_password = encrypt_launchpad_password(password)
258 self.instance.accountpassword.password = new_password257 self.instance.accountpassword.password = new_password
259 self.instance.accountpassword.save()258 self.instance.accountpassword.save()
260 self.instance.preferredemail = self.cleaned_data['preferred_email']259 if 'preferred_email' in self.cleaned_data:
260 self.instance.preferredemail = self.cleaned_data['preferred_email']
261261
262 super(EditAccountForm, self).save()262 super(EditAccountForm, self).save()
263263
264264
=== modified file 'identityprovider/models/account.py'
--- identityprovider/models/account.py 2013-01-22 18:28:03 +0000
+++ identityprovider/models/account.py 2013-01-25 22:16:21 +0000
@@ -225,6 +225,8 @@
225 return self._preferredemail225 return self._preferredemail
226226
227 def _set_preferredemail(self, email):227 def _set_preferredemail(self, email):
228 if not email.is_verified():
229 raise ValidationError('Email must be verified')
228 current = self.preferredemail230 current = self.preferredemail
229 if current is not None:231 if current is not None:
230 current.status = EmailStatus.VALIDATED232 current.status = EmailStatus.VALIDATED
@@ -298,7 +300,8 @@
298300
299 @property301 @property
300 def can_reset_password(self):302 def can_reset_password(self):
301 return (self.is_active or self.can_reactivate)303 return ((self.is_active or self.can_reactivate) and
304 self.verified_emails().count() > 0)
302305
303 @property306 @property
304 def is_active(self):307 def is_active(self):
305308
=== modified file 'identityprovider/models/emailaddress.py'
--- identityprovider/models/emailaddress.py 2013-01-22 18:28:03 +0000
+++ identityprovider/models/emailaddress.py 2013-01-25 22:16:21 +0000
@@ -39,6 +39,9 @@
39 def is_preferred(self):39 def is_preferred(self):
40 return self.status == EmailStatus.PREFERRED40 return self.status == EmailStatus.PREFERRED
4141
42 def is_verified(self):
43 return self.status in (EmailStatus.VALIDATED, EmailStatus.PREFERRED)
44
42 def account_admin_link(self):45 def account_admin_link(self):
43 if self.account is None:46 if self.account is None:
44 return "None"47 return "None"
4548
=== modified file 'identityprovider/tests/unit/test_forms.py'
--- identityprovider/tests/unit/test_forms.py 2012-12-17 16:27:17 +0000
+++ identityprovider/tests/unit/test_forms.py 2013-01-25 22:16:21 +0000
@@ -9,6 +9,7 @@
9from openid.extensions.sreg import SRegRequest9from openid.extensions.sreg import SRegRequest
1010
11from identityprovider.models.account import Account11from identityprovider.models.account import Account
12from identityprovider.models.const import EmailStatus
12from identityprovider.forms import (13from identityprovider.forms import (
13 DeviceRenameForm,14 DeviceRenameForm,
14 EditAccountForm,15 EditAccountForm,
@@ -49,6 +50,40 @@
4950
50 self.assertEqual(form.initial.get('displayname', ''), "Sample Person")51 self.assertEqual(form.initial.get('displayname', ''), "Sample Person")
5152
53 def test_account_without_validated_emails_no_field(self):
54 self.account.emailaddress_set.update(status=EmailStatus.NEW)
55 form = EditAccountForm(instance=self.account)
56 self.assertNotIn('preferred_email', form.fields)
57
58 def test_account_with_validated_emails_editable_field(self):
59 form = EditAccountForm(instance=self.account)
60 self.assertIn('preferred_email', form.fields)
61 choices = form.fields['preferred_email'].queryset.all()
62 valid_choices = self.account.verified_emails()
63 self.assertEqual(choices.count(), valid_choices.count())
64 for email in choices:
65 self.assertIn(email, valid_choices)
66
67 def test_account_without_validated_emails_post_preferred_email(self):
68 self.account.emailaddress_set.update(status=EmailStatus.NEW)
69 original_value = self.account.preferredemail
70 invalid_email = self.account.unverified_emails().exclude(
71 email=original_value)[0]
72 form = EditAccountForm(instance=self.account,
73 data={'displayname': self.account.displayname,
74 'preferred_email': invalid_email.email})
75 self.assertTrue(form.is_valid())
76 form.save()
77 account = Account.objects.get(id=self.account.id)
78 self.assertEqual(account.preferredemail, original_value)
79
80 def test_account_with_validated_email_changing_to_unvalidate(self):
81 invalid_email = self.account.unverified_emails()[0]
82 form = EditAccountForm(instance=self.account,
83 data={'displayname': self.account.displayname,
84 'preferred_email': invalid_email.id})
85 self.assertFalse(form.is_valid())
86
52 def test_displayname_validation(self):87 def test_displayname_validation(self):
53 form = EditAccountForm(instance=self.account,88 form = EditAccountForm(instance=self.account,
54 data={'displayname': ' '})89 data={'displayname': ' '})
5590
=== modified file 'identityprovider/tests/unit/test_models_account.py'
--- identityprovider/tests/unit/test_models_account.py 2013-01-14 12:31:37 +0000
+++ identityprovider/tests/unit/test_models_account.py 2013-01-25 22:16:21 +0000
@@ -209,6 +209,15 @@
209 account = self.factory.make_account(email_validated=False)209 account = self.factory.make_account(email_validated=False)
210 self.assertIsNone(account.preferredemail)210 self.assertIsNone(account.preferredemail)
211211
212 def test_set_preferred_with_unverified_email(self):
213 account = self.factory.make_account(email_validated=False)
214 with self.assertRaises(ValidationError):
215 account.preferredemail = account.emailaddress_set.get()
216
217 def test_cant_reset_password_without_verified_email(self):
218 account = self.factory.make_account(email_validated=False)
219 self.assertFalse(account.can_reset_password)
220
212 def test_validated_email_address_is_preferred_email_by_default(self):221 def test_validated_email_address_is_preferred_email_by_default(self):
213 account = self.factory.make_account(email_validated=False)222 account = self.factory.make_account(email_validated=False)
214 email_address = account.emailaddress_set.create(223 email_address = account.emailaddress_set.create(
215224
=== modified file 'identityprovider/tests/unit/test_models_emailaddress.py'
--- identityprovider/tests/unit/test_models_emailaddress.py 2012-10-24 20:04:50 +0000
+++ identityprovider/tests/unit/test_models_emailaddress.py 2013-01-25 22:16:21 +0000
@@ -1,6 +1,7 @@
1from mock import patch1from mock import patch
22
3from identityprovider.models import Account, EmailAddress3from identityprovider.models import Account, EmailAddress
4from identityprovider.models.const import EmailStatus
4from identityprovider.tests.utils import SSOBaseTestCase5from identityprovider.tests.utils import SSOBaseTestCase
56
67
@@ -12,6 +13,15 @@
12 email = EmailAddress.objects.get(email='test@canonical.com')13 email = EmailAddress.objects.get(email='test@canonical.com')
13 self.assertEqual(account, email.account)14 self.assertEqual(account, email.account)
1415
16 def test_emailaddress_is_verified(self):
17 email = EmailAddress.objects.get(email='test@canonical.com')
18 assert email.status == EmailStatus.PREFERRED
19 self.assertTrue(email.is_verified())
20 email.status = EmailStatus.VALIDATED
21 self.assertTrue(email.is_verified())
22 email.status = EmailStatus.NEW
23 self.assertFalse(email.is_verified())
24
15 @patch('identityprovider.models.emailaddress.reverse')25 @patch('identityprovider.models.emailaddress.reverse')
16 def test_account_admin_link(self, mock_reverse):26 def test_account_admin_link(self, mock_reverse):
17 mock_reverse.return_value = '/foo'27 mock_reverse.return_value = '/foo'
1828
=== modified file 'ubuntu_sso_saml/tests.py'
--- ubuntu_sso_saml/tests.py 2013-01-15 18:20:50 +0000
+++ ubuntu_sso_saml/tests.py 2013-01-25 22:16:21 +0000
@@ -176,7 +176,7 @@
176 alternate_email = 'alternate@example.com'176 alternate_email = 'alternate@example.com'
177 alternate, _ = EmailAddress.objects.get_or_create(177 alternate, _ = EmailAddress.objects.get_or_create(
178 email=alternate_email, defaults={'account': account,178 email=alternate_email, defaults={'account': account,
179 'status': EmailStatus.NEW})179 'status': EmailStatus.VALIDATED})
180 # and make the alternate email the preferred180 # and make the alternate email the preferred
181 account = change_preferred_email(self.login_email, alternate_email)181 account = change_preferred_email(self.login_email, alternate_email)
182 self.assertEqual(account.preferredemail, alternate)182 self.assertEqual(account.preferredemail, alternate)
183183
=== modified file 'webui/templates/account/edit.html'
--- webui/templates/account/edit.html 2012-12-04 18:51:42 +0000
+++ webui/templates/account/edit.html 2013-01-25 22:16:21 +0000
@@ -95,13 +95,22 @@
95 </span>95 </span>
96 </label>96 </label>
97 <br />97 <br />
98 {% if form.preferred_email.errors %}98 {% if form.preferred_email %}
99 <span class="error">99 {% if form.preferred_email.errors %}
100 {{ form.preferred_email.errors|first }}100 <span class="error">
101 </span>101 {{ form.preferred_email.errors|first }}
102 <br />102 </span>
103 <br />
104 {% endif %}
105 {{ form.preferred_email }}
106 <span class="formHelp">{{ form.preferred_email.help_text }}</span>
107 {% else %}
108 {# Manually added, it will be ignored by the django form #}
109 {% with form.instance.preferredemail as unvalidated_preferred %}
110 <input type="text" value="{{ unvalidated_preferred }}" disabled="true" />
111 <span class="formHelp">You still need to <a href="{% url verify_email %}?id={{ unvalidated_preferred.id }}">verify</a> your email address.</span>
112 {% endwith %}
103 {% endif %}113 {% endif %}
104 {{ form.preferred_email }}
105 </p>114 </p>
106 {% if not embedded %}115 {% if not embedded %}
107 <p><a href="/+emails">{% trans "Manage email addresses" %}</a></p>116 <p><a href="/+emails">{% trans "Manage email addresses" %}</a></p>
108117
=== modified file 'webui/tests/test_views_account.py'
--- webui/tests/test_views_account.py 2013-01-17 20:27:23 +0000
+++ webui/tests/test_views_account.py 2013-01-25 22:16:21 +0000
@@ -414,6 +414,8 @@
414 def test_deactivate_account(self):414 def test_deactivate_account(self):
415 # make sure we have a preferredemail415 # make sure we have a preferredemail
416 email = self.account.emailaddress_set.all()[0]416 email = self.account.emailaddress_set.all()[0]
417 email.status = EmailStatus.VALIDATED
418 email.save()
417 self.account.preferredemail = email419 self.account.preferredemail = email
418420
419 # deactivate account421 # deactivate account
420422
=== modified file 'webui/tests/test_views_ui.py'
--- webui/tests/test_views_ui.py 2013-01-22 18:28:03 +0000
+++ webui/tests/test_views_ui.py 2013-01-25 22:16:21 +0000
@@ -273,26 +273,17 @@
273 self.assertContains(response, "_gaq.push(['_setAccount', '12345']);")273 self.assertContains(response, "_gaq.push(['_setAccount', '12345']);")
274274
275 def test_claim_token_for_password_recovery_no_preferredemail(self):275 def test_claim_token_for_password_recovery_no_preferredemail(self):
276 """ According to bug #524582 this should be handled gracefully by276 """Prevent password reset for unvalidated emails."""
277 verifying the email address and resetting the password. """
278 account = Account.objects.get_by_email('test@canonical.com')277 account = Account.objects.get_by_email('test@canonical.com')
279 _preferred_email = account.preferredemail
280 for email_obj in account.emailaddress_set.all():278 for email_obj in account.emailaddress_set.all():
281 email_obj.status = EmailStatus.NEW279 email_obj.status = EmailStatus.NEW
282 email_obj.save()280 email_obj.save()
283281
284 r = self.client.post(reverse('forgot_password'),282 self.client.post(reverse('forgot_password'),
285 {'email': 'test@canonical.com'})283 {'email': 'test@canonical.com'})
286284
287 # reset password285 # since there is verified email address, no email was sent
288 data = {'password': 'Password1', 'passwordconfirm': 'Password1'}286 self.assertEqual(self.mock_send_email.call_count, 0)
289 r = self.client.post(self._token_url(), data)
290 self.assertRedirects(r, reverse('account-index'))
291
292 for email_obj in account.emailaddress_set.all():
293 email_obj.status = EmailStatus.VALIDATED
294 email_obj.save()
295 account.preferredemail = _preferred_email
296287
297288
298class EnterTokenTestCase(UIViewsBaseTestCase):289class EnterTokenTestCase(UIViewsBaseTestCase):
@@ -638,8 +629,7 @@
638 self.assertRedirects(r, reverse('account-index'))629 self.assertRedirects(r, reverse('account-index'))
639630
640 def test_reset_password_when_account_deactivated(self):631 def test_reset_password_when_account_deactivated(self):
641 """ Deactivated accounts can be reactivated using the reset password632 """Prevent password reset for unvalidated emails."""
642 functionality. Bug #556878 """
643 r = self.client.post(reverse('forgot_password'),633 r = self.client.post(reverse('forgot_password'),
644 {'email': 'test@canonical.com'})634 {'email': 'test@canonical.com'})
645635
@@ -656,38 +646,41 @@
656 def test_reset_password_when_account_deactivated_no_preferred_email(self):646 def test_reset_password_when_account_deactivated_no_preferred_email(self):
657 account = Account.objects.get_by_email('test@canonical.com')647 account = Account.objects.get_by_email('test@canonical.com')
658 # make sure the account is deactivated and preferred email is removed648 # make sure the account is deactivated and preferred email is removed
659 _preferred_email = account.preferredemail
660 account.status = AccountStatus.DEACTIVATED649 account.status = AccountStatus.DEACTIVATED
661 # Blow out the cached value650 # Blow out the cached value
662 account.save()651 account.save()
663 for email_obj in account.emailaddress_set.all():652 account.emailaddress_set.update(status=EmailStatus.NEW)
664 email_obj.status = EmailStatus.NEW
665 email_obj.save()
666653
667 account = Account.objects.get_by_email('test@canonical.com')654 account = Account.objects.get_by_email('test@canonical.com')
668655
669 if gargoyle.is_active('ALLOW_UNVALIDATED'):656 if gargoyle.is_active('ALLOW_UNVALIDATED'):
670 self.assertEqual(account.preferredemail.email, 'test@canoical.com')657 self.assertEqual(account.preferredemail.email,
658 'test@canonical.com')
671 else:659 else:
672 self.assertEqual(account.preferredemail, None)660 self.assertEqual(account.preferredemail, None)
673661
674 r = self.client.post(reverse('forgot_password'),662 self.client.post(reverse('forgot_password'),
675 {'email': 'test@canonical.com'})663 {'email': 'test@canonical.com'})
676664
677 self.assertEqual(self.mock_send_email.call_count, 1)665 # since there is verified email address, no email was sent
678666 self.assertEqual(self.mock_send_email.call_count, 0)
679 # reset password667
680 data = {'password': 'Password1', 'passwordconfirm': 'Password1'}668 def test_reset_password_when_account_no_verified_emails(self):
681 r = self.client.post(self._token_url(), data)669 account = Account.objects.get_by_email('test@canonical.com')
682 self.assertRedirects(r, reverse('account-index'))670 account.emailaddress_set.update(status=EmailStatus.NEW)
683671 self.client.post(reverse('forgot_password'),
684 account = Account.objects.get_by_email('test@canonical.com')672 {'email': 'test@canonical.com'})
685 self.assertEqual(account.preferredemail.email, 'test@canonical.com')673 # since there is verified email address, no email was sent
686674 self.assertEqual(self.mock_send_email.call_count, 0)
687 for email_obj in account.emailaddress_set.all():675
688 email_obj.status = EmailStatus.VALIDATED676 def test_reset_password_unverified_email_but_account_verified_email(self):
689 email_obj.save()677 account = Account.objects.get_by_email('test@canonical.com')
690 account.preferredemail = _preferred_email678 verified = account.verified_emails()
679 unverified = account.unverified_emails()[0]
680 self.client.post(reverse('forgot_password'),
681 {'email': unverified.email})
682 # email sent to verified email addresses instead
683 self.assertEqual(self.mock_send_email.call_count, len(verified))
691684
692 def test_reset_password_when_account_noaccount(self):685 def test_reset_password_when_account_noaccount(self):
693 r = self.client.post(reverse('forgot_password'),686 r = self.client.post(reverse('forgot_password'),
694687
=== modified file 'webui/views/ui.py'
--- webui/views/ui.py 2013-01-22 18:28:03 +0000
+++ webui/views/ui.py 2013-01-25 22:16:21 +0000
@@ -768,30 +768,36 @@
768768
769769
770def _handle_reset_request(request, email, token):770def _handle_reset_request(request, email, token):
771 # options for actions to be taken
772 NONE, RESET, CREATE = range(3)
773 # assume invalid
774 send_email = NONE
775 account = Account.objects.get_by_email(email)771 account = Account.objects.get_by_email(email)
776 if account is not None:772 if account is not None:
773 verified_emails = account.verified_emails()
777 if account.can_reset_password:774 if account.can_reset_password:
778 send_email = RESET775 redirection_url = redirection_url_for_token(token)
776 if not verified_emails.filter(email=email).count() > 0:
777 # provided email is not verified
778 # send email to verified addresses instead
779 for email_obj in verified_emails:
780 emailutils.send_password_reset_email(account,
781 email_obj.email,
782 redirection_url)
783 else:
784 emailutils.send_password_reset_email(account, email,
785 redirection_url)
786 set_session_email(request.session, email)
787 elif verified_emails.count() == 0:
788 # user does not have any verified email address
789 # he should contact support
790 condition = ("account '%s' has no verified email address" %
791 account.displayname)
792 logger.debug("In view 'forgot_password' email was not "
793 "sent out because %s" % condition)
779 else:794 else:
780 send_email = NONE
781 # log why email was not sent795 # log why email was not sent
782 condition = ("account '%s' is not active" %796 condition = ("account '%s' is not active" %
783 account.displayname)797 account.displayname)
784 logger.debug("In view 'forgot_password' email was not "798 logger.debug("In view 'forgot_password' email was not "
785 "sent out because %s" % condition)799 "sent out because %s" % condition)
786 else:800 else:
787 send_email = CREATE
788
789 # we should know now the action we need to take
790 if send_email == RESET:
791 redirection_url = redirection_url_for_token(token)
792 emailutils.send_password_reset_email(account, email, redirection_url)
793 set_session_email(request.session, email)
794 elif send_email == CREATE:
795 # they've tried to reset with an invalid email, so send them an email801 # they've tried to reset with an invalid email, so send them an email
796 # on how to create an account802 # on how to create an account
797 emailutils.send_invitation_after_password_reset(email)803 emailutils.send_invitation_after_password_reset(email)
@@ -833,18 +839,26 @@
833 msgs = {839 msgs = {
834 'email_to': email,840 'email_to': email,
835 'email_from': settings.NOREPLY_FROM_ADDRESS,841 'email_from': settings.NOREPLY_FROM_ADDRESS,
842 'support_form_url': settings.SUPPORT_FORM_URL,
836 }843 }
844 reason = _(
845 "We've just emailed "
846 "%(email_to)s (from %(email_from)s) with "
847 "instructions on resetting your password.<br/><br/>"
848 "If the email address you provided has not been verified "
849 "we'll use your account's verified email address instead. "
850 "If you don't have a verified email address please "
851 "<a href='%(support_form_url)s'>contact support</a>."
852 ) % msgs
837 return display_email_sent(853 return display_email_sent(
838 request,854 request,
839 email,855 email,
840 _("Forgotten your password?"),856 _("Forgotten your password?"),
841 _("We&rsquo;ve just emailed "857 reason,
842 "%(email_to)s (from %(email_from)s) with "
843 "instructions on resetting your password.") % msgs,
844 _("Check that you&rsquo;ve actually "858 _("Check that you&rsquo;ve actually "
845 "entered a subscribed email address."),859 "entered a subscribed email address."),
846 token=token,860 token=token,
847 rpconfig=rpconfig861 rpconfig=rpconfig,
848 )862 )
849 else:863 else:
850 # track form errors864 # track form errors
@@ -864,6 +878,7 @@
864878
865 atrequest = verify_token_string(authtoken, email_address)879 atrequest = verify_token_string(authtoken, email_address)
866 account = atrequest and atrequest.requester880 account = atrequest and atrequest.requester
881
867 if (atrequest is None or882 if (atrequest is None or
868 atrequest.token_type != TokenType.PASSWORDRECOVERY or883 atrequest.token_type != TokenType.PASSWORDRECOVERY or
869 not account.can_reset_password):884 not account.can_reset_password):
@@ -894,14 +909,6 @@
894 msg = _("Your account was reactivated, and the preferred "909 msg = _("Your account was reactivated, and the preferred "
895 "email address was set to {email}.")910 "email address was set to {email}.")
896 messages.info(request, msg.format(email=email_obj.email))911 messages.info(request, msg.format(email=email_obj.email))
897 elif account.preferredemail is None:
898 # handle this case gracefully according to #524582
899 email_obj, created = EmailAddress.objects.get_or_create(
900 account=account,
901 email=atrequest.requester_email,
902 defaults={'status': EmailStatus.PREFERRED})
903 if not created:
904 account.preferredemail = email_obj
905 email = account.preferredemail.email912 email = account.preferredemail.email
906 account.set_password(password)913 account.set_password(password)
907 user = auth.authenticate(username=email, password=password)914 user = auth.authenticate(username=email, password=password)