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
=== removed directory '.pc'
=== removed file '.pc/.quilt_patches'
--- .pc/.quilt_patches 2012-07-02 12:31:05 +0000
+++ .pc/.quilt_patches 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1debian/patches
20
=== removed file '.pc/.quilt_series'
--- .pc/.quilt_series 2012-07-02 12:31:05 +0000
+++ .pc/.quilt_series 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1series
20
=== removed file '.pc/.version'
--- .pc/.version 2012-06-27 08:25:19 +0000
+++ .pc/.version 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
12
20
=== removed file '.pc/applied-patches'
--- .pc/applied-patches 2012-06-27 08:25:19 +0000
+++ .pc/applied-patches 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1fix_settings_database.patch
20
=== removed directory '.pc/fix_settings_database.patch'
=== removed directory '.pc/fix_settings_database.patch/example_consumer'
=== removed file '.pc/fix_settings_database.patch/example_consumer/settings.py'
--- .pc/fix_settings_database.patch/example_consumer/settings.py 2012-06-27 08:25:19 +0000
+++ .pc/fix_settings_database.patch/example_consumer/settings.py 1970-01-01 00:00:00 +0000
@@ -1,143 +0,0 @@
1# django-openid-auth - OpenID integration for django.contrib.auth
2#
3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10# * Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright
14# notice, this list of conditions and the following disclaimer in the
15# documentation and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29
30# Django settings for example project.
31
32DEBUG = True
33TEMPLATE_DEBUG = DEBUG
34
35ADMINS = (
36 # ('Your Name', 'your_email@domain.com'),
37)
38
39MANAGERS = ADMINS
40
41DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
42DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3.
43DATABASE_USER = '' # Not used with sqlite3.
44DATABASE_PASSWORD = '' # Not used with sqlite3.
45DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
46DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
47
48# Local time zone for this installation. Choices can be found here:
49# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
50# although not all variations may be possible on all operating systems.
51# If running in a Windows environment this must be set to the same as your
52# system time zone.
53TIME_ZONE = 'America/Chicago'
54
55# Language code for this installation. All choices can be found here:
56# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
57# http://blogs.law.harvard.edu/tech/stories/storyReader$15
58LANGUAGE_CODE = 'en-us'
59
60SITE_ID = 1
61
62# If you set this to False, Django will make some optimizations so as not
63# to load the internationalization machinery.
64USE_I18N = True
65
66# Absolute path to the directory that holds media.
67# Example: "/home/media/media.lawrence.com/"
68MEDIA_ROOT = ''
69
70# URL that handles the media served from MEDIA_ROOT.
71# Example: "http://media.lawrence.com"
72MEDIA_URL = ''
73
74# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
75# trailing slash.
76# Examples: "http://foo.com/media/", "/media/".
77ADMIN_MEDIA_PREFIX = '/media/'
78
79# Make this unique, and don't share it with anybody.
80SECRET_KEY = '34958734985734985734985798437'
81
82# List of callables that know how to import templates from various sources.
83TEMPLATE_LOADERS = (
84 'django.template.loaders.filesystem.load_template_source',
85 'django.template.loaders.app_directories.load_template_source',
86# 'django.template.loaders.eggs.load_template_source',
87)
88
89# django-openid-auth will *not* work with Django 1.1.1 or older, as it's
90# missing the csrf_token template tag. This will allow it to work with
91# Django 1.1.2 or later:
92try:
93 import django.middleware.csrf
94except ImportError:
95 csrf_middleware = 'django.contrib.csrf.middleware.CsrfViewMiddleware'
96else:
97 csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware'
98
99MIDDLEWARE_CLASSES = (
100 'django.middleware.common.CommonMiddleware',
101 'django.contrib.sessions.middleware.SessionMiddleware',
102 'django.contrib.auth.middleware.AuthenticationMiddleware',
103 csrf_middleware,
104)
105
106ROOT_URLCONF = 'example_consumer.urls'
107
108TEMPLATE_DIRS = (
109 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
110 # Always use forward slashes, even on Windows.
111 # Don't forget to use absolute paths, not relative paths.
112)
113
114INSTALLED_APPS = (
115 'django.contrib.auth',
116 'django.contrib.contenttypes',
117 'django.contrib.sessions',
118 'django.contrib.admin',
119 'django_openid_auth',
120)
121
122AUTHENTICATION_BACKENDS = (
123 'django_openid_auth.auth.OpenIDBackend',
124 'django.contrib.auth.backends.ModelBackend',
125)
126
127# Should users be created when new OpenIDs are used to log in?
128OPENID_CREATE_USERS = True
129
130# When logging in again, should we overwrite user details based on
131# data received via Simple Registration?
132OPENID_UPDATE_DETAILS_FROM_SREG = True
133
134# If set, always use this as the identity URL rather than asking the
135# user. This only makes sense if it is a server URL.
136OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/'
137
138# Tell django.contrib.auth to use the OpenID signin URLs.
139LOGIN_URL = '/openid/login/'
140LOGIN_REDIRECT_URL = '/'
141
142# Should django_auth_openid be used to sign into the admin interface?
143OPENID_USE_AS_ADMIN_LOGIN = False
1440
=== added file 'PKG-INFO'
--- PKG-INFO 1970-01-01 00:00:00 +0000
+++ PKG-INFO 2013-03-26 11:57:40 +0000
@@ -0,0 +1,31 @@
1Metadata-Version: 1.1
2Name: django-openid-auth
3Version: 0.5
4Summary: OpenID integration for django.contrib.auth
5Home-page: https://launchpad.net/django-openid-auth
6Author: Canonical Ltd
7Author-email: UNKNOWN
8License: BSD
9Download-URL: http://launchpad.net/django-openid-auth/trunk/0.5/+download/django-openid-auth-0.5.tar.gz
10Description: A library that can be used to add OpenID support to Django applications.
11 The library integrates with Django's built in authentication system, so
12 most applications require minimal changes to support OpenID llogin. The
13 library also includes the following features:
14 * Basic user details are transferred from the OpenID server via the
15 Simple Registration extension or Attribute Exchange extension.
16 * can be configured to use a fixed OpenID server URL, for use in SSO.
17 * supports the launchpad.net teams extension to get team membership
18 info.
19
20Platform: any
21Classifier: Development Status :: 4 - Beta
22Classifier: Environment :: Web Environment
23Classifier: Framework :: Django
24Classifier: Intended Audience :: Developers
25Classifier: License :: OSI Approved :: BSD License
26Classifier: Operating System :: OS Independent
27Classifier: Programming Language :: Python
28Classifier: Topic :: Software Development :: Libraries :: Python Modules
29Requires: django (>=1.1.2)
30Requires: openid (>=2.2.0)
31Provides: django_openid_auth
032
=== modified file 'README.txt'
--- README.txt 2012-06-27 08:25:19 +0000
+++ README.txt 2013-03-26 11:57:40 +0000
@@ -166,3 +166,14 @@
166166
167This function must return a Django.http.HttpResponse instance.167This function must return a Django.http.HttpResponse instance.
168168
169== Use the user's email for suggested usernames ==
170
171You can optionally strip out non-alphanumeric characters from the user's email
172to generate a preferred username, if the server doesn't provide nick
173information, by setting the following setting:
174
175 OPENID_USE_EMAIL_FOR_USERNAME = True
176
177Otherwise, and by default, if the server omits nick information and a user is
178created it'll receive a username 'openiduser' + a number.
179Consider also the OPENID_STRICT_USERNAMES setting (see ``Require a valid nickname``)
169180
=== modified file 'debian/changelog'
--- debian/changelog 2012-07-02 12:31:05 +0000
+++ debian/changelog 2013-03-26 11:57:40 +0000
@@ -1,3 +1,12 @@
1django-openid-auth (0.5-0ubuntu1) raring; urgency=low
2
3 * New upstream release.
4 * Bump Standards-Version to 3.9.4
5 * Dropped openid.txt from the package
6 * Removed fix_settings_database.patch as the fix was incorporated upstream
7
8 -- Chris Johnston <chrisjohnston@ubuntu.com> Mon, 25 Mar 2013 15:16:38 -0400
9
1django-openid-auth (0.4-1ubuntu1) quantal; urgency=low10django-openid-auth (0.4-1ubuntu1) quantal; urgency=low
211
3 * Replaces python-django-openid-auth to to remove the Ubuntu-only 12 * Replaces python-django-openid-auth to to remove the Ubuntu-only
413
=== modified file 'debian/control'
--- debian/control 2012-07-02 12:31:05 +0000
+++ debian/control 2013-03-26 11:57:40 +0000
@@ -9,7 +9,7 @@
9 python-django (>= 1.2),9 python-django (>= 1.2),
10 python-openid,10 python-openid,
11 python-support11 python-support
12Standards-Version: 3.9.312Standards-Version: 3.9.4
13XS-Python-Version: >= 2.613XS-Python-Version: >= 2.6
14Homepage: https://launchpad.net/django-openid-auth14Homepage: https://launchpad.net/django-openid-auth
15Vcs-Svn: svn://svn.debian.org/python-modules/packages/django-openid-auth/trunk/15Vcs-Svn: svn://svn.debian.org/python-modules/packages/django-openid-auth/trunk/
1616
=== modified file 'debian/docs'
--- debian/docs 2012-06-27 08:25:19 +0000
+++ debian/docs 2013-03-26 11:57:40 +0000
@@ -1,3 +1,2 @@
1README.txt1README.txt
2TODO.txt2TODO.txt
3openid.txt
43
=== removed directory 'debian/patches'
=== removed file 'debian/patches/fix_settings_database.patch'
--- debian/patches/fix_settings_database.patch 2012-06-27 08:25:19 +0000
+++ debian/patches/fix_settings_database.patch 1970-01-01 00:00:00 +0000
@@ -1,42 +0,0 @@
1Description: Use Django 1.3 DATABASE structure
2 Upstream still ships the old database configuration for Django in its
3 example consumer. This patch changes it to the new dictionary structure.
4Author: Michael Fladischer <FladischerMichael@fladi.at>
5Last-Update: 2012-04-20
6Forwarded: no
7
8--- a/example_consumer/settings.py
9+++ b/example_consumer/settings.py
10@@ -38,12 +38,13 @@
11
12 MANAGERS = ADMINS
13
14-DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
15-DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3.
16-DATABASE_USER = '' # Not used with sqlite3.
17-DATABASE_PASSWORD = '' # Not used with sqlite3.
18-DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
19-DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
20+DATABASES = {
21+ 'default': {
22+ 'ENGINE': 'django.db.backends.sqlite3',
23+ 'NAME': 'example_consumer/test.db3',
24+ }
25+}
26+
27
28 # Local time zone for this installation. Choices can be found here:
29 # http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
30@@ -81,9 +82,9 @@
31
32 # List of callables that know how to import templates from various sources.
33 TEMPLATE_LOADERS = (
34- 'django.template.loaders.filesystem.load_template_source',
35- 'django.template.loaders.app_directories.load_template_source',
36-# 'django.template.loaders.eggs.load_template_source',
37+ 'django.template.loaders.filesystem.Loader',
38+ 'django.template.loaders.app_directories.Loader',
39+# 'django.template.loaders.eggs.Loader',
40 )
41
42 # django-openid-auth will *not* work with Django 1.1.1 or older, as it's
430
=== removed file 'debian/patches/series'
--- debian/patches/series 2012-06-27 08:25:19 +0000
+++ debian/patches/series 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1fix_settings_database.patch
20
=== modified file 'django_openid_auth/__init__.py'
--- django_openid_auth/__init__.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/__init__.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== removed file 'django_openid_auth/__init__.pyc'
9Binary 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 differ9Binary 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
=== modified file 'django_openid_auth/admin.py'
--- django_openid_auth/admin.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/admin.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2008-2010 Canonical Ltd.3# Copyright (C) 2008-2013 Canonical Ltd.
4# Copyright (C) 2010 Dave Walker4# Copyright (C) 2010 Dave Walker
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
@@ -62,6 +62,7 @@
6262
6363
64class UserOpenIDAdmin(admin.ModelAdmin):64class UserOpenIDAdmin(admin.ModelAdmin):
65 raw_id_fields = ('user',)
65 list_display = ('user', 'claimed_id')66 list_display = ('user', 'claimed_id')
66 search_fields = ('claimed_id',)67 search_fields = ('claimed_id',)
6768
@@ -76,15 +77,14 @@
76 def _openid_login(self, request, error_message='', extra_context=None):77 def _openid_login(self, request, error_message='', extra_context=None):
77 if request.user.is_authenticated():78 if request.user.is_authenticated():
78 if not request.user.is_staff:79 if not request.user.is_staff:
79 return views.render_failure(80 return views.default_render_failure(
80 request, "User %s does not have admin access."81 request, "User %s does not have admin access."
81 % request.user.username)82 % request.user.username)
82 return views.render_failure(83 assert error_message, "Unknown Error: %s" % error_message
83 request, "Unknown Error: %s" % error_message)
84 else:84 else:
85 # Redirect to openid login path,85 # Redirect to openid login path,
86 return HttpResponseRedirect(86 return HttpResponseRedirect(
87 settings.LOGIN_URL + "?next=" + request.get_full_path())87 settings.LOGIN_URL + "?next=" + request.get_full_path())
8888
89 # Overide the standard admin login form.89 # Overide the standard admin login form.
90 admin.sites.AdminSite.display_login_form = _openid_login90 admin.sites.AdminSite.login = _openid_login
9191
=== modified file 'django_openid_auth/auth.py'
--- django_openid_auth/auth.py 2012-06-27 08:25:19 +0000
+++ django_openid_auth/auth.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2008-2010 Canonical Ltd.3# Copyright (C) 2008-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
@@ -49,6 +49,9 @@
49 """A django.contrib.auth backend that authenticates the user based on49 """A django.contrib.auth backend that authenticates the user based on
50 an OpenID response."""50 an OpenID response."""
5151
52 supports_object_permissions = False
53 supports_anonymous_user = True
54
52 def get_user(self, user_id):55 def get_user(self, user_id):
53 try:56 try:
54 return User.objects.get(pk=user_id)57 return User.objects.get(pk=user_id)
@@ -146,6 +149,16 @@
146 return dict(email=email, nickname=nickname,149 return dict(email=email, nickname=nickname,
147 first_name=first_name, last_name=last_name)150 first_name=first_name, last_name=last_name)
148151
152 def _get_preferred_username(self, nickname, email):
153 if nickname:
154 return nickname
155 if email and getattr(settings, 'OPENID_USE_EMAIL_FOR_USERNAME',
156 False):
157 suggestion = ''.join([x for x in email if x.isalnum()])
158 if suggestion:
159 return suggestion
160 return 'openiduser'
161
149 def _get_available_username(self, nickname, identity_url):162 def _get_available_username(self, nickname, identity_url):
150 # If we're being strict about usernames, throw an error if we didn't163 # If we're being strict about usernames, throw an error if we didn't
151 # get one back from the provider164 # get one back from the provider
@@ -198,8 +211,10 @@
198 "already in use for a different account." % nickname)211 "already in use for a different account." % nickname)
199212
200 # Pick a username for the user based on their nickname,213 # Pick a username for the user based on their nickname,
201 # checking for conflicts.214 # checking for conflicts. Start with number of existing users who's
202 i = 1215 # username starts with this nickname to avoid having to iterate over
216 # all of the existing ones.
217 i = User.objects.filter(username__startswith=nickname).count() + 1
203 while True:218 while True:
204 username = nickname219 username = nickname
205 if i > 1:220 if i > 1:
@@ -223,10 +238,12 @@
223 "An attribute required for logging in was not "238 "An attribute required for logging in was not "
224 "returned ({0}).".format(required_attr))239 "returned ({0}).".format(required_attr))
225240
226 nickname = details['nickname'] or 'openiduser'241 nickname = self._get_preferred_username(details['nickname'],
242 details['email'])
227 email = details['email'] or ''243 email = details['email'] or ''
228244
229 username = self._get_available_username(details['nickname'], openid_response.identity_url)245 username = self._get_available_username(nickname,
246 openid_response.identity_url)
230247
231 user = User.objects.create_user(username, email, password=None)248 user = User.objects.create_user(username, email, password=None)
232 self.associate_openid(user, openid_response)249 self.associate_openid(user, openid_response)
233250
=== removed file 'django_openid_auth/auth.py.~1~'
--- django_openid_auth/auth.py.~1~ 2012-06-27 08:25:19 +0000
+++ django_openid_auth/auth.py.~1~ 1970-01-01 00:00:00 +0000
@@ -1,313 +0,0 @@
1# django-openid-auth - OpenID integration for django.contrib.auth
2#
3# Copyright (C) 2008-2010 Canonical Ltd.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#
12# * Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE.
28
29"""Glue between OpenID and django.contrib.auth."""
30
31__metaclass__ = type
32
33from django.conf import settings
34from django.contrib.auth.models import User, Group
35from openid.consumer.consumer import SUCCESS
36from openid.extensions import ax, sreg, pape
37
38from django_openid_auth import teams
39from django_openid_auth.models import UserOpenID
40from django_openid_auth.exceptions import (
41 IdentityAlreadyClaimed,
42 DuplicateUsernameViolation,
43 MissingUsernameViolation,
44 MissingPhysicalMultiFactor,
45 RequiredAttributeNotReturned,
46)
47
48class OpenIDBackend:
49 """A django.contrib.auth backend that authenticates the user based on
50 an OpenID response."""
51
52 def get_user(self, user_id):
53 try:
54 return User.objects.get(pk=user_id)
55 except User.DoesNotExist:
56 return None
57
58 def authenticate(self, **kwargs):
59 """Authenticate the user based on an OpenID response."""
60 # Require that the OpenID response be passed in as a keyword
61 # argument, to make sure we don't match the username/password
62 # calling conventions of authenticate.
63
64 openid_response = kwargs.get('openid_response')
65 if openid_response is None:
66 return None
67
68 if openid_response.status != SUCCESS:
69 return None
70
71 user = None
72 try:
73 user_openid = UserOpenID.objects.get(
74 claimed_id__exact=openid_response.identity_url)
75 except UserOpenID.DoesNotExist:
76 if getattr(settings, 'OPENID_CREATE_USERS', False):
77 user = self.create_user_from_openid(openid_response)
78 else:
79 user = user_openid.user
80
81 if user is None:
82 return None
83
84 if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False):
85 details = self._extract_user_details(openid_response)
86 self.update_user_details(user, details, openid_response)
87
88 if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False):
89 pape_response = pape.Response.fromSuccessResponse(openid_response)
90 if pape_response is None or \
91 pape.AUTH_MULTI_FACTOR_PHYSICAL not in pape_response.auth_policies:
92 raise MissingPhysicalMultiFactor()
93
94 teams_response = teams.TeamsResponse.fromSuccessResponse(
95 openid_response)
96 if teams_response:
97 self.update_groups_from_teams(user, teams_response)
98 self.update_staff_status_from_teams(user, teams_response)
99
100 return user
101
102 def _extract_user_details(self, openid_response):
103 email = fullname = first_name = last_name = nickname = None
104 sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response)
105 if sreg_response:
106 email = sreg_response.get('email')
107 fullname = sreg_response.get('fullname')
108 nickname = sreg_response.get('nickname')
109 # If any attributes are provided via Attribute Exchange, use
110 # them in preference.
111 fetch_response = ax.FetchResponse.fromSuccessResponse(openid_response)
112 if fetch_response:
113 # The myOpenID provider advertises AX support, but uses
114 # attribute names from an obsolete draft of the
115 # specification. We check for them first so the common
116 # names take precedence.
117 email = fetch_response.getSingle(
118 'http://schema.openid.net/contact/email', email)
119 fullname = fetch_response.getSingle(
120 'http://schema.openid.net/namePerson', fullname)
121 nickname = fetch_response.getSingle(
122 'http://schema.openid.net/namePerson/friendly', nickname)
123
124 email = fetch_response.getSingle(
125 'http://axschema.org/contact/email', email)
126 fullname = fetch_response.getSingle(
127 'http://axschema.org/namePerson', fullname)
128 first_name = fetch_response.getSingle(
129 'http://axschema.org/namePerson/first', first_name)
130 last_name = fetch_response.getSingle(
131 'http://axschema.org/namePerson/last', last_name)
132 nickname = fetch_response.getSingle(
133 'http://axschema.org/namePerson/friendly', nickname)
134
135 if fullname and not (first_name or last_name):
136 # Django wants to store first and last names separately,
137 # so we do our best to split the full name.
138 fullname = fullname.strip()
139 split_names = fullname.rsplit(None, 1)
140 if len(split_names) == 2:
141 first_name, last_name = split_names
142 else:
143 first_name = u''
144 last_name = fullname
145
146 return dict(email=email, nickname=nickname,
147 first_name=first_name, last_name=last_name)
148
149 def _get_available_username(self, nickname, identity_url):
150 # If we're being strict about usernames, throw an error if we didn't
151 # get one back from the provider
152 if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
153 if nickname is None or nickname == '':
154 raise MissingUsernameViolation()
155
156 # If we don't have a nickname, and we're not being strict, use a default
157 nickname = nickname or 'openiduser'
158
159 # See if we already have this nickname assigned to a username
160 try:
161 user = User.objects.get(username__exact=nickname)
162 except User.DoesNotExist:
163 # No conflict, we can use this nickname
164 return nickname
165
166 # Check if we already have nickname+i for this identity_url
167 try:
168 user_openid = UserOpenID.objects.get(
169 claimed_id__exact=identity_url,
170 user__username__startswith=nickname)
171 # No exception means we have an existing user for this identity
172 # that starts with this nickname.
173
174 # If they are an exact match, the user already exists and hasn't
175 # changed their username, so continue to use it
176 if nickname == user_openid.user.username:
177 return nickname
178
179 # It is possible we've had to assign them to nickname+i already.
180 oid_username = user_openid.user.username
181 if len(oid_username) > len(nickname):
182 try:
183 # check that it ends with a number
184 int(oid_username[len(nickname):])
185 return oid_username
186 except ValueError:
187 # username starts with nickname, but isn't nickname+#
188 pass
189 except UserOpenID.DoesNotExist:
190 # No user associated with this identity_url
191 pass
192
193
194 if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
195 if User.objects.filter(username__exact=nickname).count() > 0:
196 raise DuplicateUsernameViolation(
197 "The username (%s) with which you tried to log in is "
198 "already in use for a different account." % nickname)
199
200 # Pick a username for the user based on their nickname,
201 # checking for conflicts.
202 i = 1
203 while True:
204 username = nickname
205 if i > 1:
206 username += str(i)
207 try:
208 user = User.objects.get(username__exact=username)
209 except User.DoesNotExist:
210 break
211 i += 1
212 return username
213
214 def create_user_from_openid(self, openid_response):
215 details = self._extract_user_details(openid_response)
216 required_attrs = getattr(settings, 'OPENID_SREG_REQUIRED_FIELDS', [])
217 if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
218 required_attrs.append('nickname')
219
220 for required_attr in required_attrs:
221 if required_attr not in details or not details[required_attr]:
222 raise RequiredAttributeNotReturned(
223 "An attribute required for logging in was not "
224 "returned ({0}).".format(required_attr))
225
226 nickname = details['nickname'] or 'openiduser'
227 email = details['email'] or ''
228
229 username = self._get_available_username(details['nickname'], openid_response.identity_url)
230
231 user = User.objects.create_user(username, email, password=None)
232 self.associate_openid(user, openid_response)
233 self.update_user_details(user, details, openid_response)
234
235 return user
236
237 def associate_openid(self, user, openid_response):
238 """Associate an OpenID with a user account."""
239 # Check to see if this OpenID has already been claimed.
240 try:
241 user_openid = UserOpenID.objects.get(
242 claimed_id__exact=openid_response.identity_url)
243 except UserOpenID.DoesNotExist:
244 user_openid = UserOpenID(
245 user=user,
246 claimed_id=openid_response.identity_url,
247 display_id=openid_response.endpoint.getDisplayIdentifier())
248 user_openid.save()
249 else:
250 if user_openid.user != user:
251 raise IdentityAlreadyClaimed(
252 "The identity %s has already been claimed"
253 % openid_response.identity_url)
254
255 return user_openid
256
257 def update_user_details(self, user, details, openid_response):
258 updated = False
259 if details['first_name']:
260 user.first_name = details['first_name'][:30]
261 updated = True
262 if details['last_name']:
263 user.last_name = details['last_name'][:30]
264 updated = True
265 if details['email']:
266 user.email = details['email']
267 updated = True
268 if getattr(settings, 'OPENID_FOLLOW_RENAMES', False):
269 user.username = self._get_available_username(details['nickname'], openid_response.identity_url)
270 updated = True
271
272 if updated:
273 user.save()
274
275 def update_groups_from_teams(self, user, teams_response):
276 teams_mapping_auto = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False)
277 teams_mapping_auto_blacklist = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST', [])
278 teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
279 if teams_mapping_auto:
280 #ignore teams_mapping. use all django-groups
281 teams_mapping = dict()
282 all_groups = Group.objects.exclude(name__in=teams_mapping_auto_blacklist)
283 for group in all_groups:
284 teams_mapping[group.name] = group.name
285
286 if len(teams_mapping) == 0:
287 return
288
289 current_groups = set(user.groups.filter(
290 name__in=teams_mapping.values()))
291 desired_groups = set(Group.objects.filter(
292 name__in=[teams_mapping[lp_team]
293 for lp_team in teams_response.is_member
294 if lp_team in teams_mapping]))
295 for group in current_groups - desired_groups:
296 user.groups.remove(group)
297 for group in desired_groups - current_groups:
298 user.groups.add(group)
299
300 def update_staff_status_from_teams(self, user, teams_response):
301 if not hasattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS'):
302 return
303
304 staff_teams = getattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS', [])
305 user.is_staff = False
306
307 for lp_team in teams_response.is_member:
308 if lp_team in staff_teams:
309 user.is_staff = True
310 break
311
312 user.save()
313
3140
=== removed file 'django_openid_auth/auth.pyc'
315Binary 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 differ1Binary 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
=== modified file 'django_openid_auth/exceptions.py'
--- django_openid_auth/exceptions.py 2012-06-27 08:25:19 +0000
+++ django_openid_auth/exceptions.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2008-2010 Canonical Ltd.3# Copyright (C) 2008-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
77
=== removed file 'django_openid_auth/exceptions.pyc'
8Binary 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 differ8Binary 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
=== modified file 'django_openid_auth/forms.py'
--- django_openid_auth/forms.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/forms.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== removed file 'django_openid_auth/forms.pyc'
9Binary 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 differ9Binary 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
=== modified file 'django_openid_auth/management/__init__.py'
--- django_openid_auth/management/__init__.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/management/__init__.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2009-2010 Canonical Ltd.3# Copyright (C) 2009-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
77
=== removed file 'django_openid_auth/management/__init__.pyc'
8Binary 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 differ8Binary 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
=== modified file 'django_openid_auth/management/commands/__init__.py'
--- django_openid_auth/management/commands/__init__.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/management/commands/__init__.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2009-2010 Canonical Ltd.3# Copyright (C) 2009-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
77
=== modified file 'django_openid_auth/management/commands/openid_cleanup.py'
--- django_openid_auth/management/commands/openid_cleanup.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/management/commands/openid_cleanup.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2009-2010 Canonical Ltd.3# Copyright (C) 2009-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
77
=== modified file 'django_openid_auth/models.py'
--- django_openid_auth/models.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/models.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== removed file 'django_openid_auth/models.pyc'
9Binary 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 differ9Binary 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
=== modified file 'django_openid_auth/signals.py'
--- django_openid_auth/signals.py 2012-06-27 08:25:19 +0000
+++ django_openid_auth/signals.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== removed file 'django_openid_auth/signals.pyc'
9Binary 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 differ9Binary 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
=== modified file 'django_openid_auth/store.py'
--- django_openid_auth/store.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/store.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== removed file 'django_openid_auth/store.pyc'
9Binary 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 differ9Binary 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
=== modified file 'django_openid_auth/teams.py'
--- django_openid_auth/teams.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/teams.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# Launchpad OpenID Teams Extension support for python-openid1# Launchpad OpenID Teams Extension support for python-openid
2#2#
3# Copyright (C) 2008-2010 Canonical Ltd.3# Copyright (C) 2008-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
77
=== removed file 'django_openid_auth/teams.pyc'
8Binary 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 differ8Binary 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
=== modified file 'django_openid_auth/tests/__init__.py'
--- django_openid_auth/tests/__init__.py 2012-06-27 08:25:19 +0000
+++ django_openid_auth/tests/__init__.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2009-2010 Canonical Ltd.3# Copyright (C) 2009-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
@@ -30,11 +30,12 @@
30from test_views import *30from test_views import *
31from test_store import *31from test_store import *
32from test_auth import *32from test_auth import *
33from test_admin import *
3334
3435
35def suite():36def suite():
36 suite = unittest.TestSuite()37 suite = unittest.TestSuite()
37 for name in ['test_auth', 'test_store', 'test_views']:38 for name in ['test_auth', 'test_store', 'test_views', 'test_admin']:
38 mod = __import__('%s.%s' % (__name__, name), {}, {}, ['suite'])39 mod = __import__('%s.%s' % (__name__, name), {}, {}, ['suite'])
39 suite.addTest(mod.suite())40 suite.addTest(mod.suite())
40 return suite41 return suite
4142
=== removed file 'django_openid_auth/tests/__init__.pyc'
42Binary 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 differ43Binary 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
=== added file 'django_openid_auth/tests/test_admin.py'
--- django_openid_auth/tests/test_admin.py 1970-01-01 00:00:00 +0000
+++ django_openid_auth/tests/test_admin.py 2013-03-26 11:57:40 +0000
@@ -0,0 +1,88 @@
1# django-openid-auth - OpenID integration for django.contrib.auth
2#
3# Copyright (C) 2009-2013 Canonical Ltd.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#
12# * Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE.
28"""
29Tests for the django_openid_auth Admin login form replacement.
30"""
31
32import os
33import unittest
34
35from django.conf import settings
36from django.contrib.auth.models import User, AnonymousUser
37
38settings.OPENID_USE_AS_ADMIN_LOGIN = True
39from django_openid_auth import admin
40
41from django.test import TestCase
42
43
44def create_user(is_staff=False, authenticated=True):
45 """
46 Create and return a user, either the AnonymousUser or a normal Django user,
47 setting the is_staff attribute if appropriate.
48 """
49 if not authenticated:
50 return AnonymousUser()
51 else:
52 user = User(
53 username=u'testing', email='testing@example.com',
54 is_staff=is_staff)
55 user.set_password(u'test')
56 user.save()
57
58
59class SiteAdminTests(TestCase):
60 """
61 TestCase for accessing /admin/ when the django_openid_auth form replacement
62 is in use.
63 """
64
65 def test_admin_site_with_openid_login_authenticated_non_staff(self):
66 """
67 If the request has an authenticated user, who is not flagged as a
68 staff member, then they get a failure response.
69 """
70 create_user()
71 self.client.login(username='testing', password='test')
72 response = self.client.get('/admin/')
73 self.assertTrue('User testing does not have admin access.' in
74 response.content, 'Missing error message in response')
75
76 def test_admin_site_with_openid_login_non_authenticated_user(self):
77 """
78 Unauthenticated users accessing the admin page should be directed to
79 the OpenID login url.
80 """
81 response = self.client.get('/admin/')
82 self.assertEqual(302, response.status_code)
83 self.assertEqual('http://testserver/openid/login/?next=/admin/',
84 response['Location'])
85
86
87def suite():
88 return unittest.TestLoader().loadTestsFromName(__name__)
089
=== modified file 'django_openid_auth/tests/test_auth.py'
--- django_openid_auth/tests/test_auth.py 2012-06-27 08:25:19 +0000
+++ django_openid_auth/tests/test_auth.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2010 Canonical Ltd.3# Copyright (C) 2010-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
@@ -28,6 +28,7 @@
2828
29import unittest29import unittest
3030
31from django.conf import settings
31from django.contrib.auth.models import User32from django.contrib.auth.models import User
32from django.test import TestCase33from django.test import TestCase
3334
@@ -45,6 +46,12 @@
45 def setUp(self):46 def setUp(self):
46 super(OpenIDBackendTests, self).setUp()47 super(OpenIDBackendTests, self).setUp()
47 self.backend = OpenIDBackend()48 self.backend = OpenIDBackend()
49 self.old_openid_use_email_for_username = getattr(settings,
50 'OPENID_USE_EMAIL_FOR_USERNAME', False)
51
52 def tearDown(self):
53 settings.OPENID_USE_EMAIL_FOR_USERNAME = \
54 self.old_openid_use_email_for_username
4855
49 def test_extract_user_details_sreg(self):56 def test_extract_user_details_sreg(self):
50 endpoint = OpenIDServiceEndpoint()57 endpoint = OpenIDServiceEndpoint()
@@ -150,6 +157,29 @@
150 self.assertEqual("Some", data['first_name'])157 self.assertEqual("Some", data['first_name'])
151 self.assertEqual("User", data['last_name'])158 self.assertEqual("User", data['last_name'])
152159
160 def test_preferred_username_email_munging(self):
161 settings.OPENID_USE_EMAIL_FOR_USERNAME = True
162 for nick, email, expected in [
163 ('nickcomesfirst', 'foo@example.com', 'nickcomesfirst'),
164 ('', 'foo@example.com', 'fooexamplecom'),
165 ('noemail', '', 'noemail'),
166 ('', '@%.-', 'openiduser'),
167 ('', '', 'openiduser'),
168 (None, None, 'openiduser')]:
169 self.assertEqual(expected,
170 self.backend._get_preferred_username(nick, email))
171
172 def test_preferred_username_no_email_munging(self):
173 for nick, email, expected in [
174 ('nickcomesfirst', 'foo@example.com', 'nickcomesfirst'),
175 ('', 'foo@example.com', 'openiduser'),
176 ('noemail', '', 'noemail'),
177 ('', '@%.-', 'openiduser'),
178 ('', '', 'openiduser'),
179 (None, None, 'openiduser')]:
180 self.assertEqual(expected,
181 self.backend._get_preferred_username(nick, email))
182
153183
154def suite():184def suite():
155 return unittest.TestLoader().loadTestsFromName(__name__)185 return unittest.TestLoader().loadTestsFromName(__name__)
156186
=== removed file 'django_openid_auth/tests/test_auth.pyc'
157Binary 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 differ187Binary 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
=== modified file 'django_openid_auth/tests/test_store.py'
--- django_openid_auth/tests/test_store.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/tests/test_store.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2009-2010 Canonical Ltd.3# Copyright (C) 2009-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
77
=== removed file 'django_openid_auth/tests/test_store.pyc'
8Binary 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 differ8Binary 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
=== modified file 'django_openid_auth/tests/test_views.py'
--- django_openid_auth/tests/test_views.py 2012-06-27 08:25:19 +0000
+++ django_openid_auth/tests/test_views.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,7 @@
1# -*- coding: utf-8 -*-
1# django-openid-auth - OpenID integration for django.contrib.auth2# django-openid-auth - OpenID integration for django.contrib.auth
2#3#
3# Copyright (C) 2009-2010 Canonical Ltd.4# Copyright (C) 2009-2013 Canonical Ltd.
4#5#
5# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
@@ -42,7 +43,7 @@
42from openid.oidutil import importElementTree43from openid.oidutil import importElementTree
43from openid.server.server import BROWSER_REQUEST_MODES, ENCODE_URL, Server44from openid.server.server import BROWSER_REQUEST_MODES, ENCODE_URL, Server
44from openid.store.memstore import MemoryStore45from openid.store.memstore import MemoryStore
45from openid.message import OPENID1_URL_LIMIT, IDENTIFIER_SELECT46from openid.message import IDENTIFIER_SELECT
4647
47from django_openid_auth import teams48from django_openid_auth import teams
48from django_openid_auth.models import UserOpenID49from django_openid_auth.models import UserOpenID
@@ -50,7 +51,6 @@
50 sanitise_redirect_url, 51 sanitise_redirect_url,
51 make_consumer,52 make_consumer,
52)53)
53from django_openid_auth.auth import OpenIDBackend
54from django_openid_auth.signals import openid_login_complete54from django_openid_auth.signals import openid_login_complete
55from django_openid_auth.store import DjangoOpenIDStore55from django_openid_auth.store import DjangoOpenIDStore
56from django_openid_auth.exceptions import (56from django_openid_auth.exceptions import (
@@ -169,9 +169,10 @@
169 self.req = DummyDjangoRequest('http://localhost/')169 self.req = DummyDjangoRequest('http://localhost/')
170 self.endpoint = OpenIDServiceEndpoint()170 self.endpoint = OpenIDServiceEndpoint()
171 self.endpoint.claimed_id = 'http://example.com/identity'171 self.endpoint.claimed_id = 'http://example.com/identity'
172 self.endpoint.server_url = 'http://example.com/'172 server_url = 'http://example.com/'
173 self.endpoint.server_url = server_url
173 self.consumer = make_consumer(self.req)174 self.consumer = make_consumer(self.req)
174 self.server = Server(DjangoOpenIDStore())175 self.server = Server(DjangoOpenIDStore(), op_endpoint=server_url)
175 setDefaultFetcher(self.provider, wrap_exceptions=False)176 setDefaultFetcher(self.provider, wrap_exceptions=False)
176177
177 self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')178 self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')
@@ -185,6 +186,8 @@
185 self.old_physical_multifactor = getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False)186 self.old_physical_multifactor = getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False)
186 self.old_login_render_failure = getattr(settings, 'OPENID_RENDER_FAILURE', None)187 self.old_login_render_failure = getattr(settings, 'OPENID_RENDER_FAILURE', None)
187 self.old_consumer_complete = Consumer.complete188 self.old_consumer_complete = Consumer.complete
189 self.old_openid_use_email_for_username = getattr(settings,
190 'OPENID_USE_EMAIL_FOR_USERNAME', False)
188191
189 self.old_required_fields = getattr(192 self.old_required_fields = getattr(
190 settings, 'OPENID_SREG_REQUIRED_FIELDS', [])193 settings, 'OPENID_SREG_REQUIRED_FIELDS', [])
@@ -198,6 +201,7 @@
198 settings.OPENID_FOLLOW_RENAMES = False201 settings.OPENID_FOLLOW_RENAMES = False
199 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False202 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False
200 settings.OPENID_SREG_REQUIRED_FIELDS = []203 settings.OPENID_SREG_REQUIRED_FIELDS = []
204 settings.OPENID_USE_EMAIL_FOR_USERNAME = False
201205
202 def tearDown(self):206 def tearDown(self):
203 settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url207 settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url
@@ -212,6 +216,7 @@
212 settings.OPENID_RENDER_FAILURE = self.old_login_render_failure216 settings.OPENID_RENDER_FAILURE = self.old_login_render_failure
213 Consumer.complete = self.old_consumer_complete217 Consumer.complete = self.old_consumer_complete
214 settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields218 settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields
219 settings.OPENID_USE_EMAIL_FOR_USERNAME = self.old_openid_use_email_for_username
215220
216 setDefaultFetcher(None)221 setDefaultFetcher(None)
217 super(RelyingPartyTests, self).tearDown()222 super(RelyingPartyTests, self).tearDown()
@@ -261,6 +266,13 @@
261 response = self.client.get('/getuser/')266 response = self.client.get('/getuser/')
262 self.assertEquals(response.content, 'someuser')267 self.assertEquals(response.content, 'someuser')
263268
269 def test_login_with_nonascii_return_to(self):
270 """Ensure non-ascii characters can be used for the 'next' arg."""
271 response = self.client.post('/openid/login/',
272 {'openid_identifier': 'http://example.com/identity',
273 'next': u'/files/ñandú.jpg'.encode('utf-8')})
274 self.assertContains(response, 'OpenID transaction in progress')
275
264 def test_login_no_next(self):276 def test_login_no_next(self):
265 """Logins with no next parameter redirect to LOGIN_REDIRECT_URL."""277 """Logins with no next parameter redirect to LOGIN_REDIRECT_URL."""
266 user = User.objects.create_user('someuser', 'someone@example.com')278 user = User.objects.create_user('someuser', 'someone@example.com')
@@ -569,6 +581,112 @@
569 self.assertEquals(user.last_name, 'User')581 self.assertEquals(user.last_name, 'User')
570 self.assertEquals(user.email, 'foo@example.com')582 self.assertEquals(user.email, 'foo@example.com')
571583
584 def test_login_without_nickname_with_email_suggestion(self):
585 settings.OPENID_CREATE_USERS = True
586 settings.OPENID_USE_EMAIL_FOR_USERNAME = True
587
588 openid_req = {'openid_identifier': 'http://example.com/identity',
589 'next': '/getuser/'}
590 openid_resp = {'nickname': '', 'fullname': 'Openid User',
591 'email': 'foo@example.com'}
592 self._do_user_login(openid_req, openid_resp)
593 response = self.client.get('/getuser/')
594
595 # username defaults to a munged version of the email
596 self.assertEquals(response.content, 'fooexamplecom')
597
598 def test_login_duplicate_username_numbering(self):
599 settings.OPENID_FOLLOW_RENAMES = False
600 settings.OPENID_CREATE_USERS = True
601 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
602 # Setup existing user who's name we're going to conflict with
603 user = User.objects.create_user('testuser', 'someone@example.com')
604
605 # identity url is for 'renameuser'
606 openid_req = {'openid_identifier': 'http://example.com/identity',
607 'next': '/getuser/'}
608 # but returned username is for 'testuser', which already exists for another identity
609 openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
610 'email': 'test@example.com'}
611 self._do_user_login(openid_req, openid_resp)
612 response = self.client.get('/getuser/')
613
614 # Since this username is already taken by someone else, we go through
615 # the process of adding +i to it, and get testuser2.
616 self.assertEquals(response.content, 'testuser2')
617
618 def test_login_duplicate_username_numbering_with_conflicts(self):
619 settings.OPENID_FOLLOW_RENAMES = False
620 settings.OPENID_CREATE_USERS = True
621 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
622 # Setup existing user who's name we're going to conflict with
623 user = User.objects.create_user('testuser', 'someone@example.com')
624 user = User.objects.create_user('testuser3', 'someone@example.com')
625
626 # identity url is for 'renameuser'
627 openid_req = {'openid_identifier': 'http://example.com/identity',
628 'next': '/getuser/'}
629 # but returned username is for 'testuser', which already exists for another identity
630 openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
631 'email': 'test@example.com'}
632 self._do_user_login(openid_req, openid_resp)
633 response = self.client.get('/getuser/')
634
635 # Since this username is already taken by someone else, we go through
636 # the process of adding +i to it starting with the count of users with
637 # username starting with 'testuser', of which there are 2. i should
638 # start at 3, which already exists, so it should skip to 4.
639 self.assertEquals(response.content, 'testuser4')
640
641 def test_login_duplicate_username_numbering_with_holes(self):
642 settings.OPENID_FOLLOW_RENAMES = False
643 settings.OPENID_CREATE_USERS = True
644 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
645 # Setup existing user who's name we're going to conflict with
646 user = User.objects.create_user('testuser', 'someone@example.com')
647 user = User.objects.create_user('testuser1', 'someone@example.com')
648 user = User.objects.create_user('testuser6', 'someone@example.com')
649 user = User.objects.create_user('testuser7', 'someone@example.com')
650 user = User.objects.create_user('testuser8', 'someone@example.com')
651
652 # identity url is for 'renameuser'
653 openid_req = {'openid_identifier': 'http://example.com/identity',
654 'next': '/getuser/'}
655 # but returned username is for 'testuser', which already exists for another identity
656 openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
657 'email': 'test@example.com'}
658 self._do_user_login(openid_req, openid_resp)
659 response = self.client.get('/getuser/')
660
661 # Since this username is already taken by someone else, we go through
662 # the process of adding +i to it starting with the count of users with
663 # username starting with 'testuser', of which there are 5. i should
664 # start at 6, and increment until it reaches 9.
665 self.assertEquals(response.content, 'testuser9')
666
667 def test_login_duplicate_username_numbering_with_nonsequential_matches(self):
668 settings.OPENID_FOLLOW_RENAMES = False
669 settings.OPENID_CREATE_USERS = True
670 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
671 # Setup existing user who's name we're going to conflict with
672 user = User.objects.create_user('testuser', 'someone@example.com')
673 user = User.objects.create_user('testuserfoo', 'someone@example.com')
674
675 # identity url is for 'renameuser'
676 openid_req = {'openid_identifier': 'http://example.com/identity',
677 'next': '/getuser/'}
678 # but returned username is for 'testuser', which already exists for another identity
679 openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
680 'email': 'test@example.com'}
681 self._do_user_login(openid_req, openid_resp)
682 response = self.client.get('/getuser/')
683
684 # Since this username is already taken by someone else, we go through
685 # the process of adding +i to it starting with the count of users with
686 # username starting with 'testuser', of which there are 2. i should
687 # start at 3, which will be available.
688 self.assertEquals(response.content, 'testuser3')
689
572 def test_login_follow_rename(self):690 def test_login_follow_rename(self):
573 settings.OPENID_FOLLOW_RENAMES = True691 settings.OPENID_FOLLOW_RENAMES = True
574 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True692 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
575693
=== removed file 'django_openid_auth/tests/test_views.py.~1~'
--- django_openid_auth/tests/test_views.py.~1~ 2012-06-27 08:25:19 +0000
+++ django_openid_auth/tests/test_views.py.~1~ 1970-01-01 00:00:00 +0000
@@ -1,1258 +0,0 @@
1# django-openid-auth - OpenID integration for django.contrib.auth
2#
3# Copyright (C) 2009-2010 Canonical Ltd.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#
12# * Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE.
28
29import cgi
30import unittest
31from urllib import quote_plus
32
33from django.conf import settings
34from django.contrib.auth.models import User, Group
35from django.http import HttpRequest, HttpResponse
36from django.test import TestCase
37from openid.consumer.consumer import Consumer, SuccessResponse
38from openid.consumer.discover import OpenIDServiceEndpoint
39from openid.extensions import ax, sreg, pape
40from openid.fetchers import (
41 HTTPFetcher, HTTPFetchingError, HTTPResponse, setDefaultFetcher)
42from openid.oidutil import importElementTree
43from openid.server.server import BROWSER_REQUEST_MODES, ENCODE_URL, Server
44from openid.store.memstore import MemoryStore
45from openid.message import OPENID1_URL_LIMIT, IDENTIFIER_SELECT
46
47from django_openid_auth import teams
48from django_openid_auth.models import UserOpenID
49from django_openid_auth.views import (
50 sanitise_redirect_url,
51 make_consumer,
52)
53from django_openid_auth.auth import OpenIDBackend
54from django_openid_auth.signals import openid_login_complete
55from django_openid_auth.store import DjangoOpenIDStore
56from django_openid_auth.exceptions import (
57 MissingUsernameViolation,
58 DuplicateUsernameViolation,
59 MissingPhysicalMultiFactor,
60 RequiredAttributeNotReturned,
61)
62
63ET = importElementTree()
64
65class StubOpenIDProvider(HTTPFetcher):
66
67 def __init__(self, base_url):
68 self.store = MemoryStore()
69 self.identity_url = base_url + 'identity'
70 self.localid_url = base_url + 'localid'
71 self.endpoint_url = base_url + 'endpoint'
72 self.server = Server(self.store, self.endpoint_url)
73 self.last_request = None
74 self.type_uris = ['http://specs.openid.net/auth/2.0/signon']
75
76 def fetch(self, url, body=None, headers=None):
77 if url == self.identity_url:
78 # Serve an XRDS document directly, pointing at our endpoint.
79 type_uris = ['<Type>%s</Type>' % uri for uri in self.type_uris]
80 return HTTPResponse(
81 url, 200, {'content-type': 'application/xrds+xml'}, """\
82<?xml version="1.0"?>
83<xrds:XRDS
84 xmlns="xri://$xrd*($v*2.0)"
85 xmlns:xrds="xri://$xrds">
86 <XRD>
87 <Service priority="0">
88 %s
89 <URI>%s</URI>
90 <LocalID>%s</LocalID>
91 </Service>
92 </XRD>
93</xrds:XRDS>
94""" % ('\n'.join(type_uris), self.endpoint_url, self.localid_url))
95 elif url.startswith(self.endpoint_url):
96 # Gather query parameters
97 query = {}
98 if '?' in url:
99 query.update(cgi.parse_qsl(url.split('?', 1)[1]))
100 if body is not None:
101 query.update(cgi.parse_qsl(body))
102 self.last_request = self.server.decodeRequest(query)
103
104 # The browser based requests should not be handled through
105 # the fetcher interface.
106 assert self.last_request.mode not in BROWSER_REQUEST_MODES
107
108 response = self.server.handleRequest(self.last_request)
109 webresponse = self.server.encodeResponse(response)
110 return HTTPResponse(url, webresponse.code, webresponse.headers,
111 webresponse.body)
112 else:
113 raise HTTPFetchingError('unknown URL %s' % url)
114
115 def parseFormPost(self, content):
116 """Parse an HTML form post to create an OpenID request."""
117 # Hack to make the javascript XML compliant ...
118 content = content.replace('i < elements.length',
119 'i &lt; elements.length')
120 tree = ET.XML(content)
121 form = tree.find('.//form')
122 assert form is not None, 'No form in document'
123 assert form.get('action') == self.endpoint_url, (
124 'Form posts to %s instead of %s' % (form.get('action'),
125 self.endpoint_url))
126 query = {}
127 for input in form.findall('input'):
128 if input.get('type') != 'hidden':
129 continue
130 query[input.get('name').encode('UTF-8')] = \
131 input.get('value').encode('UTF-8')
132 self.last_request = self.server.decodeRequest(query)
133 return self.last_request
134
135
136class DummyDjangoRequest(object):
137 def __init__(self, request_path):
138 self.request_path = request_path
139 self.META = {
140 'HTTP_HOST': "localhost",
141 'SCRIPT_NAME': "http://localhost",
142 'SERVER_PROTOCOL': "http",
143 }
144 self.POST = {
145 'openid_identifier': "http://example.com/identity",
146 }
147 self.GET = {}
148 self.session = {}
149
150 def get_full_path(self):
151 return self.META['SCRIPT_NAME'] + self.request_path
152
153 def build_absolute_uri(self):
154 return self.META['SCRIPT_NAME'] + self.request_path
155
156 def _combined_request(self):
157 request = {}
158 request.update(self.POST)
159 request.update(self.GET)
160 return request
161 REQUEST = property(_combined_request)
162
163class RelyingPartyTests(TestCase):
164 urls = 'django_openid_auth.tests.urls'
165
166 def setUp(self):
167 super(RelyingPartyTests, self).setUp()
168 self.provider = StubOpenIDProvider('http://example.com/')
169 self.req = DummyDjangoRequest('http://localhost/')
170 self.endpoint = OpenIDServiceEndpoint()
171 self.endpoint.claimed_id = 'http://example.com/identity'
172 self.endpoint.server_url = 'http://example.com/'
173 self.consumer = make_consumer(self.req)
174 self.server = Server(DjangoOpenIDStore())
175 setDefaultFetcher(self.provider, wrap_exceptions=False)
176
177 self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')
178 self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False)
179 self.old_strict_usernames = getattr(settings, 'OPENID_STRICT_USERNAMES', False)
180 self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False)
181 self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)
182 self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
183 self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False)
184 self.old_follow_renames = getattr(settings, 'OPENID_FOLLOW_RENAMES', False)
185 self.old_physical_multifactor = getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False)
186 self.old_login_render_failure = getattr(settings, 'OPENID_RENDER_FAILURE', None)
187 self.old_consumer_complete = Consumer.complete
188
189 self.old_required_fields = getattr(
190 settings, 'OPENID_SREG_REQUIRED_FIELDS', [])
191
192 settings.OPENID_CREATE_USERS = False
193 settings.OPENID_STRICT_USERNAMES = False
194 settings.OPENID_UPDATE_DETAILS_FROM_SREG = False
195 settings.OPENID_SSO_SERVER_URL = None
196 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {}
197 settings.OPENID_USE_AS_ADMIN_LOGIN = False
198 settings.OPENID_FOLLOW_RENAMES = False
199 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False
200 settings.OPENID_SREG_REQUIRED_FIELDS = []
201
202 def tearDown(self):
203 settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url
204 settings.OPENID_CREATE_USERS = self.old_create_users
205 settings.OPENID_STRICT_USERNAMES = self.old_strict_usernames
206 settings.OPENID_UPDATE_DETAILS_FROM_SREG = self.old_update_details
207 settings.OPENID_SSO_SERVER_URL = self.old_sso_server_url
208 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map
209 settings.OPENID_USE_AS_ADMIN_LOGIN = self.old_use_as_admin_login
210 settings.OPENID_FOLLOW_RENAMES = self.old_follow_renames
211 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = self.old_physical_multifactor
212 settings.OPENID_RENDER_FAILURE = self.old_login_render_failure
213 Consumer.complete = self.old_consumer_complete
214 settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields
215
216 setDefaultFetcher(None)
217 super(RelyingPartyTests, self).tearDown()
218
219 def complete(self, openid_response):
220 """Complete an OpenID authentication request."""
221 # The server can generate either a redirect or a form post
222 # here. For simplicity, force generation of a redirect.
223 openid_response.whichEncoding = lambda: ENCODE_URL
224 webresponse = self.provider.server.encodeResponse(openid_response)
225 self.assertEquals(webresponse.code, 302)
226 redirect_to = webresponse.headers['location']
227 self.assertTrue(redirect_to.startswith(
228 'http://testserver/openid/complete/'))
229 return self.client.get('/openid/complete/',
230 dict(cgi.parse_qsl(redirect_to.split('?', 1)[1])))
231
232 def test_login(self):
233 user = User.objects.create_user('someuser', 'someone@example.com')
234 useropenid = UserOpenID(
235 user=user,
236 claimed_id='http://example.com/identity',
237 display_id='http://example.com/identity')
238 useropenid.save()
239
240 # The login form is displayed:
241 response = self.client.get('/openid/login/')
242 self.assertTemplateUsed(response, 'openid/login.html')
243
244 # Posting in an identity URL begins the authentication request:
245 response = self.client.post('/openid/login/',
246 {'openid_identifier': 'http://example.com/identity',
247 'next': '/getuser/'})
248 self.assertContains(response, 'OpenID transaction in progress')
249
250 openid_request = self.provider.parseFormPost(response.content)
251 self.assertEquals(openid_request.mode, 'checkid_setup')
252 self.assertTrue(openid_request.return_to.startswith(
253 'http://testserver/openid/complete/'))
254
255 # Complete the request. The user is redirected to the next URL.
256 openid_response = openid_request.answer(True)
257 response = self.complete(openid_response)
258 self.assertRedirects(response, 'http://testserver/getuser/')
259
260 # And they are now logged in:
261 response = self.client.get('/getuser/')
262 self.assertEquals(response.content, 'someuser')
263
264 def test_login_no_next(self):
265 """Logins with no next parameter redirect to LOGIN_REDIRECT_URL."""
266 user = User.objects.create_user('someuser', 'someone@example.com')
267 useropenid = UserOpenID(
268 user=user,
269 claimed_id='http://example.com/identity',
270 display_id='http://example.com/identity')
271 useropenid.save()
272
273 settings.LOGIN_REDIRECT_URL = '/getuser/'
274 response = self.client.post('/openid/login/',
275 {'openid_identifier': 'http://example.com/identity'})
276 self.assertContains(response, 'OpenID transaction in progress')
277
278 openid_request = self.provider.parseFormPost(response.content)
279 self.assertEquals(openid_request.mode, 'checkid_setup')
280 self.assertTrue(openid_request.return_to.startswith(
281 'http://testserver/openid/complete/'))
282
283 # Complete the request. The user is redirected to the next URL.
284 openid_response = openid_request.answer(True)
285 response = self.complete(openid_response)
286 self.assertRedirects(
287 response, 'http://testserver' + settings.LOGIN_REDIRECT_URL)
288
289 def test_login_sso(self):
290 settings.OPENID_SSO_SERVER_URL = 'http://example.com/identity'
291 user = User.objects.create_user('someuser', 'someone@example.com')
292 useropenid = UserOpenID(
293 user=user,
294 claimed_id='http://example.com/identity',
295 display_id='http://example.com/identity')
296 useropenid.save()
297
298 # Requesting the login form immediately begins an
299 # authentication request.
300 response = self.client.get('/openid/login/', {'next': '/getuser/'})
301 self.assertEquals(response.status_code, 200)
302 self.assertContains(response, 'OpenID transaction in progress')
303
304 openid_request = self.provider.parseFormPost(response.content)
305 self.assertEquals(openid_request.mode, 'checkid_setup')
306 self.assertTrue(openid_request.return_to.startswith(
307 'http://testserver/openid/complete/'))
308
309 # Complete the request. The user is redirected to the next URL.
310 openid_response = openid_request.answer(True)
311 response = self.complete(openid_response)
312 self.assertRedirects(response, 'http://testserver/getuser/')
313
314 # And they are now logged in:
315 response = self.client.get('/getuser/')
316 self.assertEquals(response.content, 'someuser')
317
318 def test_login_create_users(self):
319 settings.OPENID_CREATE_USERS = True
320 # Create a user with the same name as we'll pass back via sreg.
321 User.objects.create_user('someuser', 'someone@example.com')
322
323 # Posting in an identity URL begins the authentication request:
324 response = self.client.post('/openid/login/',
325 {'openid_identifier': 'http://example.com/identity',
326 'next': '/getuser/'})
327 self.assertContains(response, 'OpenID transaction in progress')
328
329 # Complete the request, passing back some simple registration
330 # data. The user is redirected to the next URL.
331 openid_request = self.provider.parseFormPost(response.content)
332 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
333 openid_response = openid_request.answer(True)
334 sreg_response = sreg.SRegResponse.extractResponse(
335 sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
336 'email': 'foo@example.com'})
337 openid_response.addExtension(sreg_response)
338 response = self.complete(openid_response)
339 self.assertRedirects(response, 'http://testserver/getuser/')
340
341 # And they are now logged in as a new user (they haven't taken
342 # over the existing "someuser" user).
343 response = self.client.get('/getuser/')
344 self.assertEquals(response.content, 'someuser2')
345
346 # Check the details of the new user.
347 user = User.objects.get(username='someuser2')
348 self.assertEquals(user.first_name, 'Some')
349 self.assertEquals(user.last_name, 'User')
350 self.assertEquals(user.email, 'foo@example.com')
351
352 def _do_user_login(self, req_data, resp_data, use_sreg=True, use_pape=None):
353 openid_request = self._get_login_request(req_data)
354 openid_response = self._get_login_response(openid_request, resp_data, use_sreg, use_pape)
355 response = self.complete(openid_response)
356 self.assertRedirects(response, 'http://testserver/getuser/')
357 return response
358
359 def _get_login_request(self, req_data):
360 # Posting in an identity URL begins the authentication request:
361 response = self.client.post('/openid/login/', req_data)
362 self.assertContains(response, 'OpenID transaction in progress')
363
364 # Complete the request, passing back some simple registration
365 # data. The user is redirected to the next URL.
366 openid_request = self.provider.parseFormPost(response.content)
367 return openid_request
368
369 def _get_login_response(self, openid_request, resp_data, use_sreg, use_pape):
370 openid_response = openid_request.answer(True)
371
372 if use_sreg:
373 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
374 sreg_response = sreg.SRegResponse.extractResponse(
375 sreg_request, resp_data)
376 openid_response.addExtension(sreg_response)
377 if use_pape is not None:
378 policies = [
379 use_pape
380 ]
381 pape_response = pape.Response(auth_policies=policies)
382 openid_response.addExtension(pape_response)
383 return openid_response
384
385 def parse_query_string(self, query_str):
386 query_items = map(tuple,
387 [item.split('=') for item in query_str.split('&')])
388 query = dict(query_items)
389 return query
390
391 def test_login_physical_multifactor_request(self):
392 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
393 preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
394 self.provider.type_uris.append(pape.ns_uri)
395
396 openid_req = {'openid_identifier': 'http://example.com/identity',
397 'next': '/getuser/'}
398 response = self.client.post('/openid/login/', openid_req)
399 openid_request = self.provider.parseFormPost(response.content)
400
401 request_auth = openid_request.message.getArg(
402 'http://specs.openid.net/extensions/pape/1.0',
403 'preferred_auth_policies',
404 )
405 self.assertEqual(request_auth, preferred_auth)
406
407 def test_login_physical_multifactor_response(self):
408 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
409 preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
410 self.provider.type_uris.append(pape.ns_uri)
411
412 def mock_complete(this, request_args, return_to):
413 request = {'openid.mode': 'checkid_setup',
414 'openid.trust_root': 'http://localhost/',
415 'openid.return_to': 'http://localhost/',
416 'openid.identity': IDENTIFIER_SELECT,
417 'openid.ns.pape' : pape.ns_uri,
418 'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE),
419 }
420 openid_server = self.provider.server
421 orequest = openid_server.decodeRequest(request)
422 response = SuccessResponse(
423 self.endpoint, orequest.message,
424 signed_fields=['openid.pape.auth_policies',])
425 return response
426 Consumer.complete = mock_complete
427
428 user = User.objects.create_user('testuser', 'test@example.com')
429 useropenid = UserOpenID(
430 user=user,
431 claimed_id='http://example.com/identity',
432 display_id='http://example.com/identity')
433 useropenid.save()
434
435 openid_req = {'openid_identifier': 'http://example.com/identity',
436 'next': '/getuser/'}
437 openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User',
438 'email': 'test@example.com'}
439
440 response = self._do_user_login(openid_req, openid_resp, use_pape=pape.AUTH_MULTI_FACTOR_PHYSICAL)
441
442 query = self.parse_query_string(response.request['QUERY_STRING'])
443 self.assertTrue('openid.pape.auth_policies' in query)
444 self.assertEqual(query['openid.pape.auth_policies'],
445 quote_plus(preferred_auth))
446
447 response = self.client.get('/getuser/')
448 self.assertEqual(response.content, 'testuser')
449
450
451 def test_login_physical_multifactor_not_provided(self):
452 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
453 preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
454 self.provider.type_uris.append(pape.ns_uri)
455
456 def mock_complete(this, request_args, return_to):
457 request = {'openid.mode': 'checkid_setup',
458 'openid.trust_root': 'http://localhost/',
459 'openid.return_to': 'http://localhost/',
460 'openid.identity': IDENTIFIER_SELECT,
461 'openid.ns.pape' : pape.ns_uri,
462 'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE),
463 }
464 openid_server = self.provider.server
465 orequest = openid_server.decodeRequest(request)
466 response = SuccessResponse(
467 self.endpoint, orequest.message,
468 signed_fields=['openid.pape.auth_policies',])
469 return response
470 Consumer.complete = mock_complete
471
472 user = User.objects.create_user('testuser', 'test@example.com')
473 useropenid = UserOpenID(
474 user=user,
475 claimed_id='http://example.com/identity',
476 display_id='http://example.com/identity')
477 useropenid.save()
478
479 openid_req = {'openid_identifier': 'http://example.com/identity',
480 'next': '/getuser/'}
481 openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User',
482 'email': 'test@example.com'}
483
484 openid_request = self._get_login_request(openid_req)
485 openid_response = self._get_login_response(openid_request, openid_req, openid_resp, use_pape=pape.AUTH_NONE)
486
487 response_auth = openid_request.message.getArg(
488 'http://specs.openid.net/extensions/pape/1.0',
489 'auth_policies',
490 )
491 self.assertNotEqual(response_auth, preferred_auth)
492
493 response = self.complete(openid_response)
494 self.assertEquals(403, response.status_code)
495 self.assertContains(response, '<h1>OpenID failed</h1>', status_code=403)
496 self.assertContains(response, '<p>Login requires physical multi-factor authentication.</p>', status_code=403)
497
498 def test_login_physical_multifactor_not_provided_override(self):
499 settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
500 preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
501 self.provider.type_uris.append(pape.ns_uri)
502
503 # Override the login_failure handler
504 def mock_login_failure_handler(request, message, status=403,
505 template_name=None,
506 exception=None):
507 self.assertTrue(isinstance(exception, MissingPhysicalMultiFactor))
508 return HttpResponse('Test Failure Override', status=200)
509 settings.OPENID_RENDER_FAILURE = mock_login_failure_handler
510
511 def mock_complete(this, request_args, return_to):
512 request = {'openid.mode': 'checkid_setup',
513 'openid.trust_root': 'http://localhost/',
514 'openid.return_to': 'http://localhost/',
515 'openid.identity': IDENTIFIER_SELECT,
516 'openid.ns.pape' : pape.ns_uri,
517 'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE),
518 }
519 openid_server = self.provider.server
520 orequest = openid_server.decodeRequest(request)
521 response = SuccessResponse(
522 self.endpoint, orequest.message,
523 signed_fields=['openid.pape.auth_policies',])
524 return response
525 Consumer.complete = mock_complete
526
527 user = User.objects.create_user('testuser', 'test@example.com')
528 useropenid = UserOpenID(
529 user=user,
530 claimed_id='http://example.com/identity',
531 display_id='http://example.com/identity')
532 useropenid.save()
533
534 openid_req = {'openid_identifier': 'http://example.com/identity',
535 'next': '/getuser/'}
536 openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User',
537 'email': 'test@example.com'}
538
539 openid_request = self._get_login_request(openid_req)
540 openid_response = self._get_login_response(openid_request, openid_req, openid_resp, use_pape=pape.AUTH_NONE)
541
542 response_auth = openid_request.message.getArg(
543 'http://specs.openid.net/extensions/pape/1.0',
544 'auth_policies',
545 )
546 self.assertNotEqual(response_auth, preferred_auth)
547
548 # Status code should be 200, since we over-rode the login_failure handler
549 response = self.complete(openid_response)
550 self.assertEquals(200, response.status_code)
551 self.assertContains(response, 'Test Failure Override')
552
553 def test_login_without_nickname(self):
554 settings.OPENID_CREATE_USERS = True
555
556 openid_req = {'openid_identifier': 'http://example.com/identity',
557 'next': '/getuser/'}
558 openid_resp = {'nickname': '', 'fullname': 'Openid User',
559 'email': 'foo@example.com'}
560 self._do_user_login(openid_req, openid_resp)
561 response = self.client.get('/getuser/')
562
563 # username defaults to 'openiduser'
564 self.assertEquals(response.content, 'openiduser')
565
566 # The user's full name and email have been updated.
567 user = User.objects.get(username=response.content)
568 self.assertEquals(user.first_name, 'Openid')
569 self.assertEquals(user.last_name, 'User')
570 self.assertEquals(user.email, 'foo@example.com')
571
572 def test_login_follow_rename(self):
573 settings.OPENID_FOLLOW_RENAMES = True
574 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
575 user = User.objects.create_user('testuser', 'someone@example.com')
576 useropenid = UserOpenID(
577 user=user,
578 claimed_id='http://example.com/identity',
579 display_id='http://example.com/identity')
580 useropenid.save()
581
582 openid_req = {'openid_identifier': 'http://example.com/identity',
583 'next': '/getuser/'}
584 openid_resp = {'nickname': 'someuser', 'fullname': 'Some User',
585 'email': 'foo@example.com'}
586 self._do_user_login(openid_req, openid_resp)
587 response = self.client.get('/getuser/')
588
589 # If OPENID_FOLLOW_RENAMES, they are logged in as
590 # someuser (the passed in nickname has changed the username)
591 self.assertEquals(response.content, 'someuser')
592
593 # The user's full name and email have been updated.
594 user = User.objects.get(username=response.content)
595 self.assertEquals(user.first_name, 'Some')
596 self.assertEquals(user.last_name, 'User')
597 self.assertEquals(user.email, 'foo@example.com')
598
599 def test_login_follow_rename_without_nickname_change(self):
600 settings.OPENID_FOLLOW_RENAMES = True
601 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
602 settings.OPENID_STRICT_USERNAMES = True
603 user = User.objects.create_user('testuser', 'someone@example.com')
604 useropenid = UserOpenID(
605 user=user,
606 claimed_id='http://example.com/identity',
607 display_id='http://example.com/identity')
608 useropenid.save()
609
610 openid_req = {'openid_identifier': 'http://example.com/identity',
611 'next': '/getuser/'}
612 openid_resp = {'nickname': 'testuser', 'fullname': 'Some User',
613 'email': 'foo@example.com'}
614 self._do_user_login(openid_req, openid_resp)
615 response = self.client.get('/getuser/')
616
617 # Username should not have changed
618 self.assertEquals(response.content, 'testuser')
619
620 # The user's full name and email have been updated.
621 user = User.objects.get(username=response.content)
622 self.assertEquals(user.first_name, 'Some')
623 self.assertEquals(user.last_name, 'User')
624 self.assertEquals(user.email, 'foo@example.com')
625
626 def test_login_follow_rename_conflict(self):
627 settings.OPENID_FOLLOW_RENAMES = True
628 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
629 # Setup existing user who's name we're going to switch to
630 user = User.objects.create_user('testuser', 'someone@example.com')
631 UserOpenID.objects.get_or_create(
632 user=user,
633 claimed_id='http://example.com/existing_identity',
634 display_id='http://example.com/existing_identity')
635
636 # Setup user who is going to try to change username to 'testuser'
637 renamed_user = User.objects.create_user('renameuser', 'someone@example.com')
638 UserOpenID.objects.get_or_create(
639 user=renamed_user,
640 claimed_id='http://example.com/identity',
641 display_id='http://example.com/identity')
642
643 # identity url is for 'renameuser'
644 openid_req = {'openid_identifier': 'http://example.com/identity',
645 'next': '/getuser/'}
646 # but returned username is for 'testuser', which already exists for another identity
647 openid_resp = {'nickname': 'testuser', 'fullname': 'Rename User',
648 'email': 'rename@example.com'}
649 self._do_user_login(openid_req, openid_resp)
650 response = self.client.get('/getuser/')
651
652 # If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser'
653 # but since that username is already taken by someone else, we go through
654 # the process of adding +i to it, and get testuser2.
655 self.assertEquals(response.content, 'testuser2')
656
657 # The user's full name and email have been updated.
658 user = User.objects.get(username=response.content)
659 self.assertEquals(user.first_name, 'Rename')
660 self.assertEquals(user.last_name, 'User')
661 self.assertEquals(user.email, 'rename@example.com')
662
663 def test_login_follow_rename_false_onlyonce(self):
664 settings.OPENID_FOLLOW_RENAMES = True
665 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
666 # Setup existing user who's name we're going to switch to
667 user = User.objects.create_user('testuser', 'someone@example.com')
668 UserOpenID.objects.get_or_create(
669 user=user,
670 claimed_id='http://example.com/existing_identity',
671 display_id='http://example.com/existing_identity')
672
673 # Setup user who is going to try to change username to 'testuser'
674 renamed_user = User.objects.create_user('testuser2000eight', 'someone@example.com')
675 UserOpenID.objects.get_or_create(
676 user=renamed_user,
677 claimed_id='http://example.com/identity',
678 display_id='http://example.com/identity')
679
680 # identity url is for 'testuser2000eight'
681 openid_req = {'openid_identifier': 'http://example.com/identity',
682 'next': '/getuser/'}
683 # but returned username is for 'testuser', which already exists for another identity
684 openid_resp = {'nickname': 'testuser2', 'fullname': 'Rename User',
685 'email': 'rename@example.com'}
686 self._do_user_login(openid_req, openid_resp)
687 response = self.client.get('/getuser/')
688
689 # If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser'
690 # but since that username is already taken by someone else, we go through
691 # the process of adding +i to it. Even though it looks like the username
692 # follows the nickname+i scheme, it has non-numbers in the suffix, so
693 # it's not an auto-generated one. The regular process of renaming to
694 # 'testuser' has a conflict, so we get +2 at the end.
695 self.assertEquals(response.content, 'testuser2')
696
697 # The user's full name and email have been updated.
698 user = User.objects.get(username=response.content)
699 self.assertEquals(user.first_name, 'Rename')
700 self.assertEquals(user.last_name, 'User')
701 self.assertEquals(user.email, 'rename@example.com')
702
703 def test_login_follow_rename_conflict_onlyonce(self):
704 settings.OPENID_FOLLOW_RENAMES = True
705 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
706 # Setup existing user who's name we're going to switch to
707 user = User.objects.create_user('testuser', 'someone@example.com')
708 UserOpenID.objects.get_or_create(
709 user=user,
710 claimed_id='http://example.com/existing_identity',
711 display_id='http://example.com/existing_identity')
712
713 # Setup user who is going to try to change username to 'testuser'
714 renamed_user = User.objects.create_user('testuser2000', 'someone@example.com')
715 UserOpenID.objects.get_or_create(
716 user=renamed_user,
717 claimed_id='http://example.com/identity',
718 display_id='http://example.com/identity')
719
720 # identity url is for 'testuser2000'
721 openid_req = {'openid_identifier': 'http://example.com/identity',
722 'next': '/getuser/'}
723 # but returned username is for 'testuser', which already exists for another identity
724 openid_resp = {'nickname': 'testuser', 'fullname': 'Rename User',
725 'email': 'rename@example.com'}
726 self._do_user_login(openid_req, openid_resp)
727 response = self.client.get('/getuser/')
728
729 # If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser'
730 # but since that username is already taken by someone else, we go through
731 # the process of adding +i to it. Since the user for this identity url
732 # already has a name matching that pattern, check if first.
733 self.assertEquals(response.content, 'testuser2000')
734
735 # The user's full name and email have been updated.
736 user = User.objects.get(username=response.content)
737 self.assertEquals(user.first_name, 'Rename')
738 self.assertEquals(user.last_name, 'User')
739 self.assertEquals(user.email, 'rename@example.com')
740
741 def test_login_follow_rename_false_conflict(self):
742 settings.OPENID_FOLLOW_RENAMES = True
743 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
744 # Setup existing user who's username matches the name+i pattern
745 user = User.objects.create_user('testuser2', 'someone@example.com')
746 UserOpenID.objects.get_or_create(
747 user=user,
748 claimed_id='http://example.com/identity',
749 display_id='http://example.com/identity')
750
751 # identity url is for 'testuser2'
752 openid_req = {'openid_identifier': 'http://example.com/identity',
753 'next': '/getuser/'}
754 # but returned username is for 'testuser', which looks like we've done
755 # a username+1 for them already, but 'testuser' isn't actually taken
756 openid_resp = {'nickname': 'testuser', 'fullname': 'Same User',
757 'email': 'same@example.com'}
758 self._do_user_login(openid_req, openid_resp)
759 response = self.client.get('/getuser/')
760
761 # If OPENID_FOLLOW_RENAMES, username should be changed to 'testuser'
762 # because it wasn't currently taken
763 self.assertEquals(response.content, 'testuser')
764
765 # The user's full name and email have been updated.
766 user = User.objects.get(username=response.content)
767 self.assertEquals(user.first_name, 'Same')
768 self.assertEquals(user.last_name, 'User')
769 self.assertEquals(user.email, 'same@example.com')
770
771 def test_strict_username_no_nickname(self):
772 settings.OPENID_CREATE_USERS = True
773 settings.OPENID_STRICT_USERNAMES = True
774 settings.OPENID_SREG_REQUIRED_FIELDS = []
775
776 # Posting in an identity URL begins the authentication request:
777 response = self.client.post('/openid/login/',
778 {'openid_identifier': 'http://example.com/identity',
779 'next': '/getuser/'})
780 self.assertContains(response, 'OpenID transaction in progress')
781
782 # Complete the request, passing back some simple registration
783 # data. The user is redirected to the next URL.
784 openid_request = self.provider.parseFormPost(response.content)
785 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
786 openid_response = openid_request.answer(True)
787 sreg_response = sreg.SRegResponse.extractResponse(
788 sreg_request, {'nickname': '', # No nickname
789 'fullname': 'Some User',
790 'email': 'foo@example.com'})
791 openid_response.addExtension(sreg_response)
792 response = self.complete(openid_response)
793
794 # Status code should be 403: Forbidden
795 self.assertEquals(403, response.status_code)
796 self.assertContains(response, '<h1>OpenID failed</h1>', status_code=403)
797 self.assertContains(response, "An attribute required for logging in was not returned "
798 "(nickname)", status_code=403)
799
800 def test_strict_username_no_nickname_override(self):
801 settings.OPENID_CREATE_USERS = True
802 settings.OPENID_STRICT_USERNAMES = True
803 settings.OPENID_SREG_REQUIRED_FIELDS = []
804
805 # Override the login_failure handler
806 def mock_login_failure_handler(request, message, status=403,
807 template_name=None,
808 exception=None):
809 self.assertTrue(isinstance(exception, (RequiredAttributeNotReturned, MissingUsernameViolation)))
810 return HttpResponse('Test Failure Override', status=200)
811 settings.OPENID_RENDER_FAILURE = mock_login_failure_handler
812
813 # Posting in an identity URL begins the authentication request:
814 response = self.client.post('/openid/login/',
815 {'openid_identifier': 'http://example.com/identity',
816 'next': '/getuser/'})
817 self.assertContains(response, 'OpenID transaction in progress')
818
819 # Complete the request, passing back some simple registration
820 # data. The user is redirected to the next URL.
821 openid_request = self.provider.parseFormPost(response.content)
822 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
823 openid_response = openid_request.answer(True)
824 sreg_response = sreg.SRegResponse.extractResponse(
825 sreg_request, {'nickname': '', # No nickname
826 'fullname': 'Some User',
827 'email': 'foo@example.com'})
828 openid_response.addExtension(sreg_response)
829 response = self.complete(openid_response)
830
831 # Status code should be 200, since we over-rode the login_failure handler
832 self.assertEquals(200, response.status_code)
833 self.assertContains(response, 'Test Failure Override')
834
835 def test_strict_username_duplicate_user(self):
836 settings.OPENID_CREATE_USERS = True
837 settings.OPENID_STRICT_USERNAMES = True
838 # Create a user with the same name as we'll pass back via sreg.
839 user = User.objects.create_user('someuser', 'someone@example.com')
840 useropenid = UserOpenID(
841 user=user,
842 claimed_id='http://example.com/different_identity',
843 display_id='http://example.com/different_identity')
844 useropenid.save()
845
846 # Posting in an identity URL begins the authentication request:
847 response = self.client.post('/openid/login/',
848 {'openid_identifier': 'http://example.com/identity',
849 'next': '/getuser/'})
850 self.assertContains(response, 'OpenID transaction in progress')
851
852 # Complete the request, passing back some simple registration
853 # data. The user is redirected to the next URL.
854 openid_request = self.provider.parseFormPost(response.content)
855 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
856 openid_response = openid_request.answer(True)
857 sreg_response = sreg.SRegResponse.extractResponse(
858 sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
859 'email': 'foo@example.com'})
860 openid_response.addExtension(sreg_response)
861 response = self.complete(openid_response)
862
863 # Status code should be 403: Forbidden
864 self.assertEquals(403, response.status_code)
865 self.assertContains(response, '<h1>OpenID failed</h1>', status_code=403)
866 self.assertContains(response,
867 "The username (someuser) with which you tried to log in is "
868 "already in use for a different account.",
869 status_code=403)
870
871 def test_strict_username_duplicate_user_override(self):
872 settings.OPENID_CREATE_USERS = True
873 settings.OPENID_STRICT_USERNAMES = True
874
875 # Override the login_failure handler
876 def mock_login_failure_handler(request, message, status=403,
877 template_name=None,
878 exception=None):
879 self.assertTrue(isinstance(exception, DuplicateUsernameViolation))
880 return HttpResponse('Test Failure Override', status=200)
881 settings.OPENID_RENDER_FAILURE = mock_login_failure_handler
882
883 # Create a user with the same name as we'll pass back via sreg.
884 user = User.objects.create_user('someuser', 'someone@example.com')
885 useropenid = UserOpenID(
886 user=user,
887 claimed_id='http://example.com/different_identity',
888 display_id='http://example.com/different_identity')
889 useropenid.save()
890
891 # Posting in an identity URL begins the authentication request:
892 response = self.client.post('/openid/login/',
893 {'openid_identifier': 'http://example.com/identity',
894 'next': '/getuser/'})
895 self.assertContains(response, 'OpenID transaction in progress')
896
897 # Complete the request, passing back some simple registration
898 # data. The user is redirected to the next URL.
899 openid_request = self.provider.parseFormPost(response.content)
900 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
901 openid_response = openid_request.answer(True)
902 sreg_response = sreg.SRegResponse.extractResponse(
903 sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
904 'email': 'foo@example.com'})
905 openid_response.addExtension(sreg_response)
906 response = self.complete(openid_response)
907
908 # Status code should be 200, since we over-rode the login_failure handler
909 self.assertEquals(200, response.status_code)
910 self.assertContains(response, 'Test Failure Override')
911
912 def test_login_requires_sreg_required_fields(self):
913 # If any required attributes are not included in the response,
914 # we fail with a forbidden.
915 settings.OPENID_CREATE_USERS = True
916 settings.OPENID_SREG_REQUIRED_FIELDS = ('email', 'language')
917 # Posting in an identity URL begins the authentication request:
918 response = self.client.post('/openid/login/',
919 {'openid_identifier': 'http://example.com/identity',
920 'next': '/getuser/'})
921 self.assertContains(response, 'OpenID transaction in progress')
922
923 # Complete the request, passing back some simple registration
924 # data. The user is redirected to the next URL.
925 openid_request = self.provider.parseFormPost(response.content)
926 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
927 openid_response = openid_request.answer(True)
928 sreg_response = sreg.SRegResponse.extractResponse(
929 sreg_request, {'nickname': 'foo',
930 'fullname': 'Some User',
931 'email': 'foo@example.com'})
932 openid_response.addExtension(sreg_response)
933 response = self.complete(openid_response)
934
935 # Status code should be 403: Forbidden as we didn't include
936 # a required field - language.
937 self.assertContains(response,
938 "An attribute required for logging in was not returned "
939 "(language)", status_code=403)
940
941 def test_login_update_details(self):
942 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
943 user = User.objects.create_user('testuser', 'someone@example.com')
944 useropenid = UserOpenID(
945 user=user,
946 claimed_id='http://example.com/identity',
947 display_id='http://example.com/identity')
948 useropenid.save()
949
950 openid_req = {'openid_identifier': 'http://example.com/identity',
951 'next': '/getuser/'}
952 openid_resp = {'nickname': 'testuser', 'fullname': 'Some User',
953 'email': 'foo@example.com'}
954 self._do_user_login(openid_req, openid_resp)
955 response = self.client.get('/getuser/')
956
957 self.assertEquals(response.content, 'testuser')
958
959 # The user's full name and email have been updated.
960 user = User.objects.get(username=response.content)
961 self.assertEquals(user.first_name, 'Some')
962 self.assertEquals(user.last_name, 'User')
963 self.assertEquals(user.email, 'foo@example.com')
964
965 def test_login_uses_sreg_extra_fields(self):
966 # The configurable sreg attributes are used in the request.
967 settings.OPENID_SREG_EXTRA_FIELDS = ('language',)
968 user = User.objects.create_user('testuser', 'someone@example.com')
969 useropenid = UserOpenID(
970 user=user,
971 claimed_id='http://example.com/identity',
972 display_id='http://example.com/identity')
973 useropenid.save()
974
975 # Posting in an identity URL begins the authentication request:
976 response = self.client.post('/openid/login/',
977 {'openid_identifier': 'http://example.com/identity',
978 'next': '/getuser/'})
979
980 openid_request = self.provider.parseFormPost(response.content)
981 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
982 for field in ('email', 'fullname', 'nickname', 'language'):
983 self.assertTrue(field in sreg_request)
984
985 def test_login_uses_sreg_required_fields(self):
986 # The configurable sreg attributes are used in the request.
987 settings.OPENID_SREG_REQUIRED_FIELDS = ('email', 'language')
988 user = User.objects.create_user('testuser', 'someone@example.com')
989 useropenid = UserOpenID(
990 user=user,
991 claimed_id='http://example.com/identity',
992 display_id='http://example.com/identity')
993 useropenid.save()
994
995 # Posting in an identity URL begins the authentication request:
996 response = self.client.post('/openid/login/',
997 {'openid_identifier': 'http://example.com/identity',
998 'next': '/getuser/'})
999
1000 openid_request = self.provider.parseFormPost(response.content)
1001 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
1002
1003 self.assertEqual(['email', 'language'], sreg_request.required)
1004 self.assertEqual(['fullname', 'nickname'], sreg_request.optional)
1005
1006 def test_login_attribute_exchange(self):
1007 settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
1008 user = User.objects.create_user('testuser', 'someone@example.com')
1009 useropenid = UserOpenID(
1010 user=user,
1011 claimed_id='http://example.com/identity',
1012 display_id='http://example.com/identity')
1013 useropenid.save()
1014
1015 # Configure the provider to advertise attribute exchange
1016 # protocol and start the authentication process:
1017 self.provider.type_uris.append('http://openid.net/srv/ax/1.0')
1018 response = self.client.post('/openid/login/',
1019 {'openid_identifier': 'http://example.com/identity',
1020 'next': '/getuser/'})
1021 self.assertContains(response, 'OpenID transaction in progress')
1022
1023 # The resulting OpenID request uses the Attribute Exchange
1024 # extension rather than the Simple Registration extension.
1025 openid_request = self.provider.parseFormPost(response.content)
1026 sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
1027 self.assertEqual(sreg_request.required, [])
1028 self.assertEqual(sreg_request.optional, [])
1029
1030 fetch_request = ax.FetchRequest.fromOpenIDRequest(openid_request)
1031 self.assertTrue(fetch_request.has_key(
1032 'http://axschema.org/contact/email'))
1033 self.assertTrue(fetch_request.has_key(
1034 'http://axschema.org/namePerson'))
1035 self.assertTrue(fetch_request.has_key(
1036 'http://axschema.org/namePerson/first'))
1037 self.assertTrue(fetch_request.has_key(
1038 'http://axschema.org/namePerson/last'))
1039 self.assertTrue(fetch_request.has_key(
1040 'http://axschema.org/namePerson/friendly'))
1041 # myOpenID compatibilty attributes:
1042 self.assertTrue(fetch_request.has_key(
1043 'http://schema.openid.net/contact/email'))
1044 self.assertTrue(fetch_request.has_key(
1045 'http://schema.openid.net/namePerson'))
1046 self.assertTrue(fetch_request.has_key(
1047 'http://schema.openid.net/namePerson/friendly'))
1048
1049 # Build up a response including AX data.
1050 openid_response = openid_request.answer(True)
1051 fetch_response = ax.FetchResponse(fetch_request)
1052 fetch_response.addValue(
1053 'http://axschema.org/contact/email', 'foo@example.com')
1054 fetch_response.addValue(
1055 'http://axschema.org/namePerson/first', 'Firstname')
1056 fetch_response.addValue(
1057 'http://axschema.org/namePerson/last', 'Lastname')
1058 fetch_response.addValue(
1059 'http://axschema.org/namePerson/friendly', 'someuser')
1060 openid_response.addExtension(fetch_response)
1061 response = self.complete(openid_response)
1062 self.assertRedirects(response, 'http://testserver/getuser/')
1063
1064 # And they are now logged in as testuser (the passed in
1065 # nickname has not caused the username to change).
1066 response = self.client.get('/getuser/')
1067 self.assertEquals(response.content, 'testuser')
1068
1069 # The user's full name and email have been updated.
1070 user = User.objects.get(username='testuser')
1071 self.assertEquals(user.first_name, 'Firstname')
1072 self.assertEquals(user.last_name, 'Lastname')
1073 self.assertEquals(user.email, 'foo@example.com')
1074
1075 def test_login_teams(self):
1076 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = False
1077 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname',
1078 'otherteam': 'othergroup'}
1079 user = User.objects.create_user('testuser', 'someone@example.com')
1080 group = Group(name='groupname')
1081 group.save()
1082 ogroup = Group(name='othergroup')
1083 ogroup.save()
1084 user.groups.add(ogroup)
1085 user.save()
1086 useropenid = UserOpenID(
1087 user=user,
1088 claimed_id='http://example.com/identity',
1089 display_id='http://example.com/identity')
1090 useropenid.save()
1091
1092 # Posting in an identity URL begins the authentication request:
1093 response = self.client.post('/openid/login/',
1094 {'openid_identifier': 'http://example.com/identity',
1095 'next': '/getuser/'})
1096 self.assertContains(response, 'OpenID transaction in progress')
1097
1098 # Complete the request
1099 openid_request = self.provider.parseFormPost(response.content)
1100 openid_response = openid_request.answer(True)
1101 teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request)
1102 teams_response = teams.TeamsResponse.extractResponse(
1103 teams_request, 'teamname,some-other-team')
1104 openid_response.addExtension(teams_response)
1105 response = self.complete(openid_response)
1106 self.assertRedirects(response, 'http://testserver/getuser/')
1107
1108 # And they are now logged in as testuser
1109 response = self.client.get('/getuser/')
1110 self.assertEquals(response.content, 'testuser')
1111
1112 # The user's groups have been updated.
1113 user = User.objects.get(username='testuser')
1114 self.assertTrue(group in user.groups.all())
1115 self.assertTrue(ogroup not in user.groups.all())
1116
1117 def test_login_teams_automapping(self):
1118 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname',
1119 'otherteam': 'othergroup'}
1120 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = True
1121 settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST = ['django-group1', 'django-group2']
1122 user = User.objects.create_user('testuser', 'someone@example.com')
1123 group1 = Group(name='django-group1')
1124 group1.save()
1125 group2 = Group(name='django-group2')
1126 group2.save()
1127 group3 = Group(name='django-group3')
1128 group3.save()
1129 user.save()
1130 useropenid = UserOpenID(
1131 user=user,
1132 claimed_id='http://example.com/identity',
1133 display_id='http://example.com/identity')
1134 useropenid.save()
1135
1136 # Posting in an identity URL begins the authentication request:
1137 response = self.client.post('/openid/login/',
1138 {'openid_identifier': 'http://example.com/identity',
1139 'next': '/getuser/'})
1140 self.assertContains(response, 'OpenID transaction in progress')
1141
1142 # Complete the request
1143 openid_request = self.provider.parseFormPost(response.content)
1144 openid_response = openid_request.answer(True)
1145 teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request)
1146
1147 self.assertEqual(group1 in user.groups.all(), False)
1148 self.assertEqual(group2 in user.groups.all(), False)
1149 self.assertTrue(group3 not in user.groups.all())
1150
1151 def test_login_teams_staff_not_defined(self):
1152 delattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS')
1153 user = User.objects.create_user('testuser', 'someone@example.com')
1154 user.is_staff = True
1155 user.save()
1156 self.assertTrue(user.is_staff)
1157
1158 user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team')
1159 self.assertTrue(user.is_staff)
1160
1161 def test_login_teams_staff_assignment(self):
1162 settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('teamname',)
1163 user = User.objects.create_user('testuser', 'someone@example.com')
1164 user.is_staff = False
1165 user.save()
1166 self.assertFalse(user.is_staff)
1167
1168 user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team')
1169 self.assertTrue(user.is_staff)
1170
1171 def test_login_teams_staff_unassignment(self):
1172 settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('different-teamname',)
1173 user = User.objects.create_user('testuser', 'someone@example.com')
1174 user.is_staff = True
1175 user.save()
1176 self.assertTrue(user.is_staff)
1177
1178 user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team')
1179 self.assertFalse(user.is_staff)
1180
1181 def get_openid_authed_user_with_teams(self, user, teams_str):
1182 useropenid = UserOpenID(
1183 user=user,
1184 claimed_id='http://example.com/identity',
1185 display_id='http://example.com/identity')
1186 useropenid.save()
1187
1188 # Posting in an identity URL begins the authentication request:
1189 response = self.client.post('/openid/login/',
1190 {'openid_identifier': 'http://example.com/identity'})
1191
1192 # Complete the request
1193 openid_request = self.provider.parseFormPost(response.content)
1194 openid_response = openid_request.answer(True)
1195 teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request)
1196 teams_response = teams.TeamsResponse.extractResponse(
1197 teams_request, teams_str)
1198 openid_response.addExtension(teams_response)
1199 response = self.complete(openid_response)
1200 return User.objects.get(username=user.username)
1201
1202 def test_login_complete_signals_login(self):
1203 # An oauth_login_complete signal is emitted including the
1204 # request and sreg_response.
1205 user = User.objects.create_user('someuser', 'someone@example.com')
1206 useropenid = UserOpenID(
1207 user=user,
1208 claimed_id='http://example.com/identity',
1209 display_id='http://example.com/identity')
1210 useropenid.save()
1211 response = self.client.post('/openid/login/',
1212 {'openid_identifier': 'http://example.com/identity'})
1213 openid_request = self.provider.parseFormPost(response.content)
1214 openid_response = openid_request.answer(True)
1215 # Use a closure to test whether the signal handler was called.
1216 self.signal_handler_called = False
1217 def login_callback(sender, **kwargs):
1218 self.assertTrue(isinstance(
1219 kwargs.get('request', None), HttpRequest))
1220 self.assertTrue(isinstance(
1221 kwargs.get('openid_response', None), SuccessResponse))
1222 self.signal_handler_called = True
1223 openid_login_complete.connect(login_callback)
1224
1225 response = self.complete(openid_response)
1226
1227 self.assertTrue(self.signal_handler_called)
1228 openid_login_complete.disconnect(login_callback)
1229
1230
1231class HelperFunctionsTest(TestCase):
1232 def test_sanitise_redirect_url(self):
1233 settings.ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = [
1234 "example.com", "example.org"]
1235 # list of URLs and whether they should be passed or not
1236 urls = [
1237 ("http://example.com", True),
1238 ("http://example.org/", True),
1239 ("http://example.org/foo/bar", True),
1240 ("http://example.org/foo/bar?baz=quux", True),
1241 ("http://example.org:9999/foo/bar?baz=quux", True),
1242 ("http://www.example.org/", False),
1243 ("http://example.net/foo/bar?baz=quux", False),
1244 ("/somewhere/local", True),
1245 ("/somewhere/local?url=http://fail.com/bar", True),
1246 # An empty path, as seen when no "next" parameter is passed.
1247 ("", False),
1248 ("/path with spaces", False),
1249 ]
1250 for url, returns_self in urls:
1251 sanitised = sanitise_redirect_url(url)
1252 if returns_self:
1253 self.assertEqual(url, sanitised)
1254 else:
1255 self.assertEqual(settings.LOGIN_REDIRECT_URL, sanitised)
1256
1257def suite():
1258 return unittest.TestLoader().loadTestsFromName(__name__)
12590
=== removed file 'django_openid_auth/tests/test_views.pyc'
1260Binary 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 differ1Binary 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
=== modified file 'django_openid_auth/tests/urls.py'
--- django_openid_auth/tests/urls.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/tests/urls.py 2013-03-26 11:57:40 +0000
@@ -1,6 +1,6 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2009-2010 Canonical Ltd.3# Copyright (C) 2009-2013 Canonical Ltd.
4#4#
5# Redistribution and use in source and binary forms, with or without5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions6# modification, are permitted provided that the following conditions
77
=== removed file 'django_openid_auth/tests/urls.pyc'
8Binary 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 differ8Binary 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
=== modified file 'django_openid_auth/urls.py'
--- django_openid_auth/urls.py 2010-12-03 00:46:25 +0000
+++ django_openid_auth/urls.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== removed file 'django_openid_auth/urls.pyc'
9Binary 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 differ9Binary 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
=== modified file 'django_openid_auth/views.py'
--- django_openid_auth/views.py 2012-06-27 08:25:19 +0000
+++ django_openid_auth/views.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
@@ -244,7 +244,11 @@
244 return_to += '&'244 return_to += '&'
245 else:245 else:
246 return_to += '?'246 return_to += '?'
247 return_to += urllib.urlencode({redirect_field_name: redirect_to})247 # Django gives us Unicode, which is great. We must encode URI.
248 # urllib enforces str. We can't trust anything about the default
249 # encoding inside str(foo) , so we must explicitly make foo a str.
250 return_to += urllib.urlencode(
251 {redirect_field_name: redirect_to.encode("UTF-8")})
248252
249 return render_openid_request(request, openid_request, return_to)253 return render_openid_request(request, openid_request, return_to)
250254
251255
=== removed file 'django_openid_auth/views.pyc'
252Binary 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 differ256Binary 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
=== modified file 'example_consumer/__init__.py'
--- example_consumer/__init__.py 2010-12-03 00:46:25 +0000
+++ example_consumer/__init__.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== modified file 'example_consumer/settings.py'
--- example_consumer/settings.py 2012-06-27 08:25:19 +0000
+++ example_consumer/settings.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
@@ -28,7 +28,8 @@
28# POSSIBILITY OF SUCH DAMAGE.28# POSSIBILITY OF SUCH DAMAGE.
2929
30# Django settings for example project.30# Django settings for example project.
3131import django
32django_version = django.get_version()
32DEBUG = True33DEBUG = True
33TEMPLATE_DEBUG = DEBUG34TEMPLATE_DEBUG = DEBUG
3435
@@ -38,13 +39,30 @@
3839
39MANAGERS = ADMINS40MANAGERS = ADMINS
4041
41DATABASES = {42if django_version >= "1.2":
42 'default': {43 csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware'
43 'ENGINE': 'django.db.backends.sqlite3',44 DATABASES = {
44 'NAME': 'example_consumer/test.db3',45 'default': {
45 }46 'ENGINE': 'django.db.backends.sqlite3',
46}47 'NAME': 'sqlite.db'
4748 }
49 }
50 TEMPLATE_LOADERS = (
51 'django.template.loaders.filesystem.Loader',
52 'django.template.loaders.app_directories.Loader',
53 )
54else:
55 csrf_middleware = 'django.contrib.csrf.middleware.CsrfViewMiddleware'
56 TEMPLATE_LOADERS = (
57 'django.template.loaders.filesystem.load_template_source',
58 'django.template.loaders.app_directories.load_template_source',
59 )
60 DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
61 DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3.
62 DATABASE_USER = '' # Not used with sqlite3.
63 DATABASE_PASSWORD = '' # Not used with sqlite3.
64 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
65 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
4866
49# Local time zone for this installation. Choices can be found here:67# Local time zone for this installation. Choices can be found here:
50# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE68# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
@@ -80,23 +98,6 @@
80# Make this unique, and don't share it with anybody.98# Make this unique, and don't share it with anybody.
81SECRET_KEY = '34958734985734985734985798437'99SECRET_KEY = '34958734985734985734985798437'
82100
83# List of callables that know how to import templates from various sources.
84TEMPLATE_LOADERS = (
85 'django.template.loaders.filesystem.Loader',
86 'django.template.loaders.app_directories.Loader',
87# 'django.template.loaders.eggs.Loader',
88)
89
90# django-openid-auth will *not* work with Django 1.1.1 or older, as it's
91# missing the csrf_token template tag. This will allow it to work with
92# Django 1.1.2 or later:
93try:
94 import django.middleware.csrf
95except ImportError:
96 csrf_middleware = 'django.contrib.csrf.middleware.CsrfViewMiddleware'
97else:
98 csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware'
99
100MIDDLEWARE_CLASSES = (101MIDDLEWARE_CLASSES = (
101 'django.middleware.common.CommonMiddleware',102 'django.middleware.common.CommonMiddleware',
102 'django.contrib.sessions.middleware.SessionMiddleware',103 'django.contrib.sessions.middleware.SessionMiddleware',
103104
=== modified file 'example_consumer/urls.py'
--- example_consumer/urls.py 2010-12-03 00:46:25 +0000
+++ example_consumer/urls.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
@@ -41,5 +41,5 @@
41 (r'^logout/$', 'django.contrib.auth.views.logout'),41 (r'^logout/$', 'django.contrib.auth.views.logout'),
42 (r'^private/$', views.require_authentication),42 (r'^private/$', views.require_authentication),
4343
44 (r'^admin/(.*)', admin.site.root),44 (r'^admin/', include(admin.site.urls)),
45)45)
4646
=== modified file 'example_consumer/views.py'
--- example_consumer/views.py 2010-12-03 00:46:25 +0000
+++ example_consumer/views.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1# django-openid-auth - OpenID integration for django.contrib.auth1# django-openid-auth - OpenID integration for django.contrib.auth
2#2#
3# Copyright (C) 2007 Simon Willison3# Copyright (C) 2007 Simon Willison
4# Copyright (C) 2008-2010 Canonical Ltd.4# Copyright (C) 2008-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
88
=== removed file 'openid.html'
--- openid.html 2012-06-27 08:25:19 +0000
+++ openid.html 1970-01-01 00:00:00 +0000
@@ -1,150 +0,0 @@
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
2 "http://www.w3.org/TR/html4/strict.dtd">
3<html>
4<head><title>OpenID in Django</title></head>
5<body>
6<h1>OpenID in Django</h1>
7<p>The <tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt> package contains all of the code needed to set up
8your Django application as an OpenID consumer. You can use it to allow OpenID
9users to sign in to your site without having to create a new username and
10password.</p>
11<div class="section">
12<h2><a id="overview">Overview</a></h2>
13<p>The OpenID consumer system consists of:</p>
14<ul class="simple">
15<li>Views for you to hook in to your application.</li>
16<li>Database models implementing the persistence layer of an OpenID consumer.</li>
17<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>
18properties available to your application views.</li>
19</ul>
20</div>
21<div class="section">
22<h2><a id="dependencies">Dependencies</a></h2>
23<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
24installed separately somewhere on the Python path. You should install the 1.2.0
25&#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>
26<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>
27</div>
28<div class="section">
29<h2><a id="installation">Installation</a></h2>
30<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
31add OpenID consumer support to an application by doing the following:</p>
32<ol class="arabic">
33<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>
34</li>
35<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>
36</li>
37<li><p class="first">Add <tt class="docutils literal"><span class="pre">django_openidconsumer.middleware.OpenIDMiddleware</span></tt> to your list
38of <tt class="docutils literal"><span class="pre">MIDDLEWARE_CLASSES</span></tt>, somewhere after the Session middleware.</p>
39</li>
40<li><p class="first">Add the following views to your urlconf:</p>
41<pre class="literal-block">
42(r'^openid/$', 'django_openidconsumer.views.begin'),
43(r'^openid/complete/$', 'django_openidconsumer.views.complete'),
44(r'^openid/signout/$', 'django_openidconsumer.views.signout'),
45</pre>
46</li>
47</ol>
48<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
49an OpenID.</p>
50</div>
51<div class="section">
52<h2><a id="using-the-openid-middleware">Using the OpenID middleware</a></h2>
53<p>With the Middleware installed, your views will have access to the user&#8217;s OpenID
54as 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
55yet authenticated; otherwise it will be a <tt class="docutils literal"><span class="pre">django_openidconsumer.util.OpenID</span></tt>
56instance.</p>
57<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
58OpenID instance:</p>
59<pre class="literal-block">
60def example_view(request):
61 if request.openid:
62 return HttpResponse(&quot;OpenID is %s&quot; % escape(str(request.openid)))
63 else:
64 return HttpResponse(&quot;No OpenID&quot;)
65</pre>
66<p>Users can sign in with more than one OpenID. This is supported by the
67<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
68in which they were authenticated. <tt class="docutils literal"><span class="pre">request.openid</span></tt> merely returns the last
69item in this list.</p>
70</div>
71<div class="section">
72<h2><a id="using-simple-registration">Using simple registration</a></h2>
73<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
74that allows you to request extra details about a user from their OpenID
75provider. It is frequently used to pre-populate registration forms with
76information such as the user&#8217;s name, e-mail address or date of birth.</p>
77<p>Be aware that not all OpenID providers support sreg, and there is no guarantee
78that the information you have requested will be returned. Simple registration
79should be used as a convenience for your users rather than as a required step in
80your authentication process.</p>
81<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>,
82<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>.
83Full details are available in the <a class="reference" href="http://openid.net/specs/openid-simple-registration-extension-1_0.html">spec</a>.</p>
84<p>To request this information, pass the fields that you wish to retrieve as an
85additional <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>
86<pre class="literal-block">
87(r'^openid/$', 'django_openidconsumer.views.begin', {
88 'sreg': 'email,nickname'
89}),
90</pre>
91<p>Any simple registration fields that are returned will be available in a
92dictionary as the <tt class="docutils literal"><span class="pre">sreg</span></tt> property of the OpenID object:</p>
93<pre class="literal-block">
94def example_sreg(request):
95 if request.openid and request.openid.sreg.has_key('email'):
96 return HttpResponse(&quot;Your e-mail address is: %s&quot; % escape(
97 request.openid.sreg['email']
98 ))
99 else:
100 return HttpResponse(&quot;No e-mail address&quot;)
101</pre>
102</div>
103<div class="section">
104<h2><a id="customisation">Customisation</a></h2>
105<p><tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt> uses two templates:</p>
106<dl class="docutils">
107<dt><tt class="docutils literal"><span class="pre">openid_signin.html</span></tt></dt>
108<dd>The form presented to the user when they sign in.</dd>
109<dt><tt class="docutils literal"><span class="pre">openid_failure.html</span></tt></dt>
110<dd>The template used to display an error message when something goes wrong.</dd>
111</dl>
112<p>You can over-ride the default templates by creating templates of the same name
113and placing them somewhere on your template path. You can find the example
114templates in the <tt class="docutils literal"><span class="pre">django_openidconsumer/templates</span></tt> directory.</p>
115<p>The OpenID specification strongly recommends that any OpenID registration form
116has 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
117displays the <a class="reference" href="http://openid.net/login-bg.gif">OpenID logo</a> inline in the form field using the following CSS:</p>
118<pre class="literal-block">
119input.openid {
120 background: url(/path/to/login-bg.gif) no-repeat;
121 background-position: 0 50%;
122 padding-left: 16px;
123}
124</pre>
125<p>By default, the package expects the <tt class="docutils literal"><span class="pre">django_openidconsumer.views.complete</span></tt>
126view to be located at <tt class="docutils literal"><span class="pre">/openid/complete/</span></tt>. This is the view that the OpenID
127provider will redirect the user to after they have authenticated. If you want to
128put it somewhere else you can either pass an extra <tt class="docutils literal"><span class="pre">redirect_to</span></tt> argument to
129<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
130to <tt class="docutils literal"><span class="pre">settings.py</span></tt>.</p>
131<p>You can pass a <tt class="docutils literal"><span class="pre">?next=</span></tt> query string argument containing a relative URL to
132the <tt class="docutils literal"><span class="pre">begin</span></tt> view to control where the user will be redirected to having
133returned to your site. You can also set the default redirection location
134using the <tt class="docutils literal"><span class="pre">OPENID_REDIRECT_NEXT</span></tt> setting; if you do set set a default the user
135will be redirected to your homepage.</p>
136</div>
137<div class="section">
138<h2><a id="i-names">i-names</a></h2>
139<p><a class="reference" href="http://www.inames.net/">i-names</a> are part of the OpenID 2.0 specification, which is currently being
140developed. They are supported by the python-openid library, and hence are also
141supported by <tt class="docutils literal"><span class="pre">django_openidconsumer</span></tt>. You can tell if an OpenID is an i-name
142by checking the <tt class="docutils literal"><span class="pre">request.openid.is_iname</span></tt> property.</p>
143<p>If you wish to disable i-name support, you can do so by adding the following to
144your <tt class="docutils literal"><span class="pre">settings.py</span></tt>:</p>
145<pre class="literal-block">
146OPENID_DISALLOW_INAMES = True
147</pre>
148</div>
149</body>
150</html>
151\ No newline at end of file0\ No newline at end of file
1521
=== removed file 'openid.txt'
--- openid.txt 2012-06-27 08:25:19 +0000
+++ openid.txt 1970-01-01 00:00:00 +0000
@@ -1,165 +0,0 @@
1================
2OpenID in Django
3================
4
5The ``django_openidconsumer`` package contains all of the code needed to set up
6your Django application as an OpenID consumer. You can use it to allow OpenID
7users to sign in to your site without having to create a new username and
8password.
9
10Overview
11========
12
13The OpenID consumer system consists of:
14
15 * Views for you to hook in to your application.
16 * Database models implementing the persistence layer of an OpenID consumer.
17 * Middleware that makes ``request.openid`` and ``request.openids``
18 properties available to your application views.
19
20Dependencies
21============
22
23``django_openidconsumer`` uses the `python-openid library`_, which must be
24installed separately somewhere on the Python path. You should install the 1.2.0
25"combo" package which includes the ``yadis`` and ``urljr`` libraries.
26
27The package also depends on the availability of Django's `session support`_.
28
29.. _python-openid library: http://www.openidenabled.com/openid/libraries/python/
30.. _session support: http://www.djangoproject.com/documentation/sessions/
31
32Installation
33============
34
35Having ensured that both the ``python-openid`` library and the ``django_openidconsumer`` package are available on your Python path, you can
36add OpenID consumer support to an application by doing the following:
37
38 1. Put ``django_openidconsumer`` in your ``INSTALLED_APPS`` setting.
39 2. Run the command ``manage.py syncdb`` to create the necessary tables.
40 3. Add ``django_openidconsumer.middleware.OpenIDMiddleware`` to your list
41 of ``MIDDLEWARE_CLASSES``, somewhere after the Session middleware.
42 4. Add the following views to your urlconf::
43
44 (r'^openid/$', 'django_openidconsumer.views.begin'),
45 (r'^openid/complete/$', 'django_openidconsumer.views.complete'),
46 (r'^openid/signout/$', 'django_openidconsumer.views.signout'),
47
48You will then be able to browse to ``example.com/openid/`` and sign in using
49an OpenID.
50
51Using the OpenID middleware
52===========================
53
54With the Middleware installed, your views will have access to the user's OpenID
55as the ``request.openid`` property. This will be ``None`` if the user has not
56yet authenticated; otherwise it will be a ``django_openidconsumer.util.OpenID``
57instance.
58
59If you want the user's OpenID as a string, call the ``str()`` builtin on the
60OpenID instance::
61
62 def example_view(request):
63 if request.openid:
64 return HttpResponse("OpenID is %s" % escape(str(request.openid)))
65 else:
66 return HttpResponse("No OpenID")
67
68Users can sign in with more than one OpenID. This is supported by the
69``request.openids`` property, which is a list of ``OpenID`` objects in the order
70in which they were authenticated. ``request.openid`` merely returns the last
71item in this list.
72
73Using simple registration
74=========================
75
76Simple registration (or `sreg`_) is an extension to the OpenID specification
77that allows you to request extra details about a user from their OpenID
78provider. It is frequently used to pre-populate registration forms with
79information such as the user's name, e-mail address or date of birth.
80
81.. _sreg: http://openid.net/specs/openid-simple-registration-extension-1_0.html
82
83Be aware that not all OpenID providers support sreg, and there is no guarantee
84that the information you have requested will be returned. Simple registration
85should be used as a convenience for your users rather than as a required step in
86your authentication process.
87
88Available simple registration fields are ``nickname``, ``email``, ``fullname``,
89``dob``, ``gender``, ``postcode``, ``country``, ``language`` and ``timezone``.
90Full details are available in the `spec`_.
91
92.. _spec: http://openid.net/specs/openid-simple-registration-extension-1_0.html
93
94To request this information, pass the fields that you wish to retrieve as an
95additional ``sreg`` argument to the ``django_openidconsumer.views.begin`` view::
96
97 (r'^openid/$', 'django_openidconsumer.views.begin', {
98 'sreg': 'email,nickname'
99 }),
100
101Any simple registration fields that are returned will be available in a
102dictionary as the ``sreg`` property of the OpenID object::
103
104 def example_sreg(request):
105 if request.openid and request.openid.sreg.has_key('email'):
106 return HttpResponse("Your e-mail address is: %s" % escape(
107 request.openid.sreg['email']
108 ))
109 else:
110 return HttpResponse("No e-mail address")
111
112Customisation
113=============
114
115``django_openidconsumer`` uses two templates:
116
117``openid_signin.html``
118 The form presented to the user when they sign in.
119
120``openid_failure.html``
121 The template used to display an error message when something goes wrong.
122
123You can over-ride the default templates by creating templates of the same name
124and placing them somewhere on your template path. You can find the example
125templates in the ``django_openidconsumer/templates`` directory.
126
127The OpenID specification strongly recommends that any OpenID registration form
128has a ``name`` attribute of ``openid_url`` to aid browser autocompletion, and
129displays the `OpenID logo`_ inline in the form field using the following CSS::
130
131 input.openid {
132 background: url(/path/to/login-bg.gif) no-repeat;
133 background-position: 0 50%;
134 padding-left: 16px;
135 }
136
137.. _OpenID logo: http://openid.net/login-bg.gif
138
139By default, the package expects the ``django_openidconsumer.views.complete``
140view to be located at ``/openid/complete/``. This is the view that the OpenID
141provider will redirect the user to after they have authenticated. If you want to
142put it somewhere else you can either pass an extra ``redirect_to`` argument to
143``django_openidconsumer.views.begin`` or add an ``OPENID_REDIRECT_TO`` setting
144to ``settings.py``.
145
146You can pass a ``?next=`` query string argument containing a relative URL to
147the ``begin`` view to control where the user will be redirected to having
148returned to your site. You can also set the default redirection location
149using the ``OPENID_REDIRECT_NEXT`` setting; if you do set set a default the user
150will be redirected to your homepage.
151
152i-names
153=======
154
155`i-names`_ are part of the OpenID 2.0 specification, which is currently being
156developed. They are supported by the python-openid library, and hence are also
157supported by ``django_openidconsumer``. You can tell if an OpenID is an i-name
158by checking the ``request.openid.is_iname`` property.
159
160.. _i-names: http://www.inames.net/
161
162If you wish to disable i-name support, you can do so by adding the following to
163your ``settings.py``::
164
165 OPENID_DISALLOW_INAMES = True
1660
=== modified file 'setup.py'
--- setup.py 2012-06-27 08:25:19 +0000
+++ setup.py 2013-03-26 11:57:40 +0000
@@ -1,7 +1,7 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# django-openid-auth - OpenID integration for django.contrib.auth2# django-openid-auth - OpenID integration for django.contrib.auth
3#3#
4# Copyright (C) 2009-2010 Canonical Ltd.4# Copyright (C) 2009-2013 Canonical Ltd.
5#5#
6# Redistribution and use in source and binary forms, with or without6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions7# modification, are permitted provided that the following conditions
@@ -43,7 +43,7 @@
4343
4444
45description, long_description = __doc__.split('\n\n', 1)45description, long_description = __doc__.split('\n\n', 1)
46VERSION = '0.4'46VERSION = '0.5'
4747
48setup(48setup(
49 name='django-openid-auth',49 name='django-openid-auth',

Subscribers

People subscribed via source and target branches