Merge lp:~fgallina/django-openid-auth/more-backend-tests into lp:django-openid-auth
- more-backend-tests
- Merge into trunk
Proposed by
Fabián Ezequiel Gallina
Status: | Merged |
---|---|
Approved by: | Fabián Ezequiel Gallina |
Approved revision: | 119 |
Merge reported by: | Ubuntu One Auto Pilot |
Merged at revision: | not available |
Proposed branch: | lp:~fgallina/django-openid-auth/more-backend-tests |
Merge into: | lp:django-openid-auth |
Diff against target: |
493 lines (+353/-36) 3 files modified
django_openid_auth/auth.py (+1/-3) django_openid_auth/tests/test_auth.py (+351/-32) tox.ini (+1/-1) |
To merge this branch: | bzr merge lp:~fgallina/django-openid-auth/more-backend-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Fabián Ezequiel Gallina (community) | Approve | ||
Facundo Batista (community) | Approve | ||
Review via email: mp+302659@code.launchpad.net |
Commit message
Added more tests `auth.OpenIDBac
Some of the cases were indirectly asserted in views tests but not all
the flows seemed properly covered, this should get us a solid
groundwork for any changes to come, exercizing the `authenticate`
method which is the main entrypoint of the backend.
Also included:
- Ensure Django 1.7 runs on older Tox versions
- Cleanup lint errors on auth module
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'django_openid_auth/auth.py' |
2 | --- django_openid_auth/auth.py 2015-09-21 20:42:53 +0000 |
3 | +++ django_openid_auth/auth.py 2016-08-11 11:45:39 +0000 |
4 | @@ -30,8 +30,6 @@ |
5 | |
6 | from __future__ import unicode_literals |
7 | |
8 | -__metaclass__ = type |
9 | - |
10 | import re |
11 | |
12 | from django.conf import settings |
13 | @@ -54,7 +52,7 @@ |
14 | User = get_user_model() |
15 | |
16 | |
17 | -class OpenIDBackend: |
18 | +class OpenIDBackend(object): |
19 | """A django.contrib.auth backend that authenticates the user based on |
20 | an OpenID response.""" |
21 | |
22 | |
23 | === modified file 'django_openid_auth/tests/test_auth.py' |
24 | --- django_openid_auth/tests/test_auth.py 2015-04-23 20:39:02 +0000 |
25 | +++ django_openid_auth/tests/test_auth.py 2016-08-11 11:45:39 +0000 |
26 | @@ -28,19 +28,35 @@ |
27 | |
28 | from __future__ import unicode_literals |
29 | |
30 | +import re |
31 | + |
32 | from django.contrib.auth.models import Group, Permission, User |
33 | from django.test import TestCase |
34 | from django.test.utils import override_settings |
35 | |
36 | -from openid.consumer.consumer import SuccessResponse |
37 | +from openid.consumer.consumer import ( |
38 | + CancelResponse, |
39 | + FailureResponse, |
40 | + SetupNeededResponse, |
41 | + SuccessResponse, |
42 | +) |
43 | + |
44 | from openid.consumer.discover import OpenIDServiceEndpoint |
45 | +from openid.extensions import pape |
46 | from openid.message import Message, OPENID2_NS |
47 | |
48 | from django_openid_auth.auth import OpenIDBackend |
49 | +from django_openid_auth.exceptions import ( |
50 | + DuplicateUsernameViolation, |
51 | + MissingPhysicalMultiFactor, |
52 | + MissingUsernameViolation, |
53 | + RequiredAttributeNotReturned, |
54 | +) |
55 | from django_openid_auth.models import UserOpenID |
56 | from django_openid_auth.teams import ns_uri as TEAMS_NS |
57 | from django_openid_auth.tests.helpers import override_session_serializer |
58 | |
59 | + |
60 | SREG_NS = "http://openid.net/sreg/1.0" |
61 | AX_NS = "http://openid.net/srv/ax/1.0" |
62 | |
63 | @@ -60,6 +76,7 @@ |
64 | def make_openid_response(self, sreg_args=None, teams_args=None): |
65 | endpoint = OpenIDServiceEndpoint() |
66 | endpoint.claimed_id = 'some-id' |
67 | + endpoint.server_url = 'http://example.com/' |
68 | |
69 | message = Message(OPENID2_NS) |
70 | if sreg_args is not None: |
71 | @@ -77,6 +94,9 @@ |
72 | fullname="Some User", nickname="someuser", email="foo@example.com", |
73 | first=None, last=None, verified=False): |
74 | endpoint = OpenIDServiceEndpoint() |
75 | + endpoint.claimed_id = 'some-id' |
76 | + endpoint.server_url = 'http://example.com/' |
77 | + |
78 | message = Message(OPENID2_NS) |
79 | attributes = [ |
80 | ("nickname", schema + "namePerson/friendly", nickname), |
81 | @@ -112,34 +132,28 @@ |
82 | user=user, claimed_id=claimed_id, display_id=display_id) |
83 | return user_openid |
84 | |
85 | - def assert_account_verified(self, user, initially_verified, verified): |
86 | - # set user's verification status |
87 | + def _assert_account_verified(self, user, expected): |
88 | permission = Permission.objects.get(codename='account_verified') |
89 | - if initially_verified: |
90 | - user.user_permissions.add(permission) |
91 | - else: |
92 | - user.user_permissions.remove(permission) |
93 | - |
94 | - user = User.objects.get(pk=user.pk) |
95 | - has_perm = user.has_perm('django_openid_auth.account_verified') |
96 | - assert has_perm == initially_verified |
97 | - |
98 | - if hasattr(user, '_perm_cache'): |
99 | - del user._perm_cache |
100 | - |
101 | - # get a response including verification status |
102 | - response = self.make_response_ax() |
103 | - data = dict(first_name=u"Some56789012345678901234567890123", |
104 | - last_name=u"User56789012345678901234567890123", |
105 | - email=u"someotheruser@example.com", |
106 | - account_verified=verified) |
107 | - self.backend.update_user_details(user, data, response) |
108 | - |
109 | - # refresh object from the database |
110 | - user = User.objects.get(pk=user.pk) |
111 | - # check the verification status |
112 | - self.assertEqual( |
113 | - user.has_perm('django_openid_auth.account_verified'), verified) |
114 | + perm_label = '%s.%s' % (permission.content_type.app_label, |
115 | + permission.codename) |
116 | + # Always invalidate the per-request perm cache |
117 | + for attr in user.__dict__.keys(): |
118 | + if attr.endswith('_perm_cache'): |
119 | + delattr(user, attr) |
120 | + |
121 | + self.assertEqual(user.has_perm(perm_label), expected) |
122 | + |
123 | + def assert_account_not_verified(self, user): |
124 | + self._assert_account_verified(user, False) |
125 | + |
126 | + def assert_account_verified(self, user): |
127 | + self._assert_account_verified(user, True) |
128 | + |
129 | + def assert_no_users_created(self, expected_count=0): |
130 | + current_count = User.objects.count() |
131 | + msg = 'New users found (expected: %i, current: %i)' % ( |
132 | + expected_count, current_count) |
133 | + self.assertEqual(current_count, expected_count, msg) |
134 | |
135 | def test_extract_user_details_sreg(self): |
136 | expected = { |
137 | @@ -217,23 +231,53 @@ |
138 | self.assertEqual("Some56789012345678901234567890", user.first_name) |
139 | self.assertEqual("User56789012345678901234567890", user.last_name) |
140 | |
141 | + def _test_update_user_perms_account_verified( |
142 | + self, user, initially_verified, verified): |
143 | + # set user's verification status |
144 | + permission = Permission.objects.get(codename='account_verified') |
145 | + if initially_verified: |
146 | + user.user_permissions.add(permission) |
147 | + else: |
148 | + user.user_permissions.remove(permission) |
149 | + |
150 | + if initially_verified: |
151 | + self.assert_account_verified(user) |
152 | + else: |
153 | + self.assert_account_not_verified(user) |
154 | + |
155 | + # get a response including verification status |
156 | + response = self.make_response_ax() |
157 | + data = dict(first_name=u"Some56789012345678901234567890123", |
158 | + last_name=u"User56789012345678901234567890123", |
159 | + email=u"someotheruser@example.com", |
160 | + account_verified=verified) |
161 | + self.backend.update_user_details(user, data, response) |
162 | + |
163 | + # refresh object from the database |
164 | + user = User.objects.get(pk=user.pk) |
165 | + |
166 | + if verified: |
167 | + self.assert_account_verified(user) |
168 | + else: |
169 | + self.assert_account_not_verified(user) |
170 | + |
171 | def test_update_user_perms_initially_verified_then_verified(self): |
172 | - self.assert_account_verified( |
173 | + self._test_update_user_perms_account_verified( |
174 | self.make_user_openid().user, |
175 | initially_verified=True, verified=True) |
176 | |
177 | def test_update_user_perms_initially_verified_then_unverified(self): |
178 | - self.assert_account_verified( |
179 | + self._test_update_user_perms_account_verified( |
180 | self.make_user_openid().user, |
181 | initially_verified=True, verified=False) |
182 | |
183 | def test_update_user_perms_initially_not_verified_then_verified(self): |
184 | - self.assert_account_verified( |
185 | + self._test_update_user_perms_account_verified( |
186 | self.make_user_openid().user, |
187 | initially_verified=False, verified=True) |
188 | |
189 | def test_update_user_perms_initially_not_verified_then_unverified(self): |
190 | - self.assert_account_verified( |
191 | + self._test_update_user_perms_account_verified( |
192 | self.make_user_openid().user, |
193 | initially_verified=False, verified=False) |
194 | |
195 | @@ -266,6 +310,7 @@ |
196 | expected, |
197 | self.backend._get_preferred_username(nick, email)) |
198 | |
199 | + @override_settings(OPENID_USE_EMAIL_FOR_USERNAME=False) |
200 | def test_preferred_username_no_email_munging(self): |
201 | for nick, email, expected in [ |
202 | ('nickcomesfirst', 'foo@example.com', 'nickcomesfirst'), |
203 | @@ -388,3 +433,277 @@ |
204 | user = self.backend.authenticate(openid_response=response) |
205 | |
206 | self.assertIsNone(user) |
207 | + |
208 | + def test_auth_no_response(self): |
209 | + self.assertIsNone(self.backend.authenticate()) |
210 | + self.assert_no_users_created() |
211 | + |
212 | + def test_auth_cancel_response(self): |
213 | + response = CancelResponse(OpenIDServiceEndpoint()) |
214 | + |
215 | + self.assertIsNone(self.backend.authenticate(openid_response=response)) |
216 | + self.assert_no_users_created() |
217 | + |
218 | + def test_auth_failure_response(self): |
219 | + response = FailureResponse(OpenIDServiceEndpoint()) |
220 | + |
221 | + self.assertIsNone(self.backend.authenticate(openid_response=response)) |
222 | + self.assert_no_users_created() |
223 | + |
224 | + def test_auth_setup_needed_response(self): |
225 | + response = SetupNeededResponse(OpenIDServiceEndpoint()) |
226 | + |
227 | + self.assertIsNone(self.backend.authenticate(openid_response=response)) |
228 | + self.assert_no_users_created() |
229 | + |
230 | + @override_settings(OPENID_CREATE_USERS=False) |
231 | + def test_auth_no_create_users(self): |
232 | + response = self.make_openid_response( |
233 | + sreg_args=dict(email='bar@foo.com'), |
234 | + teams_args=dict(is_member='foo')) |
235 | + user = self.backend.authenticate(openid_response=response) |
236 | + |
237 | + self.assertIsNone(user) |
238 | + self.assert_no_users_created() |
239 | + |
240 | + @override_settings(OPENID_CREATE_USERS=False) |
241 | + def test_auth_no_create_users_existing_user(self): |
242 | + response = self.make_openid_response( |
243 | + sreg_args=dict(email='bar@foo.com'), |
244 | + teams_args=dict(is_member='foo')) |
245 | + existing_openid = self.make_user_openid( |
246 | + claimed_id=response.identity_url) |
247 | + expected_user_count = User.objects.count() |
248 | + |
249 | + user = self.backend.authenticate(openid_response=response) |
250 | + |
251 | + self.assertIsNotNone(user) |
252 | + self.assertEqual(user, existing_openid.user) |
253 | + self.assert_no_users_created(expected_count=expected_user_count) |
254 | + |
255 | + @override_settings( |
256 | + OPENID_UPDATE_DETAILS_FROM_SREG=True, |
257 | + OPENID_VALID_VERIFICATION_SCHEMES={ |
258 | + 'http://example.com/': {'token_via_email'}}) |
259 | + def test_auth_update_details_from_sreg(self): |
260 | + first_name = 'a' * 31 |
261 | + last_name = 'b' * 31 |
262 | + email = 'new@email.com' |
263 | + response = self.make_response_ax( |
264 | + fullname=first_name + ' ' + last_name, |
265 | + nickname='newnickname', |
266 | + email=email, |
267 | + first=first_name, |
268 | + last=last_name, |
269 | + verified=True, |
270 | + ) |
271 | + existing_openid = self.make_user_openid( |
272 | + claimed_id=response.identity_url) |
273 | + original_username = existing_openid.user.username |
274 | + expected_user_count = User.objects.count() |
275 | + |
276 | + self.assert_account_not_verified(existing_openid.user) |
277 | + |
278 | + user = self.backend.authenticate(openid_response=response) |
279 | + |
280 | + self.assertEqual(user, existing_openid.user) |
281 | + self.assertEqual( |
282 | + user.username, original_username, |
283 | + 'Username must not be updated unless OPENID_FOLLOW_RENAMES=True.') |
284 | + self.assertEqual(user.email, email) |
285 | + self.assertEqual(user.first_name, first_name[:30]) |
286 | + self.assertEqual(user.last_name, last_name[:30]) |
287 | + self.assert_account_verified(user) |
288 | + self.assert_no_users_created(expected_count=expected_user_count) |
289 | + |
290 | + @override_settings( |
291 | + OPENID_UPDATE_DETAILS_FROM_SREG=True, |
292 | + OPENID_VALID_VERIFICATION_SCHEMES={ |
293 | + 'http://example.com/': {'token_via_email'}}) |
294 | + def test_auth_update_details_from_sreg_unverifies_account(self): |
295 | + first_name = 'a' * 31 |
296 | + last_name = 'b' * 31 |
297 | + email = 'new@email.com' |
298 | + kwargs = dict( |
299 | + fullname=first_name + ' ' + last_name, |
300 | + nickname='newnickname', |
301 | + email=email, |
302 | + first=first_name, |
303 | + last=last_name, |
304 | + verified=True, |
305 | + ) |
306 | + verified_response = self.make_response_ax(**kwargs) |
307 | + verified_user = self.backend.authenticate( |
308 | + openid_response=verified_response) |
309 | + |
310 | + self.assert_account_verified(verified_user) |
311 | + expected_user_count = User.objects.count() |
312 | + |
313 | + kwargs['verified'] = False |
314 | + unverified_response = self.make_response_ax(**kwargs) |
315 | + unverified_user = self.backend.authenticate( |
316 | + openid_response=unverified_response) |
317 | + |
318 | + self.assertEqual(verified_user, unverified_user) |
319 | + self.assert_account_not_verified(unverified_user) |
320 | + self.assert_no_users_created(expected_count=expected_user_count) |
321 | + |
322 | + @override_settings(OPENID_PHYSICAL_MULTIFACTOR_REQUIRED=True) |
323 | + def test_physical_multifactor_required_not_given(self): |
324 | + response = self.make_openid_response() |
325 | + |
326 | + with self.assertRaises(MissingPhysicalMultiFactor): |
327 | + self.backend.authenticate(openid_response=response) |
328 | + |
329 | + self.assertTrue( |
330 | + UserOpenID.objects.filter(claimed_id='some-id').exists(), |
331 | + 'User must be created anyways.') |
332 | + |
333 | + @override_settings(OPENID_PHYSICAL_MULTIFACTOR_REQUIRED=True) |
334 | + def test_physical_multifactor_required_invalid_auth_policy(self): |
335 | + response = self.make_openid_response() |
336 | + message = response.message |
337 | + message.setArg( |
338 | + pape.ns_uri, 'auth_policies', |
339 | + pape.AUTH_MULTI_FACTOR + ' ' + pape.AUTH_PHISHING_RESISTANT) |
340 | + response = SuccessResponse( |
341 | + response.endpoint, message, |
342 | + signed_fields=message.toPostArgs().keys()) |
343 | + |
344 | + with self.assertRaises(MissingPhysicalMultiFactor): |
345 | + self.backend.authenticate(openid_response=response) |
346 | + |
347 | + self.assertTrue( |
348 | + UserOpenID.objects.filter(claimed_id='some-id').exists(), |
349 | + 'User must be created anyways.') |
350 | + |
351 | + @override_settings(OPENID_PHYSICAL_MULTIFACTOR_REQUIRED=True) |
352 | + def test_physical_multifactor_required_valid_auth_policy(self): |
353 | + response = self.make_openid_response() |
354 | + message = response.message |
355 | + message.setArg( |
356 | + pape.ns_uri, 'auth_policies', |
357 | + pape.AUTH_MULTI_FACTOR_PHYSICAL) |
358 | + response = SuccessResponse( |
359 | + response.endpoint, message, |
360 | + signed_fields=message.toPostArgs().keys()) |
361 | + |
362 | + user = self.backend.authenticate(openid_response=response) |
363 | + |
364 | + self.assertIsNotNone(user) |
365 | + |
366 | + @override_settings(OPENID_STRICT_USERNAMES=True) |
367 | + def test_auth_strict_usernames(self): |
368 | + username = 'nickname' |
369 | + response = self.make_openid_response( |
370 | + sreg_args=dict(nickname=username, email='bar@foo.com'), |
371 | + teams_args=dict(is_member='foo')) |
372 | + user = self.backend.authenticate(openid_response=response) |
373 | + |
374 | + self.assertIsNotNone(user, 'User must be created') |
375 | + self.assertEqual(user.username, username) |
376 | + |
377 | + @override_settings(OPENID_STRICT_USERNAMES=True) |
378 | + def test_auth_strict_usernames_no_nickname(self): |
379 | + response = self.make_openid_response( |
380 | + sreg_args=dict(nickname='', email='bar@foo.com'), |
381 | + teams_args=dict(is_member='foo')) |
382 | + |
383 | + msg = re.escape( |
384 | + "An attribute required for logging in was not returned (nickname)") |
385 | + |
386 | + with self.assertRaisesRegexp(RequiredAttributeNotReturned, msg): |
387 | + self.backend.authenticate(openid_response=response) |
388 | + |
389 | + self.assert_no_users_created() |
390 | + |
391 | + @override_settings( |
392 | + OPENID_STRICT_USERNAMES=True, |
393 | + OPENID_UPDATE_DETAILS_FROM_SREG=True) |
394 | + def test_auth_strict_usernames_conflict(self): |
395 | + existing_openid = self.make_user_openid() |
396 | + expected_user_count = User.objects.count() |
397 | + |
398 | + response = self.make_openid_response( |
399 | + sreg_args=dict( |
400 | + nickname=existing_openid.user.username, email='bar@foo.com'), |
401 | + teams_args=dict(is_member='foo')) |
402 | + |
403 | + with self.assertRaises(DuplicateUsernameViolation): |
404 | + self.backend.authenticate(openid_response=response) |
405 | + |
406 | + self.assert_no_users_created(expected_count=expected_user_count) |
407 | + |
408 | + @override_settings( |
409 | + OPENID_FOLLOW_RENAMES=True, |
410 | + OPENID_STRICT_USERNAMES=True, |
411 | + OPENID_UPDATE_DETAILS_FROM_SREG=True) |
412 | + def test_auth_follow_renames(self): |
413 | + new_username = 'new' |
414 | + original_response = self.make_openid_response( |
415 | + sreg_args=dict(nickname='username', email='bar@foo.com'), |
416 | + teams_args=dict(is_member='foo')) |
417 | + rename_response = self.make_openid_response( |
418 | + sreg_args=dict(nickname=new_username, email='bar@foo.com'), |
419 | + teams_args=dict(is_member='foo')) |
420 | + user = self.backend.authenticate(openid_response=original_response) |
421 | + expected_user_count = User.objects.count() |
422 | + |
423 | + self.assertIsNotNone(user, 'User must be created') |
424 | + |
425 | + renamed_user = self.backend.authenticate( |
426 | + openid_response=rename_response) |
427 | + |
428 | + self.assertEqual(user.pk, renamed_user.pk) |
429 | + self.assertEqual(renamed_user.username, new_username) |
430 | + self.assert_no_users_created(expected_count=expected_user_count) |
431 | + |
432 | + @override_settings( |
433 | + OPENID_FOLLOW_RENAMES=True, |
434 | + OPENID_STRICT_USERNAMES=True, |
435 | + OPENID_UPDATE_DETAILS_FROM_SREG=True) |
436 | + def test_auth_follow_renames_strict_usernames_no_nickname(self): |
437 | + response = self.make_openid_response( |
438 | + sreg_args=dict(nickname='nickame', email='bar@foo.com'), |
439 | + teams_args=dict(is_member='foo')) |
440 | + user = self.backend.authenticate(openid_response=response) |
441 | + expected_user_count = User.objects.count() |
442 | + |
443 | + self.assertIsNotNone(user, 'User must be created') |
444 | + |
445 | + response = self.make_openid_response( |
446 | + sreg_args=dict(nickname='', email='bar@foo.com'), |
447 | + teams_args=dict(is_member='foo')) |
448 | + |
449 | + # XXX: Check possibilities to normalize this error into a |
450 | + # `RequiredAttributeNotReturned`. |
451 | + with self.assertRaises(MissingUsernameViolation): |
452 | + self.backend.authenticate(openid_response=response) |
453 | + |
454 | + self.assert_no_users_created(expected_count=expected_user_count) |
455 | + |
456 | + @override_settings( |
457 | + OPENID_FOLLOW_RENAMES=True, |
458 | + OPENID_STRICT_USERNAMES=True, |
459 | + OPENID_UPDATE_DETAILS_FROM_SREG=True) |
460 | + def test_auth_follow_renames_strict_usernames_rename_conflict(self): |
461 | + existing_openid = self.make_user_openid() |
462 | + original_username = 'nickame' |
463 | + good_response = self.make_openid_response( |
464 | + sreg_args=dict(nickname=original_username, email='bar@foo.com'), |
465 | + teams_args=dict(is_member='foo')) |
466 | + conflict_response = self.make_openid_response( |
467 | + sreg_args=dict( |
468 | + nickname=existing_openid.user.username, email='bar@foo.com'), |
469 | + teams_args=dict(is_member='foo')) |
470 | + user = self.backend.authenticate(openid_response=good_response) |
471 | + expected_user_count = User.objects.count() |
472 | + |
473 | + self.assertIsNotNone(user, 'First request should succeed') |
474 | + |
475 | + with self.assertRaises(DuplicateUsernameViolation): |
476 | + self.backend.authenticate(openid_response=conflict_response) |
477 | + |
478 | + db_user = User.objects.get(pk=user.pk) |
479 | + self.assertEqual(db_user.username, original_username) |
480 | + self.assert_no_users_created(expected_count=expected_user_count) |
481 | |
482 | === modified file 'tox.ini' |
483 | --- tox.ini 2016-08-10 21:32:13 +0000 |
484 | +++ tox.ini 2016-08-11 11:45:39 +0000 |
485 | @@ -10,7 +10,7 @@ |
486 | [testenv:py2.7-django1.7] |
487 | basepython = python2.7 |
488 | deps = |
489 | - django >= 1.7, < 1.8 |
490 | + django == 1.7.11 |
491 | {[testenv]deps} |
492 | |
493 | [testenv:py2.7-django1.8] |
Tests FTW!