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