Merge lp:~fgallina/django-openid-auth/more-backend-tests into lp:django-openid-auth

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
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.OpenIDBackend`

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

To post a comment you must log in.
Revision history for this message
Facundo Batista (facundo) wrote :

Tests FTW!

review: Approve
Revision history for this message
Fabián Ezequiel Gallina (fgallina) wrote :

LGTM

review: Approve

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]

Subscribers

People subscribed via source and target branches