Merge lp:~canonical-isd-hackers/canonical-identity-provider/password-db-entry-missing into lp:canonical-identity-provider/release
- password-db-entry-missing
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Łukasz Czyżykowski (community) | Approve | ||
Review via email: mp+25001@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Łukasz Czyżykowski (lukasz-czyzykowski) wrote : | # |
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) |
- why in test_authentica te_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