Merge lp:~canonical-isd-hackers/canonical-identity-provider/700496-forget-pw-unkown-email into lp:canonical-identity-provider/release

Proposed by Simon Davy
Status: Merged
Approved by: David Owen
Approved revision: no longer in the source branch.
Merged at revision: 243
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/700496-forget-pw-unkown-email
Merge into: lp:canonical-identity-provider/release
Diff against target: 203 lines (+98/-32)
7 files modified
identityprovider/decorators.py (+1/-1)
identityprovider/templates/launchpad/email/invitation.txt (+12/-0)
identityprovider/templates/ubuntu/email/invitation.txt (+13/-0)
identityprovider/tests/test_loginservice.py (+5/-1)
identityprovider/urls.py (+4/-1)
identityprovider/views/ui.py (+62/-28)
requirements.txt (+1/-1)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/700496-forget-pw-unkown-email
Reviewer Review Type Date Requested Status
David Owen (community) Approve
Review via email: mp+85907@code.launchpad.net

Commit message

Fix for #700496 - send invitation email when a user does forgot password flow with invalid email

Description of the change

Fix for #700496 - send invitation email when a user does forgot password flow with invalid email

To post a comment you must log in.
Revision history for this message
David Owen (dsowen) wrote :

Impl. looks good. I wonder if it would be better to actually initiate the registration process for the user, and send them a token/link instead of just an invitation?

Revision history for this message
David Owen (dsowen) wrote :

Invitation-only sounds good; see comments on bug.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/decorators.py'
2--- identityprovider/decorators.py 2011-11-17 22:58:56 +0000
3+++ identityprovider/decorators.py 2011-12-15 16:33:40 +0000
4@@ -17,8 +17,8 @@
5 from identityprovider import auth
6 from identityprovider.cookies import set_test_cookie, test_cookie_worked
7
8-
9 def guest_required(func):
10+ @functools.wraps(func)
11 def _guest_required_decorator(request, *args, **kwargs):
12 if request.user.is_authenticated():
13 return HttpResponseRedirect('/')
14
15=== added file 'identityprovider/templates/launchpad/email/invitation.txt'
16--- identityprovider/templates/launchpad/email/invitation.txt 1970-01-01 00:00:00 +0000
17+++ identityprovider/templates/launchpad/email/invitation.txt 2011-12-15 16:33:40 +0000
18@@ -0,0 +1,12 @@
19+{% load i18n %}
20+{% blocktrans %}Hello
21+
22+You recently requested a password reset for the email account {{ email }} for
23+the Launchpad Login Service
24+
25+This email is not associated with any current accounts. You are very welcome
26+to create a Launchpad account - to do so, please go to {{ signup }}
27+
28+Thanks
29+
30+The Launchpad Login Service team{% endblocktrans %}
31
32=== added file 'identityprovider/templates/ubuntu/email/invitation.txt'
33--- identityprovider/templates/ubuntu/email/invitation.txt 1970-01-01 00:00:00 +0000
34+++ identityprovider/templates/ubuntu/email/invitation.txt 2011-12-15 16:33:40 +0000
35@@ -0,0 +1,13 @@
36+{% load i18n %}
37+{% blocktrans %}Hello
38+
39+You recently requested a password reset for the email account {{ email }} for
40+the Ubuntu Single Sign On service.
41+
42+This email is not associated with any current accounts. You are very welcome
43+to create an Ubuntu SSO account - to do so, please go to {{ signup }}
44+
45+Thanks
46+
47+The Ubuntu Single Sign On team
48+https://login.ubuntu.com/{% endblocktrans %}
49
50=== modified file 'identityprovider/tests/test_loginservice.py'
51--- identityprovider/tests/test_loginservice.py 2011-07-15 11:50:53 +0000
52+++ identityprovider/tests/test_loginservice.py 2011-12-15 16:33:40 +0000
53@@ -36,7 +36,11 @@
54 self.assertEquals(response.status_code, 302)
55 self.assertEquals(response['location'],
56 'http://testserver/+email-sent')
57- self.assertEquals(len(mail.outbox), 0)
58+ self.assertEquals(len(mail.outbox), 1)
59+ self.assertEquals(mail.outbox[0].subject,
60+ u"%s: Password reset request" % current_brand.name)
61+ self.assertTrue('nobody@debian.org' in mail.outbox[0].body)
62+ self.assertTrue('+new_account' in mail.outbox[0].body)
63
64
65 class ForgottenPasswordTest(SSOBaseTestCase):
66
67=== modified file 'identityprovider/urls.py'
68--- identityprovider/urls.py 2011-05-11 15:46:48 +0000
69+++ identityprovider/urls.py 2011-12-15 16:33:40 +0000
70@@ -29,7 +29,10 @@
71 urlpatterns += patterns('identityprovider.views.ui',
72 (r'^%(optional_token)s\+login$' % repls, 'login'),
73 (r'^%(optional_token)s\+logout$' % repls, 'logout'),
74- (r'^%(optional_token)s\+new_account$' % repls, 'new_account'),
75+ # expanded new_account urls these as per comment below to allow for
76+ # reverse look up
77+ (r'^\+new_account$' % repls, 'new_account'),
78+ (r'^%(token)s\+new_account$' % repls, 'new_account'),
79 (r'^%(optional_token)s\+forgot_password$' % repls, 'forgot_password'),
80 (r'^%(optional_token)s\+enter_token$' % repls, 'enter_token'),
81 (r'^token/%(authtoken)s/$' % repls, 'claim_token'),
82
83=== modified file 'identityprovider/views/ui.py'
84--- identityprovider/views/ui.py 2011-11-29 15:51:53 +0000
85+++ identityprovider/views/ui.py 2011-12-15 16:33:40 +0000
86@@ -660,6 +660,60 @@
87 return render_to_response('account/suspended.html', context)
88
89
90+def _handle_reset_request(request, email, token):
91+ # options for actions to be taken
92+ NONE, RESET, CREATE = range(3)
93+ # assume invalid
94+ send_email = NONE
95+ # looks like we need this complexity below because it seems the correct
96+ # action can by indicated by both exceptions AND flags :/
97+ try:
98+ person, account = get_person_and_account_by_email(email)
99+ if account:
100+ if account.can_reset_password:
101+ send_email = RESET
102+ else:
103+ send_email = NONE
104+ # log why email was not sent
105+ condition = ("account '%s' is not active" %
106+ account.displayname)
107+ logger.debug("In view 'forgot_password' email was not "
108+ "sent out because %s" % condition)
109+ else:
110+ send_email = CREATE
111+ except CannotResetPasswordException:
112+ send_email = NONE
113+ except PersonAndAccountNotFoundException:
114+ send_email = CREATE
115+
116+ # we should know now the action we need to take
117+ if send_email == RESET:
118+ # send reset email
119+ if token is not None:
120+ # Redirected from an OpenID request, must get back after
121+ # confirmation
122+ redirection_url = reverse(
123+ 'identityprovider.views.server.decide',
124+ kwargs={'token': token})
125+ else:
126+ redirection_url = '/'
127+ authtoken = AuthTokenFactory().new(
128+ account, email, email, LoginTokenType.PASSWORDRECOVERY,
129+ redirection_url=redirection_url)
130+ authtoken.sendPasswordResetEmail()
131+ # TODO: remove this if pos?
132+ set_session_token_info(request.session, authtoken)
133+ elif send_email == CREATE:
134+ # they've tried to reset with an invalid email, so send them an email
135+ # on how to create an account
136+ brand = unicode(current_brand.name)
137+ subject = u"%s: %s" % (brand, u"Password reset request")
138+ context = { 'email': email, 'signup': reverse(new_account) }
139+ tmpl = '%s/email/invitation.txt' % current_brand.template_dir
140+ body = render_to_string(tmpl, context)
141+ send_mail(subject, body, settings.NOREPLY_FROM_ADDRESS, [email])
142+
143+
144 @guest_required
145 @check_readonly
146 @requires_cookies
147@@ -687,35 +741,15 @@
148 stats.increment('flows.forgot_password', key='error.captcha',
149 rpconfig=rpconfig)
150 return response
151+
152 email = form.cleaned_data['email']
153- send_email = True
154- try:
155- person, account = get_person_and_account_by_email(email)
156- if account is not None and not account.can_reset_password:
157- send_email = False
158- # log why email was not sent
159- condition = ("account '%s' is not active" %
160- account.displayname)
161- logger.debug("In view 'forgot_password' email was not "
162- "sent out because %s" % condition)
163- except (CannotResetPasswordException,
164- PersonAndAccountNotFoundException):
165- send_email = False
166- view, args, kwargs = resolve(request.get_full_path())
167- if send_email:
168- if token is not None:
169- # Redirected from an OpenID request, must get back after
170- # confirmation
171- redirection_url = reverse(
172- 'identityprovider.views.server.decide',
173- kwargs={'token': token})
174- else:
175- redirection_url = '/'
176- authtoken = AuthTokenFactory().new(
177- account, email, email, LoginTokenType.PASSWORDRECOVERY,
178- redirection_url=redirection_url)
179- authtoken.sendPasswordResetEmail()
180- set_session_token_info(request.session, authtoken)
181+
182+ # handle the reset request
183+ _handle_reset_request(request, email, token)
184+
185+ #regardless of result, we always display the same information back
186+ # to the user
187+ # TODO: remove this from session and display via direct render?
188 request.session['email_feedback'] = settings.FEEDBACK_TO_ADDRESS
189 request.session['email_heading'] = _("Forgotten your password?")
190 request.session['email_reason'] = _("We’ve just emailed "
191
192=== modified file 'requirements.txt'
193--- requirements.txt 2011-12-02 20:39:13 +0000
194+++ requirements.txt 2011-12-15 16:33:40 +0000
195@@ -7,7 +7,7 @@
196 svn+http://django-saml2-idp.googlecode.com/svn/tags/0.14#egg=saml2idp
197
198 # testing dependencies
199-BeautifulSoup==3.1.0.1
200+BeautifulSoup
201 NoseDjango==0.8.1
202 nosexcover
203 coverage==3.4