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
1=== modified file 'README.txt'
2--- README.txt 2010-05-25 07:24:14 +0000
3+++ README.txt 2011-03-23 19:28:45 +0000
4@@ -125,3 +125,13 @@
5 It 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".
6 The 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
7 openid user to be staff.
8+
9+== Require a valid nickname ==
10+
11+If you must have a valid, unique nickname in order to create a user accont, add the following setting:
12+
13+ OPENID_STRICT_USERNAMES = True
14+
15+This 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.
16+Without 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.
17+
18
19=== modified file 'django_openid_auth/auth.py'
20--- django_openid_auth/auth.py 2010-10-15 09:08:10 +0000
21+++ django_openid_auth/auth.py 2011-03-23 19:28:45 +0000
22@@ -42,7 +42,9 @@
23 class IdentityAlreadyClaimed(Exception):
24 pass
25
26-
27+class StrictUsernameViolation(Exception):
28+ pass
29+
30 class OpenIDBackend:
31 """A django.contrib.auth backend that authenticates the user based on
32 an OpenID response."""
33@@ -72,7 +74,10 @@
34 claimed_id__exact=openid_response.identity_url)
35 except UserOpenID.DoesNotExist:
36 if getattr(settings, 'OPENID_CREATE_USERS', False):
37- user = self.create_user_from_openid(openid_response)
38+ try:
39+ user = self.create_user_from_openid(openid_response)
40+ except StrictUsernameViolation:
41+ return None
42 else:
43 user = user_openid.user
44
45@@ -142,6 +147,12 @@
46 nickname = details['nickname'] or 'openiduser'
47 email = details['email'] or ''
48
49+ if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
50+ if details['nickname'] is None or details['nickname'] == '':
51+ raise StrictUsernameViolation("No username")
52+ if User.objects.filter(username__exact=nickname).count() > 0:
53+ raise StrictUsernameViolation("Duplicate username: %s" % nickname)
54+
55 # Pick a username for the user based on their nickname,
56 # checking for conflicts.
57 i = 1
58
59=== modified file 'django_openid_auth/tests/test_views.py'
60--- django_openid_auth/tests/test_views.py 2011-01-13 08:38:01 +0000
61+++ django_openid_auth/tests/test_views.py 2011-03-23 19:28:45 +0000
62@@ -131,12 +131,14 @@
63
64 self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')
65 self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False)
66+ self.old_strict_usernames = getattr(settings, 'OPENID_STRICT_USERNAMES', False)
67 self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False)
68 self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)
69 self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
70 self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False)
71
72 settings.OPENID_CREATE_USERS = False
73+ settings.OPENID_STRICT_USERNAMES = False
74 settings.OPENID_UPDATE_DETAILS_FROM_SREG = False
75 settings.OPENID_SSO_SERVER_URL = None
76 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {}
77@@ -145,6 +147,7 @@
78 def tearDown(self):
79 settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url
80 settings.OPENID_CREATE_USERS = self.old_create_users
81+ settings.OPENID_STRICT_USERNAMES = self.old_strict_usernames
82 settings.OPENID_UPDATE_DETAILS_FROM_SREG = self.old_update_details
83 settings.OPENID_SSO_SERVER_URL = self.old_sso_server_url
84 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map
85@@ -286,6 +289,62 @@
86 self.assertEquals(user.last_name, 'User')
87 self.assertEquals(user.email, 'foo@example.com')
88
89+ def test_strict_username_no_nickname(self):
90+ settings.OPENID_CREATE_USERS = True
91+ settings.OPENID_STRICT_USERNAMES = True
92+
93+ # Posting in an identity URL begins the authentication request:
94+ response = self.client.post('/openid/login/',
95+ {'openid_identifier': 'http://example.com/identity',
96+ 'next': '/getuser/'})
97+ self.assertContains(response, 'OpenID transaction in progress')
98+
99+ # Complete the request, passing back some simple registration
100+ # data. The user is redirected to the next URL.
101+ openid_request = self.provider.parseFormPost(response.content)
102+ sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
103+ openid_response = openid_request.answer(True)
104+ sreg_response = sreg.SRegResponse.extractResponse(
105+ sreg_request, {'nickname': '', # No nickname
106+ 'fullname': 'Some User',
107+ 'email': 'foo@example.com'})
108+ openid_response.addExtension(sreg_response)
109+ response = self.complete(openid_response)
110+
111+ # Status code should be 403: Forbidden
112+ self.assertEquals(403, response.status_code)
113+
114+ def test_strict_username_duplicate_user(self):
115+ settings.OPENID_CREATE_USERS = True
116+ settings.OPENID_STRICT_USERNAMES = True
117+ # Create a user with the same name as we'll pass back via sreg.
118+ user = User.objects.create_user('someuser', 'someone@example.com')
119+ useropenid = UserOpenID(
120+ user=user,
121+ claimed_id='http://example.com/different_identity',
122+ display_id='http://example.com/different_identity')
123+ useropenid.save()
124+
125+ # Posting in an identity URL begins the authentication request:
126+ response = self.client.post('/openid/login/',
127+ {'openid_identifier': 'http://example.com/identity',
128+ 'next': '/getuser/'})
129+ self.assertContains(response, 'OpenID transaction in progress')
130+
131+ # Complete the request, passing back some simple registration
132+ # data. The user is redirected to the next URL.
133+ openid_request = self.provider.parseFormPost(response.content)
134+ sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
135+ openid_response = openid_request.answer(True)
136+ sreg_response = sreg.SRegResponse.extractResponse(
137+ sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
138+ 'email': 'foo@example.com'})
139+ openid_response.addExtension(sreg_response)
140+ response = self.complete(openid_response)
141+
142+ # Status code should be 403: Forbidden
143+ self.assertEquals(403, response.status_code)
144+
145 def test_login_update_details(self):
146 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
147 user = User.objects.create_user('testuser', 'someone@example.com')

Subscribers

People subscribed via source and target branches