Merge lp:~cjohnston/ubuntu/raring/django-openid-auth/release-0-5 into lp:ubuntu/raring/django-openid-auth
- Raring (13.04)
- release-0-5
- Merge into raring
Proposed by
Chris Johnston
Status: | Merged |
---|---|
Merged at revision: | 6 |
Proposed branch: | lp:~cjohnston/ubuntu/raring/django-openid-auth/release-0-5 |
Merge into: | lp:ubuntu/raring/django-openid-auth |
Diff against target: |
3084 lines (+376/-2143) 41 files modified
.pc/.quilt_patches (+0/-1) .pc/.quilt_series (+0/-1) .pc/.version (+0/-1) .pc/applied-patches (+0/-1) .pc/fix_settings_database.patch/example_consumer/settings.py (+0/-143) PKG-INFO (+31/-0) README.txt (+11/-0) debian/changelog (+9/-0) debian/control (+1/-1) debian/docs (+0/-1) debian/patches/fix_settings_database.patch (+0/-42) debian/patches/series (+0/-1) django_openid_auth/__init__.py (+1/-1) django_openid_auth/admin.py (+5/-5) django_openid_auth/auth.py (+22/-5) django_openid_auth/auth.py.~1~ (+0/-313) django_openid_auth/exceptions.py (+1/-1) django_openid_auth/forms.py (+1/-1) django_openid_auth/management/__init__.py (+1/-1) django_openid_auth/management/commands/__init__.py (+1/-1) django_openid_auth/management/commands/openid_cleanup.py (+1/-1) django_openid_auth/models.py (+1/-1) django_openid_auth/signals.py (+1/-1) django_openid_auth/store.py (+1/-1) django_openid_auth/teams.py (+1/-1) django_openid_auth/tests/__init__.py (+3/-2) django_openid_auth/tests/test_admin.py (+88/-0) django_openid_auth/tests/test_auth.py (+31/-1) django_openid_auth/tests/test_store.py (+1/-1) django_openid_auth/tests/test_views.py (+123/-5) django_openid_auth/tests/test_views.py.~1~ (+0/-1258) django_openid_auth/tests/urls.py (+1/-1) django_openid_auth/urls.py (+1/-1) django_openid_auth/views.py (+6/-2) example_consumer/__init__.py (+1/-1) example_consumer/settings.py (+27/-26) example_consumer/urls.py (+2/-2) example_consumer/views.py (+1/-1) openid.html (+0/-150) openid.txt (+0/-165) setup.py (+2/-2) |
To merge this branch: | bzr merge lp:~cjohnston/ubuntu/raring/django-openid-auth/release-0-5 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
Chris Johnston (community) | Needs Resubmitting | ||
Ubuntu branches | Pending | ||
Review via email: mp+155330@code.launchpad.net |
Commit message
Description of the change
* Fixes tests failing with django 1.4
* Fixes AttributeError when not having admin access (LP: #897651)
* Fixes UnicodeError when handling redirect (LP: #936153)
* Changed UserOpenID.user admin widget to raw_id (LP: #873338)
* Strip non-alphanumeric characters out of email for better username suggestions (LP: #1007281)
* skip ahead to a number that is likely available when generating a sequence number to append to a duplicate username
To post a comment you must log in.
Revision history for this message
Chris Johnston (cjohnston) wrote : | # |
Revision history for this message
Chris Johnston (cjohnston) wrote : | # |
Revision history for this message
Daniel Holbach (dholbach) wrote : | # |
Maybe it'd be worth pointing out in the changelog that openid.txt was dropped from debian/docs and why fix_settings_
review:
Needs Information
Revision history for this message
Chris Johnston (cjohnston) : | # |
review:
Needs Resubmitting
Revision history for this message
Daniel Holbach (dholbach) wrote : | # |
Uploaded. Thanks.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed directory '.pc' |
2 | === removed file '.pc/.quilt_patches' |
3 | --- .pc/.quilt_patches 2012-07-02 12:31:05 +0000 |
4 | +++ .pc/.quilt_patches 1970-01-01 00:00:00 +0000 |
5 | @@ -1,1 +0,0 @@ |
6 | -debian/patches |
7 | |
8 | === removed file '.pc/.quilt_series' |
9 | --- .pc/.quilt_series 2012-07-02 12:31:05 +0000 |
10 | +++ .pc/.quilt_series 1970-01-01 00:00:00 +0000 |
11 | @@ -1,1 +0,0 @@ |
12 | -series |
13 | |
14 | === removed file '.pc/.version' |
15 | --- .pc/.version 2012-06-27 08:25:19 +0000 |
16 | +++ .pc/.version 1970-01-01 00:00:00 +0000 |
17 | @@ -1,1 +0,0 @@ |
18 | -2 |
19 | |
20 | === removed file '.pc/applied-patches' |
21 | --- .pc/applied-patches 2012-06-27 08:25:19 +0000 |
22 | +++ .pc/applied-patches 1970-01-01 00:00:00 +0000 |
23 | @@ -1,1 +0,0 @@ |
24 | -fix_settings_database.patch |
25 | |
26 | === removed directory '.pc/fix_settings_database.patch' |
27 | === removed directory '.pc/fix_settings_database.patch/example_consumer' |
28 | === removed file '.pc/fix_settings_database.patch/example_consumer/settings.py' |
29 | --- .pc/fix_settings_database.patch/example_consumer/settings.py 2012-06-27 08:25:19 +0000 |
30 | +++ .pc/fix_settings_database.patch/example_consumer/settings.py 1970-01-01 00:00:00 +0000 |
31 | @@ -1,143 +0,0 @@ |
32 | -# django-openid-auth - OpenID integration for django.contrib.auth |
33 | -# |
34 | -# Copyright (C) 2007 Simon Willison |
35 | -# Copyright (C) 2008-2010 Canonical Ltd. |
36 | -# |
37 | -# Redistribution and use in source and binary forms, with or without |
38 | -# modification, are permitted provided that the following conditions |
39 | -# are met: |
40 | -# |
41 | -# * Redistributions of source code must retain the above copyright |
42 | -# notice, this list of conditions and the following disclaimer. |
43 | -# |
44 | -# * Redistributions in binary form must reproduce the above copyright |
45 | -# notice, this list of conditions and the following disclaimer in the |
46 | -# documentation and/or other materials provided with the distribution. |
47 | -# |
48 | -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
49 | -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
50 | -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
51 | -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
52 | -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
53 | -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
54 | -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
55 | -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
56 | -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
57 | -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
58 | -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
59 | -# POSSIBILITY OF SUCH DAMAGE. |
60 | - |
61 | -# Django settings for example project. |
62 | - |
63 | -DEBUG = True |
64 | -TEMPLATE_DEBUG = DEBUG |
65 | - |
66 | -ADMINS = ( |
67 | - # ('Your Name', 'your_email@domain.com'), |
68 | -) |
69 | - |
70 | -MANAGERS = ADMINS |
71 | - |
72 | -DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. |
73 | -DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3. |
74 | -DATABASE_USER = '' # Not used with sqlite3. |
75 | -DATABASE_PASSWORD = '' # Not used with sqlite3. |
76 | -DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. |
77 | -DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. |
78 | - |
79 | -# Local time zone for this installation. Choices can be found here: |
80 | -# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE |
81 | -# although not all variations may be possible on all operating systems. |
82 | -# If running in a Windows environment this must be set to the same as your |
83 | -# system time zone. |
84 | -TIME_ZONE = 'America/Chicago' |
85 | - |
86 | -# Language code for this installation. All choices can be found here: |
87 | -# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes |
88 | -# http://blogs.law.harvard.edu/tech/stories/storyReader$15 |
89 | -LANGUAGE_CODE = 'en-us' |
90 | - |
91 | -SITE_ID = 1 |
92 | - |
93 | -# If you set this to False, Django will make some optimizations so as not |
94 | -# to load the internationalization machinery. |
95 | -USE_I18N = True |
96 | - |
97 | -# Absolute path to the directory that holds media. |
98 | -# Example: "/home/media/media.lawrence.com/" |
99 | -MEDIA_ROOT = '' |
100 | - |
101 | -# URL that handles the media served from MEDIA_ROOT. |
102 | -# Example: "http://media.lawrence.com" |
103 | -MEDIA_URL = '' |
104 | - |
105 | -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a |
106 | -# trailing slash. |
107 | -# Examples: "http://foo.com/media/", "/media/". |
108 | -ADMIN_MEDIA_PREFIX = '/media/' |
109 | - |
110 | -# Make this unique, and don't share it with anybody. |
111 | -SECRET_KEY = '34958734985734985734985798437' |
112 | - |
113 | -# List of callables that know how to import templates from various sources. |
114 | -TEMPLATE_LOADERS = ( |
115 | - 'django.template.loaders.filesystem.load_template_source', |
116 | - 'django.template.loaders.app_directories.load_template_source', |
117 | -# 'django.template.loaders.eggs.load_template_source', |
118 | -) |
119 | - |
120 | -# django-openid-auth will *not* work with Django 1.1.1 or older, as it's |
121 | -# missing the csrf_token template tag. This will allow it to work with |
122 | -# Django 1.1.2 or later: |
123 | -try: |
124 | - import django.middleware.csrf |
125 | -except ImportError: |
126 | - csrf_middleware = 'django.contrib.csrf.middleware.CsrfViewMiddleware' |
127 | -else: |
128 | - csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware' |
129 | - |
130 | -MIDDLEWARE_CLASSES = ( |
131 | - 'django.middleware.common.CommonMiddleware', |
132 | - 'django.contrib.sessions.middleware.SessionMiddleware', |
133 | - 'django.contrib.auth.middleware.AuthenticationMiddleware', |
134 | - csrf_middleware, |
135 | -) |
136 | - |
137 | -ROOT_URLCONF = 'example_consumer.urls' |
138 | - |
139 | -TEMPLATE_DIRS = ( |
140 | - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". |
141 | - # Always use forward slashes, even on Windows. |
142 | - # Don't forget to use absolute paths, not relative paths. |
143 | -) |
144 | - |
145 | -INSTALLED_APPS = ( |
146 | - 'django.contrib.auth', |
147 | - 'django.contrib.contenttypes', |
148 | - 'django.contrib.sessions', |
149 | - 'django.contrib.admin', |
150 | - 'django_openid_auth', |
151 | -) |
152 | - |
153 | -AUTHENTICATION_BACKENDS = ( |
154 | - 'django_openid_auth.auth.OpenIDBackend', |
155 | - 'django.contrib.auth.backends.ModelBackend', |
156 | -) |
157 | - |
158 | -# Should users be created when new OpenIDs are used to log in? |
159 | -OPENID_CREATE_USERS = True |
160 | - |
161 | -# When logging in again, should we overwrite user details based on |
162 | -# data received via Simple Registration? |
163 | -OPENID_UPDATE_DETAILS_FROM_SREG = True |
164 | - |
165 | -# If set, always use this as the identity URL rather than asking the |
166 | -# user. This only makes sense if it is a server URL. |
167 | -OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/' |
168 | - |
169 | -# Tell django.contrib.auth to use the OpenID signin URLs. |
170 | -LOGIN_URL = '/openid/login/' |
171 | -LOGIN_REDIRECT_URL = '/' |
172 | - |
173 | -# Should django_auth_openid be used to sign into the admin interface? |
174 | -OPENID_USE_AS_ADMIN_LOGIN = False |
175 | |
176 | === added file 'PKG-INFO' |
177 | --- PKG-INFO 1970-01-01 00:00:00 +0000 |
178 | +++ PKG-INFO 2013-03-26 11:57:40 +0000 |
179 | @@ -0,0 +1,31 @@ |
180 | +Metadata-Version: 1.1 |
181 | +Name: django-openid-auth |
182 | +Version: 0.5 |
183 | +Summary: OpenID integration for django.contrib.auth |
184 | +Home-page: https://launchpad.net/django-openid-auth |
185 | +Author: Canonical Ltd |
186 | +Author-email: UNKNOWN |
187 | +License: BSD |
188 | +Download-URL: http://launchpad.net/django-openid-auth/trunk/0.5/+download/django-openid-auth-0.5.tar.gz |
189 | +Description: A library that can be used to add OpenID support to Django applications. |
190 | + The library integrates with Django's built in authentication system, so |
191 | + most applications require minimal changes to support OpenID llogin. The |
192 | + library also includes the following features: |
193 | + * Basic user details are transferred from the OpenID server via the |
194 | + Simple Registration extension or Attribute Exchange extension. |
195 | + * can be configured to use a fixed OpenID server URL, for use in SSO. |
196 | + * supports the launchpad.net teams extension to get team membership |
197 | + info. |
198 | + |
199 | +Platform: any |
200 | +Classifier: Development Status :: 4 - Beta |
201 | +Classifier: Environment :: Web Environment |
202 | +Classifier: Framework :: Django |
203 | +Classifier: Intended Audience :: Developers |
204 | +Classifier: License :: OSI Approved :: BSD License |
205 | +Classifier: Operating System :: OS Independent |
206 | +Classifier: Programming Language :: Python |
207 | +Classifier: Topic :: Software Development :: Libraries :: Python Modules |
208 | +Requires: django (>=1.1.2) |
209 | +Requires: openid (>=2.2.0) |
210 | +Provides: django_openid_auth |
211 | |
212 | === modified file 'README.txt' |
213 | --- README.txt 2012-06-27 08:25:19 +0000 |
214 | +++ README.txt 2013-03-26 11:57:40 +0000 |
215 | @@ -166,3 +166,14 @@ |
216 | |
217 | This function must return a Django.http.HttpResponse instance. |
218 | |
219 | +== Use the user's email for suggested usernames == |
220 | + |
221 | +You can optionally strip out non-alphanumeric characters from the user's email |
222 | +to generate a preferred username, if the server doesn't provide nick |
223 | +information, by setting the following setting: |
224 | + |
225 | + OPENID_USE_EMAIL_FOR_USERNAME = True |
226 | + |
227 | +Otherwise, and by default, if the server omits nick information and a user is |
228 | +created it'll receive a username 'openiduser' + a number. |
229 | +Consider also the OPENID_STRICT_USERNAMES setting (see ``Require a valid nickname``) |
230 | |
231 | === modified file 'debian/changelog' |
232 | --- debian/changelog 2012-07-02 12:31:05 +0000 |
233 | +++ debian/changelog 2013-03-26 11:57:40 +0000 |
234 | @@ -1,3 +1,12 @@ |
235 | +django-openid-auth (0.5-0ubuntu1) raring; urgency=low |
236 | + |
237 | + * New upstream release. |
238 | + * Bump Standards-Version to 3.9.4 |
239 | + * Dropped openid.txt from the package |
240 | + * Removed fix_settings_database.patch as the fix was incorporated upstream |
241 | + |
242 | + -- Chris Johnston <chrisjohnston@ubuntu.com> Mon, 25 Mar 2013 15:16:38 -0400 |
243 | + |
244 | django-openid-auth (0.4-1ubuntu1) quantal; urgency=low |
245 | |
246 | * Replaces python-django-openid-auth to to remove the Ubuntu-only |
247 | |
248 | === modified file 'debian/control' |
249 | --- debian/control 2012-07-02 12:31:05 +0000 |
250 | +++ debian/control 2013-03-26 11:57:40 +0000 |
251 | @@ -9,7 +9,7 @@ |
252 | python-django (>= 1.2), |
253 | python-openid, |
254 | python-support |
255 | -Standards-Version: 3.9.3 |
256 | +Standards-Version: 3.9.4 |
257 | XS-Python-Version: >= 2.6 |
258 | Homepage: https://launchpad.net/django-openid-auth |
259 | Vcs-Svn: svn://svn.debian.org/python-modules/packages/django-openid-auth/trunk/ |
260 | |
261 | === modified file 'debian/docs' |
262 | --- debian/docs 2012-06-27 08:25:19 +0000 |
263 | +++ debian/docs 2013-03-26 11:57:40 +0000 |
264 | @@ -1,3 +1,2 @@ |
265 | README.txt |
266 | TODO.txt |
267 | -openid.txt |
268 | |
269 | === removed directory 'debian/patches' |
270 | === removed file 'debian/patches/fix_settings_database.patch' |
271 | --- debian/patches/fix_settings_database.patch 2012-06-27 08:25:19 +0000 |
272 | +++ debian/patches/fix_settings_database.patch 1970-01-01 00:00:00 +0000 |
273 | @@ -1,42 +0,0 @@ |
274 | -Description: Use Django 1.3 DATABASE structure |
275 | - Upstream still ships the old database configuration for Django in its |
276 | - example consumer. This patch changes it to the new dictionary structure. |
277 | -Author: Michael Fladischer <FladischerMichael@fladi.at> |
278 | -Last-Update: 2012-04-20 |
279 | -Forwarded: no |
280 | - |
281 | ---- a/example_consumer/settings.py |
282 | -+++ b/example_consumer/settings.py |
283 | -@@ -38,12 +38,13 @@ |
284 | - |
285 | - MANAGERS = ADMINS |
286 | - |
287 | --DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. |
288 | --DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3. |
289 | --DATABASE_USER = '' # Not used with sqlite3. |
290 | --DATABASE_PASSWORD = '' # Not used with sqlite3. |
291 | --DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. |
292 | --DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. |
293 | -+DATABASES = { |
294 | -+ 'default': { |
295 | -+ 'ENGINE': 'django.db.backends.sqlite3', |
296 | -+ 'NAME': 'example_consumer/test.db3', |
297 | -+ } |
298 | -+} |
299 | -+ |
300 | - |
301 | - # Local time zone for this installation. Choices can be found here: |
302 | - # http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE |
303 | -@@ -81,9 +82,9 @@ |
304 | - |
305 | - # List of callables that know how to import templates from various sources. |
306 | - TEMPLATE_LOADERS = ( |
307 | -- 'django.template.loaders.filesystem.load_template_source', |
308 | -- 'django.template.loaders.app_directories.load_template_source', |
309 | --# 'django.template.loaders.eggs.load_template_source', |
310 | -+ 'django.template.loaders.filesystem.Loader', |
311 | -+ 'django.template.loaders.app_directories.Loader', |
312 | -+# 'django.template.loaders.eggs.Loader', |
313 | - ) |
314 | - |
315 | - # django-openid-auth will *not* work with Django 1.1.1 or older, as it's |
316 | |
317 | === removed file 'debian/patches/series' |
318 | --- debian/patches/series 2012-06-27 08:25:19 +0000 |
319 | +++ debian/patches/series 1970-01-01 00:00:00 +0000 |
320 | @@ -1,1 +0,0 @@ |
321 | -fix_settings_database.patch |
322 | |
323 | === modified file 'django_openid_auth/__init__.py' |
324 | --- django_openid_auth/__init__.py 2010-12-03 00:46:25 +0000 |
325 | +++ django_openid_auth/__init__.py 2013-03-26 11:57:40 +0000 |
326 | @@ -1,7 +1,7 @@ |
327 | # django-openid-auth - OpenID integration for django.contrib.auth |
328 | # |
329 | # Copyright (C) 2007 Simon Willison |
330 | -# Copyright (C) 2008-2010 Canonical Ltd. |
331 | +# Copyright (C) 2008-2013 Canonical Ltd. |
332 | # |
333 | # Redistribution and use in source and binary forms, with or without |
334 | # modification, are permitted provided that the following conditions |
335 | |
336 | === removed file 'django_openid_auth/__init__.pyc' |
337 | Binary files django_openid_auth/__init__.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/__init__.pyc 1970-01-01 00:00:00 +0000 differ |
338 | === modified file 'django_openid_auth/admin.py' |
339 | --- django_openid_auth/admin.py 2010-12-03 00:46:25 +0000 |
340 | +++ django_openid_auth/admin.py 2013-03-26 11:57:40 +0000 |
341 | @@ -1,6 +1,6 @@ |
342 | # django-openid-auth - OpenID integration for django.contrib.auth |
343 | # |
344 | -# Copyright (C) 2008-2010 Canonical Ltd. |
345 | +# Copyright (C) 2008-2013 Canonical Ltd. |
346 | # Copyright (C) 2010 Dave Walker |
347 | # |
348 | # Redistribution and use in source and binary forms, with or without |
349 | @@ -62,6 +62,7 @@ |
350 | |
351 | |
352 | class UserOpenIDAdmin(admin.ModelAdmin): |
353 | + raw_id_fields = ('user',) |
354 | list_display = ('user', 'claimed_id') |
355 | search_fields = ('claimed_id',) |
356 | |
357 | @@ -76,15 +77,14 @@ |
358 | def _openid_login(self, request, error_message='', extra_context=None): |
359 | if request.user.is_authenticated(): |
360 | if not request.user.is_staff: |
361 | - return views.render_failure( |
362 | + return views.default_render_failure( |
363 | request, "User %s does not have admin access." |
364 | % request.user.username) |
365 | - return views.render_failure( |
366 | - request, "Unknown Error: %s" % error_message) |
367 | + assert error_message, "Unknown Error: %s" % error_message |
368 | else: |
369 | # Redirect to openid login path, |
370 | return HttpResponseRedirect( |
371 | settings.LOGIN_URL + "?next=" + request.get_full_path()) |
372 | |
373 | # Overide the standard admin login form. |
374 | - admin.sites.AdminSite.display_login_form = _openid_login |
375 | + admin.sites.AdminSite.login = _openid_login |
376 | |
377 | === modified file 'django_openid_auth/auth.py' |
378 | --- django_openid_auth/auth.py 2012-06-27 08:25:19 +0000 |
379 | +++ django_openid_auth/auth.py 2013-03-26 11:57:40 +0000 |
380 | @@ -1,6 +1,6 @@ |
381 | # django-openid-auth - OpenID integration for django.contrib.auth |
382 | # |
383 | -# Copyright (C) 2008-2010 Canonical Ltd. |
384 | +# Copyright (C) 2008-2013 Canonical Ltd. |
385 | # |
386 | # Redistribution and use in source and binary forms, with or without |
387 | # modification, are permitted provided that the following conditions |
388 | @@ -49,6 +49,9 @@ |
389 | """A django.contrib.auth backend that authenticates the user based on |
390 | an OpenID response.""" |
391 | |
392 | + supports_object_permissions = False |
393 | + supports_anonymous_user = True |
394 | + |
395 | def get_user(self, user_id): |
396 | try: |
397 | return User.objects.get(pk=user_id) |
398 | @@ -146,6 +149,16 @@ |
399 | return dict(email=email, nickname=nickname, |
400 | first_name=first_name, last_name=last_name) |
401 | |
402 | + def _get_preferred_username(self, nickname, email): |
403 | + if nickname: |
404 | + return nickname |
405 | + if email and getattr(settings, 'OPENID_USE_EMAIL_FOR_USERNAME', |
406 | + False): |
407 | + suggestion = ''.join([x for x in email if x.isalnum()]) |
408 | + if suggestion: |
409 | + return suggestion |
410 | + return 'openiduser' |
411 | + |
412 | def _get_available_username(self, nickname, identity_url): |
413 | # If we're being strict about usernames, throw an error if we didn't |
414 | # get one back from the provider |
415 | @@ -198,8 +211,10 @@ |
416 | "already in use for a different account." % nickname) |
417 | |
418 | # Pick a username for the user based on their nickname, |
419 | - # checking for conflicts. |
420 | - i = 1 |
421 | + # checking for conflicts. Start with number of existing users who's |
422 | + # username starts with this nickname to avoid having to iterate over |
423 | + # all of the existing ones. |
424 | + i = User.objects.filter(username__startswith=nickname).count() + 1 |
425 | while True: |
426 | username = nickname |
427 | if i > 1: |
428 | @@ -223,10 +238,12 @@ |
429 | "An attribute required for logging in was not " |
430 | "returned ({0}).".format(required_attr)) |
431 | |
432 | - nickname = details['nickname'] or 'openiduser' |
433 | + nickname = self._get_preferred_username(details['nickname'], |
434 | + details['email']) |
435 | email = details['email'] or '' |
436 | |
437 | - username = self._get_available_username(details['nickname'], openid_response.identity_url) |
438 | + username = self._get_available_username(nickname, |
439 | + openid_response.identity_url) |
440 | |
441 | user = User.objects.create_user(username, email, password=None) |
442 | self.associate_openid(user, openid_response) |
443 | |
444 | === removed file 'django_openid_auth/auth.py.~1~' |
445 | --- django_openid_auth/auth.py.~1~ 2012-06-27 08:25:19 +0000 |
446 | +++ django_openid_auth/auth.py.~1~ 1970-01-01 00:00:00 +0000 |
447 | @@ -1,313 +0,0 @@ |
448 | -# django-openid-auth - OpenID integration for django.contrib.auth |
449 | -# |
450 | -# Copyright (C) 2008-2010 Canonical Ltd. |
451 | -# |
452 | -# Redistribution and use in source and binary forms, with or without |
453 | -# modification, are permitted provided that the following conditions |
454 | -# are met: |
455 | -# |
456 | -# * Redistributions of source code must retain the above copyright |
457 | -# notice, this list of conditions and the following disclaimer. |
458 | -# |
459 | -# * Redistributions in binary form must reproduce the above copyright |
460 | -# notice, this list of conditions and the following disclaimer in the |
461 | -# documentation and/or other materials provided with the distribution. |
462 | -# |
463 | -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
464 | -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
465 | -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
466 | -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
467 | -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
468 | -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
469 | -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
470 | -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
471 | -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
472 | -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
473 | -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
474 | -# POSSIBILITY OF SUCH DAMAGE. |
475 | - |
476 | -"""Glue between OpenID and django.contrib.auth.""" |
477 | - |
478 | -__metaclass__ = type |
479 | - |
480 | -from django.conf import settings |
481 | -from django.contrib.auth.models import User, Group |
482 | -from openid.consumer.consumer import SUCCESS |
483 | -from openid.extensions import ax, sreg, pape |
484 | - |
485 | -from django_openid_auth import teams |
486 | -from django_openid_auth.models import UserOpenID |
487 | -from django_openid_auth.exceptions import ( |
488 | - IdentityAlreadyClaimed, |
489 | - DuplicateUsernameViolation, |
490 | - MissingUsernameViolation, |
491 | - MissingPhysicalMultiFactor, |
492 | - RequiredAttributeNotReturned, |
493 | -) |
494 | - |
495 | -class OpenIDBackend: |
496 | - """A django.contrib.auth backend that authenticates the user based on |
497 | - an OpenID response.""" |
498 | - |
499 | - def get_user(self, user_id): |
500 | - try: |
501 | - return User.objects.get(pk=user_id) |
502 | - except User.DoesNotExist: |
503 | - return None |
504 | - |
505 | - def authenticate(self, **kwargs): |
506 | - """Authenticate the user based on an OpenID response.""" |
507 | - # Require that the OpenID response be passed in as a keyword |
508 | - # argument, to make sure we don't match the username/password |
509 | - # calling conventions of authenticate. |
510 | - |
511 | - openid_response = kwargs.get('openid_response') |
512 | - if openid_response is None: |
513 | - return None |
514 | - |
515 | - if openid_response.status != SUCCESS: |
516 | - return None |
517 | - |
518 | - user = None |
519 | - try: |
520 | - user_openid = UserOpenID.objects.get( |
521 | - claimed_id__exact=openid_response.identity_url) |
522 | - except UserOpenID.DoesNotExist: |
523 | - if getattr(settings, 'OPENID_CREATE_USERS', False): |
524 | - user = self.create_user_from_openid(openid_response) |
525 | - else: |
526 | - user = user_openid.user |
527 | - |
528 | - if user is None: |
529 | - return None |
530 | - |
531 | - if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False): |
532 | - details = self._extract_user_details(openid_response) |
533 | - self.update_user_details(user, details, openid_response) |
534 | - |
535 | - if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False): |
536 | - pape_response = pape.Response.fromSuccessResponse(openid_response) |
537 | - if pape_response is None or \ |
538 | - pape.AUTH_MULTI_FACTOR_PHYSICAL not in pape_response.auth_policies: |
539 | - raise MissingPhysicalMultiFactor() |
540 | - |
541 | - teams_response = teams.TeamsResponse.fromSuccessResponse( |
542 | - openid_response) |
543 | - if teams_response: |
544 | - self.update_groups_from_teams(user, teams_response) |
545 | - self.update_staff_status_from_teams(user, teams_response) |
546 | - |
547 | - return user |
548 | - |
549 | - def _extract_user_details(self, openid_response): |
550 | - email = fullname = first_name = last_name = nickname = None |
551 | - sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response) |
552 | - if sreg_response: |
553 | - email = sreg_response.get('email') |
554 | - fullname = sreg_response.get('fullname') |
555 | - nickname = sreg_response.get('nickname') |
556 | - # If any attributes are provided via Attribute Exchange, use |
557 | - # them in preference. |
558 | - fetch_response = ax.FetchResponse.fromSuccessResponse(openid_response) |
559 | - if fetch_response: |
560 | - # The myOpenID provider advertises AX support, but uses |
561 | - # attribute names from an obsolete draft of the |
562 | - # specification. We check for them first so the common |
563 | - # names take precedence. |
564 | - email = fetch_response.getSingle( |
565 | - 'http://schema.openid.net/contact/email', email) |
566 | - fullname = fetch_response.getSingle( |
567 | - 'http://schema.openid.net/namePerson', fullname) |
568 | - nickname = fetch_response.getSingle( |
569 | - 'http://schema.openid.net/namePerson/friendly', nickname) |
570 | - |
571 | - email = fetch_response.getSingle( |
572 | - 'http://axschema.org/contact/email', email) |
573 | - fullname = fetch_response.getSingle( |
574 | - 'http://axschema.org/namePerson', fullname) |
575 | - first_name = fetch_response.getSingle( |
576 | - 'http://axschema.org/namePerson/first', first_name) |
577 | - last_name = fetch_response.getSingle( |
578 | - 'http://axschema.org/namePerson/last', last_name) |
579 | - nickname = fetch_response.getSingle( |
580 | - 'http://axschema.org/namePerson/friendly', nickname) |
581 | - |
582 | - if fullname and not (first_name or last_name): |
583 | - # Django wants to store first and last names separately, |
584 | - # so we do our best to split the full name. |
585 | - fullname = fullname.strip() |
586 | - split_names = fullname.rsplit(None, 1) |
587 | - if len(split_names) == 2: |
588 | - first_name, last_name = split_names |
589 | - else: |
590 | - first_name = u'' |
591 | - last_name = fullname |
592 | - |
593 | - return dict(email=email, nickname=nickname, |
594 | - first_name=first_name, last_name=last_name) |
595 | - |
596 | - def _get_available_username(self, nickname, identity_url): |
597 | - # If we're being strict about usernames, throw an error if we didn't |
598 | - # get one back from the provider |
599 | - if getattr(settings, 'OPENID_STRICT_USERNAMES', False): |
600 | - if nickname is None or nickname == '': |
601 | - raise MissingUsernameViolation() |
602 | - |
603 | - # If we don't have a nickname, and we're not being strict, use a default |
604 | - nickname = nickname or 'openiduser' |
605 | - |
606 | - # See if we already have this nickname assigned to a username |
607 | - try: |
608 | - user = User.objects.get(username__exact=nickname) |
609 | - except User.DoesNotExist: |
610 | - # No conflict, we can use this nickname |
611 | - return nickname |
612 | - |
613 | - # Check if we already have nickname+i for this identity_url |
614 | - try: |
615 | - user_openid = UserOpenID.objects.get( |
616 | - claimed_id__exact=identity_url, |
617 | - user__username__startswith=nickname) |
618 | - # No exception means we have an existing user for this identity |
619 | - # that starts with this nickname. |
620 | - |
621 | - # If they are an exact match, the user already exists and hasn't |
622 | - # changed their username, so continue to use it |
623 | - if nickname == user_openid.user.username: |
624 | - return nickname |
625 | - |
626 | - # It is possible we've had to assign them to nickname+i already. |
627 | - oid_username = user_openid.user.username |
628 | - if len(oid_username) > len(nickname): |
629 | - try: |
630 | - # check that it ends with a number |
631 | - int(oid_username[len(nickname):]) |
632 | - return oid_username |
633 | - except ValueError: |
634 | - # username starts with nickname, but isn't nickname+# |
635 | - pass |
636 | - except UserOpenID.DoesNotExist: |
637 | - # No user associated with this identity_url |
638 | - pass |
639 | - |
640 | - |
641 | - if getattr(settings, 'OPENID_STRICT_USERNAMES', False): |
642 | - if User.objects.filter(username__exact=nickname).count() > 0: |
643 | - raise DuplicateUsernameViolation( |
644 | - "The username (%s) with which you tried to log in is " |
645 | - "already in use for a different account." % nickname) |
646 | - |
647 | - # Pick a username for the user based on their nickname, |
648 | - # checking for conflicts. |
649 | - i = 1 |
650 | - while True: |
651 | - username = nickname |
652 | - if i > 1: |
653 | - username += str(i) |
654 | - try: |
655 | - user = User.objects.get(username__exact=username) |
656 | - except User.DoesNotExist: |
657 | - break |
658 | - i += 1 |
659 | - return username |
660 | - |
661 | - def create_user_from_openid(self, openid_response): |
662 | - details = self._extract_user_details(openid_response) |
663 | - required_attrs = getattr(settings, 'OPENID_SREG_REQUIRED_FIELDS', []) |
664 | - if getattr(settings, 'OPENID_STRICT_USERNAMES', False): |
665 | - required_attrs.append('nickname') |
666 | - |
667 | - for required_attr in required_attrs: |
668 | - if required_attr not in details or not details[required_attr]: |
669 | - raise RequiredAttributeNotReturned( |
670 | - "An attribute required for logging in was not " |
671 | - "returned ({0}).".format(required_attr)) |
672 | - |
673 | - nickname = details['nickname'] or 'openiduser' |
674 | - email = details['email'] or '' |
675 | - |
676 | - username = self._get_available_username(details['nickname'], openid_response.identity_url) |
677 | - |
678 | - user = User.objects.create_user(username, email, password=None) |
679 | - self.associate_openid(user, openid_response) |
680 | - self.update_user_details(user, details, openid_response) |
681 | - |
682 | - return user |
683 | - |
684 | - def associate_openid(self, user, openid_response): |
685 | - """Associate an OpenID with a user account.""" |
686 | - # Check to see if this OpenID has already been claimed. |
687 | - try: |
688 | - user_openid = UserOpenID.objects.get( |
689 | - claimed_id__exact=openid_response.identity_url) |
690 | - except UserOpenID.DoesNotExist: |
691 | - user_openid = UserOpenID( |
692 | - user=user, |
693 | - claimed_id=openid_response.identity_url, |
694 | - display_id=openid_response.endpoint.getDisplayIdentifier()) |
695 | - user_openid.save() |
696 | - else: |
697 | - if user_openid.user != user: |
698 | - raise IdentityAlreadyClaimed( |
699 | - "The identity %s has already been claimed" |
700 | - % openid_response.identity_url) |
701 | - |
702 | - return user_openid |
703 | - |
704 | - def update_user_details(self, user, details, openid_response): |
705 | - updated = False |
706 | - if details['first_name']: |
707 | - user.first_name = details['first_name'][:30] |
708 | - updated = True |
709 | - if details['last_name']: |
710 | - user.last_name = details['last_name'][:30] |
711 | - updated = True |
712 | - if details['email']: |
713 | - user.email = details['email'] |
714 | - updated = True |
715 | - if getattr(settings, 'OPENID_FOLLOW_RENAMES', False): |
716 | - user.username = self._get_available_username(details['nickname'], openid_response.identity_url) |
717 | - updated = True |
718 | - |
719 | - if updated: |
720 | - user.save() |
721 | - |
722 | - def update_groups_from_teams(self, user, teams_response): |
723 | - teams_mapping_auto = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False) |
724 | - teams_mapping_auto_blacklist = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST', []) |
725 | - teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) |
726 | - if teams_mapping_auto: |
727 | - #ignore teams_mapping. use all django-groups |
728 | - teams_mapping = dict() |
729 | - all_groups = Group.objects.exclude(name__in=teams_mapping_auto_blacklist) |
730 | - for group in all_groups: |
731 | - teams_mapping[group.name] = group.name |
732 | - |
733 | - if len(teams_mapping) == 0: |
734 | - return |
735 | - |
736 | - current_groups = set(user.groups.filter( |
737 | - name__in=teams_mapping.values())) |
738 | - desired_groups = set(Group.objects.filter( |
739 | - name__in=[teams_mapping[lp_team] |
740 | - for lp_team in teams_response.is_member |
741 | - if lp_team in teams_mapping])) |
742 | - for group in current_groups - desired_groups: |
743 | - user.groups.remove(group) |
744 | - for group in desired_groups - current_groups: |
745 | - user.groups.add(group) |
746 | - |
747 | - def update_staff_status_from_teams(self, user, teams_response): |
748 | - if not hasattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS'): |
749 | - return |
750 | - |
751 | - staff_teams = getattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS', []) |
752 | - user.is_staff = False |
753 | - |
754 | - for lp_team in teams_response.is_member: |
755 | - if lp_team in staff_teams: |
756 | - user.is_staff = True |
757 | - break |
758 | - |
759 | - user.save() |
760 | - |
761 | |
762 | === removed file 'django_openid_auth/auth.pyc' |
763 | Binary files django_openid_auth/auth.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/auth.pyc 1970-01-01 00:00:00 +0000 differ |
764 | === modified file 'django_openid_auth/exceptions.py' |
765 | --- django_openid_auth/exceptions.py 2012-06-27 08:25:19 +0000 |
766 | +++ django_openid_auth/exceptions.py 2013-03-26 11:57:40 +0000 |
767 | @@ -1,6 +1,6 @@ |
768 | # django-openid-auth - OpenID integration for django.contrib.auth |
769 | # |
770 | -# Copyright (C) 2008-2010 Canonical Ltd. |
771 | +# Copyright (C) 2008-2013 Canonical Ltd. |
772 | # |
773 | # Redistribution and use in source and binary forms, with or without |
774 | # modification, are permitted provided that the following conditions |
775 | |
776 | === removed file 'django_openid_auth/exceptions.pyc' |
777 | Binary files django_openid_auth/exceptions.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/exceptions.pyc 1970-01-01 00:00:00 +0000 differ |
778 | === modified file 'django_openid_auth/forms.py' |
779 | --- django_openid_auth/forms.py 2010-12-03 00:46:25 +0000 |
780 | +++ django_openid_auth/forms.py 2013-03-26 11:57:40 +0000 |
781 | @@ -1,7 +1,7 @@ |
782 | # django-openid-auth - OpenID integration for django.contrib.auth |
783 | # |
784 | # Copyright (C) 2007 Simon Willison |
785 | -# Copyright (C) 2008-2010 Canonical Ltd. |
786 | +# Copyright (C) 2008-2013 Canonical Ltd. |
787 | # |
788 | # Redistribution and use in source and binary forms, with or without |
789 | # modification, are permitted provided that the following conditions |
790 | |
791 | === removed file 'django_openid_auth/forms.pyc' |
792 | Binary files django_openid_auth/forms.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/forms.pyc 1970-01-01 00:00:00 +0000 differ |
793 | === modified file 'django_openid_auth/management/__init__.py' |
794 | --- django_openid_auth/management/__init__.py 2010-12-03 00:46:25 +0000 |
795 | +++ django_openid_auth/management/__init__.py 2013-03-26 11:57:40 +0000 |
796 | @@ -1,6 +1,6 @@ |
797 | # django-openid-auth - OpenID integration for django.contrib.auth |
798 | # |
799 | -# Copyright (C) 2009-2010 Canonical Ltd. |
800 | +# Copyright (C) 2009-2013 Canonical Ltd. |
801 | # |
802 | # Redistribution and use in source and binary forms, with or without |
803 | # modification, are permitted provided that the following conditions |
804 | |
805 | === removed file 'django_openid_auth/management/__init__.pyc' |
806 | Binary files django_openid_auth/management/__init__.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/management/__init__.pyc 1970-01-01 00:00:00 +0000 differ |
807 | === modified file 'django_openid_auth/management/commands/__init__.py' |
808 | --- django_openid_auth/management/commands/__init__.py 2010-12-03 00:46:25 +0000 |
809 | +++ django_openid_auth/management/commands/__init__.py 2013-03-26 11:57:40 +0000 |
810 | @@ -1,6 +1,6 @@ |
811 | # django-openid-auth - OpenID integration for django.contrib.auth |
812 | # |
813 | -# Copyright (C) 2009-2010 Canonical Ltd. |
814 | +# Copyright (C) 2009-2013 Canonical Ltd. |
815 | # |
816 | # Redistribution and use in source and binary forms, with or without |
817 | # modification, are permitted provided that the following conditions |
818 | |
819 | === modified file 'django_openid_auth/management/commands/openid_cleanup.py' |
820 | --- django_openid_auth/management/commands/openid_cleanup.py 2010-12-03 00:46:25 +0000 |
821 | +++ django_openid_auth/management/commands/openid_cleanup.py 2013-03-26 11:57:40 +0000 |
822 | @@ -1,6 +1,6 @@ |
823 | # django-openid-auth - OpenID integration for django.contrib.auth |
824 | # |
825 | -# Copyright (C) 2009-2010 Canonical Ltd. |
826 | +# Copyright (C) 2009-2013 Canonical Ltd. |
827 | # |
828 | # Redistribution and use in source and binary forms, with or without |
829 | # modification, are permitted provided that the following conditions |
830 | |
831 | === modified file 'django_openid_auth/models.py' |
832 | --- django_openid_auth/models.py 2010-12-03 00:46:25 +0000 |
833 | +++ django_openid_auth/models.py 2013-03-26 11:57:40 +0000 |
834 | @@ -1,7 +1,7 @@ |
835 | # django-openid-auth - OpenID integration for django.contrib.auth |
836 | # |
837 | # Copyright (C) 2007 Simon Willison |
838 | -# Copyright (C) 2008-2010 Canonical Ltd. |
839 | +# Copyright (C) 2008-2013 Canonical Ltd. |
840 | # |
841 | # Redistribution and use in source and binary forms, with or without |
842 | # modification, are permitted provided that the following conditions |
843 | |
844 | === removed file 'django_openid_auth/models.pyc' |
845 | Binary files django_openid_auth/models.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/models.pyc 1970-01-01 00:00:00 +0000 differ |
846 | === modified file 'django_openid_auth/signals.py' |
847 | --- django_openid_auth/signals.py 2012-06-27 08:25:19 +0000 |
848 | +++ django_openid_auth/signals.py 2013-03-26 11:57:40 +0000 |
849 | @@ -1,7 +1,7 @@ |
850 | # django-openid-auth - OpenID integration for django.contrib.auth |
851 | # |
852 | # Copyright (C) 2007 Simon Willison |
853 | -# Copyright (C) 2008-2010 Canonical Ltd. |
854 | +# Copyright (C) 2008-2013 Canonical Ltd. |
855 | # |
856 | # Redistribution and use in source and binary forms, with or without |
857 | # modification, are permitted provided that the following conditions |
858 | |
859 | === removed file 'django_openid_auth/signals.pyc' |
860 | Binary files django_openid_auth/signals.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/signals.pyc 1970-01-01 00:00:00 +0000 differ |
861 | === modified file 'django_openid_auth/store.py' |
862 | --- django_openid_auth/store.py 2010-12-03 00:46:25 +0000 |
863 | +++ django_openid_auth/store.py 2013-03-26 11:57:40 +0000 |
864 | @@ -1,7 +1,7 @@ |
865 | # django-openid-auth - OpenID integration for django.contrib.auth |
866 | # |
867 | # Copyright (C) 2007 Simon Willison |
868 | -# Copyright (C) 2008-2010 Canonical Ltd. |
869 | +# Copyright (C) 2008-2013 Canonical Ltd. |
870 | # |
871 | # Redistribution and use in source and binary forms, with or without |
872 | # modification, are permitted provided that the following conditions |
873 | |
874 | === removed file 'django_openid_auth/store.pyc' |
875 | Binary files django_openid_auth/store.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/store.pyc 1970-01-01 00:00:00 +0000 differ |
876 | === modified file 'django_openid_auth/teams.py' |
877 | --- django_openid_auth/teams.py 2010-12-03 00:46:25 +0000 |
878 | +++ django_openid_auth/teams.py 2013-03-26 11:57:40 +0000 |
879 | @@ -1,6 +1,6 @@ |
880 | # Launchpad OpenID Teams Extension support for python-openid |
881 | # |
882 | -# Copyright (C) 2008-2010 Canonical Ltd. |
883 | +# Copyright (C) 2008-2013 Canonical Ltd. |
884 | # |
885 | # Redistribution and use in source and binary forms, with or without |
886 | # modification, are permitted provided that the following conditions |
887 | |
888 | === removed file 'django_openid_auth/teams.pyc' |
889 | Binary files django_openid_auth/teams.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/teams.pyc 1970-01-01 00:00:00 +0000 differ |
890 | === modified file 'django_openid_auth/tests/__init__.py' |
891 | --- django_openid_auth/tests/__init__.py 2012-06-27 08:25:19 +0000 |
892 | +++ django_openid_auth/tests/__init__.py 2013-03-26 11:57:40 +0000 |
893 | @@ -1,6 +1,6 @@ |
894 | # django-openid-auth - OpenID integration for django.contrib.auth |
895 | # |
896 | -# Copyright (C) 2009-2010 Canonical Ltd. |
897 | +# Copyright (C) 2009-2013 Canonical Ltd. |
898 | # |
899 | # Redistribution and use in source and binary forms, with or without |
900 | # modification, are permitted provided that the following conditions |
901 | @@ -30,11 +30,12 @@ |
902 | from test_views import * |
903 | from test_store import * |
904 | from test_auth import * |
905 | +from test_admin import * |
906 | |
907 | |
908 | def suite(): |
909 | suite = unittest.TestSuite() |
910 | - for name in ['test_auth', 'test_store', 'test_views']: |
911 | + for name in ['test_auth', 'test_store', 'test_views', 'test_admin']: |
912 | mod = __import__('%s.%s' % (__name__, name), {}, {}, ['suite']) |
913 | suite.addTest(mod.suite()) |
914 | return suite |
915 | |
916 | === removed file 'django_openid_auth/tests/__init__.pyc' |
917 | Binary files django_openid_auth/tests/__init__.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/tests/__init__.pyc 1970-01-01 00:00:00 +0000 differ |
918 | === added file 'django_openid_auth/tests/test_admin.py' |
919 | --- django_openid_auth/tests/test_admin.py 1970-01-01 00:00:00 +0000 |
920 | +++ django_openid_auth/tests/test_admin.py 2013-03-26 11:57:40 +0000 |
921 | @@ -0,0 +1,88 @@ |
922 | +# django-openid-auth - OpenID integration for django.contrib.auth |
923 | +# |
924 | +# Copyright (C) 2009-2013 Canonical Ltd. |
925 | +# |
926 | +# Redistribution and use in source and binary forms, with or without |
927 | +# modification, are permitted provided that the following conditions |
928 | +# are met: |
929 | +# |
930 | +# * Redistributions of source code must retain the above copyright |
931 | +# notice, this list of conditions and the following disclaimer. |
932 | +# |
933 | +# * Redistributions in binary form must reproduce the above copyright |
934 | +# notice, this list of conditions and the following disclaimer in the |
935 | +# documentation and/or other materials provided with the distribution. |
936 | +# |
937 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
938 | +# 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
939 | +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
940 | +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
941 | +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
942 | +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
943 | +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
944 | +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
945 | +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
946 | +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
947 | +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
948 | +# POSSIBILITY OF SUCH DAMAGE. |
949 | +""" |
950 | +Tests for the django_openid_auth Admin login form replacement. |
951 | +""" |
952 | + |
953 | +import os |
954 | +import unittest |
955 | + |
956 | +from django.conf import settings |
957 | +from django.contrib.auth.models import User, AnonymousUser |
958 | + |
959 | +settings.OPENID_USE_AS_ADMIN_LOGIN = True |
960 | +from django_openid_auth import admin |
961 | + |
962 | +from django.test import TestCase |
963 | + |
964 | + |
965 | +def create_user(is_staff=False, authenticated=True): |
966 | + """ |
967 | + Create and return a user, either the AnonymousUser or a normal Django user, |
968 | + setting the is_staff attribute if appropriate. |
969 | + """ |
970 | + if not authenticated: |
971 | + return AnonymousUser() |
972 | + else: |
973 | + user = User( |
974 | + username=u'testing', email='testing@example.com', |
975 | + is_staff=is_staff) |
976 | + user.set_password(u'test') |
977 | + user.save() |
978 | + |
979 | + |
980 | +class SiteAdminTests(TestCase): |
981 | + """ |
982 | + TestCase for accessing /admin/ when the django_openid_auth form replacement |
983 | + is in use. |
984 | + """ |
985 | + |
986 | + def test_admin_site_with_openid_login_authenticated_non_staff(self): |
987 | + """ |
988 | + If the request has an authenticated user, who is not flagged as a |
989 | + staff member, then they get a failure response. |
990 | + """ |
991 | + create_user() |
992 | + self.client.login(username='testing', password='test') |
993 | + response = self.client.get('/admin/') |
994 | + self.assertTrue('User testing does not have admin access.' in |
995 | + response.content, 'Missing error message in response') |
996 | + |
997 | + def test_admin_site_with_openid_login_non_authenticated_user(self): |
998 | + """ |
999 | + Unauthenticated users accessing the admin page should be directed to |
1000 | + the OpenID login url. |
1001 | + """ |
1002 | + response = self.client.get('/admin/') |
1003 | + self.assertEqual(302, response.status_code) |
1004 | + self.assertEqual('http://testserver/openid/login/?next=/admin/', |
1005 | + response['Location']) |
1006 | + |
1007 | + |
1008 | +def suite(): |
1009 | + return unittest.TestLoader().loadTestsFromName(__name__) |
1010 | |
1011 | === modified file 'django_openid_auth/tests/test_auth.py' |
1012 | --- django_openid_auth/tests/test_auth.py 2012-06-27 08:25:19 +0000 |
1013 | +++ django_openid_auth/tests/test_auth.py 2013-03-26 11:57:40 +0000 |
1014 | @@ -1,6 +1,6 @@ |
1015 | # django-openid-auth - OpenID integration for django.contrib.auth |
1016 | # |
1017 | -# Copyright (C) 2010 Canonical Ltd. |
1018 | +# Copyright (C) 2010-2013 Canonical Ltd. |
1019 | # |
1020 | # Redistribution and use in source and binary forms, with or without |
1021 | # modification, are permitted provided that the following conditions |
1022 | @@ -28,6 +28,7 @@ |
1023 | |
1024 | import unittest |
1025 | |
1026 | +from django.conf import settings |
1027 | from django.contrib.auth.models import User |
1028 | from django.test import TestCase |
1029 | |
1030 | @@ -45,6 +46,12 @@ |
1031 | def setUp(self): |
1032 | super(OpenIDBackendTests, self).setUp() |
1033 | self.backend = OpenIDBackend() |
1034 | + self.old_openid_use_email_for_username = getattr(settings, |
1035 | + 'OPENID_USE_EMAIL_FOR_USERNAME', False) |
1036 | + |
1037 | + def tearDown(self): |
1038 | + settings.OPENID_USE_EMAIL_FOR_USERNAME = \ |
1039 | + self.old_openid_use_email_for_username |
1040 | |
1041 | def test_extract_user_details_sreg(self): |
1042 | endpoint = OpenIDServiceEndpoint() |
1043 | @@ -150,6 +157,29 @@ |
1044 | self.assertEqual("Some", data['first_name']) |
1045 | self.assertEqual("User", data['last_name']) |
1046 | |
1047 | + def test_preferred_username_email_munging(self): |
1048 | + settings.OPENID_USE_EMAIL_FOR_USERNAME = True |
1049 | + for nick, email, expected in [ |
1050 | + ('nickcomesfirst', 'foo@example.com', 'nickcomesfirst'), |
1051 | + ('', 'foo@example.com', 'fooexamplecom'), |
1052 | + ('noemail', '', 'noemail'), |
1053 | + ('', '@%.-', 'openiduser'), |
1054 | + ('', '', 'openiduser'), |
1055 | + (None, None, 'openiduser')]: |
1056 | + self.assertEqual(expected, |
1057 | + self.backend._get_preferred_username(nick, email)) |
1058 | + |
1059 | + def test_preferred_username_no_email_munging(self): |
1060 | + for nick, email, expected in [ |
1061 | + ('nickcomesfirst', 'foo@example.com', 'nickcomesfirst'), |
1062 | + ('', 'foo@example.com', 'openiduser'), |
1063 | + ('noemail', '', 'noemail'), |
1064 | + ('', '@%.-', 'openiduser'), |
1065 | + ('', '', 'openiduser'), |
1066 | + (None, None, 'openiduser')]: |
1067 | + self.assertEqual(expected, |
1068 | + self.backend._get_preferred_username(nick, email)) |
1069 | + |
1070 | |
1071 | def suite(): |
1072 | return unittest.TestLoader().loadTestsFromName(__name__) |
1073 | |
1074 | === removed file 'django_openid_auth/tests/test_auth.pyc' |
1075 | Binary files django_openid_auth/tests/test_auth.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/tests/test_auth.pyc 1970-01-01 00:00:00 +0000 differ |
1076 | === modified file 'django_openid_auth/tests/test_store.py' |
1077 | --- django_openid_auth/tests/test_store.py 2010-12-03 00:46:25 +0000 |
1078 | +++ django_openid_auth/tests/test_store.py 2013-03-26 11:57:40 +0000 |
1079 | @@ -1,6 +1,6 @@ |
1080 | # django-openid-auth - OpenID integration for django.contrib.auth |
1081 | # |
1082 | -# Copyright (C) 2009-2010 Canonical Ltd. |
1083 | +# Copyright (C) 2009-2013 Canonical Ltd. |
1084 | # |
1085 | # Redistribution and use in source and binary forms, with or without |
1086 | # modification, are permitted provided that the following conditions |
1087 | |
1088 | === removed file 'django_openid_auth/tests/test_store.pyc' |
1089 | Binary files django_openid_auth/tests/test_store.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/tests/test_store.pyc 1970-01-01 00:00:00 +0000 differ |
1090 | === modified file 'django_openid_auth/tests/test_views.py' |
1091 | --- django_openid_auth/tests/test_views.py 2012-06-27 08:25:19 +0000 |
1092 | +++ django_openid_auth/tests/test_views.py 2013-03-26 11:57:40 +0000 |
1093 | @@ -1,6 +1,7 @@ |
1094 | +# -*- coding: utf-8 -*- |
1095 | # django-openid-auth - OpenID integration for django.contrib.auth |
1096 | # |
1097 | -# Copyright (C) 2009-2010 Canonical Ltd. |
1098 | +# Copyright (C) 2009-2013 Canonical Ltd. |
1099 | # |
1100 | # Redistribution and use in source and binary forms, with or without |
1101 | # modification, are permitted provided that the following conditions |
1102 | @@ -42,7 +43,7 @@ |
1103 | from openid.oidutil import importElementTree |
1104 | from openid.server.server import BROWSER_REQUEST_MODES, ENCODE_URL, Server |
1105 | from openid.store.memstore import MemoryStore |
1106 | -from openid.message import OPENID1_URL_LIMIT, IDENTIFIER_SELECT |
1107 | +from openid.message import IDENTIFIER_SELECT |
1108 | |
1109 | from django_openid_auth import teams |
1110 | from django_openid_auth.models import UserOpenID |
1111 | @@ -50,7 +51,6 @@ |
1112 | sanitise_redirect_url, |
1113 | make_consumer, |
1114 | ) |
1115 | -from django_openid_auth.auth import OpenIDBackend |
1116 | from django_openid_auth.signals import openid_login_complete |
1117 | from django_openid_auth.store import DjangoOpenIDStore |
1118 | from django_openid_auth.exceptions import ( |
1119 | @@ -169,9 +169,10 @@ |
1120 | self.req = DummyDjangoRequest('http://localhost/') |
1121 | self.endpoint = OpenIDServiceEndpoint() |
1122 | self.endpoint.claimed_id = 'http://example.com/identity' |
1123 | - self.endpoint.server_url = 'http://example.com/' |
1124 | + server_url = 'http://example.com/' |
1125 | + self.endpoint.server_url = server_url |
1126 | self.consumer = make_consumer(self.req) |
1127 | - self.server = Server(DjangoOpenIDStore()) |
1128 | + self.server = Server(DjangoOpenIDStore(), op_endpoint=server_url) |
1129 | setDefaultFetcher(self.provider, wrap_exceptions=False) |
1130 | |
1131 | self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/') |
1132 | @@ -185,6 +186,8 @@ |
1133 | self.old_physical_multifactor = getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False) |
1134 | self.old_login_render_failure = getattr(settings, 'OPENID_RENDER_FAILURE', None) |
1135 | self.old_consumer_complete = Consumer.complete |
1136 | + self.old_openid_use_email_for_username = getattr(settings, |
1137 | + 'OPENID_USE_EMAIL_FOR_USERNAME', False) |
1138 | |
1139 | self.old_required_fields = getattr( |
1140 | settings, 'OPENID_SREG_REQUIRED_FIELDS', []) |
1141 | @@ -198,6 +201,7 @@ |
1142 | settings.OPENID_FOLLOW_RENAMES = False |
1143 | settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False |
1144 | settings.OPENID_SREG_REQUIRED_FIELDS = [] |
1145 | + settings.OPENID_USE_EMAIL_FOR_USERNAME = False |
1146 | |
1147 | def tearDown(self): |
1148 | settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url |
1149 | @@ -212,6 +216,7 @@ |
1150 | settings.OPENID_RENDER_FAILURE = self.old_login_render_failure |
1151 | Consumer.complete = self.old_consumer_complete |
1152 | settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields |
1153 | + settings.OPENID_USE_EMAIL_FOR_USERNAME = self.old_openid_use_email_for_username |
1154 | |
1155 | setDefaultFetcher(None) |
1156 | super(RelyingPartyTests, self).tearDown() |
1157 | @@ -261,6 +266,13 @@ |
1158 | response = self.client.get('/getuser/') |
1159 | self.assertEquals(response.content, 'someuser') |
1160 | |
1161 | + def test_login_with_nonascii_return_to(self): |
1162 | + """Ensure non-ascii characters can be used for the 'next' arg.""" |
1163 | + response = self.client.post('/openid/login/', |
1164 | + {'openid_identifier': 'http://example.com/identity', |
1165 | + 'next': u'/files/ñandú.jpg'.encode('utf-8')}) |
1166 | + self.assertContains(response, 'OpenID transaction in progress') |
1167 | + |
1168 | def test_login_no_next(self): |
1169 | """Logins with no next parameter redirect to LOGIN_REDIRECT_URL.""" |
1170 | user = User.objects.create_user('someuser', 'someone@example.com') |
1171 | @@ -569,6 +581,112 @@ |
1172 | self.assertEquals(user.last_name, 'User') |
1173 | self.assertEquals(user.email, 'foo@example.com') |
1174 | |
1175 | + def test_login_without_nickname_with_email_suggestion(self): |
1176 | + settings.OPENID_CREATE_USERS = True |
1177 | + settings.OPENID_USE_EMAIL_FOR_USERNAME = True |
1178 | + |
1179 | + openid_req = {'openid_identifier': 'http://example.com/identity', |
1180 | + 'next': '/getuser/'} |
1181 | + openid_resp = {'nickname': '', 'fullname': 'Openid User', |
1182 | + 'email': 'foo@example.com'} |
1183 | + self._do_user_login(openid_req, openid_resp) |
1184 | + response = self.client.get('/getuser/') |
1185 | + |
1186 | + # username defaults to a munged version of the email |
1187 | + self.assertEquals(response.content, 'fooexamplecom') |
1188 | + |
1189 | + def test_login_duplicate_username_numbering(self): |
1190 | + settings.OPENID_FOLLOW_RENAMES = False |
1191 | + settings.OPENID_CREATE_USERS = True |
1192 | + settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1193 | + # Setup existing user who's name we're going to conflict with |
1194 | + user = User.objects.create_user('testuser', 'someone@example.com') |
1195 | + |
1196 | + # identity url is for 'renameuser' |
1197 | + openid_req = {'openid_identifier': 'http://example.com/identity', |
1198 | + 'next': '/getuser/'} |
1199 | + # but returned username is for 'testuser', which already exists for another identity |
1200 | + openid_resp = {'nickname': 'testuser', 'fullname': 'Test User', |
1201 | + 'email': 'test@example.com'} |
1202 | + self._do_user_login(openid_req, openid_resp) |
1203 | + response = self.client.get('/getuser/') |
1204 | + |
1205 | + # Since this username is already taken by someone else, we go through |
1206 | + # the process of adding +i to it, and get testuser2. |
1207 | + self.assertEquals(response.content, 'testuser2') |
1208 | + |
1209 | + def test_login_duplicate_username_numbering_with_conflicts(self): |
1210 | + settings.OPENID_FOLLOW_RENAMES = False |
1211 | + settings.OPENID_CREATE_USERS = True |
1212 | + settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1213 | + # Setup existing user who's name we're going to conflict with |
1214 | + user = User.objects.create_user('testuser', 'someone@example.com') |
1215 | + user = User.objects.create_user('testuser3', 'someone@example.com') |
1216 | + |
1217 | + # identity url is for 'renameuser' |
1218 | + openid_req = {'openid_identifier': 'http://example.com/identity', |
1219 | + 'next': '/getuser/'} |
1220 | + # but returned username is for 'testuser', which already exists for another identity |
1221 | + openid_resp = {'nickname': 'testuser', 'fullname': 'Test User', |
1222 | + 'email': 'test@example.com'} |
1223 | + self._do_user_login(openid_req, openid_resp) |
1224 | + response = self.client.get('/getuser/') |
1225 | + |
1226 | + # Since this username is already taken by someone else, we go through |
1227 | + # the process of adding +i to it starting with the count of users with |
1228 | + # username starting with 'testuser', of which there are 2. i should |
1229 | + # start at 3, which already exists, so it should skip to 4. |
1230 | + self.assertEquals(response.content, 'testuser4') |
1231 | + |
1232 | + def test_login_duplicate_username_numbering_with_holes(self): |
1233 | + settings.OPENID_FOLLOW_RENAMES = False |
1234 | + settings.OPENID_CREATE_USERS = True |
1235 | + settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1236 | + # Setup existing user who's name we're going to conflict with |
1237 | + user = User.objects.create_user('testuser', 'someone@example.com') |
1238 | + user = User.objects.create_user('testuser1', 'someone@example.com') |
1239 | + user = User.objects.create_user('testuser6', 'someone@example.com') |
1240 | + user = User.objects.create_user('testuser7', 'someone@example.com') |
1241 | + user = User.objects.create_user('testuser8', 'someone@example.com') |
1242 | + |
1243 | + # identity url is for 'renameuser' |
1244 | + openid_req = {'openid_identifier': 'http://example.com/identity', |
1245 | + 'next': '/getuser/'} |
1246 | + # but returned username is for 'testuser', which already exists for another identity |
1247 | + openid_resp = {'nickname': 'testuser', 'fullname': 'Test User', |
1248 | + 'email': 'test@example.com'} |
1249 | + self._do_user_login(openid_req, openid_resp) |
1250 | + response = self.client.get('/getuser/') |
1251 | + |
1252 | + # Since this username is already taken by someone else, we go through |
1253 | + # the process of adding +i to it starting with the count of users with |
1254 | + # username starting with 'testuser', of which there are 5. i should |
1255 | + # start at 6, and increment until it reaches 9. |
1256 | + self.assertEquals(response.content, 'testuser9') |
1257 | + |
1258 | + def test_login_duplicate_username_numbering_with_nonsequential_matches(self): |
1259 | + settings.OPENID_FOLLOW_RENAMES = False |
1260 | + settings.OPENID_CREATE_USERS = True |
1261 | + settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1262 | + # Setup existing user who's name we're going to conflict with |
1263 | + user = User.objects.create_user('testuser', 'someone@example.com') |
1264 | + user = User.objects.create_user('testuserfoo', 'someone@example.com') |
1265 | + |
1266 | + # identity url is for 'renameuser' |
1267 | + openid_req = {'openid_identifier': 'http://example.com/identity', |
1268 | + 'next': '/getuser/'} |
1269 | + # but returned username is for 'testuser', which already exists for another identity |
1270 | + openid_resp = {'nickname': 'testuser', 'fullname': 'Test User', |
1271 | + 'email': 'test@example.com'} |
1272 | + self._do_user_login(openid_req, openid_resp) |
1273 | + response = self.client.get('/getuser/') |
1274 | + |
1275 | + # Since this username is already taken by someone else, we go through |
1276 | + # the process of adding +i to it starting with the count of users with |
1277 | + # username starting with 'testuser', of which there are 2. i should |
1278 | + # start at 3, which will be available. |
1279 | + self.assertEquals(response.content, 'testuser3') |
1280 | + |
1281 | def test_login_follow_rename(self): |
1282 | settings.OPENID_FOLLOW_RENAMES = True |
1283 | settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1284 | |
1285 | === removed file 'django_openid_auth/tests/test_views.py.~1~' |
1286 | --- django_openid_auth/tests/test_views.py.~1~ 2012-06-27 08:25:19 +0000 |
1287 | +++ django_openid_auth/tests/test_views.py.~1~ 1970-01-01 00:00:00 +0000 |
1288 | @@ -1,1258 +0,0 @@ |
1289 | -# django-openid-auth - OpenID integration for django.contrib.auth |
1290 | -# |
1291 | -# Copyright (C) 2009-2010 Canonical Ltd. |
1292 | -# |
1293 | -# Redistribution and use in source and binary forms, with or without |
1294 | -# modification, are permitted provided that the following conditions |
1295 | -# are met: |
1296 | -# |
1297 | -# * Redistributions of source code must retain the above copyright |
1298 | -# notice, this list of conditions and the following disclaimer. |
1299 | -# |
1300 | -# * Redistributions in binary form must reproduce the above copyright |
1301 | -# notice, this list of conditions and the following disclaimer in the |
1302 | -# documentation and/or other materials provided with the distribution. |
1303 | -# |
1304 | -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
1305 | -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
1306 | -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
1307 | -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
1308 | -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
1309 | -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
1310 | -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
1311 | -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
1312 | -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
1313 | -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
1314 | -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
1315 | -# POSSIBILITY OF SUCH DAMAGE. |
1316 | - |
1317 | -import cgi |
1318 | -import unittest |
1319 | -from urllib import quote_plus |
1320 | - |
1321 | -from django.conf import settings |
1322 | -from django.contrib.auth.models import User, Group |
1323 | -from django.http import HttpRequest, HttpResponse |
1324 | -from django.test import TestCase |
1325 | -from openid.consumer.consumer import Consumer, SuccessResponse |
1326 | -from openid.consumer.discover import OpenIDServiceEndpoint |
1327 | -from openid.extensions import ax, sreg, pape |
1328 | -from openid.fetchers import ( |
1329 | - HTTPFetcher, HTTPFetchingError, HTTPResponse, setDefaultFetcher) |
1330 | -from openid.oidutil import importElementTree |
1331 | -from openid.server.server import BROWSER_REQUEST_MODES, ENCODE_URL, Server |
1332 | -from openid.store.memstore import MemoryStore |
1333 | -from openid.message import OPENID1_URL_LIMIT, IDENTIFIER_SELECT |
1334 | - |
1335 | -from django_openid_auth import teams |
1336 | -from django_openid_auth.models import UserOpenID |
1337 | -from django_openid_auth.views import ( |
1338 | - sanitise_redirect_url, |
1339 | - make_consumer, |
1340 | -) |
1341 | -from django_openid_auth.auth import OpenIDBackend |
1342 | -from django_openid_auth.signals import openid_login_complete |
1343 | -from django_openid_auth.store import DjangoOpenIDStore |
1344 | -from django_openid_auth.exceptions import ( |
1345 | - MissingUsernameViolation, |
1346 | - DuplicateUsernameViolation, |
1347 | - MissingPhysicalMultiFactor, |
1348 | - RequiredAttributeNotReturned, |
1349 | -) |
1350 | - |
1351 | -ET = importElementTree() |
1352 | - |
1353 | -class StubOpenIDProvider(HTTPFetcher): |
1354 | - |
1355 | - def __init__(self, base_url): |
1356 | - self.store = MemoryStore() |
1357 | - self.identity_url = base_url + 'identity' |
1358 | - self.localid_url = base_url + 'localid' |
1359 | - self.endpoint_url = base_url + 'endpoint' |
1360 | - self.server = Server(self.store, self.endpoint_url) |
1361 | - self.last_request = None |
1362 | - self.type_uris = ['http://specs.openid.net/auth/2.0/signon'] |
1363 | - |
1364 | - def fetch(self, url, body=None, headers=None): |
1365 | - if url == self.identity_url: |
1366 | - # Serve an XRDS document directly, pointing at our endpoint. |
1367 | - type_uris = ['<Type>%s</Type>' % uri for uri in self.type_uris] |
1368 | - return HTTPResponse( |
1369 | - url, 200, {'content-type': 'application/xrds+xml'}, """\ |
1370 | -<?xml version="1.0"?> |
1371 | -<xrds:XRDS |
1372 | - xmlns="xri://$xrd*($v*2.0)" |
1373 | - xmlns:xrds="xri://$xrds"> |
1374 | - <XRD> |
1375 | - <Service priority="0"> |
1376 | - %s |
1377 | - <URI>%s</URI> |
1378 | - <LocalID>%s</LocalID> |
1379 | - </Service> |
1380 | - </XRD> |
1381 | -</xrds:XRDS> |
1382 | -""" % ('\n'.join(type_uris), self.endpoint_url, self.localid_url)) |
1383 | - elif url.startswith(self.endpoint_url): |
1384 | - # Gather query parameters |
1385 | - query = {} |
1386 | - if '?' in url: |
1387 | - query.update(cgi.parse_qsl(url.split('?', 1)[1])) |
1388 | - if body is not None: |
1389 | - query.update(cgi.parse_qsl(body)) |
1390 | - self.last_request = self.server.decodeRequest(query) |
1391 | - |
1392 | - # The browser based requests should not be handled through |
1393 | - # the fetcher interface. |
1394 | - assert self.last_request.mode not in BROWSER_REQUEST_MODES |
1395 | - |
1396 | - response = self.server.handleRequest(self.last_request) |
1397 | - webresponse = self.server.encodeResponse(response) |
1398 | - return HTTPResponse(url, webresponse.code, webresponse.headers, |
1399 | - webresponse.body) |
1400 | - else: |
1401 | - raise HTTPFetchingError('unknown URL %s' % url) |
1402 | - |
1403 | - def parseFormPost(self, content): |
1404 | - """Parse an HTML form post to create an OpenID request.""" |
1405 | - # Hack to make the javascript XML compliant ... |
1406 | - content = content.replace('i < elements.length', |
1407 | - 'i < elements.length') |
1408 | - tree = ET.XML(content) |
1409 | - form = tree.find('.//form') |
1410 | - assert form is not None, 'No form in document' |
1411 | - assert form.get('action') == self.endpoint_url, ( |
1412 | - 'Form posts to %s instead of %s' % (form.get('action'), |
1413 | - self.endpoint_url)) |
1414 | - query = {} |
1415 | - for input in form.findall('input'): |
1416 | - if input.get('type') != 'hidden': |
1417 | - continue |
1418 | - query[input.get('name').encode('UTF-8')] = \ |
1419 | - input.get('value').encode('UTF-8') |
1420 | - self.last_request = self.server.decodeRequest(query) |
1421 | - return self.last_request |
1422 | - |
1423 | - |
1424 | -class DummyDjangoRequest(object): |
1425 | - def __init__(self, request_path): |
1426 | - self.request_path = request_path |
1427 | - self.META = { |
1428 | - 'HTTP_HOST': "localhost", |
1429 | - 'SCRIPT_NAME': "http://localhost", |
1430 | - 'SERVER_PROTOCOL': "http", |
1431 | - } |
1432 | - self.POST = { |
1433 | - 'openid_identifier': "http://example.com/identity", |
1434 | - } |
1435 | - self.GET = {} |
1436 | - self.session = {} |
1437 | - |
1438 | - def get_full_path(self): |
1439 | - return self.META['SCRIPT_NAME'] + self.request_path |
1440 | - |
1441 | - def build_absolute_uri(self): |
1442 | - return self.META['SCRIPT_NAME'] + self.request_path |
1443 | - |
1444 | - def _combined_request(self): |
1445 | - request = {} |
1446 | - request.update(self.POST) |
1447 | - request.update(self.GET) |
1448 | - return request |
1449 | - REQUEST = property(_combined_request) |
1450 | - |
1451 | -class RelyingPartyTests(TestCase): |
1452 | - urls = 'django_openid_auth.tests.urls' |
1453 | - |
1454 | - def setUp(self): |
1455 | - super(RelyingPartyTests, self).setUp() |
1456 | - self.provider = StubOpenIDProvider('http://example.com/') |
1457 | - self.req = DummyDjangoRequest('http://localhost/') |
1458 | - self.endpoint = OpenIDServiceEndpoint() |
1459 | - self.endpoint.claimed_id = 'http://example.com/identity' |
1460 | - self.endpoint.server_url = 'http://example.com/' |
1461 | - self.consumer = make_consumer(self.req) |
1462 | - self.server = Server(DjangoOpenIDStore()) |
1463 | - setDefaultFetcher(self.provider, wrap_exceptions=False) |
1464 | - |
1465 | - self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/') |
1466 | - self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False) |
1467 | - self.old_strict_usernames = getattr(settings, 'OPENID_STRICT_USERNAMES', False) |
1468 | - self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False) |
1469 | - self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None) |
1470 | - self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) |
1471 | - self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False) |
1472 | - self.old_follow_renames = getattr(settings, 'OPENID_FOLLOW_RENAMES', False) |
1473 | - self.old_physical_multifactor = getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False) |
1474 | - self.old_login_render_failure = getattr(settings, 'OPENID_RENDER_FAILURE', None) |
1475 | - self.old_consumer_complete = Consumer.complete |
1476 | - |
1477 | - self.old_required_fields = getattr( |
1478 | - settings, 'OPENID_SREG_REQUIRED_FIELDS', []) |
1479 | - |
1480 | - settings.OPENID_CREATE_USERS = False |
1481 | - settings.OPENID_STRICT_USERNAMES = False |
1482 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = False |
1483 | - settings.OPENID_SSO_SERVER_URL = None |
1484 | - settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {} |
1485 | - settings.OPENID_USE_AS_ADMIN_LOGIN = False |
1486 | - settings.OPENID_FOLLOW_RENAMES = False |
1487 | - settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False |
1488 | - settings.OPENID_SREG_REQUIRED_FIELDS = [] |
1489 | - |
1490 | - def tearDown(self): |
1491 | - settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url |
1492 | - settings.OPENID_CREATE_USERS = self.old_create_users |
1493 | - settings.OPENID_STRICT_USERNAMES = self.old_strict_usernames |
1494 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = self.old_update_details |
1495 | - settings.OPENID_SSO_SERVER_URL = self.old_sso_server_url |
1496 | - settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map |
1497 | - settings.OPENID_USE_AS_ADMIN_LOGIN = self.old_use_as_admin_login |
1498 | - settings.OPENID_FOLLOW_RENAMES = self.old_follow_renames |
1499 | - settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = self.old_physical_multifactor |
1500 | - settings.OPENID_RENDER_FAILURE = self.old_login_render_failure |
1501 | - Consumer.complete = self.old_consumer_complete |
1502 | - settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields |
1503 | - |
1504 | - setDefaultFetcher(None) |
1505 | - super(RelyingPartyTests, self).tearDown() |
1506 | - |
1507 | - def complete(self, openid_response): |
1508 | - """Complete an OpenID authentication request.""" |
1509 | - # The server can generate either a redirect or a form post |
1510 | - # here. For simplicity, force generation of a redirect. |
1511 | - openid_response.whichEncoding = lambda: ENCODE_URL |
1512 | - webresponse = self.provider.server.encodeResponse(openid_response) |
1513 | - self.assertEquals(webresponse.code, 302) |
1514 | - redirect_to = webresponse.headers['location'] |
1515 | - self.assertTrue(redirect_to.startswith( |
1516 | - 'http://testserver/openid/complete/')) |
1517 | - return self.client.get('/openid/complete/', |
1518 | - dict(cgi.parse_qsl(redirect_to.split('?', 1)[1]))) |
1519 | - |
1520 | - def test_login(self): |
1521 | - user = User.objects.create_user('someuser', 'someone@example.com') |
1522 | - useropenid = UserOpenID( |
1523 | - user=user, |
1524 | - claimed_id='http://example.com/identity', |
1525 | - display_id='http://example.com/identity') |
1526 | - useropenid.save() |
1527 | - |
1528 | - # The login form is displayed: |
1529 | - response = self.client.get('/openid/login/') |
1530 | - self.assertTemplateUsed(response, 'openid/login.html') |
1531 | - |
1532 | - # Posting in an identity URL begins the authentication request: |
1533 | - response = self.client.post('/openid/login/', |
1534 | - {'openid_identifier': 'http://example.com/identity', |
1535 | - 'next': '/getuser/'}) |
1536 | - self.assertContains(response, 'OpenID transaction in progress') |
1537 | - |
1538 | - openid_request = self.provider.parseFormPost(response.content) |
1539 | - self.assertEquals(openid_request.mode, 'checkid_setup') |
1540 | - self.assertTrue(openid_request.return_to.startswith( |
1541 | - 'http://testserver/openid/complete/')) |
1542 | - |
1543 | - # Complete the request. The user is redirected to the next URL. |
1544 | - openid_response = openid_request.answer(True) |
1545 | - response = self.complete(openid_response) |
1546 | - self.assertRedirects(response, 'http://testserver/getuser/') |
1547 | - |
1548 | - # And they are now logged in: |
1549 | - response = self.client.get('/getuser/') |
1550 | - self.assertEquals(response.content, 'someuser') |
1551 | - |
1552 | - def test_login_no_next(self): |
1553 | - """Logins with no next parameter redirect to LOGIN_REDIRECT_URL.""" |
1554 | - user = User.objects.create_user('someuser', 'someone@example.com') |
1555 | - useropenid = UserOpenID( |
1556 | - user=user, |
1557 | - claimed_id='http://example.com/identity', |
1558 | - display_id='http://example.com/identity') |
1559 | - useropenid.save() |
1560 | - |
1561 | - settings.LOGIN_REDIRECT_URL = '/getuser/' |
1562 | - response = self.client.post('/openid/login/', |
1563 | - {'openid_identifier': 'http://example.com/identity'}) |
1564 | - self.assertContains(response, 'OpenID transaction in progress') |
1565 | - |
1566 | - openid_request = self.provider.parseFormPost(response.content) |
1567 | - self.assertEquals(openid_request.mode, 'checkid_setup') |
1568 | - self.assertTrue(openid_request.return_to.startswith( |
1569 | - 'http://testserver/openid/complete/')) |
1570 | - |
1571 | - # Complete the request. The user is redirected to the next URL. |
1572 | - openid_response = openid_request.answer(True) |
1573 | - response = self.complete(openid_response) |
1574 | - self.assertRedirects( |
1575 | - response, 'http://testserver' + settings.LOGIN_REDIRECT_URL) |
1576 | - |
1577 | - def test_login_sso(self): |
1578 | - settings.OPENID_SSO_SERVER_URL = 'http://example.com/identity' |
1579 | - user = User.objects.create_user('someuser', 'someone@example.com') |
1580 | - useropenid = UserOpenID( |
1581 | - user=user, |
1582 | - claimed_id='http://example.com/identity', |
1583 | - display_id='http://example.com/identity') |
1584 | - useropenid.save() |
1585 | - |
1586 | - # Requesting the login form immediately begins an |
1587 | - # authentication request. |
1588 | - response = self.client.get('/openid/login/', {'next': '/getuser/'}) |
1589 | - self.assertEquals(response.status_code, 200) |
1590 | - self.assertContains(response, 'OpenID transaction in progress') |
1591 | - |
1592 | - openid_request = self.provider.parseFormPost(response.content) |
1593 | - self.assertEquals(openid_request.mode, 'checkid_setup') |
1594 | - self.assertTrue(openid_request.return_to.startswith( |
1595 | - 'http://testserver/openid/complete/')) |
1596 | - |
1597 | - # Complete the request. The user is redirected to the next URL. |
1598 | - openid_response = openid_request.answer(True) |
1599 | - response = self.complete(openid_response) |
1600 | - self.assertRedirects(response, 'http://testserver/getuser/') |
1601 | - |
1602 | - # And they are now logged in: |
1603 | - response = self.client.get('/getuser/') |
1604 | - self.assertEquals(response.content, 'someuser') |
1605 | - |
1606 | - def test_login_create_users(self): |
1607 | - settings.OPENID_CREATE_USERS = True |
1608 | - # Create a user with the same name as we'll pass back via sreg. |
1609 | - User.objects.create_user('someuser', 'someone@example.com') |
1610 | - |
1611 | - # Posting in an identity URL begins the authentication request: |
1612 | - response = self.client.post('/openid/login/', |
1613 | - {'openid_identifier': 'http://example.com/identity', |
1614 | - 'next': '/getuser/'}) |
1615 | - self.assertContains(response, 'OpenID transaction in progress') |
1616 | - |
1617 | - # Complete the request, passing back some simple registration |
1618 | - # data. The user is redirected to the next URL. |
1619 | - openid_request = self.provider.parseFormPost(response.content) |
1620 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
1621 | - openid_response = openid_request.answer(True) |
1622 | - sreg_response = sreg.SRegResponse.extractResponse( |
1623 | - sreg_request, {'nickname': 'someuser', 'fullname': 'Some User', |
1624 | - 'email': 'foo@example.com'}) |
1625 | - openid_response.addExtension(sreg_response) |
1626 | - response = self.complete(openid_response) |
1627 | - self.assertRedirects(response, 'http://testserver/getuser/') |
1628 | - |
1629 | - # And they are now logged in as a new user (they haven't taken |
1630 | - # over the existing "someuser" user). |
1631 | - response = self.client.get('/getuser/') |
1632 | - self.assertEquals(response.content, 'someuser2') |
1633 | - |
1634 | - # Check the details of the new user. |
1635 | - user = User.objects.get(username='someuser2') |
1636 | - self.assertEquals(user.first_name, 'Some') |
1637 | - self.assertEquals(user.last_name, 'User') |
1638 | - self.assertEquals(user.email, 'foo@example.com') |
1639 | - |
1640 | - def _do_user_login(self, req_data, resp_data, use_sreg=True, use_pape=None): |
1641 | - openid_request = self._get_login_request(req_data) |
1642 | - openid_response = self._get_login_response(openid_request, resp_data, use_sreg, use_pape) |
1643 | - response = self.complete(openid_response) |
1644 | - self.assertRedirects(response, 'http://testserver/getuser/') |
1645 | - return response |
1646 | - |
1647 | - def _get_login_request(self, req_data): |
1648 | - # Posting in an identity URL begins the authentication request: |
1649 | - response = self.client.post('/openid/login/', req_data) |
1650 | - self.assertContains(response, 'OpenID transaction in progress') |
1651 | - |
1652 | - # Complete the request, passing back some simple registration |
1653 | - # data. The user is redirected to the next URL. |
1654 | - openid_request = self.provider.parseFormPost(response.content) |
1655 | - return openid_request |
1656 | - |
1657 | - def _get_login_response(self, openid_request, resp_data, use_sreg, use_pape): |
1658 | - openid_response = openid_request.answer(True) |
1659 | - |
1660 | - if use_sreg: |
1661 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
1662 | - sreg_response = sreg.SRegResponse.extractResponse( |
1663 | - sreg_request, resp_data) |
1664 | - openid_response.addExtension(sreg_response) |
1665 | - if use_pape is not None: |
1666 | - policies = [ |
1667 | - use_pape |
1668 | - ] |
1669 | - pape_response = pape.Response(auth_policies=policies) |
1670 | - openid_response.addExtension(pape_response) |
1671 | - return openid_response |
1672 | - |
1673 | - def parse_query_string(self, query_str): |
1674 | - query_items = map(tuple, |
1675 | - [item.split('=') for item in query_str.split('&')]) |
1676 | - query = dict(query_items) |
1677 | - return query |
1678 | - |
1679 | - def test_login_physical_multifactor_request(self): |
1680 | - settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True |
1681 | - preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL |
1682 | - self.provider.type_uris.append(pape.ns_uri) |
1683 | - |
1684 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1685 | - 'next': '/getuser/'} |
1686 | - response = self.client.post('/openid/login/', openid_req) |
1687 | - openid_request = self.provider.parseFormPost(response.content) |
1688 | - |
1689 | - request_auth = openid_request.message.getArg( |
1690 | - 'http://specs.openid.net/extensions/pape/1.0', |
1691 | - 'preferred_auth_policies', |
1692 | - ) |
1693 | - self.assertEqual(request_auth, preferred_auth) |
1694 | - |
1695 | - def test_login_physical_multifactor_response(self): |
1696 | - settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True |
1697 | - preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL |
1698 | - self.provider.type_uris.append(pape.ns_uri) |
1699 | - |
1700 | - def mock_complete(this, request_args, return_to): |
1701 | - request = {'openid.mode': 'checkid_setup', |
1702 | - 'openid.trust_root': 'http://localhost/', |
1703 | - 'openid.return_to': 'http://localhost/', |
1704 | - 'openid.identity': IDENTIFIER_SELECT, |
1705 | - 'openid.ns.pape' : pape.ns_uri, |
1706 | - 'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE), |
1707 | - } |
1708 | - openid_server = self.provider.server |
1709 | - orequest = openid_server.decodeRequest(request) |
1710 | - response = SuccessResponse( |
1711 | - self.endpoint, orequest.message, |
1712 | - signed_fields=['openid.pape.auth_policies',]) |
1713 | - return response |
1714 | - Consumer.complete = mock_complete |
1715 | - |
1716 | - user = User.objects.create_user('testuser', 'test@example.com') |
1717 | - useropenid = UserOpenID( |
1718 | - user=user, |
1719 | - claimed_id='http://example.com/identity', |
1720 | - display_id='http://example.com/identity') |
1721 | - useropenid.save() |
1722 | - |
1723 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1724 | - 'next': '/getuser/'} |
1725 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User', |
1726 | - 'email': 'test@example.com'} |
1727 | - |
1728 | - response = self._do_user_login(openid_req, openid_resp, use_pape=pape.AUTH_MULTI_FACTOR_PHYSICAL) |
1729 | - |
1730 | - query = self.parse_query_string(response.request['QUERY_STRING']) |
1731 | - self.assertTrue('openid.pape.auth_policies' in query) |
1732 | - self.assertEqual(query['openid.pape.auth_policies'], |
1733 | - quote_plus(preferred_auth)) |
1734 | - |
1735 | - response = self.client.get('/getuser/') |
1736 | - self.assertEqual(response.content, 'testuser') |
1737 | - |
1738 | - |
1739 | - def test_login_physical_multifactor_not_provided(self): |
1740 | - settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True |
1741 | - preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL |
1742 | - self.provider.type_uris.append(pape.ns_uri) |
1743 | - |
1744 | - def mock_complete(this, request_args, return_to): |
1745 | - request = {'openid.mode': 'checkid_setup', |
1746 | - 'openid.trust_root': 'http://localhost/', |
1747 | - 'openid.return_to': 'http://localhost/', |
1748 | - 'openid.identity': IDENTIFIER_SELECT, |
1749 | - 'openid.ns.pape' : pape.ns_uri, |
1750 | - 'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE), |
1751 | - } |
1752 | - openid_server = self.provider.server |
1753 | - orequest = openid_server.decodeRequest(request) |
1754 | - response = SuccessResponse( |
1755 | - self.endpoint, orequest.message, |
1756 | - signed_fields=['openid.pape.auth_policies',]) |
1757 | - return response |
1758 | - Consumer.complete = mock_complete |
1759 | - |
1760 | - user = User.objects.create_user('testuser', 'test@example.com') |
1761 | - useropenid = UserOpenID( |
1762 | - user=user, |
1763 | - claimed_id='http://example.com/identity', |
1764 | - display_id='http://example.com/identity') |
1765 | - useropenid.save() |
1766 | - |
1767 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1768 | - 'next': '/getuser/'} |
1769 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User', |
1770 | - 'email': 'test@example.com'} |
1771 | - |
1772 | - openid_request = self._get_login_request(openid_req) |
1773 | - openid_response = self._get_login_response(openid_request, openid_req, openid_resp, use_pape=pape.AUTH_NONE) |
1774 | - |
1775 | - response_auth = openid_request.message.getArg( |
1776 | - 'http://specs.openid.net/extensions/pape/1.0', |
1777 | - 'auth_policies', |
1778 | - ) |
1779 | - self.assertNotEqual(response_auth, preferred_auth) |
1780 | - |
1781 | - response = self.complete(openid_response) |
1782 | - self.assertEquals(403, response.status_code) |
1783 | - self.assertContains(response, '<h1>OpenID failed</h1>', status_code=403) |
1784 | - self.assertContains(response, '<p>Login requires physical multi-factor authentication.</p>', status_code=403) |
1785 | - |
1786 | - def test_login_physical_multifactor_not_provided_override(self): |
1787 | - settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True |
1788 | - preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL |
1789 | - self.provider.type_uris.append(pape.ns_uri) |
1790 | - |
1791 | - # Override the login_failure handler |
1792 | - def mock_login_failure_handler(request, message, status=403, |
1793 | - template_name=None, |
1794 | - exception=None): |
1795 | - self.assertTrue(isinstance(exception, MissingPhysicalMultiFactor)) |
1796 | - return HttpResponse('Test Failure Override', status=200) |
1797 | - settings.OPENID_RENDER_FAILURE = mock_login_failure_handler |
1798 | - |
1799 | - def mock_complete(this, request_args, return_to): |
1800 | - request = {'openid.mode': 'checkid_setup', |
1801 | - 'openid.trust_root': 'http://localhost/', |
1802 | - 'openid.return_to': 'http://localhost/', |
1803 | - 'openid.identity': IDENTIFIER_SELECT, |
1804 | - 'openid.ns.pape' : pape.ns_uri, |
1805 | - 'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE), |
1806 | - } |
1807 | - openid_server = self.provider.server |
1808 | - orequest = openid_server.decodeRequest(request) |
1809 | - response = SuccessResponse( |
1810 | - self.endpoint, orequest.message, |
1811 | - signed_fields=['openid.pape.auth_policies',]) |
1812 | - return response |
1813 | - Consumer.complete = mock_complete |
1814 | - |
1815 | - user = User.objects.create_user('testuser', 'test@example.com') |
1816 | - useropenid = UserOpenID( |
1817 | - user=user, |
1818 | - claimed_id='http://example.com/identity', |
1819 | - display_id='http://example.com/identity') |
1820 | - useropenid.save() |
1821 | - |
1822 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1823 | - 'next': '/getuser/'} |
1824 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User', |
1825 | - 'email': 'test@example.com'} |
1826 | - |
1827 | - openid_request = self._get_login_request(openid_req) |
1828 | - openid_response = self._get_login_response(openid_request, openid_req, openid_resp, use_pape=pape.AUTH_NONE) |
1829 | - |
1830 | - response_auth = openid_request.message.getArg( |
1831 | - 'http://specs.openid.net/extensions/pape/1.0', |
1832 | - 'auth_policies', |
1833 | - ) |
1834 | - self.assertNotEqual(response_auth, preferred_auth) |
1835 | - |
1836 | - # Status code should be 200, since we over-rode the login_failure handler |
1837 | - response = self.complete(openid_response) |
1838 | - self.assertEquals(200, response.status_code) |
1839 | - self.assertContains(response, 'Test Failure Override') |
1840 | - |
1841 | - def test_login_without_nickname(self): |
1842 | - settings.OPENID_CREATE_USERS = True |
1843 | - |
1844 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1845 | - 'next': '/getuser/'} |
1846 | - openid_resp = {'nickname': '', 'fullname': 'Openid User', |
1847 | - 'email': 'foo@example.com'} |
1848 | - self._do_user_login(openid_req, openid_resp) |
1849 | - response = self.client.get('/getuser/') |
1850 | - |
1851 | - # username defaults to 'openiduser' |
1852 | - self.assertEquals(response.content, 'openiduser') |
1853 | - |
1854 | - # The user's full name and email have been updated. |
1855 | - user = User.objects.get(username=response.content) |
1856 | - self.assertEquals(user.first_name, 'Openid') |
1857 | - self.assertEquals(user.last_name, 'User') |
1858 | - self.assertEquals(user.email, 'foo@example.com') |
1859 | - |
1860 | - def test_login_follow_rename(self): |
1861 | - settings.OPENID_FOLLOW_RENAMES = True |
1862 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1863 | - user = User.objects.create_user('testuser', 'someone@example.com') |
1864 | - useropenid = UserOpenID( |
1865 | - user=user, |
1866 | - claimed_id='http://example.com/identity', |
1867 | - display_id='http://example.com/identity') |
1868 | - useropenid.save() |
1869 | - |
1870 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1871 | - 'next': '/getuser/'} |
1872 | - openid_resp = {'nickname': 'someuser', 'fullname': 'Some User', |
1873 | - 'email': 'foo@example.com'} |
1874 | - self._do_user_login(openid_req, openid_resp) |
1875 | - response = self.client.get('/getuser/') |
1876 | - |
1877 | - # If OPENID_FOLLOW_RENAMES, they are logged in as |
1878 | - # someuser (the passed in nickname has changed the username) |
1879 | - self.assertEquals(response.content, 'someuser') |
1880 | - |
1881 | - # The user's full name and email have been updated. |
1882 | - user = User.objects.get(username=response.content) |
1883 | - self.assertEquals(user.first_name, 'Some') |
1884 | - self.assertEquals(user.last_name, 'User') |
1885 | - self.assertEquals(user.email, 'foo@example.com') |
1886 | - |
1887 | - def test_login_follow_rename_without_nickname_change(self): |
1888 | - settings.OPENID_FOLLOW_RENAMES = True |
1889 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1890 | - settings.OPENID_STRICT_USERNAMES = True |
1891 | - user = User.objects.create_user('testuser', 'someone@example.com') |
1892 | - useropenid = UserOpenID( |
1893 | - user=user, |
1894 | - claimed_id='http://example.com/identity', |
1895 | - display_id='http://example.com/identity') |
1896 | - useropenid.save() |
1897 | - |
1898 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1899 | - 'next': '/getuser/'} |
1900 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Some User', |
1901 | - 'email': 'foo@example.com'} |
1902 | - self._do_user_login(openid_req, openid_resp) |
1903 | - response = self.client.get('/getuser/') |
1904 | - |
1905 | - # Username should not have changed |
1906 | - self.assertEquals(response.content, 'testuser') |
1907 | - |
1908 | - # The user's full name and email have been updated. |
1909 | - user = User.objects.get(username=response.content) |
1910 | - self.assertEquals(user.first_name, 'Some') |
1911 | - self.assertEquals(user.last_name, 'User') |
1912 | - self.assertEquals(user.email, 'foo@example.com') |
1913 | - |
1914 | - def test_login_follow_rename_conflict(self): |
1915 | - settings.OPENID_FOLLOW_RENAMES = True |
1916 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1917 | - # Setup existing user who's name we're going to switch to |
1918 | - user = User.objects.create_user('testuser', 'someone@example.com') |
1919 | - UserOpenID.objects.get_or_create( |
1920 | - user=user, |
1921 | - claimed_id='http://example.com/existing_identity', |
1922 | - display_id='http://example.com/existing_identity') |
1923 | - |
1924 | - # Setup user who is going to try to change username to 'testuser' |
1925 | - renamed_user = User.objects.create_user('renameuser', 'someone@example.com') |
1926 | - UserOpenID.objects.get_or_create( |
1927 | - user=renamed_user, |
1928 | - claimed_id='http://example.com/identity', |
1929 | - display_id='http://example.com/identity') |
1930 | - |
1931 | - # identity url is for 'renameuser' |
1932 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1933 | - 'next': '/getuser/'} |
1934 | - # but returned username is for 'testuser', which already exists for another identity |
1935 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Rename User', |
1936 | - 'email': 'rename@example.com'} |
1937 | - self._do_user_login(openid_req, openid_resp) |
1938 | - response = self.client.get('/getuser/') |
1939 | - |
1940 | - # If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser' |
1941 | - # but since that username is already taken by someone else, we go through |
1942 | - # the process of adding +i to it, and get testuser2. |
1943 | - self.assertEquals(response.content, 'testuser2') |
1944 | - |
1945 | - # The user's full name and email have been updated. |
1946 | - user = User.objects.get(username=response.content) |
1947 | - self.assertEquals(user.first_name, 'Rename') |
1948 | - self.assertEquals(user.last_name, 'User') |
1949 | - self.assertEquals(user.email, 'rename@example.com') |
1950 | - |
1951 | - def test_login_follow_rename_false_onlyonce(self): |
1952 | - settings.OPENID_FOLLOW_RENAMES = True |
1953 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1954 | - # Setup existing user who's name we're going to switch to |
1955 | - user = User.objects.create_user('testuser', 'someone@example.com') |
1956 | - UserOpenID.objects.get_or_create( |
1957 | - user=user, |
1958 | - claimed_id='http://example.com/existing_identity', |
1959 | - display_id='http://example.com/existing_identity') |
1960 | - |
1961 | - # Setup user who is going to try to change username to 'testuser' |
1962 | - renamed_user = User.objects.create_user('testuser2000eight', 'someone@example.com') |
1963 | - UserOpenID.objects.get_or_create( |
1964 | - user=renamed_user, |
1965 | - claimed_id='http://example.com/identity', |
1966 | - display_id='http://example.com/identity') |
1967 | - |
1968 | - # identity url is for 'testuser2000eight' |
1969 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
1970 | - 'next': '/getuser/'} |
1971 | - # but returned username is for 'testuser', which already exists for another identity |
1972 | - openid_resp = {'nickname': 'testuser2', 'fullname': 'Rename User', |
1973 | - 'email': 'rename@example.com'} |
1974 | - self._do_user_login(openid_req, openid_resp) |
1975 | - response = self.client.get('/getuser/') |
1976 | - |
1977 | - # If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser' |
1978 | - # but since that username is already taken by someone else, we go through |
1979 | - # the process of adding +i to it. Even though it looks like the username |
1980 | - # follows the nickname+i scheme, it has non-numbers in the suffix, so |
1981 | - # it's not an auto-generated one. The regular process of renaming to |
1982 | - # 'testuser' has a conflict, so we get +2 at the end. |
1983 | - self.assertEquals(response.content, 'testuser2') |
1984 | - |
1985 | - # The user's full name and email have been updated. |
1986 | - user = User.objects.get(username=response.content) |
1987 | - self.assertEquals(user.first_name, 'Rename') |
1988 | - self.assertEquals(user.last_name, 'User') |
1989 | - self.assertEquals(user.email, 'rename@example.com') |
1990 | - |
1991 | - def test_login_follow_rename_conflict_onlyonce(self): |
1992 | - settings.OPENID_FOLLOW_RENAMES = True |
1993 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
1994 | - # Setup existing user who's name we're going to switch to |
1995 | - user = User.objects.create_user('testuser', 'someone@example.com') |
1996 | - UserOpenID.objects.get_or_create( |
1997 | - user=user, |
1998 | - claimed_id='http://example.com/existing_identity', |
1999 | - display_id='http://example.com/existing_identity') |
2000 | - |
2001 | - # Setup user who is going to try to change username to 'testuser' |
2002 | - renamed_user = User.objects.create_user('testuser2000', 'someone@example.com') |
2003 | - UserOpenID.objects.get_or_create( |
2004 | - user=renamed_user, |
2005 | - claimed_id='http://example.com/identity', |
2006 | - display_id='http://example.com/identity') |
2007 | - |
2008 | - # identity url is for 'testuser2000' |
2009 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
2010 | - 'next': '/getuser/'} |
2011 | - # but returned username is for 'testuser', which already exists for another identity |
2012 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Rename User', |
2013 | - 'email': 'rename@example.com'} |
2014 | - self._do_user_login(openid_req, openid_resp) |
2015 | - response = self.client.get('/getuser/') |
2016 | - |
2017 | - # If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser' |
2018 | - # but since that username is already taken by someone else, we go through |
2019 | - # the process of adding +i to it. Since the user for this identity url |
2020 | - # already has a name matching that pattern, check if first. |
2021 | - self.assertEquals(response.content, 'testuser2000') |
2022 | - |
2023 | - # The user's full name and email have been updated. |
2024 | - user = User.objects.get(username=response.content) |
2025 | - self.assertEquals(user.first_name, 'Rename') |
2026 | - self.assertEquals(user.last_name, 'User') |
2027 | - self.assertEquals(user.email, 'rename@example.com') |
2028 | - |
2029 | - def test_login_follow_rename_false_conflict(self): |
2030 | - settings.OPENID_FOLLOW_RENAMES = True |
2031 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
2032 | - # Setup existing user who's username matches the name+i pattern |
2033 | - user = User.objects.create_user('testuser2', 'someone@example.com') |
2034 | - UserOpenID.objects.get_or_create( |
2035 | - user=user, |
2036 | - claimed_id='http://example.com/identity', |
2037 | - display_id='http://example.com/identity') |
2038 | - |
2039 | - # identity url is for 'testuser2' |
2040 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
2041 | - 'next': '/getuser/'} |
2042 | - # but returned username is for 'testuser', which looks like we've done |
2043 | - # a username+1 for them already, but 'testuser' isn't actually taken |
2044 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Same User', |
2045 | - 'email': 'same@example.com'} |
2046 | - self._do_user_login(openid_req, openid_resp) |
2047 | - response = self.client.get('/getuser/') |
2048 | - |
2049 | - # If OPENID_FOLLOW_RENAMES, username should be changed to 'testuser' |
2050 | - # because it wasn't currently taken |
2051 | - self.assertEquals(response.content, 'testuser') |
2052 | - |
2053 | - # The user's full name and email have been updated. |
2054 | - user = User.objects.get(username=response.content) |
2055 | - self.assertEquals(user.first_name, 'Same') |
2056 | - self.assertEquals(user.last_name, 'User') |
2057 | - self.assertEquals(user.email, 'same@example.com') |
2058 | - |
2059 | - def test_strict_username_no_nickname(self): |
2060 | - settings.OPENID_CREATE_USERS = True |
2061 | - settings.OPENID_STRICT_USERNAMES = True |
2062 | - settings.OPENID_SREG_REQUIRED_FIELDS = [] |
2063 | - |
2064 | - # Posting in an identity URL begins the authentication request: |
2065 | - response = self.client.post('/openid/login/', |
2066 | - {'openid_identifier': 'http://example.com/identity', |
2067 | - 'next': '/getuser/'}) |
2068 | - self.assertContains(response, 'OpenID transaction in progress') |
2069 | - |
2070 | - # Complete the request, passing back some simple registration |
2071 | - # data. The user is redirected to the next URL. |
2072 | - openid_request = self.provider.parseFormPost(response.content) |
2073 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2074 | - openid_response = openid_request.answer(True) |
2075 | - sreg_response = sreg.SRegResponse.extractResponse( |
2076 | - sreg_request, {'nickname': '', # No nickname |
2077 | - 'fullname': 'Some User', |
2078 | - 'email': 'foo@example.com'}) |
2079 | - openid_response.addExtension(sreg_response) |
2080 | - response = self.complete(openid_response) |
2081 | - |
2082 | - # Status code should be 403: Forbidden |
2083 | - self.assertEquals(403, response.status_code) |
2084 | - self.assertContains(response, '<h1>OpenID failed</h1>', status_code=403) |
2085 | - self.assertContains(response, "An attribute required for logging in was not returned " |
2086 | - "(nickname)", status_code=403) |
2087 | - |
2088 | - def test_strict_username_no_nickname_override(self): |
2089 | - settings.OPENID_CREATE_USERS = True |
2090 | - settings.OPENID_STRICT_USERNAMES = True |
2091 | - settings.OPENID_SREG_REQUIRED_FIELDS = [] |
2092 | - |
2093 | - # Override the login_failure handler |
2094 | - def mock_login_failure_handler(request, message, status=403, |
2095 | - template_name=None, |
2096 | - exception=None): |
2097 | - self.assertTrue(isinstance(exception, (RequiredAttributeNotReturned, MissingUsernameViolation))) |
2098 | - return HttpResponse('Test Failure Override', status=200) |
2099 | - settings.OPENID_RENDER_FAILURE = mock_login_failure_handler |
2100 | - |
2101 | - # Posting in an identity URL begins the authentication request: |
2102 | - response = self.client.post('/openid/login/', |
2103 | - {'openid_identifier': 'http://example.com/identity', |
2104 | - 'next': '/getuser/'}) |
2105 | - self.assertContains(response, 'OpenID transaction in progress') |
2106 | - |
2107 | - # Complete the request, passing back some simple registration |
2108 | - # data. The user is redirected to the next URL. |
2109 | - openid_request = self.provider.parseFormPost(response.content) |
2110 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2111 | - openid_response = openid_request.answer(True) |
2112 | - sreg_response = sreg.SRegResponse.extractResponse( |
2113 | - sreg_request, {'nickname': '', # No nickname |
2114 | - 'fullname': 'Some User', |
2115 | - 'email': 'foo@example.com'}) |
2116 | - openid_response.addExtension(sreg_response) |
2117 | - response = self.complete(openid_response) |
2118 | - |
2119 | - # Status code should be 200, since we over-rode the login_failure handler |
2120 | - self.assertEquals(200, response.status_code) |
2121 | - self.assertContains(response, 'Test Failure Override') |
2122 | - |
2123 | - def test_strict_username_duplicate_user(self): |
2124 | - settings.OPENID_CREATE_USERS = True |
2125 | - settings.OPENID_STRICT_USERNAMES = True |
2126 | - # Create a user with the same name as we'll pass back via sreg. |
2127 | - user = User.objects.create_user('someuser', 'someone@example.com') |
2128 | - useropenid = UserOpenID( |
2129 | - user=user, |
2130 | - claimed_id='http://example.com/different_identity', |
2131 | - display_id='http://example.com/different_identity') |
2132 | - useropenid.save() |
2133 | - |
2134 | - # Posting in an identity URL begins the authentication request: |
2135 | - response = self.client.post('/openid/login/', |
2136 | - {'openid_identifier': 'http://example.com/identity', |
2137 | - 'next': '/getuser/'}) |
2138 | - self.assertContains(response, 'OpenID transaction in progress') |
2139 | - |
2140 | - # Complete the request, passing back some simple registration |
2141 | - # data. The user is redirected to the next URL. |
2142 | - openid_request = self.provider.parseFormPost(response.content) |
2143 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2144 | - openid_response = openid_request.answer(True) |
2145 | - sreg_response = sreg.SRegResponse.extractResponse( |
2146 | - sreg_request, {'nickname': 'someuser', 'fullname': 'Some User', |
2147 | - 'email': 'foo@example.com'}) |
2148 | - openid_response.addExtension(sreg_response) |
2149 | - response = self.complete(openid_response) |
2150 | - |
2151 | - # Status code should be 403: Forbidden |
2152 | - self.assertEquals(403, response.status_code) |
2153 | - self.assertContains(response, '<h1>OpenID failed</h1>', status_code=403) |
2154 | - self.assertContains(response, |
2155 | - "The username (someuser) with which you tried to log in is " |
2156 | - "already in use for a different account.", |
2157 | - status_code=403) |
2158 | - |
2159 | - def test_strict_username_duplicate_user_override(self): |
2160 | - settings.OPENID_CREATE_USERS = True |
2161 | - settings.OPENID_STRICT_USERNAMES = True |
2162 | - |
2163 | - # Override the login_failure handler |
2164 | - def mock_login_failure_handler(request, message, status=403, |
2165 | - template_name=None, |
2166 | - exception=None): |
2167 | - self.assertTrue(isinstance(exception, DuplicateUsernameViolation)) |
2168 | - return HttpResponse('Test Failure Override', status=200) |
2169 | - settings.OPENID_RENDER_FAILURE = mock_login_failure_handler |
2170 | - |
2171 | - # Create a user with the same name as we'll pass back via sreg. |
2172 | - user = User.objects.create_user('someuser', 'someone@example.com') |
2173 | - useropenid = UserOpenID( |
2174 | - user=user, |
2175 | - claimed_id='http://example.com/different_identity', |
2176 | - display_id='http://example.com/different_identity') |
2177 | - useropenid.save() |
2178 | - |
2179 | - # Posting in an identity URL begins the authentication request: |
2180 | - response = self.client.post('/openid/login/', |
2181 | - {'openid_identifier': 'http://example.com/identity', |
2182 | - 'next': '/getuser/'}) |
2183 | - self.assertContains(response, 'OpenID transaction in progress') |
2184 | - |
2185 | - # Complete the request, passing back some simple registration |
2186 | - # data. The user is redirected to the next URL. |
2187 | - openid_request = self.provider.parseFormPost(response.content) |
2188 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2189 | - openid_response = openid_request.answer(True) |
2190 | - sreg_response = sreg.SRegResponse.extractResponse( |
2191 | - sreg_request, {'nickname': 'someuser', 'fullname': 'Some User', |
2192 | - 'email': 'foo@example.com'}) |
2193 | - openid_response.addExtension(sreg_response) |
2194 | - response = self.complete(openid_response) |
2195 | - |
2196 | - # Status code should be 200, since we over-rode the login_failure handler |
2197 | - self.assertEquals(200, response.status_code) |
2198 | - self.assertContains(response, 'Test Failure Override') |
2199 | - |
2200 | - def test_login_requires_sreg_required_fields(self): |
2201 | - # If any required attributes are not included in the response, |
2202 | - # we fail with a forbidden. |
2203 | - settings.OPENID_CREATE_USERS = True |
2204 | - settings.OPENID_SREG_REQUIRED_FIELDS = ('email', 'language') |
2205 | - # Posting in an identity URL begins the authentication request: |
2206 | - response = self.client.post('/openid/login/', |
2207 | - {'openid_identifier': 'http://example.com/identity', |
2208 | - 'next': '/getuser/'}) |
2209 | - self.assertContains(response, 'OpenID transaction in progress') |
2210 | - |
2211 | - # Complete the request, passing back some simple registration |
2212 | - # data. The user is redirected to the next URL. |
2213 | - openid_request = self.provider.parseFormPost(response.content) |
2214 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2215 | - openid_response = openid_request.answer(True) |
2216 | - sreg_response = sreg.SRegResponse.extractResponse( |
2217 | - sreg_request, {'nickname': 'foo', |
2218 | - 'fullname': 'Some User', |
2219 | - 'email': 'foo@example.com'}) |
2220 | - openid_response.addExtension(sreg_response) |
2221 | - response = self.complete(openid_response) |
2222 | - |
2223 | - # Status code should be 403: Forbidden as we didn't include |
2224 | - # a required field - language. |
2225 | - self.assertContains(response, |
2226 | - "An attribute required for logging in was not returned " |
2227 | - "(language)", status_code=403) |
2228 | - |
2229 | - def test_login_update_details(self): |
2230 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
2231 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2232 | - useropenid = UserOpenID( |
2233 | - user=user, |
2234 | - claimed_id='http://example.com/identity', |
2235 | - display_id='http://example.com/identity') |
2236 | - useropenid.save() |
2237 | - |
2238 | - openid_req = {'openid_identifier': 'http://example.com/identity', |
2239 | - 'next': '/getuser/'} |
2240 | - openid_resp = {'nickname': 'testuser', 'fullname': 'Some User', |
2241 | - 'email': 'foo@example.com'} |
2242 | - self._do_user_login(openid_req, openid_resp) |
2243 | - response = self.client.get('/getuser/') |
2244 | - |
2245 | - self.assertEquals(response.content, 'testuser') |
2246 | - |
2247 | - # The user's full name and email have been updated. |
2248 | - user = User.objects.get(username=response.content) |
2249 | - self.assertEquals(user.first_name, 'Some') |
2250 | - self.assertEquals(user.last_name, 'User') |
2251 | - self.assertEquals(user.email, 'foo@example.com') |
2252 | - |
2253 | - def test_login_uses_sreg_extra_fields(self): |
2254 | - # The configurable sreg attributes are used in the request. |
2255 | - settings.OPENID_SREG_EXTRA_FIELDS = ('language',) |
2256 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2257 | - useropenid = UserOpenID( |
2258 | - user=user, |
2259 | - claimed_id='http://example.com/identity', |
2260 | - display_id='http://example.com/identity') |
2261 | - useropenid.save() |
2262 | - |
2263 | - # Posting in an identity URL begins the authentication request: |
2264 | - response = self.client.post('/openid/login/', |
2265 | - {'openid_identifier': 'http://example.com/identity', |
2266 | - 'next': '/getuser/'}) |
2267 | - |
2268 | - openid_request = self.provider.parseFormPost(response.content) |
2269 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2270 | - for field in ('email', 'fullname', 'nickname', 'language'): |
2271 | - self.assertTrue(field in sreg_request) |
2272 | - |
2273 | - def test_login_uses_sreg_required_fields(self): |
2274 | - # The configurable sreg attributes are used in the request. |
2275 | - settings.OPENID_SREG_REQUIRED_FIELDS = ('email', 'language') |
2276 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2277 | - useropenid = UserOpenID( |
2278 | - user=user, |
2279 | - claimed_id='http://example.com/identity', |
2280 | - display_id='http://example.com/identity') |
2281 | - useropenid.save() |
2282 | - |
2283 | - # Posting in an identity URL begins the authentication request: |
2284 | - response = self.client.post('/openid/login/', |
2285 | - {'openid_identifier': 'http://example.com/identity', |
2286 | - 'next': '/getuser/'}) |
2287 | - |
2288 | - openid_request = self.provider.parseFormPost(response.content) |
2289 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2290 | - |
2291 | - self.assertEqual(['email', 'language'], sreg_request.required) |
2292 | - self.assertEqual(['fullname', 'nickname'], sreg_request.optional) |
2293 | - |
2294 | - def test_login_attribute_exchange(self): |
2295 | - settings.OPENID_UPDATE_DETAILS_FROM_SREG = True |
2296 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2297 | - useropenid = UserOpenID( |
2298 | - user=user, |
2299 | - claimed_id='http://example.com/identity', |
2300 | - display_id='http://example.com/identity') |
2301 | - useropenid.save() |
2302 | - |
2303 | - # Configure the provider to advertise attribute exchange |
2304 | - # protocol and start the authentication process: |
2305 | - self.provider.type_uris.append('http://openid.net/srv/ax/1.0') |
2306 | - response = self.client.post('/openid/login/', |
2307 | - {'openid_identifier': 'http://example.com/identity', |
2308 | - 'next': '/getuser/'}) |
2309 | - self.assertContains(response, 'OpenID transaction in progress') |
2310 | - |
2311 | - # The resulting OpenID request uses the Attribute Exchange |
2312 | - # extension rather than the Simple Registration extension. |
2313 | - openid_request = self.provider.parseFormPost(response.content) |
2314 | - sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) |
2315 | - self.assertEqual(sreg_request.required, []) |
2316 | - self.assertEqual(sreg_request.optional, []) |
2317 | - |
2318 | - fetch_request = ax.FetchRequest.fromOpenIDRequest(openid_request) |
2319 | - self.assertTrue(fetch_request.has_key( |
2320 | - 'http://axschema.org/contact/email')) |
2321 | - self.assertTrue(fetch_request.has_key( |
2322 | - 'http://axschema.org/namePerson')) |
2323 | - self.assertTrue(fetch_request.has_key( |
2324 | - 'http://axschema.org/namePerson/first')) |
2325 | - self.assertTrue(fetch_request.has_key( |
2326 | - 'http://axschema.org/namePerson/last')) |
2327 | - self.assertTrue(fetch_request.has_key( |
2328 | - 'http://axschema.org/namePerson/friendly')) |
2329 | - # myOpenID compatibilty attributes: |
2330 | - self.assertTrue(fetch_request.has_key( |
2331 | - 'http://schema.openid.net/contact/email')) |
2332 | - self.assertTrue(fetch_request.has_key( |
2333 | - 'http://schema.openid.net/namePerson')) |
2334 | - self.assertTrue(fetch_request.has_key( |
2335 | - 'http://schema.openid.net/namePerson/friendly')) |
2336 | - |
2337 | - # Build up a response including AX data. |
2338 | - openid_response = openid_request.answer(True) |
2339 | - fetch_response = ax.FetchResponse(fetch_request) |
2340 | - fetch_response.addValue( |
2341 | - 'http://axschema.org/contact/email', 'foo@example.com') |
2342 | - fetch_response.addValue( |
2343 | - 'http://axschema.org/namePerson/first', 'Firstname') |
2344 | - fetch_response.addValue( |
2345 | - 'http://axschema.org/namePerson/last', 'Lastname') |
2346 | - fetch_response.addValue( |
2347 | - 'http://axschema.org/namePerson/friendly', 'someuser') |
2348 | - openid_response.addExtension(fetch_response) |
2349 | - response = self.complete(openid_response) |
2350 | - self.assertRedirects(response, 'http://testserver/getuser/') |
2351 | - |
2352 | - # And they are now logged in as testuser (the passed in |
2353 | - # nickname has not caused the username to change). |
2354 | - response = self.client.get('/getuser/') |
2355 | - self.assertEquals(response.content, 'testuser') |
2356 | - |
2357 | - # The user's full name and email have been updated. |
2358 | - user = User.objects.get(username='testuser') |
2359 | - self.assertEquals(user.first_name, 'Firstname') |
2360 | - self.assertEquals(user.last_name, 'Lastname') |
2361 | - self.assertEquals(user.email, 'foo@example.com') |
2362 | - |
2363 | - def test_login_teams(self): |
2364 | - settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = False |
2365 | - settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname', |
2366 | - 'otherteam': 'othergroup'} |
2367 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2368 | - group = Group(name='groupname') |
2369 | - group.save() |
2370 | - ogroup = Group(name='othergroup') |
2371 | - ogroup.save() |
2372 | - user.groups.add(ogroup) |
2373 | - user.save() |
2374 | - useropenid = UserOpenID( |
2375 | - user=user, |
2376 | - claimed_id='http://example.com/identity', |
2377 | - display_id='http://example.com/identity') |
2378 | - useropenid.save() |
2379 | - |
2380 | - # Posting in an identity URL begins the authentication request: |
2381 | - response = self.client.post('/openid/login/', |
2382 | - {'openid_identifier': 'http://example.com/identity', |
2383 | - 'next': '/getuser/'}) |
2384 | - self.assertContains(response, 'OpenID transaction in progress') |
2385 | - |
2386 | - # Complete the request |
2387 | - openid_request = self.provider.parseFormPost(response.content) |
2388 | - openid_response = openid_request.answer(True) |
2389 | - teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request) |
2390 | - teams_response = teams.TeamsResponse.extractResponse( |
2391 | - teams_request, 'teamname,some-other-team') |
2392 | - openid_response.addExtension(teams_response) |
2393 | - response = self.complete(openid_response) |
2394 | - self.assertRedirects(response, 'http://testserver/getuser/') |
2395 | - |
2396 | - # And they are now logged in as testuser |
2397 | - response = self.client.get('/getuser/') |
2398 | - self.assertEquals(response.content, 'testuser') |
2399 | - |
2400 | - # The user's groups have been updated. |
2401 | - user = User.objects.get(username='testuser') |
2402 | - self.assertTrue(group in user.groups.all()) |
2403 | - self.assertTrue(ogroup not in user.groups.all()) |
2404 | - |
2405 | - def test_login_teams_automapping(self): |
2406 | - settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname', |
2407 | - 'otherteam': 'othergroup'} |
2408 | - settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = True |
2409 | - settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST = ['django-group1', 'django-group2'] |
2410 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2411 | - group1 = Group(name='django-group1') |
2412 | - group1.save() |
2413 | - group2 = Group(name='django-group2') |
2414 | - group2.save() |
2415 | - group3 = Group(name='django-group3') |
2416 | - group3.save() |
2417 | - user.save() |
2418 | - useropenid = UserOpenID( |
2419 | - user=user, |
2420 | - claimed_id='http://example.com/identity', |
2421 | - display_id='http://example.com/identity') |
2422 | - useropenid.save() |
2423 | - |
2424 | - # Posting in an identity URL begins the authentication request: |
2425 | - response = self.client.post('/openid/login/', |
2426 | - {'openid_identifier': 'http://example.com/identity', |
2427 | - 'next': '/getuser/'}) |
2428 | - self.assertContains(response, 'OpenID transaction in progress') |
2429 | - |
2430 | - # Complete the request |
2431 | - openid_request = self.provider.parseFormPost(response.content) |
2432 | - openid_response = openid_request.answer(True) |
2433 | - teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request) |
2434 | - |
2435 | - self.assertEqual(group1 in user.groups.all(), False) |
2436 | - self.assertEqual(group2 in user.groups.all(), False) |
2437 | - self.assertTrue(group3 not in user.groups.all()) |
2438 | - |
2439 | - def test_login_teams_staff_not_defined(self): |
2440 | - delattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS') |
2441 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2442 | - user.is_staff = True |
2443 | - user.save() |
2444 | - self.assertTrue(user.is_staff) |
2445 | - |
2446 | - user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team') |
2447 | - self.assertTrue(user.is_staff) |
2448 | - |
2449 | - def test_login_teams_staff_assignment(self): |
2450 | - settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('teamname',) |
2451 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2452 | - user.is_staff = False |
2453 | - user.save() |
2454 | - self.assertFalse(user.is_staff) |
2455 | - |
2456 | - user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team') |
2457 | - self.assertTrue(user.is_staff) |
2458 | - |
2459 | - def test_login_teams_staff_unassignment(self): |
2460 | - settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('different-teamname',) |
2461 | - user = User.objects.create_user('testuser', 'someone@example.com') |
2462 | - user.is_staff = True |
2463 | - user.save() |
2464 | - self.assertTrue(user.is_staff) |
2465 | - |
2466 | - user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team') |
2467 | - self.assertFalse(user.is_staff) |
2468 | - |
2469 | - def get_openid_authed_user_with_teams(self, user, teams_str): |
2470 | - useropenid = UserOpenID( |
2471 | - user=user, |
2472 | - claimed_id='http://example.com/identity', |
2473 | - display_id='http://example.com/identity') |
2474 | - useropenid.save() |
2475 | - |
2476 | - # Posting in an identity URL begins the authentication request: |
2477 | - response = self.client.post('/openid/login/', |
2478 | - {'openid_identifier': 'http://example.com/identity'}) |
2479 | - |
2480 | - # Complete the request |
2481 | - openid_request = self.provider.parseFormPost(response.content) |
2482 | - openid_response = openid_request.answer(True) |
2483 | - teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request) |
2484 | - teams_response = teams.TeamsResponse.extractResponse( |
2485 | - teams_request, teams_str) |
2486 | - openid_response.addExtension(teams_response) |
2487 | - response = self.complete(openid_response) |
2488 | - return User.objects.get(username=user.username) |
2489 | - |
2490 | - def test_login_complete_signals_login(self): |
2491 | - # An oauth_login_complete signal is emitted including the |
2492 | - # request and sreg_response. |
2493 | - user = User.objects.create_user('someuser', 'someone@example.com') |
2494 | - useropenid = UserOpenID( |
2495 | - user=user, |
2496 | - claimed_id='http://example.com/identity', |
2497 | - display_id='http://example.com/identity') |
2498 | - useropenid.save() |
2499 | - response = self.client.post('/openid/login/', |
2500 | - {'openid_identifier': 'http://example.com/identity'}) |
2501 | - openid_request = self.provider.parseFormPost(response.content) |
2502 | - openid_response = openid_request.answer(True) |
2503 | - # Use a closure to test whether the signal handler was called. |
2504 | - self.signal_handler_called = False |
2505 | - def login_callback(sender, **kwargs): |
2506 | - self.assertTrue(isinstance( |
2507 | - kwargs.get('request', None), HttpRequest)) |
2508 | - self.assertTrue(isinstance( |
2509 | - kwargs.get('openid_response', None), SuccessResponse)) |
2510 | - self.signal_handler_called = True |
2511 | - openid_login_complete.connect(login_callback) |
2512 | - |
2513 | - response = self.complete(openid_response) |
2514 | - |
2515 | - self.assertTrue(self.signal_handler_called) |
2516 | - openid_login_complete.disconnect(login_callback) |
2517 | - |
2518 | - |
2519 | -class HelperFunctionsTest(TestCase): |
2520 | - def test_sanitise_redirect_url(self): |
2521 | - settings.ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = [ |
2522 | - "example.com", "example.org"] |
2523 | - # list of URLs and whether they should be passed or not |
2524 | - urls = [ |
2525 | - ("http://example.com", True), |
2526 | - ("http://example.org/", True), |
2527 | - ("http://example.org/foo/bar", True), |
2528 | - ("http://example.org/foo/bar?baz=quux", True), |
2529 | - ("http://example.org:9999/foo/bar?baz=quux", True), |
2530 | - ("http://www.example.org/", False), |
2531 | - ("http://example.net/foo/bar?baz=quux", False), |
2532 | - ("/somewhere/local", True), |
2533 | - ("/somewhere/local?url=http://fail.com/bar", True), |
2534 | - # An empty path, as seen when no "next" parameter is passed. |
2535 | - ("", False), |
2536 | - ("/path with spaces", False), |
2537 | - ] |
2538 | - for url, returns_self in urls: |
2539 | - sanitised = sanitise_redirect_url(url) |
2540 | - if returns_self: |
2541 | - self.assertEqual(url, sanitised) |
2542 | - else: |
2543 | - self.assertEqual(settings.LOGIN_REDIRECT_URL, sanitised) |
2544 | - |
2545 | -def suite(): |
2546 | - return unittest.TestLoader().loadTestsFromName(__name__) |
2547 | |
2548 | === removed file 'django_openid_auth/tests/test_views.pyc' |
2549 | Binary files django_openid_auth/tests/test_views.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/tests/test_views.pyc 1970-01-01 00:00:00 +0000 differ |
2550 | === modified file 'django_openid_auth/tests/urls.py' |
2551 | --- django_openid_auth/tests/urls.py 2010-12-03 00:46:25 +0000 |
2552 | +++ django_openid_auth/tests/urls.py 2013-03-26 11:57:40 +0000 |
2553 | @@ -1,6 +1,6 @@ |
2554 | # django-openid-auth - OpenID integration for django.contrib.auth |
2555 | # |
2556 | -# Copyright (C) 2009-2010 Canonical Ltd. |
2557 | +# Copyright (C) 2009-2013 Canonical Ltd. |
2558 | # |
2559 | # Redistribution and use in source and binary forms, with or without |
2560 | # modification, are permitted provided that the following conditions |
2561 | |
2562 | === removed file 'django_openid_auth/tests/urls.pyc' |
2563 | Binary files django_openid_auth/tests/urls.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/tests/urls.pyc 1970-01-01 00:00:00 +0000 differ |
2564 | === modified file 'django_openid_auth/urls.py' |
2565 | --- django_openid_auth/urls.py 2010-12-03 00:46:25 +0000 |
2566 | +++ django_openid_auth/urls.py 2013-03-26 11:57:40 +0000 |
2567 | @@ -1,7 +1,7 @@ |
2568 | # django-openid-auth - OpenID integration for django.contrib.auth |
2569 | # |
2570 | # Copyright (C) 2007 Simon Willison |
2571 | -# Copyright (C) 2008-2010 Canonical Ltd. |
2572 | +# Copyright (C) 2008-2013 Canonical Ltd. |
2573 | # |
2574 | # Redistribution and use in source and binary forms, with or without |
2575 | # modification, are permitted provided that the following conditions |
2576 | |
2577 | === removed file 'django_openid_auth/urls.pyc' |
2578 | Binary files django_openid_auth/urls.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/urls.pyc 1970-01-01 00:00:00 +0000 differ |
2579 | === modified file 'django_openid_auth/views.py' |
2580 | --- django_openid_auth/views.py 2012-06-27 08:25:19 +0000 |
2581 | +++ django_openid_auth/views.py 2013-03-26 11:57:40 +0000 |
2582 | @@ -1,7 +1,7 @@ |
2583 | # django-openid-auth - OpenID integration for django.contrib.auth |
2584 | # |
2585 | # Copyright (C) 2007 Simon Willison |
2586 | -# Copyright (C) 2008-2010 Canonical Ltd. |
2587 | +# Copyright (C) 2008-2013 Canonical Ltd. |
2588 | # |
2589 | # Redistribution and use in source and binary forms, with or without |
2590 | # modification, are permitted provided that the following conditions |
2591 | @@ -244,7 +244,11 @@ |
2592 | return_to += '&' |
2593 | else: |
2594 | return_to += '?' |
2595 | - return_to += urllib.urlencode({redirect_field_name: redirect_to}) |
2596 | + # Django gives us Unicode, which is great. We must encode URI. |
2597 | + # urllib enforces str. We can't trust anything about the default |
2598 | + # encoding inside str(foo) , so we must explicitly make foo a str. |
2599 | + return_to += urllib.urlencode( |
2600 | + {redirect_field_name: redirect_to.encode("UTF-8")}) |
2601 | |
2602 | return render_openid_request(request, openid_request, return_to) |
2603 | |
2604 | |
2605 | === removed file 'django_openid_auth/views.pyc' |
2606 | Binary files django_openid_auth/views.pyc 2012-06-27 08:25:19 +0000 and django_openid_auth/views.pyc 1970-01-01 00:00:00 +0000 differ |
2607 | === modified file 'example_consumer/__init__.py' |
2608 | --- example_consumer/__init__.py 2010-12-03 00:46:25 +0000 |
2609 | +++ example_consumer/__init__.py 2013-03-26 11:57:40 +0000 |
2610 | @@ -1,7 +1,7 @@ |
2611 | # django-openid-auth - OpenID integration for django.contrib.auth |
2612 | # |
2613 | # Copyright (C) 2007 Simon Willison |
2614 | -# Copyright (C) 2008-2010 Canonical Ltd. |
2615 | +# Copyright (C) 2008-2013 Canonical Ltd. |
2616 | # |
2617 | # Redistribution and use in source and binary forms, with or without |
2618 | # modification, are permitted provided that the following conditions |
2619 | |
2620 | === modified file 'example_consumer/settings.py' |
2621 | --- example_consumer/settings.py 2012-06-27 08:25:19 +0000 |
2622 | +++ example_consumer/settings.py 2013-03-26 11:57:40 +0000 |
2623 | @@ -1,7 +1,7 @@ |
2624 | # django-openid-auth - OpenID integration for django.contrib.auth |
2625 | # |
2626 | # Copyright (C) 2007 Simon Willison |
2627 | -# Copyright (C) 2008-2010 Canonical Ltd. |
2628 | +# Copyright (C) 2008-2013 Canonical Ltd. |
2629 | # |
2630 | # Redistribution and use in source and binary forms, with or without |
2631 | # modification, are permitted provided that the following conditions |
2632 | @@ -28,7 +28,8 @@ |
2633 | # POSSIBILITY OF SUCH DAMAGE. |
2634 | |
2635 | # Django settings for example project. |
2636 | - |
2637 | +import django |
2638 | +django_version = django.get_version() |
2639 | DEBUG = True |
2640 | TEMPLATE_DEBUG = DEBUG |
2641 | |
2642 | @@ -38,13 +39,30 @@ |
2643 | |
2644 | MANAGERS = ADMINS |
2645 | |
2646 | -DATABASES = { |
2647 | - 'default': { |
2648 | - 'ENGINE': 'django.db.backends.sqlite3', |
2649 | - 'NAME': 'example_consumer/test.db3', |
2650 | - } |
2651 | -} |
2652 | - |
2653 | +if django_version >= "1.2": |
2654 | + csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware' |
2655 | + DATABASES = { |
2656 | + 'default': { |
2657 | + 'ENGINE': 'django.db.backends.sqlite3', |
2658 | + 'NAME': 'sqlite.db' |
2659 | + } |
2660 | + } |
2661 | + TEMPLATE_LOADERS = ( |
2662 | + 'django.template.loaders.filesystem.Loader', |
2663 | + 'django.template.loaders.app_directories.Loader', |
2664 | + ) |
2665 | +else: |
2666 | + csrf_middleware = 'django.contrib.csrf.middleware.CsrfViewMiddleware' |
2667 | + TEMPLATE_LOADERS = ( |
2668 | + 'django.template.loaders.filesystem.load_template_source', |
2669 | + 'django.template.loaders.app_directories.load_template_source', |
2670 | + ) |
2671 | + DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. |
2672 | + DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3. |
2673 | + DATABASE_USER = '' # Not used with sqlite3. |
2674 | + DATABASE_PASSWORD = '' # Not used with sqlite3. |
2675 | + DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. |
2676 | + DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. |
2677 | |
2678 | # Local time zone for this installation. Choices can be found here: |
2679 | # http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE |
2680 | @@ -80,23 +98,6 @@ |
2681 | # Make this unique, and don't share it with anybody. |
2682 | SECRET_KEY = '34958734985734985734985798437' |
2683 | |
2684 | -# List of callables that know how to import templates from various sources. |
2685 | -TEMPLATE_LOADERS = ( |
2686 | - 'django.template.loaders.filesystem.Loader', |
2687 | - 'django.template.loaders.app_directories.Loader', |
2688 | -# 'django.template.loaders.eggs.Loader', |
2689 | -) |
2690 | - |
2691 | -# django-openid-auth will *not* work with Django 1.1.1 or older, as it's |
2692 | -# missing the csrf_token template tag. This will allow it to work with |
2693 | -# Django 1.1.2 or later: |
2694 | -try: |
2695 | - import django.middleware.csrf |
2696 | -except ImportError: |
2697 | - csrf_middleware = 'django.contrib.csrf.middleware.CsrfViewMiddleware' |
2698 | -else: |
2699 | - csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware' |
2700 | - |
2701 | MIDDLEWARE_CLASSES = ( |
2702 | 'django.middleware.common.CommonMiddleware', |
2703 | 'django.contrib.sessions.middleware.SessionMiddleware', |
2704 | |
2705 | === modified file 'example_consumer/urls.py' |
2706 | --- example_consumer/urls.py 2010-12-03 00:46:25 +0000 |
2707 | +++ example_consumer/urls.py 2013-03-26 11:57:40 +0000 |
2708 | @@ -1,7 +1,7 @@ |
2709 | # django-openid-auth - OpenID integration for django.contrib.auth |
2710 | # |
2711 | # Copyright (C) 2007 Simon Willison |
2712 | -# Copyright (C) 2008-2010 Canonical Ltd. |
2713 | +# Copyright (C) 2008-2013 Canonical Ltd. |
2714 | # |
2715 | # Redistribution and use in source and binary forms, with or without |
2716 | # modification, are permitted provided that the following conditions |
2717 | @@ -41,5 +41,5 @@ |
2718 | (r'^logout/$', 'django.contrib.auth.views.logout'), |
2719 | (r'^private/$', views.require_authentication), |
2720 | |
2721 | - (r'^admin/(.*)', admin.site.root), |
2722 | + (r'^admin/', include(admin.site.urls)), |
2723 | ) |
2724 | |
2725 | === modified file 'example_consumer/views.py' |
2726 | --- example_consumer/views.py 2010-12-03 00:46:25 +0000 |
2727 | +++ example_consumer/views.py 2013-03-26 11:57:40 +0000 |
2728 | @@ -1,7 +1,7 @@ |
2729 | # django-openid-auth - OpenID integration for django.contrib.auth |
2730 | # |
2731 | # Copyright (C) 2007 Simon Willison |
2732 | -# Copyright (C) 2008-2010 Canonical Ltd. |
2733 | +# Copyright (C) 2008-2013 Canonical Ltd. |
2734 | # |
2735 | # Redistribution and use in source and binary forms, with or without |
2736 | # modification, are permitted provided that the following conditions |
2737 | |
2738 | === removed file 'openid.html' |
2739 | --- openid.html 2012-06-27 08:25:19 +0000 |
2740 | +++ openid.html 1970-01-01 00:00:00 +0000 |
2741 | @@ -1,150 +0,0 @@ |
2742 | -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" |
2743 | - "http://www.w3.org/TR/html4/strict.dtd"> |
2744 | -<html> |
2745 | -<head><title>OpenID in Django</title></head> |
2746 | -<body> |
2747 | -<h1>OpenID in Django</h1> |
2748 | -<p>The <tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt> package contains all of the code needed to set up |
2749 | -your Django application as an OpenID consumer. You can use it to allow OpenID |
2750 | -users to sign in to your site without having to create a new username and |
2751 | -password.</p> |
2752 | -<div class="section"> |
2753 | -<h2><a id="overview">Overview</a></h2> |
2754 | -<p>The OpenID consumer system consists of:</p> |
2755 | -<ul class="simple"> |
2756 | -<li>Views for you to hook in to your application.</li> |
2757 | -<li>Database models implementing the persistence layer of an OpenID consumer.</li> |
2758 | -<li>Middleware that makes <tt class="docutils literal"><span class="pre">request.openid</span></tt> and <tt class="docutils literal"><span class="pre">request.openids</span></tt> |
2759 | -properties available to your application views.</li> |
2760 | -</ul> |
2761 | -</div> |
2762 | -<div class="section"> |
2763 | -<h2><a id="dependencies">Dependencies</a></h2> |
2764 | -<p><tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt> uses the <a class="reference" href="http://www.openidenabled.com/openid/libraries/python/">python-openid library</a>, which must be |
2765 | -installed separately somewhere on the Python path. You should install the 1.2.0 |
2766 | -“combo” package which includes the <tt class="docutils literal"><span class="pre">yadis</span></tt> and <tt class="docutils literal"><span class="pre">urljr</span></tt> libraries.</p> |
2767 | -<p>The package also depends on the availability of Django’s <a class="reference" href="http://www.djangoproject.com/documentation/sessions/">session support</a>.</p> |
2768 | -</div> |
2769 | -<div class="section"> |
2770 | -<h2><a id="installation">Installation</a></h2> |
2771 | -<p>Having ensured that both the <tt class="docutils literal"><span class="pre">python-openid</span></tt> library and the <tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt> package are available on your Python path, you can |
2772 | -add OpenID consumer support to an application by doing the following:</p> |
2773 | -<ol class="arabic"> |
2774 | -<li><p class="first">Put <tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt> in your <tt class="docutils literal"><span class="pre">INSTALLED_APPS</span></tt> setting.</p> |
2775 | -</li> |
2776 | -<li><p class="first">Run the command <tt class="docutils literal"><span class="pre">manage.py</span> <span class="pre">syncdb</span></tt> to create the necessary tables.</p> |
2777 | -</li> |
2778 | -<li><p class="first">Add <tt class="docutils literal"><span class="pre">django_openidconsumer.middleware.OpenIDMiddleware</span></tt> to your list |
2779 | -of <tt class="docutils literal"><span class="pre">MIDDLEWARE_CLASSES</span></tt>, somewhere after the Session middleware.</p> |
2780 | -</li> |
2781 | -<li><p class="first">Add the following views to your urlconf:</p> |
2782 | -<pre class="literal-block"> |
2783 | -(r'^openid/$', 'django_openidconsumer.views.begin'), |
2784 | -(r'^openid/complete/$', 'django_openidconsumer.views.complete'), |
2785 | -(r'^openid/signout/$', 'django_openidconsumer.views.signout'), |
2786 | -</pre> |
2787 | -</li> |
2788 | -</ol> |
2789 | -<p>You will then be able to browse to <tt class="docutils literal"><span class="pre">example.com/openid/</span></tt> and sign in using |
2790 | -an OpenID.</p> |
2791 | -</div> |
2792 | -<div class="section"> |
2793 | -<h2><a id="using-the-openid-middleware">Using the OpenID middleware</a></h2> |
2794 | -<p>With the Middleware installed, your views will have access to the user’s OpenID |
2795 | -as the <tt class="docutils literal"><span class="pre">request.openid</span></tt> property. This will be <tt class="docutils literal"><span class="pre">None</span></tt> if the user has not |
2796 | -yet authenticated; otherwise it will be a <tt class="docutils literal"><span class="pre">django_openidconsumer.util.OpenID</span></tt> |
2797 | -instance.</p> |
2798 | -<p>If you want the user’s OpenID as a string, call the <tt class="docutils literal"><span class="pre">str()</span></tt> builtin on the |
2799 | -OpenID instance:</p> |
2800 | -<pre class="literal-block"> |
2801 | -def example_view(request): |
2802 | - if request.openid: |
2803 | - return HttpResponse("OpenID is %s" % escape(str(request.openid))) |
2804 | - else: |
2805 | - return HttpResponse("No OpenID") |
2806 | -</pre> |
2807 | -<p>Users can sign in with more than one OpenID. This is supported by the |
2808 | -<tt class="docutils literal"><span class="pre">request.openids</span></tt> property, which is a list of <tt class="docutils literal"><span class="pre">OpenID</span></tt> objects in the order |
2809 | -in which they were authenticated. <tt class="docutils literal"><span class="pre">request.openid</span></tt> merely returns the last |
2810 | -item in this list.</p> |
2811 | -</div> |
2812 | -<div class="section"> |
2813 | -<h2><a id="using-simple-registration">Using simple registration</a></h2> |
2814 | -<p>Simple registration (or <a class="reference" href="http://openid.net/specs/openid-simple-registration-extension-1_0.html">sreg</a>) is an extension to the OpenID specification |
2815 | -that allows you to request extra details about a user from their OpenID |
2816 | -provider. It is frequently used to pre-populate registration forms with |
2817 | -information such as the user’s name, e-mail address or date of birth.</p> |
2818 | -<p>Be aware that not all OpenID providers support sreg, and there is no guarantee |
2819 | -that the information you have requested will be returned. Simple registration |
2820 | -should be used as a convenience for your users rather than as a required step in |
2821 | -your authentication process.</p> |
2822 | -<p>Available simple registration fields are <tt class="docutils literal"><span class="pre">nickname</span></tt>, <tt class="docutils literal"><span class="pre">email</span></tt>, <tt class="docutils literal"><span class="pre">fullname</span></tt>, |
2823 | -<tt class="docutils literal"><span class="pre">dob</span></tt>, <tt class="docutils literal"><span class="pre">gender</span></tt>, <tt class="docutils literal"><span class="pre">postcode</span></tt>, <tt class="docutils literal"><span class="pre">country</span></tt>, <tt class="docutils literal"><span class="pre">language</span></tt> and <tt class="docutils literal"><span class="pre">timezone</span></tt>. |
2824 | -Full details are available in the <a class="reference" href="http://openid.net/specs/openid-simple-registration-extension-1_0.html">spec</a>.</p> |
2825 | -<p>To request this information, pass the fields that you wish to retrieve as an |
2826 | -additional <tt class="docutils literal"><span class="pre">sreg</span></tt> argument to the <tt class="docutils literal"><span class="pre">django_openidconsumer.views.begin</span></tt> view:</p> |
2827 | -<pre class="literal-block"> |
2828 | -(r'^openid/$', 'django_openidconsumer.views.begin', { |
2829 | - 'sreg': 'email,nickname' |
2830 | -}), |
2831 | -</pre> |
2832 | -<p>Any simple registration fields that are returned will be available in a |
2833 | -dictionary as the <tt class="docutils literal"><span class="pre">sreg</span></tt> property of the OpenID object:</p> |
2834 | -<pre class="literal-block"> |
2835 | -def example_sreg(request): |
2836 | - if request.openid and request.openid.sreg.has_key('email'): |
2837 | - return HttpResponse("Your e-mail address is: %s" % escape( |
2838 | - request.openid.sreg['email'] |
2839 | - )) |
2840 | - else: |
2841 | - return HttpResponse("No e-mail address") |
2842 | -</pre> |
2843 | -</div> |
2844 | -<div class="section"> |
2845 | -<h2><a id="customisation">Customisation</a></h2> |
2846 | -<p><tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt> uses two templates:</p> |
2847 | -<dl class="docutils"> |
2848 | -<dt><tt class="docutils literal"><span class="pre">openid_signin.html</span></tt></dt> |
2849 | -<dd>The form presented to the user when they sign in.</dd> |
2850 | -<dt><tt class="docutils literal"><span class="pre">openid_failure.html</span></tt></dt> |
2851 | -<dd>The template used to display an error message when something goes wrong.</dd> |
2852 | -</dl> |
2853 | -<p>You can over-ride the default templates by creating templates of the same name |
2854 | -and placing them somewhere on your template path. You can find the example |
2855 | -templates in the <tt class="docutils literal"><span class="pre">django_openidconsumer/templates</span></tt> directory.</p> |
2856 | -<p>The OpenID specification strongly recommends that any OpenID registration form |
2857 | -has a <tt class="docutils literal"><span class="pre">name</span></tt> attribute of <tt class="docutils literal"><span class="pre">openid_url</span></tt> to aid browser autocompletion, and |
2858 | -displays the <a class="reference" href="http://openid.net/login-bg.gif">OpenID logo</a> inline in the form field using the following CSS:</p> |
2859 | -<pre class="literal-block"> |
2860 | -input.openid { |
2861 | - background: url(/path/to/login-bg.gif) no-repeat; |
2862 | - background-position: 0 50%; |
2863 | - padding-left: 16px; |
2864 | -} |
2865 | -</pre> |
2866 | -<p>By default, the package expects the <tt class="docutils literal"><span class="pre">django_openidconsumer.views.complete</span></tt> |
2867 | -view to be located at <tt class="docutils literal"><span class="pre">/openid/complete/</span></tt>. This is the view that the OpenID |
2868 | -provider will redirect the user to after they have authenticated. If you want to |
2869 | -put it somewhere else you can either pass an extra <tt class="docutils literal"><span class="pre">redirect_to</span></tt> argument to |
2870 | -<tt class="docutils literal"><span class="pre">django_openidconsumer.views.begin</span></tt> or add an <tt class="docutils literal"><span class="pre">OPENID_REDIRECT_TO</span></tt> setting |
2871 | -to <tt class="docutils literal"><span class="pre">settings.py</span></tt>.</p> |
2872 | -<p>You can pass a <tt class="docutils literal"><span class="pre">?next=</span></tt> query string argument containing a relative URL to |
2873 | -the <tt class="docutils literal"><span class="pre">begin</span></tt> view to control where the user will be redirected to having |
2874 | -returned to your site. You can also set the default redirection location |
2875 | -using the <tt class="docutils literal"><span class="pre">OPENID_REDIRECT_NEXT</span></tt> setting; if you do set set a default the user |
2876 | -will be redirected to your homepage.</p> |
2877 | -</div> |
2878 | -<div class="section"> |
2879 | -<h2><a id="i-names">i-names</a></h2> |
2880 | -<p><a class="reference" href="http://www.inames.net/">i-names</a> are part of the OpenID 2.0 specification, which is currently being |
2881 | -developed. They are supported by the python-openid library, and hence are also |
2882 | -supported by <tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt>. You can tell if an OpenID is an i-name |
2883 | -by checking the <tt class="docutils literal"><span class="pre">request.openid.is_iname</span></tt> property.</p> |
2884 | -<p>If you wish to disable i-name support, you can do so by adding the following to |
2885 | -your <tt class="docutils literal"><span class="pre">settings.py</span></tt>:</p> |
2886 | -<pre class="literal-block"> |
2887 | -OPENID_DISALLOW_INAMES = True |
2888 | -</pre> |
2889 | -</div> |
2890 | -</body> |
2891 | -</html> |
2892 | \ No newline at end of file |
2893 | |
2894 | === removed file 'openid.txt' |
2895 | --- openid.txt 2012-06-27 08:25:19 +0000 |
2896 | +++ openid.txt 1970-01-01 00:00:00 +0000 |
2897 | @@ -1,165 +0,0 @@ |
2898 | -================ |
2899 | -OpenID in Django |
2900 | -================ |
2901 | - |
2902 | -The ``django_openidconsumer`` package contains all of the code needed to set up |
2903 | -your Django application as an OpenID consumer. You can use it to allow OpenID |
2904 | -users to sign in to your site without having to create a new username and |
2905 | -password. |
2906 | - |
2907 | -Overview |
2908 | -======== |
2909 | - |
2910 | -The OpenID consumer system consists of: |
2911 | - |
2912 | - * Views for you to hook in to your application. |
2913 | - * Database models implementing the persistence layer of an OpenID consumer. |
2914 | - * Middleware that makes ``request.openid`` and ``request.openids`` |
2915 | - properties available to your application views. |
2916 | - |
2917 | -Dependencies |
2918 | -============ |
2919 | - |
2920 | -``django_openidconsumer`` uses the `python-openid library`_, which must be |
2921 | -installed separately somewhere on the Python path. You should install the 1.2.0 |
2922 | -"combo" package which includes the ``yadis`` and ``urljr`` libraries. |
2923 | - |
2924 | -The package also depends on the availability of Django's `session support`_. |
2925 | - |
2926 | -.. _python-openid library: http://www.openidenabled.com/openid/libraries/python/ |
2927 | -.. _session support: http://www.djangoproject.com/documentation/sessions/ |
2928 | - |
2929 | -Installation |
2930 | -============ |
2931 | - |
2932 | -Having ensured that both the ``python-openid`` library and the ``django_openidconsumer`` package are available on your Python path, you can |
2933 | -add OpenID consumer support to an application by doing the following: |
2934 | - |
2935 | - 1. Put ``django_openidconsumer`` in your ``INSTALLED_APPS`` setting. |
2936 | - 2. Run the command ``manage.py syncdb`` to create the necessary tables. |
2937 | - 3. Add ``django_openidconsumer.middleware.OpenIDMiddleware`` to your list |
2938 | - of ``MIDDLEWARE_CLASSES``, somewhere after the Session middleware. |
2939 | - 4. Add the following views to your urlconf:: |
2940 | - |
2941 | - (r'^openid/$', 'django_openidconsumer.views.begin'), |
2942 | - (r'^openid/complete/$', 'django_openidconsumer.views.complete'), |
2943 | - (r'^openid/signout/$', 'django_openidconsumer.views.signout'), |
2944 | - |
2945 | -You will then be able to browse to ``example.com/openid/`` and sign in using |
2946 | -an OpenID. |
2947 | - |
2948 | -Using the OpenID middleware |
2949 | -=========================== |
2950 | - |
2951 | -With the Middleware installed, your views will have access to the user's OpenID |
2952 | -as the ``request.openid`` property. This will be ``None`` if the user has not |
2953 | -yet authenticated; otherwise it will be a ``django_openidconsumer.util.OpenID`` |
2954 | -instance. |
2955 | - |
2956 | -If you want the user's OpenID as a string, call the ``str()`` builtin on the |
2957 | -OpenID instance:: |
2958 | - |
2959 | - def example_view(request): |
2960 | - if request.openid: |
2961 | - return HttpResponse("OpenID is %s" % escape(str(request.openid))) |
2962 | - else: |
2963 | - return HttpResponse("No OpenID") |
2964 | - |
2965 | -Users can sign in with more than one OpenID. This is supported by the |
2966 | -``request.openids`` property, which is a list of ``OpenID`` objects in the order |
2967 | -in which they were authenticated. ``request.openid`` merely returns the last |
2968 | -item in this list. |
2969 | - |
2970 | -Using simple registration |
2971 | -========================= |
2972 | - |
2973 | -Simple registration (or `sreg`_) is an extension to the OpenID specification |
2974 | -that allows you to request extra details about a user from their OpenID |
2975 | -provider. It is frequently used to pre-populate registration forms with |
2976 | -information such as the user's name, e-mail address or date of birth. |
2977 | - |
2978 | -.. _sreg: http://openid.net/specs/openid-simple-registration-extension-1_0.html |
2979 | - |
2980 | -Be aware that not all OpenID providers support sreg, and there is no guarantee |
2981 | -that the information you have requested will be returned. Simple registration |
2982 | -should be used as a convenience for your users rather than as a required step in |
2983 | -your authentication process. |
2984 | - |
2985 | -Available simple registration fields are ``nickname``, ``email``, ``fullname``, |
2986 | -``dob``, ``gender``, ``postcode``, ``country``, ``language`` and ``timezone``. |
2987 | -Full details are available in the `spec`_. |
2988 | - |
2989 | -.. _spec: http://openid.net/specs/openid-simple-registration-extension-1_0.html |
2990 | - |
2991 | -To request this information, pass the fields that you wish to retrieve as an |
2992 | -additional ``sreg`` argument to the ``django_openidconsumer.views.begin`` view:: |
2993 | - |
2994 | - (r'^openid/$', 'django_openidconsumer.views.begin', { |
2995 | - 'sreg': 'email,nickname' |
2996 | - }), |
2997 | - |
2998 | -Any simple registration fields that are returned will be available in a |
2999 | -dictionary as the ``sreg`` property of the OpenID object:: |
3000 | - |
3001 | - def example_sreg(request): |
3002 | - if request.openid and request.openid.sreg.has_key('email'): |
3003 | - return HttpResponse("Your e-mail address is: %s" % escape( |
3004 | - request.openid.sreg['email'] |
3005 | - )) |
3006 | - else: |
3007 | - return HttpResponse("No e-mail address") |
3008 | - |
3009 | -Customisation |
3010 | -============= |
3011 | - |
3012 | -``django_openidconsumer`` uses two templates: |
3013 | - |
3014 | -``openid_signin.html`` |
3015 | - The form presented to the user when they sign in. |
3016 | - |
3017 | -``openid_failure.html`` |
3018 | - The template used to display an error message when something goes wrong. |
3019 | - |
3020 | -You can over-ride the default templates by creating templates of the same name |
3021 | -and placing them somewhere on your template path. You can find the example |
3022 | -templates in the ``django_openidconsumer/templates`` directory. |
3023 | - |
3024 | -The OpenID specification strongly recommends that any OpenID registration form |
3025 | -has a ``name`` attribute of ``openid_url`` to aid browser autocompletion, and |
3026 | -displays the `OpenID logo`_ inline in the form field using the following CSS:: |
3027 | - |
3028 | - input.openid { |
3029 | - background: url(/path/to/login-bg.gif) no-repeat; |
3030 | - background-position: 0 50%; |
3031 | - padding-left: 16px; |
3032 | - } |
3033 | - |
3034 | -.. _OpenID logo: http://openid.net/login-bg.gif |
3035 | - |
3036 | -By default, the package expects the ``django_openidconsumer.views.complete`` |
3037 | -view to be located at ``/openid/complete/``. This is the view that the OpenID |
3038 | -provider will redirect the user to after they have authenticated. If you want to |
3039 | -put it somewhere else you can either pass an extra ``redirect_to`` argument to |
3040 | -``django_openidconsumer.views.begin`` or add an ``OPENID_REDIRECT_TO`` setting |
3041 | -to ``settings.py``. |
3042 | - |
3043 | -You can pass a ``?next=`` query string argument containing a relative URL to |
3044 | -the ``begin`` view to control where the user will be redirected to having |
3045 | -returned to your site. You can also set the default redirection location |
3046 | -using the ``OPENID_REDIRECT_NEXT`` setting; if you do set set a default the user |
3047 | -will be redirected to your homepage. |
3048 | - |
3049 | -i-names |
3050 | -======= |
3051 | - |
3052 | -`i-names`_ are part of the OpenID 2.0 specification, which is currently being |
3053 | -developed. They are supported by the python-openid library, and hence are also |
3054 | -supported by ``django_openidconsumer``. You can tell if an OpenID is an i-name |
3055 | -by checking the ``request.openid.is_iname`` property. |
3056 | - |
3057 | -.. _i-names: http://www.inames.net/ |
3058 | - |
3059 | -If you wish to disable i-name support, you can do so by adding the following to |
3060 | -your ``settings.py``:: |
3061 | - |
3062 | - OPENID_DISALLOW_INAMES = True |
3063 | |
3064 | === modified file 'setup.py' |
3065 | --- setup.py 2012-06-27 08:25:19 +0000 |
3066 | +++ setup.py 2013-03-26 11:57:40 +0000 |
3067 | @@ -1,7 +1,7 @@ |
3068 | #!/usr/bin/env python |
3069 | # django-openid-auth - OpenID integration for django.contrib.auth |
3070 | # |
3071 | -# Copyright (C) 2009-2010 Canonical Ltd. |
3072 | +# Copyright (C) 2009-2013 Canonical Ltd. |
3073 | # |
3074 | # Redistribution and use in source and binary forms, with or without |
3075 | # modification, are permitted provided that the following conditions |
3076 | @@ -43,7 +43,7 @@ |
3077 | |
3078 | |
3079 | description, long_description = __doc__.split('\n\n', 1) |
3080 | -VERSION = '0.4' |
3081 | +VERSION = '0.5' |
3082 | |
3083 | setup( |
3084 | name='django-openid-auth', |
I have built and tested this update.