Merge lp:~canonical-isd-hackers/canonical-identity-provider/session-refactor-email-sent into lp:canonical-identity-provider/release

Proposed by Simon Davy
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: no longer in the source branch.
Merged at revision: 244
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/session-refactor-email-sent
Merge into: lp:canonical-identity-provider/release
Diff against target: 500 lines (+83/-111)
8 files modified
identityprovider/templates/launchpad/email/invitation.txt (+2/-4)
identityprovider/templates/ubuntu/email/invitation.txt (+2/-4)
identityprovider/tests/test_loginservice.py (+15/-15)
identityprovider/tests/test_views_account.py (+5/-5)
identityprovider/tests/test_views_ui.py (+21/-27)
identityprovider/urls.py (+0/-1)
identityprovider/views/account.py (+15/-17)
identityprovider/views/ui.py (+23/-38)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/session-refactor-email-sent
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+85930@code.launchpad.net

Commit message

Refactor to not use session/redirect for displaying email sent page. Instead, we just render the page directly

Description of the change

Refactor to not use session/redirect for displaying email sent page. Instead, we just render the page directly

To post a comment you must log in.
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

I like avoiding the use of session for this (+1)

- tiny stylistic issue: we prefer singular assertion methods (assertEqual)
- l. 249: always use RequestContext, otherwise context processors won't be applied (you won't get a style page for example)

One last question: given that there were certain precautions not to break the openid dance in this view, are we certain not redirecting will keep those scenarios working?

review: Needs Fixing
Revision history for this message
Simon Davy (bloodearnest) wrote :

> I like avoiding the use of session for this (+1)
>
> - tiny stylistic issue: we prefer singular assertion methods (assertEqual)
> - l. 249: always use RequestContext, otherwise context processors won't be
> applied (you won't get a style page for example)

Fixed

> One last question: given that there were certain precautions not to break the
> openid dance in this view, are we certain not redirecting will keep those
> scenarios working?

I'm not sure, as I'm not clear on the exact steps of the dance :) I just left anything relating to tokens alone, or reproduced it in the new code. Some day you'll have to teach me the dance :)

Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/templates/launchpad/email/invitation.txt'
2--- identityprovider/templates/launchpad/email/invitation.txt 2011-12-15 16:29:12 +0000
3+++ identityprovider/templates/launchpad/email/invitation.txt 2011-12-16 12:35:28 +0000
4@@ -1,11 +1,9 @@
5 {% load i18n %}
6 {% blocktrans %}Hello
7
8-You recently requested a password reset for the email account {{ email }} for
9-the Launchpad Login Service
10+You recently requested a password reset for the email account {{ email }} for the Launchpad Login Service
11
12-This email is not associated with any current accounts. You are very welcome
13-to create a Launchpad account - to do so, please go to {{ signup }}
14+This email is not associated with any current accounts. You are very welcome to create a Launchpad account - to do so, please go to {{ signup }}
15
16 Thanks
17
18
19=== modified file 'identityprovider/templates/ubuntu/email/invitation.txt'
20--- identityprovider/templates/ubuntu/email/invitation.txt 2011-12-15 16:29:12 +0000
21+++ identityprovider/templates/ubuntu/email/invitation.txt 2011-12-16 12:35:28 +0000
22@@ -1,11 +1,9 @@
23 {% load i18n %}
24 {% blocktrans %}Hello
25
26-You recently requested a password reset for the email account {{ email }} for
27-the Ubuntu Single Sign On service.
28+You recently requested a password reset for the email account {{ email }} for the Ubuntu Single Sign On service.
29
30-This email is not associated with any current accounts. You are very welcome
31-to create an Ubuntu SSO account - to do so, please go to {{ signup }}
32+This email is not associated with any current accounts. You are very welcome to create an Ubuntu SSO account - to do so, please go to {{ signup }}
33
34 Thanks
35
36
37=== modified file 'identityprovider/tests/test_loginservice.py'
38--- identityprovider/tests/test_loginservice.py 2011-12-15 16:29:12 +0000
39+++ identityprovider/tests/test_loginservice.py 2011-12-16 12:35:28 +0000
40@@ -33,11 +33,11 @@
41 'email': 'nobody@debian.org',
42 }
43 response = self.client.post('/+forgot_password', query)
44- self.assertEquals(response.status_code, 302)
45- self.assertEquals(response['location'],
46- 'http://testserver/+email-sent')
47- self.assertEquals(len(mail.outbox), 1)
48- self.assertEquals(mail.outbox[0].subject,
49+ self.assertEqual(response.status_code, 200)
50+ self.assertIn("Forgotten your password?", response.content)
51+ self.assertIn("nobody@debian.org", response.content)
52+ self.assertEqual(len(mail.outbox), 1)
53+ self.assertEqual(mail.outbox[0].subject,
54 u"%s: Password reset request" % current_brand.name)
55 self.assertTrue('nobody@debian.org' in mail.outbox[0].body)
56 self.assertTrue('+new_account' in mail.outbox[0].body)
57@@ -55,11 +55,11 @@
58 'passwordconfirm': 'Testing123'
59 }
60 response = self.client.post('/+new_account', query)
61- self.assertEquals(response.status_code, 302)
62- self.assertEquals(response['location'],
63- 'http://testserver/+email-sent')
64- self.assertEquals(len(mail.outbox), 1)
65- self.assertEquals(mail.outbox[0].subject,
66+ self.assertEqual(response.status_code, 200)
67+ self.assertIn("Registration mail sent", response.content)
68+ self.assertIn("test@canonical.com", response.content)
69+ self.assertEqual(len(mail.outbox), 1)
70+ self.assertEqual(mail.outbox[0].subject,
71 u"%s: Warning" % current_brand.name)
72 mail.outbox = []
73
74@@ -68,11 +68,11 @@
75 'email': 'test@canonical.com',
76 }
77 response = self.client.post('/+forgot_password', query)
78- self.assertEquals(response.status_code, 302)
79- self.assertEquals(response['location'],
80- 'http://testserver/+email-sent')
81- self.assertEquals(len(mail.outbox), 1)
82- self.assertEquals(mail.outbox[0].subject,
83+ self.assertEqual(response.status_code, 200)
84+ self.assertIn("Forgotten your password?", response.content)
85+ self.assertIn("test@canonical.com", response.content)
86+ self.assertEqual(len(mail.outbox), 1)
87+ self.assertEqual(mail.outbox[0].subject,
88 u"%s: Forgotten Password" % current_brand.name)
89 mail.outbox = []
90
91
92=== modified file 'identityprovider/tests/test_views_account.py'
93--- identityprovider/tests/test_views_account.py 2011-09-09 14:31:09 +0000
94+++ identityprovider/tests/test_views_account.py 2011-12-16 12:35:28 +0000
95@@ -85,7 +85,7 @@
96 account=account,
97 status=EmailStatus.NEW)
98 r = self.client.get('/+verify-email', {'id': email.id})
99- self.assertEqual(r.status_code, 302)
100+ self.assertEqual(r.status_code, 200)
101 self.assertEqual(len(mail.outbox), 1)
102 # Erasing emails. Necessary to make other tests pass.
103 mail.outbox = []
104@@ -97,13 +97,13 @@
105 def test_new_email_post(self):
106 r = self.client.post('/+new-email',
107 {'newemail': "very-new-email@example.com"})
108- self.assertRedirects(r, '/+email-sent')
109+ self.assertEqual(r.status_code, 200)
110 mail.outbox = []
111
112 def test_new_email_post_with_token(self):
113 r = self.client.post('/thisissuperrando/+new-email',
114 {'newemail': "very-new-email@example.com"})
115- self.assertEquals(len(mail.outbox), 1)
116+ self.assertEqual(len(mail.outbox), 1)
117 mail.outbox = []
118
119
120@@ -189,7 +189,7 @@
121 self.assertRedirects(r, '/+deactivated')
122
123 # test token is preserved
124- self.assertEquals(self.client.session.get(token),
125+ self.assertEqual(self.client.session.get(token),
126 'raw_orequest content')
127
128
129@@ -237,4 +237,4 @@
130 r = self.client.post('/+applications', {'token_id': token.token})
131
132 self.assertRedirects(r, '/+applications')
133- self.assertEquals(Token.objects.filter(token=token.token).count(), 1)
134+ self.assertEqual(Token.objects.filter(token=token.token).count(), 1)
135
136=== modified file 'identityprovider/tests/test_views_ui.py'
137--- identityprovider/tests/test_views_ui.py 2011-11-29 18:06:51 +0000
138+++ identityprovider/tests/test_views_ui.py 2011-12-16 12:35:28 +0000
139@@ -83,7 +83,7 @@
140
141 r = _post_new_account(self.client, email='mark@example.com')
142
143- self.assertRedirects(r, '/+email-sent')
144+ self.assertEqual(r.status_code, 200)
145 self.assertEqual(len(mail.outbox), 1)
146
147
148@@ -202,7 +202,7 @@
149 session.save()
150
151 self.client.get('/%s/+logout' % token)
152- self.assertEquals(self.client.session.get(token),
153+ self.assertEqual(self.client.session.get(token),
154 'raw_orequest content')
155
156 def create_token(self, token_type, email=None, redirection_url=None,
157@@ -253,13 +253,13 @@
158
159 def test_claim_unexisting_token(self):
160 r = self.client.get('/token/%s/' % 0, {'email': 'fake%40example.com'})
161- self.assertEquals(r.status_code, 404)
162+ self.assertEqual(r.status_code, 404)
163
164 def test_claim_token_for_unexisting_token_type(self):
165 token = self.create_token(9999, 'fake@example.com')
166 r = self.client.get('/token/%s/' % token.token,
167 {'email': 'fake%40example.com'})
168- self.assertEquals(r.status_code, 404)
169+ self.assertEqual(r.status_code, 404)
170
171 def test_claim_consumed_token(self):
172 self.client.post('/+forgot_password',
173@@ -306,11 +306,11 @@
174
175 def test_token_form_email_when_it_is_turned_off(self):
176 r = self.get_debug_token_response(False)
177- self.assertEquals(r.status_code, 404)
178+ self.assertEqual(r.status_code, 404)
179
180 def test_bad_token(self):
181 r = self.client.get('/+bad-token')
182- self.assertEquals(r.status_code, 200)
183+ self.assertEqual(r.status_code, 200)
184
185 def test_non_field_errors_is_not_in_html(self):
186 r = self.client.post('/+enter_token', {
187@@ -322,7 +322,7 @@
188
189 def test_logout_to_confirm(self):
190 r = self.client.get('/+logout-to-confirm')
191- self.assertEquals(r.status_code, 200)
192+ self.assertEqual(r.status_code, 200)
193
194 def test_confirm_account_while_logged_in(self):
195 token = self.create_token(at.LoginTokenType.NEWPERSONLESSACCOUNT,
196@@ -343,7 +343,7 @@
197 # get a valid session
198 token1 = 'a' * 16
199 r = _post_new_account(self.client, token=token1)
200- self.assertRedirects(r, '/%s/+email-sent' % token1)
201+ self.assertEqual(r.status_code, 200)
202
203 # claim token
204 r = self.client.post(self._token_url('+newaccount'),
205@@ -355,7 +355,7 @@
206 # get a valid session
207 token1 = create_token(16)
208 r = _post_new_account(self.client, token=token1)
209- self.assertRedirects(r, '/%s/+email-sent' % token1)
210+ self.assertEqual(r.status_code, 200)
211
212 # claim token
213 r = self.client.post(self._token_url('+newaccount'),
214@@ -366,7 +366,7 @@
215 def test_confirm_account_redirect_to_decide_with_rpconfig(self):
216 token1 = create_token(16)
217 r = _post_new_account(self.client, token=token1)
218- self.assertRedirects(r, '/%s/+email-sent' % token1)
219+ self.assertEqual(r.status_code, 200)
220
221 request = {'openid.mode': 'checkid_setup',
222 'openid.trust_root': 'http://localhost/',
223@@ -664,7 +664,7 @@
224 account.save()
225
226 r = _post_new_account(self.client, email='mark@example.com')
227- self.assertRedirects(r, '/+email-sent')
228+ self.assertEqual(r.status_code, 200)
229
230 if status == AccountStatus.ACTIVE:
231 mails_sent = 1
232@@ -736,7 +736,7 @@
233 email.delete()
234 # use verification token
235 r = self.client.get("/token/%s/+newemail" % token.token)
236- self.assertEquals(r.status_code, 404)
237+ self.assertEqual(r.status_code, 404)
238
239 def test_confirm_email_get(self):
240 self.authenticate()
241@@ -761,10 +761,10 @@
242
243 token_id = token.pk
244
245- self.assertEquals(r.status_code, 404)
246- self.assertEquals(0, at.AuthToken.objects.filter(pk=token_id).count())
247+ self.assertEqual(r.status_code, 404)
248+ self.assertEqual(0, at.AuthToken.objects.filter(pk=token_id).count())
249 email = EmailAddress.objects.get(email='newemail2@example.com')
250- self.assertEquals(EmailStatus.NEW, email.status)
251+ self.assertEqual(EmailStatus.NEW, email.status)
252
253 def test_config_email_when_not_logged_in_redirects_to_login(self):
254 account = Account.objects.get_by_email('mark@example.com')
255@@ -803,12 +803,12 @@
256
257 def test_invalid_email_does_not_blow_up(self):
258 r = _post_new_account(self.client, email='what<~@ever.com')
259- self.assertEquals(200, r.status_code)
260+ self.assertEqual(200, r.status_code)
261 self.assertContains(r, 'Invalid email')
262
263 def test_valid_email_redirects(self):
264 r = _post_new_account(self.client, email='what@ever.com')
265- self.assertRedirects(r, '/+email-sent')
266+ self.assertEqual(r.status_code, 200)
267
268
269 @skipOnSqlite
270@@ -826,7 +826,7 @@
271 try:
272 # test normal workflow
273 r = _post_new_account(self.client, email='mark@example.com')
274- self.assertRedirects(r, '/+email-sent')
275+ self.assertEqual(r.status_code, 200)
276 finally:
277 # restore model integrity
278 email.lp_person_id = lp_person_id
279@@ -866,10 +866,7 @@
280 response = _post_new_account(
281 self.client,
282 email=email)
283- # should redirect to '/+email-sent'
284- self.assertEqual(response.status_code, 302)
285- redirect = response['location']
286- self.assertTrue(redirect.endswith('/+email-sent'))
287+ self.assertEqual(response.status_code, 200)
288
289 def test_new_account_captcha_whitelist_with_uuid(self):
290 email = 'canonicaltest+something@gmail.com'
291@@ -882,10 +879,7 @@
292 response = _post_new_account(
293 self.client,
294 email=email)
295- # should redirect to '/+email-sent'
296- self.assertEqual(response.status_code, 302)
297- redirect = response['location']
298- self.assertTrue(redirect.endswith('/+email-sent'))
299+ self.assertEqual(response.status_code, 200)
300
301 def test_new_account_captcha_whitelist_fail(self):
302 email = 'notcanonicaltest@gmail.com'
303@@ -976,7 +970,7 @@
304
305 def test_cookie_is_sessionless(self):
306 self.client.get('/+login')
307- self.assertEquals(self._count_sessions(), 0)
308+ self.assertEqual(self._count_sessions(), 0)
309
310
311 class LoginFlowStatsTestCase(SSOBaseTestCase):
312
313=== modified file 'identityprovider/urls.py'
314--- identityprovider/urls.py 2011-12-15 16:29:12 +0000
315+++ identityprovider/urls.py 2011-12-16 12:35:28 +0000
316@@ -59,7 +59,6 @@
317
318 (r'^\+bad-token', 'bad_token'),
319 (r'^\+logout-to-confirm', 'logout_to_confirm'),
320- (r'^%(optional_token)s\+email-sent$' % repls, 'email_sent'),
321 (r'^debug-token/(?P<email>.*\@.*\..*)$', 'token_from_email'),
322 (r'^\+deactivated$', 'deactivated'),
323 (r'^\+suspended$', 'suspended'),
324
325=== modified file 'identityprovider/views/account.py'
326--- identityprovider/views/account.py 2011-11-21 19:57:56 +0000
327+++ identityprovider/views/account.py 2011-12-16 12:35:28 +0000
328@@ -23,7 +23,7 @@
329 from identityprovider.signals import account_email_added
330
331 from identityprovider.views.server import xrds
332-
333+from identityprovider.views.ui import display_email_sent
334
335 @vary_on_headers('Accept')
336 def index(request, token=None):
337@@ -124,7 +124,7 @@
338 return HttpResponseRedirect('/+deactivated')
339
340
341-def _send_verification_email(account, session, email, tokenid=None):
342+def _send_verification_email(request, email, tokenid=None):
343 # TODO: This makes use of `tokenid` if it's passed in, but the
344 # call to `send_validation_email_request` always generates a new
345 # token. Is this right?
346@@ -134,22 +134,22 @@
347 status=EmailStatus.NEW).delete()
348
349 # Ensure that this account has such email address; return value is not used
350- account.emailaddress_set.get_or_create(
351- email=email, lp_person=account.person, status=EmailStatus.NEW)
352+ request.user.emailaddress_set.get_or_create(
353+ email=email, lp_person=request.user.person, status=EmailStatus.NEW)
354
355 redirection_url = redirection_url_for_token(tokenid)
356
357- token = send_validation_email_request(account, email, redirection_url)
358- set_session_token_info(session, token)
359+ token = send_validation_email_request(request.user, email, redirection_url)
360+ set_session_token_info(request.session, token)
361
362- session['email_feedback'] = settings.FEEDBACK_TO_ADDRESS
363- session['email_heading'] = _("Validate your email address")
364- session['email_reason'] = _("We&rsquo;ve just "
365- "emailed %(email_to)s (from %(email_from)s) "
366- "with instructions on validating your email address.") % {
367+ return display_email_sent(request,
368+ _("Validate your email address"),
369+ _("We&rsquo;ve just "
370+ "emailed %(email_to)s (from %(email_from)s) "
371+ "with instructions on validating your email address.") % {
372 'email_to': email,
373- 'email_from': settings.NOREPLY_FROM_ADDRESS,
374- }
375+ 'email_from': settings.NOREPLY_FROM_ADDRESS}
376+ )
377
378 return HttpResponseRedirect('./+email-sent')
379
380@@ -167,8 +167,7 @@
381 account_email_added.send(
382 openid_identifier=request.user.openid_identifier,
383 sender=None)
384- return _send_verification_email(request.user, request.session,
385- email, token)
386+ return _send_verification_email(request, email, token)
387
388 context = RequestContext(request, {'form': form})
389 return render_to_response('account/new_email.html', context)
390@@ -180,8 +179,7 @@
391 emailid = request.GET.get('id', 0)
392 email = get_object_or_404(request.user.emailaddress_set, pk=emailid,
393 status=EmailStatus.NEW)
394- return _send_verification_email(request.user, request.session, email.email,
395- token)
396+ return _send_verification_email(request, email.email, token)
397
398
399 @login_required
400
401=== modified file 'identityprovider/views/ui.py'
402--- identityprovider/views/ui.py 2011-12-15 16:29:12 +0000
403+++ identityprovider/views/ui.py 2011-12-16 12:35:28 +0000
404@@ -455,22 +455,15 @@
405 atoken.sendNewUserEmail(old=old)
406 set_session_token_info(request.session, atoken)
407
408- request.session['email_feedback'] = settings.FEEDBACK_TO_ADDRESS
409- request.session['email_heading'] = _("Registration mail sent")
410- request.session['email_reason'] = _("We&rsquo;ve just emailed "
411+ return display_email_sent(request,
412+ _("Registration mail sent"),
413+ _("We&rsquo;ve just emailed "
414 "%(email_to)s (from %(email_from)s) to confirm "
415 "your address.") % {
416 'email_to': email,
417- 'email_from': settings.NOREPLY_FROM_ADDRESS,
418- }
419- request.session['email_notreceived_extra'] = None
420- if token is not None:
421- # Redirected from an OpenID request, must get back after
422- # confirmation
423- redirection_url = '/%s/+email-sent' % token
424- else:
425- redirection_url = '/+email-sent'
426- return HttpResponseRedirect(redirection_url)
427+ 'email_from': settings.NOREPLY_FROM_ADDRESS },
428+ token = token
429+ )
430 else:
431 polite_form_errors(form._errors)
432 # track number of form errors
433@@ -619,17 +612,14 @@
434 return HttpResponseNotFound()
435
436
437-def email_sent(request, token=None):
438- request.token = token
439+def display_email_sent(request, heading, reason, extra=None, token=None):
440 context = RequestContext(request, {
441- 'email_feedback': request.session.get('email_feedback'),
442- 'email_heading': request.session.get('email_heading'),
443- 'email_reason': request.session.get('email_reason'),
444- 'email_notreceived_extra': request.session.get(
445- 'email_notreceived_extra'),
446+ 'email_feedback': settings.FEEDBACK_TO_ADDRESS,
447+ 'email_heading': heading,
448+ 'email_reason': reason,
449+ 'email_notreceived_extra': extra,
450 'token': token,
451 })
452-
453 return render_to_response('registration/email_sent.html', context)
454
455
456@@ -708,7 +698,8 @@
457 # on how to create an account
458 brand = unicode(current_brand.name)
459 subject = u"%s: %s" % (brand, u"Password reset request")
460- context = { 'email': email, 'signup': reverse(new_account) }
461+ url = urljoin(settings.SSO_ROOT_URL, reverse(new_account))
462+ context = { 'email': email, 'signup': url }
463 tmpl = '%s/email/invitation.txt' % current_brand.template_dir
464 body = render_to_string(tmpl, context)
465 send_mail(subject, body, settings.NOREPLY_FROM_ADDRESS, [email])
466@@ -749,24 +740,18 @@
467
468 #regardless of result, we always display the same information back
469 # to the user
470- # TODO: remove this from session and display via direct render?
471- request.session['email_feedback'] = settings.FEEDBACK_TO_ADDRESS
472- request.session['email_heading'] = _("Forgotten your password?")
473- request.session['email_reason'] = _("We&rsquo;ve just emailed "
474- "%(email_to)s (from %(email_from)s) with "
475- "instructions on resetting your password.") % {
476+ return display_email_sent(request,
477+ _("Forgotten your password?"),
478+ _("We&rsquo;ve just emailed "
479+ "%(email_to)s (from %(email_from)s) with "
480+ "instructions on resetting your password.") % {
481 'email_to': email,
482 'email_from': settings.NOREPLY_FROM_ADDRESS,
483- }
484- request.session['email_notreceived_extra'] = _("Check that "
485- "you&rsquo;ve actually entered a subscribed email address.")
486- if token is not None:
487- # Redirected from an OpenID request, must get back after
488- # confirmation
489- redirection_url = '/%s/+email-sent' % token
490- else:
491- redirection_url = '/+email-sent'
492- return HttpResponseRedirect(redirection_url)
493+ },
494+ _("Check that you&rsquo;ve actually "
495+ "entered a subscribed email address."),
496+ token=token
497+ )
498 else:
499 # track form errors
500 stats.increment('flows.forgot_password', key='error.form',