Merge lp:~canonical-isd-hackers/canonical-identity-provider/password-db-entry-missing into lp:canonical-identity-provider/release

Proposed by Szilveszter Farkas
Status: Merged
Approved by: Danny Tamez
Approved revision: no longer in the source branch.
Merged at revision: 17
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/password-db-entry-missing
Merge into: lp:canonical-identity-provider/release
Diff against target: 423 lines (+123/-122)
5 files modified
identityprovider/auth.py (+7/-2)
identityprovider/models/account.py (+2/-1)
identityprovider/tests/test_auth.py (+10/-0)
identityprovider/tests/test_views_ui.py (+95/-116)
identityprovider/views/ui.py (+9/-3)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/password-db-entry-missing
Reviewer Review Type Date Requested Status
Łukasz Czyżykowski (community) Approve
Review via email: mp+25001@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Łukasz Czyżykowski (lukasz-czyzykowski) wrote :

- why in test_authenticate_account_no_password you're getting object again? looks like this was asserted in the beginning of the test

- in test_reset_password_when_account_{active, active_no_password} I would rather see account.status = AccountStatus.ACTIVE; account.save() instead of the assert, as this more in line of what is checked in those tests

- I feel that the logic in views.ui for accessing account password should be moved to account model

Revision history for this message
Szilveszter Farkas (phanatic) wrote :

1) I wanted to make sure that the Account object isn't deleted (there's a OneToOne relationship between Account and AccountPassword).

2) That's something not introduced by my changes, but I'm happy to change that.

3) We discussed that on IRC.

Revision history for this message
Łukasz Czyżykowski (lukasz-czyzykowski) wrote :

As we discussed on IRC, there's no clean way of fixing that in Django and current db schema.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/auth.py'
2--- identityprovider/auth.py 2010-04-21 15:29:24 +0000
3+++ identityprovider/auth.py 2010-05-11 12:40:55 +0000
4@@ -3,7 +3,8 @@
5
6 from datetime import datetime
7
8-from identityprovider.models import Account, EmailAddress, Consumer, Token
9+from identityprovider.models import (Account, AccountPassword, EmailAddress,
10+ Consumer, Token)
11 from identityprovider.models.api import APIUser
12 from identityprovider.models.const import EmailStatus
13 from identityprovider.utils import validate_launchpad_password
14@@ -27,7 +28,11 @@
15 if not account.is_active:
16 return None
17
18- encrypted = account.accountpassword.password
19+ try:
20+ encrypted = account.accountpassword.password
21+ except AccountPassword.DoesNotExist:
22+ # password lost somehow, user should reset it
23+ return None
24 if validate_launchpad_password(password, encrypted):
25 account.last_login = datetime.now()
26 return account
27
28=== modified file 'identityprovider/models/account.py'
29--- identityprovider/models/account.py 2010-05-04 22:26:22 +0000
30+++ identityprovider/models/account.py 2010-05-11 12:40:55 +0000
31@@ -155,7 +155,8 @@
32
33 @property
34 def can_reactivate(self):
35- return (self.status == AccountStatus.DEACTIVATED)
36+ return (self.status == AccountStatus.DEACTIVATED
37+ or self.status == AccountStatus.NOACCOUNT)
38
39 @property
40 def can_reset_password(self):
41
42=== modified file 'identityprovider/tests/test_auth.py'
43--- identityprovider/tests/test_auth.py 2010-04-21 15:29:24 +0000
44+++ identityprovider/tests/test_auth.py 2010-05-11 12:40:55 +0000
45@@ -71,6 +71,16 @@
46 account.status = _status
47 account.save()
48
49+ def test_authenticate_account_no_password(self):
50+ account = Account.objects.get_by_email('mark@example.com')
51+ account.accountpassword.delete()
52+
53+ response = self.backend.authenticate('mark@example.com', 'test')
54+
55+ self.assertTrue(response is None)
56+ account = Account.objects.get_by_email('mark@example.com')
57+ self.assertTrue(account is not None)
58+
59 def test_oauth_authenticate_account_active(self):
60 account = Account.objects.get_by_email('mark@example.com')
61 consumer, created = Consumer.objects.get_or_create(account=account)
62
63=== modified file 'identityprovider/tests/test_views_ui.py'
64--- identityprovider/tests/test_views_ui.py 2010-05-10 16:59:23 +0000
65+++ identityprovider/tests/test_views_ui.py 2010-05-11 12:40:55 +0000
66@@ -32,6 +32,12 @@
67 def setUp(self):
68 logging.disable(logging.WARNING)
69
70+ # disable csrf
71+ self.disable_csrf()
72+
73+ def tearDown(self):
74+ self.reset_csrf()
75+
76 def reset_csrf(self):
77 settings.MIDDLEWARE_CLASSES = self._MIDDLEWARE_CLASSES
78
79@@ -136,9 +142,6 @@
80 email_obj.status = EmailStatus.NEW
81 email_obj.save()
82
83- # disable csrf
84- self.disable_csrf()
85-
86 r = self.client.post('/+forgot_password',
87 {'email': 'test@canonical.com'})
88
89@@ -154,7 +157,6 @@
90 email_obj.status = EmailStatus.VALIDATED
91 email_obj.save()
92 account.preferredemail = _preferred_email
93- self.reset_csrf()
94
95 def test_claim_token_for_validate_email(self):
96 token = self.create_token(at.LoginTokenType.VALIDATEEMAIL)
97@@ -169,7 +171,6 @@
98
99 def test_config_email(self):
100 self.authenticate()
101- self.disable_csrf()
102
103 token = self.create_token(at.LoginTokenType.VALIDATEEMAIL,
104 email="mark@example.com",
105@@ -178,8 +179,6 @@
106 {'post': 'yes'})
107 self.assertRedirects(r, token.redirection_url)
108
109- self.reset_csrf()
110-
111 def test_token_from_email_when_it_is_turned_on(self):
112 r = self.get_debug_token_response(True)
113 self.assertTemplateUsed(r, 'registration/token.html')
114@@ -215,9 +214,6 @@
115 self.assertTemplateUsed(r, 'registration/forgot_password.html')
116
117 def test_forgot_password_email_when_account_active(self):
118- # disable CSRF
119- self.disable_csrf()
120-
121 account = Account.objects.get_by_email('test@canonical.com')
122
123 r = self.client.post('/+forgot_password',
124@@ -226,14 +222,9 @@
125 self.assertEqual(len(mail.outbox), 1)
126 mail.outbox = []
127
128- self.reset_csrf()
129-
130 def test_forgot_password_email_when_account_deactivated(self):
131 """ Deactivated accounts can be reactivated using the reset password
132 functionality. Bug #556878 """
133- # disable CSRF
134- self.disable_csrf()
135-
136 account = Account.objects.get_by_email('test@canonical.com')
137 # make sure the account is deactivated
138 _status = account.status
139@@ -247,61 +238,69 @@
140 mail.outbox = []
141 self.assertEqual(account.preferredemail.email, 'test@canonical.com')
142
143- account.status = _status
144- account.save()
145-
146- self.reset_csrf()
147-
148- def test_forgot_password_email_when_account_inactive(self):
149- # disable CSRF
150- self.disable_csrf()
151-
152- account = Account.objects.get_by_email('test@canonical.com')
153-
154- for status, _ in AccountStatus._get_choices():
155- if status in (AccountStatus.ACTIVE, AccountStatus.DEACTIVATED):
156- # skip as this is already tested in
157- # test_forgot_password_email_when_account_active and
158- # test_forgot_password_email_when_account_deactivated
159- continue
160-
161- account.status = status
162- account.save()
163-
164- r = self.client.post('/+forgot_password',
165- {'email': 'test@canonical.com'})
166-
167- self.assertEqual(len(mail.outbox), 0)
168- mail.outbox = []
169-
170- self.reset_csrf()
171+ def test_forgot_password_email_when_account_noaccount(self):
172+ account = Account.objects.get_by_email('test@canonical.com')
173+ # make sure the account is deactivated
174+ account.status = AccountStatus.NOACCOUNT
175+ account.save()
176+
177+ r = self.client.post('/+forgot_password',
178+ {'email': 'test@canonical.com'})
179+
180+ self.assertEqual(len(mail.outbox), 1)
181+ mail.outbox = []
182+ self.assertEqual(account.preferredemail.email, 'test@canonical.com')
183+
184+ def test_forgot_password_email_when_account_suspended(self):
185+ account = Account.objects.get_by_email('test@canonical.com')
186+
187+ # make sure the account is suspended
188+ _status = account.status
189+ account.status = AccountStatus.SUSPENDED
190+ account.save()
191+
192+ r = self.client.post('/+forgot_password',
193+ {'email': 'test@canonical.com'})
194+
195+ self.assertEqual(len(mail.outbox), 0)
196+ mail.outbox = []
197
198 def test_reset_password_when_account_active(self):
199- # disable CSRF
200- self.disable_csrf()
201-
202- r = self.client.post('/+forgot_password',
203- {'email': 'test@canonical.com'})
204- # claim token
205- token = self.client.session['token_forgotpassword']
206-
207- account = Account.objects.get_by_email('test@canonical.com')
208- # make sure the account is active
209- self.assertEqual(account.status, AccountStatus.ACTIVE)
210-
211- # confirm account
212- data = {'password': 'Password1', 'passwordconfirm': 'Password1'}
213- r = self.client.post('/token/%s/+resetpassword' % token, data)
214- self.assertRedirects(r, '/')
215-
216- self.reset_csrf()
217+ r = self.client.post('/+forgot_password',
218+ {'email': 'test@canonical.com'})
219+ # claim token
220+ token = self.client.session['token_forgotpassword']
221+
222+ account = Account.objects.get_by_email('test@canonical.com')
223+ # make sure the account is active
224+ account.status = AccountStatus.ACTIVE
225+ account.save()
226+
227+ # confirm account
228+ data = {'password': 'Password1', 'passwordconfirm': 'Password1'}
229+ r = self.client.post('/token/%s/+resetpassword' % token, data)
230+ self.assertRedirects(r, '/')
231+
232+ def test_reset_password_when_account_active_no_password(self):
233+ r = self.client.post('/+forgot_password',
234+ {'email': 'test@canonical.com'})
235+ # claim token
236+ token = self.client.session['token_forgotpassword']
237+
238+ account = Account.objects.get_by_email('test@canonical.com')
239+ account.accountpassword.delete()
240+ # make sure the account is active
241+ account.status = AccountStatus.ACTIVE
242+ account.save()
243+
244+ # confirm account
245+ data = {'password': 'Password1', 'passwordconfirm': 'Password1'}
246+ r = self.client.post('/token/%s/+resetpassword' % token, data)
247+ self.assertRedirects(r, '/')
248
249 def test_reset_password_when_account_deactivated(self):
250 """ Deactivated accounts can be reactivated using the reset password
251 functionality. Bug #556878 """
252- # disable CSRF
253- self.disable_csrf()
254-
255 r = self.client.post('/+forgot_password',
256 {'email': 'test@canonical.com'})
257 # claim token
258@@ -309,7 +308,6 @@
259
260 account = Account.objects.get_by_email('test@canonical.com')
261 # make sure the account is deactivated
262- _status = account.status
263 account.status = AccountStatus.DEACTIVATED
264 account.save()
265
266@@ -318,18 +316,9 @@
267 r = self.client.post('/token/%s/+resetpassword' % token, data)
268 self.assertRedirects(r, '/')
269
270- account.status = _status
271- account.save()
272-
273- self.reset_csrf()
274-
275 def test_reset_password_when_account_deactivated_no_preferred_email(self):
276- # disable CSRF
277- self.disable_csrf()
278-
279 account = Account.objects.get_by_email('test@canonical.com')
280 # make sure the account is deactivated and preferred email is removed
281- _status = account.status
282 _preferred_email = account.preferredemail
283 account.status = AccountStatus.DEACTIVATED
284 account.save()
285@@ -358,40 +347,38 @@
286 email_obj.status = EmailStatus.VALIDATED
287 email_obj.save()
288 account.preferredemail = _preferred_email
289- account.status = _status
290- account.save()
291-
292- self.reset_csrf()
293-
294- def test_reset_password_when_account_inactive(self):
295- # disable CSRF
296- self.disable_csrf()
297-
298- r = self.client.post('/+forgot_password',
299- {'email': 'test@canonical.com'})
300- # claim token
301- token = self.client.session['token_forgotpassword']
302-
303- account = Account.objects.get_by_email('test@canonical.com')
304- data = {'password': 'Password1', 'passwordconfirm': 'Password1'}
305-
306- # test by account status
307- for status, _ in AccountStatus._get_choices():
308- if status in (AccountStatus.ACTIVE, AccountStatus.DEACTIVATED):
309- # skip as this is tested in
310- # test_reset_password_when_account_active and
311- # test_reset_password_when_account_deactivateds
312- continue
313-
314- # change account status
315- account.status = status
316- account.save()
317-
318- # confirm account
319- r = self.client.post('/token/%s/+resetpassword' % token, data)
320- self.assertRedirects(r, '/+bad-token')
321-
322- self.reset_csrf()
323+
324+ def test_reset_password_when_account_noaccount(self):
325+ r = self.client.post('/+forgot_password',
326+ {'email': 'test@canonical.com'})
327+ # claim token
328+ token = self.client.session['token_forgotpassword']
329+
330+ account = Account.objects.get_by_email('test@canonical.com')
331+ # make sure the account is deactivated
332+ account.status = AccountStatus.NOACCOUNT
333+ account.save()
334+
335+ # confirm account
336+ data = {'password': 'Password1', 'passwordconfirm': 'Password1'}
337+ r = self.client.post('/token/%s/+resetpassword' % token, data)
338+ self.assertRedirects(r, '/')
339+
340+ def test_reset_password_when_account_suspended(self):
341+ r = self.client.post('/+forgot_password',
342+ {'email': 'test@canonical.com'})
343+ # claim token
344+ token = self.client.session['token_forgotpassword']
345+
346+ account = Account.objects.get_by_email('test@canonical.com')
347+ account.status = AccountStatus.SUSPENDED
348+ account.save()
349+
350+ data = {'password': 'Password1', 'passwordconfirm': 'Password1'}
351+
352+ # confirm account
353+ r = self.client.post('/token/%s/+resetpassword' % token, data)
354+ self.assertRedirects(r, '/+bad-token')
355
356 def request_when_captcha_fails(self, url, data):
357 class MockCaptcha(object):
358@@ -424,9 +411,6 @@
359 self.assertTemplateUsed(r, 'registration/new_account.html')
360
361 def test_new_account_when_inactive(self):
362- # disable CSRF
363- self.disable_csrf()
364-
365 account = Account.objects.get_by_email('mark@example.com')
366
367 for status, _ in AccountStatus._get_choices():
368@@ -445,8 +429,6 @@
369 self.assertEqual(len(mail.outbox), mails_sent)
370 mail.outbox = []
371
372- self.reset_csrf()
373-
374 def test_edit_account_template(self):
375 r = self.client.post('/+login', {'email': 'mark@example.com',
376 'password': 'test',
377@@ -511,7 +493,6 @@
378 self.assertEqual(email.status, EmailStatus.NEW)
379
380 def test_confirm_email_post(self):
381- self.disable_csrf()
382 self.authenticate()
383
384 account, email, token = self._setup_account_with_new_email()
385@@ -526,8 +507,6 @@
386 email = EmailAddress.objects.get(email=email.email)
387 self.assertEqual(email.status, EmailStatus.VALIDATED)
388
389- self.reset_csrf()
390-
391 def test_confirm_email_as_another_user_fails(self):
392 self.authenticate()
393 account = Account.objects.get_by_email('test@canonical.com')
394
395=== modified file 'identityprovider/views/ui.py'
396--- identityprovider/views/ui.py 2010-05-05 13:11:46 +0000
397+++ identityprovider/views/ui.py 2010-05-11 12:40:55 +0000
398@@ -22,8 +22,8 @@
399 from identityprovider.decorators import guest_required, dont_cache, limitlogin
400 from identityprovider.forms import (LoginForm, ForgotPasswordForm,
401 ResetPasswordForm, NewAccountForm, ConfirmNewAccountForm)
402-from identityprovider.models import (Account, AuthToken, AuthTokenFactory,
403- EmailAddress)
404+from identityprovider.models import (Account, AccountPassword, AuthToken,
405+ AuthTokenFactory, EmailAddress)
406 from identityprovider.models.const import (AccountStatus, EmailStatus,
407 LoginTokenType)
408 import identityprovider.signed as signed
409@@ -423,7 +423,13 @@
410 if not created:
411 account.preferredemail = email_obj
412 email = account.preferredemail.email
413- password_obj = account.accountpassword
414+ try:
415+ password_obj = account.accountpassword
416+ except AccountPassword.DoesNotExist:
417+ password_obj = AccountPassword.objects.create(
418+ account=account,
419+ password='invalid',
420+ )
421 password_obj.password = encrypt_launchpad_password(password)
422 password_obj.save()
423 user = auth.authenticate(username=email, password=password)