Merge lp:~mhall119/django-openid-auth/strict-username-requirements into lp:~django-openid-auth/django-openid-auth/trunk

Proposed by Michael Hall
Status: Merged
Approved by: James Henstridge
Approved revision: 81
Merged at revision: 80
Proposed branch: lp:~mhall119/django-openid-auth/strict-username-requirements
Merge into: lp:~django-openid-auth/django-openid-auth/trunk
Diff against target: 147 lines (+82/-2)
3 files modified
README.txt (+10/-0)
django_openid_auth/auth.py (+13/-2)
django_openid_auth/tests/test_views.py (+59/-0)
To merge this branch: bzr merge lp:~mhall119/django-openid-auth/strict-username-requirements
Reviewer Review Type Date Requested Status
James Henstridge Approve
Anthony Lenton Approve
Review via email: mp+54065@code.launchpad.net

This proposal supersedes a proposal from 2010-10-15.

Description of the change

Adds an extra OPENID_STRICT_USERNAMES setting that will cause the authentication to fail if the OpenID response does not contain a nickname (used for User.username in Django), or if a Django User with that username already exists but doesn't have the same OpenID identity_url

To post a comment you must log in.
Revision history for this message
Anthony Lenton (elachuni) wrote : Posted in a previous version of this proposal

Tests would be nice, but it looks ok to me :)

review: Approve
Revision history for this message
James Henstridge (jamesh) wrote : Posted in a previous version of this proposal

I agree with Anthony here.

Here is a quick sketch for the test:

1. add a test to test_views.py. Using test_login_create_users() as a base would probably be easiest.
2. create a User and UserOpenID object with a given nickname and identity.
3. set OPENID_STRICT_USERNAMES, and try to log in using a different identity URL but the same nickname
4. verify that the login has failed.

After adding the test, this change looks fine.

review: Needs Fixing
Revision history for this message
Anthony Lenton (elachuni) wrote :

Hi Michael,

With the tests this now looks fine. Could you add a brief snippet to README.txt to mention this new setting and what it does?

81. By Michael Hall

Added notes about the new settings option to README.txt

Revision history for this message
Anthony Lenton (elachuni) :
review: Approve
Revision history for this message
James Henstridge (jamesh) wrote :

Looks good. In test_strict_username_no_nickname, I'd leave out the nickname from the SReg response entirely though, since that is what you'd see from a provider that doesn't support nicknames.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README.txt'
--- README.txt 2010-05-25 07:24:14 +0000
+++ README.txt 2011-03-23 19:28:45 +0000
@@ -125,3 +125,13 @@
125It is worth noting that a user needs to be be marked as a "staff user" to be able to access the admin interface. A new openid user will not normally be a "staff user". 125It is worth noting that a user needs to be be marked as a "staff user" to be able to access the admin interface. A new openid user will not normally be a "staff user".
126The easiest way to resolve this is to use traditional authentication (OPENID_USE_AS_ADMIN_LOGIN = False) to sign in as your first user with a password and authorise your 126The easiest way to resolve this is to use traditional authentication (OPENID_USE_AS_ADMIN_LOGIN = False) to sign in as your first user with a password and authorise your
127openid user to be staff.127openid user to be staff.
128
129== Require a valid nickname ==
130
131If you must have a valid, unique nickname in order to create a user accont, add the following setting:
132
133 OPENID_STRICT_USERNAMES = True
134
135This will cause an OpenID login attempt to fail if the provider does not return a 'nickname' (username) for the user, or if the nickname conflicts with an existing user with a different openid identiy url.
136Without this setting, logins without a nickname will be given the username 'openiduser', and upon conflicts with existing username, an incrementing number will be appended to the username until it is unique.
137
128138
=== modified file 'django_openid_auth/auth.py'
--- django_openid_auth/auth.py 2010-10-15 09:08:10 +0000
+++ django_openid_auth/auth.py 2011-03-23 19:28:45 +0000
@@ -42,7 +42,9 @@
42class IdentityAlreadyClaimed(Exception):42class IdentityAlreadyClaimed(Exception):
43 pass43 pass
4444
4545class StrictUsernameViolation(Exception):
46 pass
47
46class OpenIDBackend:48class OpenIDBackend:
47 """A django.contrib.auth backend that authenticates the user based on49 """A django.contrib.auth backend that authenticates the user based on
48 an OpenID response."""50 an OpenID response."""
@@ -72,7 +74,10 @@
72 claimed_id__exact=openid_response.identity_url)74 claimed_id__exact=openid_response.identity_url)
73 except UserOpenID.DoesNotExist:75 except UserOpenID.DoesNotExist:
74 if getattr(settings, 'OPENID_CREATE_USERS', False):76 if getattr(settings, 'OPENID_CREATE_USERS', False):
75 user = self.create_user_from_openid(openid_response)77 try:
78 user = self.create_user_from_openid(openid_response)
79 except StrictUsernameViolation:
80 return None
76 else:81 else:
77 user = user_openid.user82 user = user_openid.user
7883
@@ -142,6 +147,12 @@
142 nickname = details['nickname'] or 'openiduser'147 nickname = details['nickname'] or 'openiduser'
143 email = details['email'] or ''148 email = details['email'] or ''
144149
150 if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
151 if details['nickname'] is None or details['nickname'] == '':
152 raise StrictUsernameViolation("No username")
153 if User.objects.filter(username__exact=nickname).count() > 0:
154 raise StrictUsernameViolation("Duplicate username: %s" % nickname)
155
145 # Pick a username for the user based on their nickname,156 # Pick a username for the user based on their nickname,
146 # checking for conflicts.157 # checking for conflicts.
147 i = 1158 i = 1
148159
=== modified file 'django_openid_auth/tests/test_views.py'
--- django_openid_auth/tests/test_views.py 2011-01-13 08:38:01 +0000
+++ django_openid_auth/tests/test_views.py 2011-03-23 19:28:45 +0000
@@ -131,12 +131,14 @@
131131
132 self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')132 self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')
133 self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False)133 self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False)
134 self.old_strict_usernames = getattr(settings, 'OPENID_STRICT_USERNAMES', False)
134 self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False)135 self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False)
135 self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)136 self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)
136 self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})137 self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
137 self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False)138 self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False)
138139
139 settings.OPENID_CREATE_USERS = False140 settings.OPENID_CREATE_USERS = False
141 settings.OPENID_STRICT_USERNAMES = False
140 settings.OPENID_UPDATE_DETAILS_FROM_SREG = False142 settings.OPENID_UPDATE_DETAILS_FROM_SREG = False
141 settings.OPENID_SSO_SERVER_URL = None143 settings.OPENID_SSO_SERVER_URL = None
142 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {}144 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {}
@@ -145,6 +147,7 @@
145 def tearDown(self):147 def tearDown(self):
146 settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url148 settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url
147 settings.OPENID_CREATE_USERS = self.old_create_users149 settings.OPENID_CREATE_USERS = self.old_create_users
150 settings.OPENID_STRICT_USERNAMES = self.old_strict_usernames
148 settings.OPENID_UPDATE_DETAILS_FROM_SREG = self.old_update_details151 settings.OPENID_UPDATE_DETAILS_FROM_SREG = self.old_update_details
149 settings.OPENID_SSO_SERVER_URL = self.old_sso_server_url152 settings.OPENID_SSO_SERVER_URL = self.old_sso_server_url
150 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map153 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map
@@ -286,6 +289,62 @@
286 self.assertEquals(user.last_name, 'User')289 self.assertEquals(user.last_name, 'User')
287 self.assertEquals(user.email, 'foo@example.com')290 self.assertEquals(user.email, 'foo@example.com')
288291
292 def test_strict_username_no_nickname(self):
293 settings.OPENID_CREATE_USERS = True
294 settings.OPENID_STRICT_USERNAMES = True
295
296 # Posting in an identity URL begins the authentication request:
297 response = self.client.post('/openid/login/',
298 {'openid_identifier': 'http://example.com/identity',
299 'next': '/getuser/'})
300 self.assertContains(response, 'OpenID transaction in progress')
301
302 # Complete the request, passing back some simple registration
303 # data. The user is redirected to the next URL.
304 openid_request = self.provider.parseFormPost(response.content)
305 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
306 openid_response = openid_request.answer(True)
307 sreg_response = sreg.SRegResponse.extractResponse(
308 sreg_request, {'nickname': '', # No nickname
309 'fullname': 'Some User',
310 'email': 'foo@example.com'})
311 openid_response.addExtension(sreg_response)
312 response = self.complete(openid_response)
313
314 # Status code should be 403: Forbidden
315 self.assertEquals(403, response.status_code)
316
317 def test_strict_username_duplicate_user(self):
318 settings.OPENID_CREATE_USERS = True
319 settings.OPENID_STRICT_USERNAMES = True
320 # Create a user with the same name as we'll pass back via sreg.
321 user = User.objects.create_user('someuser', 'someone@example.com')
322 useropenid = UserOpenID(
323 user=user,
324 claimed_id='http://example.com/different_identity',
325 display_id='http://example.com/different_identity')
326 useropenid.save()
327
328 # Posting in an identity URL begins the authentication request:
329 response = self.client.post('/openid/login/',
330 {'openid_identifier': 'http://example.com/identity',
331 'next': '/getuser/'})
332 self.assertContains(response, 'OpenID transaction in progress')
333
334 # Complete the request, passing back some simple registration
335 # data. The user is redirected to the next URL.
336 openid_request = self.provider.parseFormPost(response.content)
337 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
338 openid_response = openid_request.answer(True)
339 sreg_response = sreg.SRegResponse.extractResponse(
340 sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
341 'email': 'foo@example.com'})
342 openid_response.addExtension(sreg_response)
343 response = self.complete(openid_response)
344
345 # Status code should be 403: Forbidden
346 self.assertEquals(403, response.status_code)
347
289 def test_login_update_details(self):348 def test_login_update_details(self):
290 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True349 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
291 user = User.objects.create_user('testuser', 'someone@example.com')350 user = User.objects.create_user('testuser', 'someone@example.com')

Subscribers

People subscribed via source and target branches