Merge lp:~cjohnston/ubuntu/raring/django-openid-auth/release-0-5 into lp:ubuntu/raring/django-openid-auth

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
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

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 :

I have built and tested this update.

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_database.patch was removed as well.

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'
337Binary 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'
763Binary 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'
777Binary 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'
792Binary 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'
806Binary 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'
845Binary 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'
860Binary 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'
875Binary 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'
889Binary 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'
917Binary 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'
1075Binary 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'
1089Binary 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 &lt; 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'
2549Binary 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'
2563Binary 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'
2578Binary 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'
2606Binary 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-&#8220;combo&#8221; 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&#8217;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&#8217;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&#8217;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(&quot;OpenID is %s&quot; % escape(str(request.openid)))
2804- else:
2805- return HttpResponse(&quot;No OpenID&quot;)
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&#8217;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(&quot;Your e-mail address is: %s&quot; % escape(
2838- request.openid.sreg['email']
2839- ))
2840- else:
2841- return HttpResponse(&quot;No e-mail address&quot;)
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',

Subscribers

People subscribed via source and target branches