Merge lp:~maxiberta/canonical-identity-provider/max-emails-per-account into lp:canonical-identity-provider/release

Proposed by Maximiliano Bertacchini
Status: Merged
Approved by: Maximiliano Bertacchini
Approved revision: no longer in the source branch.
Merged at revision: 1625
Proposed branch: lp:~maxiberta/canonical-identity-provider/max-emails-per-account
Merge into: lp:canonical-identity-provider/release
Diff against target: 237 lines (+117/-8)
6 files modified
django_project/settings_base.py (+1/-0)
src/identityprovider/forms.py (+9/-6)
src/identityprovider/tests/test_forms.py (+18/-0)
src/webui/constants.py (+4/-0)
src/webui/tests/test_views_account.py (+68/-1)
src/webui/views/account.py (+17/-1)
To merge this branch: bzr merge lp:~maxiberta/canonical-identity-provider/max-emails-per-account
Reviewer Review Type Date Requested Status
Daniel Manrique (community) Approve
Review via email: mp+345319@code.launchpad.net

Commit message

Prevent adding new emails to addresses that have reached a certain threshold.

Description of the change

Prevent adding new emails to addresses that have reached a certain threshold (settings.MAX_EMAILS_PER_ACCOUNT, default 30, based on the current max # of emails in all accounts in production); and show a warning in email management views. E.g. (with MAX_EMAILS_PER_ACCOUNT=3):

https://screenshots.firefox.com/cUqIwUu0mMpDKcQn/10.51.30.184
https://screenshots.firefox.com/F4rYwCKpZU5hQice/10.51.30.184

There's no API to add emails to an account afaict.

To post a comment you must log in.
Revision history for this message
Daniel Manrique (roadmr) wrote :

LGTM, thanks!

A couple of suggestions below.

review: Approve
Revision history for this message
Maximiliano Bertacchini (maxiberta) :
Revision history for this message
Daniel Manrique (roadmr) wrote :

Thanks! Let's merge!

review: Approve
Revision history for this message
Otto Co-Pilot (otto-copilot) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'django_project/settings_base.py'
2--- django_project/settings_base.py 2018-03-06 08:47:52 +0000
3+++ django_project/settings_base.py 2018-05-09 20:50:38 +0000
4@@ -378,6 +378,7 @@
5 MACAROON_TTL = 365 * 24 * 3600 # seconds
6 MACAROON_SERVICE_LOCATION = HOSTNAME
7 MANAGERS = []
8+MAX_EMAILS_PER_ACCOUNT = 30
9 MAX_FAILED_LOGIN_ATTEMPTS = 20
10 MAX_PASSWORD_RESET_TOKENS = 5
11 MEDIA_ROOT = ''
12
13=== modified file 'src/identityprovider/forms.py'
14--- src/identityprovider/forms.py 2018-02-08 16:34:57 +0000
15+++ src/identityprovider/forms.py 2018-05-09 20:50:38 +0000
16@@ -5,6 +5,7 @@
17 import re
18
19 from django import forms
20+from django.conf import settings
21 from django.core.urlresolvers import reverse
22 from django.forms import Form, fields, widgets
23 from django.utils import translation
24@@ -373,6 +374,12 @@
25 data = self.cleaned_data['newemail']
26 try:
27 if self.account is not None:
28+ if (self.account.emailaddress_set.count() >=
29+ settings.MAX_EMAILS_PER_ACCOUNT):
30+ raise forms.ValidationError(_(
31+ 'Account reached the maximum allowed number of email '
32+ 'addresses (%d).') % settings.MAX_EMAILS_PER_ACCOUNT)
33+
34 # check email was not previously invalidated for this account
35 invalidated = self.account.invalidatedemailaddress_set.filter(
36 email__iexact=data)
37@@ -382,13 +389,9 @@
38 ))
39 email = EmailAddress.objects.get(email__iexact=data)
40 if email.status != EmailStatus.NEW:
41- raise forms.ValidationError(_(
42- "Please verify your email."
43- ))
44+ raise forms.ValidationError(_("Please verify your email."))
45 else:
46- raise forms.ValidationError(_(
47- "Email already registered."
48- ))
49+ raise forms.ValidationError(_("Email already registered."))
50 except EmailAddress.DoesNotExist:
51 return data
52 return data
53
54=== modified file 'src/identityprovider/tests/test_forms.py'
55--- src/identityprovider/tests/test_forms.py 2018-02-16 13:29:05 +0000
56+++ src/identityprovider/tests/test_forms.py 2018-05-09 20:50:38 +0000
57@@ -5,6 +5,7 @@
58 import urllib2
59
60 from django import forms
61+from django.conf import settings
62 from django.http import HttpRequest
63 from django.test.utils import override_settings
64 from django.utils.timezone import now
65@@ -770,6 +771,23 @@
66 self.assertEqual(['Email already registered.'],
67 form.errors['newemail'])
68
69+ @override_settings(MAX_EMAILS_PER_ACCOUNT=5)
70+ def test_max_emails_per_account(self):
71+ account = self.factory.make_account()
72+
73+ for _ in range(settings.MAX_EMAILS_PER_ACCOUNT - 1):
74+ email = self.factory.make_email_address()
75+ form = NewEmailForm(dict(newemail=email), account=account)
76+ self.assertTrue(form.is_valid())
77+ self.factory.make_email_for_account(account,
78+ status=EmailStatus.NEW)
79+ self.assertEqual(
80+ settings.MAX_EMAILS_PER_ACCOUNT, account.emails().count())
81+
82+ email = self.factory.make_email_address()
83+ form = NewEmailForm({'newemail': email}, account=account)
84+ self.assertFalse(form.is_valid())
85+
86
87 class UserAttribsRequestFormTest(SSOBaseTestCase):
88
89
90=== modified file 'src/webui/constants.py'
91--- src/webui/constants.py 2015-05-08 17:24:05 +0000
92+++ src/webui/constants.py 2018-05-09 20:50:38 +0000
93@@ -3,3 +3,7 @@
94
95 EMAIL_EXISTS_ERROR = _('An Ubuntu One account already exists with this email.')
96 VERIFY_EMAIL_MESSAGE = _('Please verify your email.')
97+MAX_EMAILS_PER_ACCOUNT_EXCEEDED = _(
98+ "You have reached the maximum allowed number of email addresses per "
99+ "account (%d). Delete some of those email addresses to be able to add new "
100+ "ones.")
101
102=== modified file 'src/webui/tests/test_views_account.py'
103--- src/webui/tests/test_views_account.py 2018-04-04 15:34:30 +0000
104+++ src/webui/tests/test_views_account.py 2018-05-09 20:50:38 +0000
105@@ -41,7 +41,7 @@
106 )
107 from identityprovider.views.testing import MockLaunchpad
108 from webui import decorators
109-from webui.constants import EMAIL_EXISTS_ERROR
110+from webui.constants import EMAIL_EXISTS_ERROR, MAX_EMAILS_PER_ACCOUNT_EXCEEDED
111
112
113 class AccountEmailsViewTestCase(AuthenticatedTestCase):
114@@ -168,6 +168,27 @@
115 token=ctx['token'])
116 self.assert_add_email_action(response, token=ctx['token'])
117
118+ @override_settings(MAX_EMAILS_PER_ACCOUNT=5)
119+ def test_get_with_max_emails_per_account_exceeded(self):
120+ for _ in range(settings.MAX_EMAILS_PER_ACCOUNT - 1):
121+ r = self.client.get(reverse('account-emails'))
122+ self.assertEqual(r.status_code, 200)
123+ self.assertNotIn(
124+ (MAX_EMAILS_PER_ACCOUNT_EXCEEDED %
125+ settings.MAX_EMAILS_PER_ACCOUNT),
126+ [m.message for m in r.context['messages']])
127+ email = self.factory.make_email_address()
128+ self.factory.make_email_for_account(self.account, email=email)
129+
130+ self.assertEqual(
131+ settings.MAX_EMAILS_PER_ACCOUNT, self.account.emails().count())
132+
133+ r = self.client.get(reverse('account-emails'))
134+ self.assertEqual(r.status_code, 200)
135+ self.assertIn(
136+ MAX_EMAILS_PER_ACCOUNT_EXCEEDED % settings.MAX_EMAILS_PER_ACCOUNT,
137+ [m.message for m in r.context['messages']])
138+
139
140 class AccountViewsUnauthenticatedTestCase(SSOBaseTestCase):
141
142@@ -550,6 +571,52 @@
143 self.assertIn('newemail', response.context['form'].errors)
144 self.assertIn(unicode(EMAIL_EXISTS_ERROR), response.content)
145
146+ @override_settings(MAX_EMAILS_PER_ACCOUNT=5)
147+ def test_new_email_get_with_max_emails_per_account_exceeded(self):
148+ for _ in range(settings.MAX_EMAILS_PER_ACCOUNT - 1):
149+ r = self.client.get(reverse('new_email'))
150+ self.assertEqual(r.status_code, 200)
151+ self.assertNotIn(
152+ (MAX_EMAILS_PER_ACCOUNT_EXCEEDED %
153+ settings.MAX_EMAILS_PER_ACCOUNT),
154+ [m.message for m in r.context['messages']])
155+ email = self.factory.make_email_address()
156+ self.factory.make_email_for_account(self.account, email=email)
157+
158+ self.assertEqual(
159+ settings.MAX_EMAILS_PER_ACCOUNT, self.account.emails().count())
160+
161+ r = self.client.get(reverse('new_email'))
162+ self.assertEqual(r.status_code, 200)
163+ self.assertIn(
164+ MAX_EMAILS_PER_ACCOUNT_EXCEEDED % settings.MAX_EMAILS_PER_ACCOUNT,
165+ [m.message for m in r.context['messages']])
166+
167+ @override_settings(MAX_EMAILS_PER_ACCOUNT=5)
168+ def test_new_email_post_with_max_emails_per_account_exceeded(self):
169+ for _ in range(settings.MAX_EMAILS_PER_ACCOUNT - 1):
170+ email = self.factory.make_email_address()
171+ r = self.client.post(reverse('new_email'), {'newemail': email})
172+ self.assertEqual(r.status_code, 200)
173+ self.assertNotIn(
174+ (MAX_EMAILS_PER_ACCOUNT_EXCEEDED %
175+ settings.MAX_EMAILS_PER_ACCOUNT),
176+ [m.message for m in r.context['messages']])
177+
178+ self.assertEqual(
179+ settings.MAX_EMAILS_PER_ACCOUNT, self.account.emails().count())
180+
181+ email = self.factory.make_email_address()
182+ r = self.client.post(reverse('new_email'), {'newemail': email})
183+ self.assertEqual(r.status_code, 200)
184+ self.assertFalse(r.context['form'].is_valid())
185+ self.assertIn(
186+ MAX_EMAILS_PER_ACCOUNT_EXCEEDED % settings.MAX_EMAILS_PER_ACCOUNT,
187+ [m.message for m in r.context['messages']])
188+ # No new email was added to the account.
189+ self.assertEqual(
190+ settings.MAX_EMAILS_PER_ACCOUNT, self.account.emails().count())
191+
192 def test_delete_account_link(self):
193 response = self.client.get('/')
194 self.assertContains(response, '/+delete')
195
196=== modified file 'src/webui/views/account.py'
197--- src/webui/views/account.py 2018-04-04 15:34:30 +0000
198+++ src/webui/views/account.py 2018-05-09 20:50:38 +0000
199@@ -58,7 +58,11 @@
200 require_testing_enabled,
201 )
202
203-from webui.constants import EMAIL_EXISTS_ERROR, VERIFY_EMAIL_MESSAGE
204+from webui.constants import (
205+ EMAIL_EXISTS_ERROR,
206+ MAX_EMAILS_PER_ACCOUNT_EXCEEDED,
207+ VERIFY_EMAIL_MESSAGE,
208+)
209 from webui.decorators import check_readonly, sso_login_required
210 from webui.forms import (
211 ClaimSSHKeyForm,
212@@ -253,6 +257,12 @@
213 form = NewEmailForm()
214 verified_emails = account.verified_emails()
215 unverified_emails = account.unverified_emails()
216+
217+ if account.emailaddress_set.count() >= settings.MAX_EMAILS_PER_ACCOUNT:
218+ messages.error(
219+ request,
220+ MAX_EMAILS_PER_ACCOUNT_EXCEEDED % settings.MAX_EMAILS_PER_ACCOUNT)
221+
222 context = {
223 'current_section': 'emails',
224 'account': account,
225@@ -328,6 +338,12 @@
226 if err == VERIFY_EMAIL_MESSAGE:
227 messages.error(request, EMAIL_EXISTS_ERROR)
228
229+ if (request.user.emailaddress_set.count() >=
230+ settings.MAX_EMAILS_PER_ACCOUNT):
231+ messages.error(
232+ request,
233+ MAX_EMAILS_PER_ACCOUNT_EXCEEDED % settings.MAX_EMAILS_PER_ACCOUNT)
234+
235 context = {
236 'form': form,
237 'token': token,