django-openid-auth-0.5/ 0000775 0001750 0001750 00000000000 12120431074 016211 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/README.txt 0000664 0001750 0001750 00000016263 12120161623 017717 0 ustar anthony anthony 0000000 0000000 = Django OpenID Authentication Support =
This package provides integration between Django's authentication
system and OpenID authentication. It also includes support for using
a fixed OpenID server endpoint, which can be useful when implementing
single signon systems.
== Basic Installation ==
1. Install the Jan Rain Python OpenID library. It can be found at:
http://openidenabled.com/python-openid/
It can also be found in most Linux distributions packaged as
"python-openid". You will need version 2.2.0 or later.
2. Add 'django_openid_auth' to INSTALLED_APPS for your application.
At a minimum, you'll need the following in there:
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django_openid_auth',
)
3. Add 'django_auth_openid.auth.OpenIDBackend' to
AUTHENTICATION_BACKENDS. This should be in addition to the
default ModelBackend:
AUTHENTICATION_BACKENDS = (
'django_openid_auth.auth.OpenIDBackend',
'django.contrib.auth.backends.ModelBackend',
)
4. To create users automatically when a new OpenID is used, add the
following to the settings:
OPENID_CREATE_USERS = True
5. To have user details updated from OpenID Simple Registration or
Attribute Exchange extension data each time they log in, add the
following:
OPENID_UPDATE_DETAILS_FROM_SREG = True
6. Hook up the login URLs to your application's urlconf with
something like:
urlpatterns = patterns('',
...
(r'^openid/', include('django_openid_auth.urls')),
...
)
7. Configure the LOGIN_URL and LOGIN_REDIRECT_URL appropriately for
your site:
LOGIN_URL = '/openid/login/'
LOGIN_REDIRECT_URL = '/'
This will allow pages that use the standard @login_required
decorator to use the OpenID login page.
8. Rerun "python manage.py syncdb" to add the UserOpenID table to
your database.
== Configuring Single Sign-On ==
If you only want to accept identities from a single OpenID server and
that server implemnts OpenID 2.0 identifier select mode, add the
following setting to your app:
OPENID_SSO_SERVER_URL = 'server-endpoint-url'
With this setting enabled, the user will not be prompted to enter
their identity URL, and instead an OpenID authentication request will
be started with the given server URL.
As an example, to use Launchpad accounts for SSO, you'd use:
OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/'
== Launchpad Teams Support ==
This library supports the Launchpad Teams OpenID extension. Using
this feature, it is possible to map Launchpad team memberships to
Django group memberships. It can be configured with:
OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/'
OPENID_LAUNCHPAD_TEAMS_MAPPING = {
'launchpad-team-1': 'django-group-1',
'launchpad-team-2': 'django-group-2',
}
When a user logs in, they will be added or removed from the relevant
teams listed in the mapping.
If you have already django-groups and want to map these groups automatically, you can use the OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO variable in your settings.py file.
OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = True
If you use OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO, the variable OPENID_LAUNCHPAD_TEAMS_MAPPING will be ignored.
If you want to exclude some groups from the auto mapping, use OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST. This variable has only an effect if OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO is True.
OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST = ['django-group1', 'django-group2']
== External redirect domains ==
By default, redirecting back to an external URL after auth is forbidden. To permit redirection to external URLs on a separate domain, define ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS in your settings.py file as a list of permitted domains:
ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = ['example.com', 'example.org']
and redirects to external URLs on those domains will additionally be permitted.
== Use as /admin (django.admin.contrib) login ==
If you require openid authentication into the admin application, add the following setting:
OPENID_USE_AS_ADMIN_LOGIN = True
It is worth noting that a user needs to be be marked as a "staff user" to be able to access the admin interface. A new openid user will not normally be a "staff user".
The easiest way to resolve this is to use traditional authentication (OPENID_USE_AS_ADMIN_LOGIN = False) to sign in as your first user with a password and authorise your
openid user to be staff.
== Change Django usernames if the nickname changes on the provider ==
If you want your Django username to change when a user updates the nickname on their provider, add the following setting:
OPENID_FOLLOW_RENAMES = True
If the new nickname is available as a Django username, the user is renamed.
Otherwise the user will be renamed to nickname+i for an incrememnting value of i until no conflict occurs.
If the user has already been renamed to nickname+1 due to a conflict, and the nickname is still not available, the user will keep their existing username.
== Require a valid nickname ==
If you must have a valid, unique nickname in order to create a user accont, add the following setting:
OPENID_STRICT_USERNAMES = True
This will cause an OpenID login attempt to fail if the provider does not return a 'nickname' (username) for the user, or if the nickname conflicts with an existing user with a different openid identiy url.
Without this setting, logins without a nickname will be given the username 'openiduser', and upon conflicts with existing username, an incrementing number will be appended to the username until it is unique.
== Require Physical Multi-Factor Authentication ==
If your users should use a physical multi-factor authentication method, such as RSA tokens or YubiKey, add the following setting:
OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
If the user's OpenID provider supports the PAPE extension and provides the Physical Multifactor authentication policy, this will
cause the OpenID login to fail if the user does not provide valid physical authentication to the provider.
== Override Login Failure Handling ==
You can optionally provide your own handler for login failures by adding the following setting:
OPENID_RENDER_FAILURE = failure_handler_function
Where failure_handler_function is a function reference that will take the following parameters:
def failure_handler_function(request, message, status=None, template_name=None, exception=None)
This function must return a Django.http.HttpResponse instance.
== Use the user's email for suggested usernames ==
You can optionally strip out non-alphanumeric characters from the user's email
to generate a preferred username, if the server doesn't provide nick
information, by setting the following setting:
OPENID_USE_EMAIL_FOR_USERNAME = True
Otherwise, and by default, if the server omits nick information and a user is
created it'll receive a username 'openiduser' + a number.
Consider also the OPENID_STRICT_USERNAMES setting (see ``Require a valid nickname``)
django-openid-auth-0.5/django_openid_auth/ 0000775 0001750 0001750 00000000000 12120431074 022032 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/django_openid_auth/tests/ 0000775 0001750 0001750 00000000000 12120431074 023174 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/django_openid_auth/tests/test_views.py 0000664 0001750 0001750 00000176435 12120417252 025764 0 ustar anthony anthony 0000000 0000000 # -*- coding: utf-8 -*-
# django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import cgi
import unittest
from urllib import quote_plus
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.http import HttpRequest, HttpResponse
from django.test import TestCase
from openid.consumer.consumer import Consumer, SuccessResponse
from openid.consumer.discover import OpenIDServiceEndpoint
from openid.extensions import ax, sreg, pape
from openid.fetchers import (
HTTPFetcher, HTTPFetchingError, HTTPResponse, setDefaultFetcher)
from openid.oidutil import importElementTree
from openid.server.server import BROWSER_REQUEST_MODES, ENCODE_URL, Server
from openid.store.memstore import MemoryStore
from openid.message import IDENTIFIER_SELECT
from django_openid_auth import teams
from django_openid_auth.models import UserOpenID
from django_openid_auth.views import (
sanitise_redirect_url,
make_consumer,
)
from django_openid_auth.signals import openid_login_complete
from django_openid_auth.store import DjangoOpenIDStore
from django_openid_auth.exceptions import (
MissingUsernameViolation,
DuplicateUsernameViolation,
MissingPhysicalMultiFactor,
RequiredAttributeNotReturned,
)
ET = importElementTree()
class StubOpenIDProvider(HTTPFetcher):
def __init__(self, base_url):
self.store = MemoryStore()
self.identity_url = base_url + 'identity'
self.localid_url = base_url + 'localid'
self.endpoint_url = base_url + 'endpoint'
self.server = Server(self.store, self.endpoint_url)
self.last_request = None
self.type_uris = ['http://specs.openid.net/auth/2.0/signon']
def fetch(self, url, body=None, headers=None):
if url == self.identity_url:
# Serve an XRDS document directly, pointing at our endpoint.
type_uris = ['%s' % uri for uri in self.type_uris]
return HTTPResponse(
url, 200, {'content-type': 'application/xrds+xml'}, """\
%s
%s
%s
""" % ('\n'.join(type_uris), self.endpoint_url, self.localid_url))
elif url.startswith(self.endpoint_url):
# Gather query parameters
query = {}
if '?' in url:
query.update(cgi.parse_qsl(url.split('?', 1)[1]))
if body is not None:
query.update(cgi.parse_qsl(body))
self.last_request = self.server.decodeRequest(query)
# The browser based requests should not be handled through
# the fetcher interface.
assert self.last_request.mode not in BROWSER_REQUEST_MODES
response = self.server.handleRequest(self.last_request)
webresponse = self.server.encodeResponse(response)
return HTTPResponse(url, webresponse.code, webresponse.headers,
webresponse.body)
else:
raise HTTPFetchingError('unknown URL %s' % url)
def parseFormPost(self, content):
"""Parse an HTML form post to create an OpenID request."""
# Hack to make the javascript XML compliant ...
content = content.replace('i < elements.length',
'i < elements.length')
tree = ET.XML(content)
form = tree.find('.//form')
assert form is not None, 'No form in document'
assert form.get('action') == self.endpoint_url, (
'Form posts to %s instead of %s' % (form.get('action'),
self.endpoint_url))
query = {}
for input in form.findall('input'):
if input.get('type') != 'hidden':
continue
query[input.get('name').encode('UTF-8')] = \
input.get('value').encode('UTF-8')
self.last_request = self.server.decodeRequest(query)
return self.last_request
class DummyDjangoRequest(object):
def __init__(self, request_path):
self.request_path = request_path
self.META = {
'HTTP_HOST': "localhost",
'SCRIPT_NAME': "http://localhost",
'SERVER_PROTOCOL': "http",
}
self.POST = {
'openid_identifier': "http://example.com/identity",
}
self.GET = {}
self.session = {}
def get_full_path(self):
return self.META['SCRIPT_NAME'] + self.request_path
def build_absolute_uri(self):
return self.META['SCRIPT_NAME'] + self.request_path
def _combined_request(self):
request = {}
request.update(self.POST)
request.update(self.GET)
return request
REQUEST = property(_combined_request)
class RelyingPartyTests(TestCase):
urls = 'django_openid_auth.tests.urls'
def setUp(self):
super(RelyingPartyTests, self).setUp()
self.provider = StubOpenIDProvider('http://example.com/')
self.req = DummyDjangoRequest('http://localhost/')
self.endpoint = OpenIDServiceEndpoint()
self.endpoint.claimed_id = 'http://example.com/identity'
server_url = 'http://example.com/'
self.endpoint.server_url = server_url
self.consumer = make_consumer(self.req)
self.server = Server(DjangoOpenIDStore(), op_endpoint=server_url)
setDefaultFetcher(self.provider, wrap_exceptions=False)
self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')
self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False)
self.old_strict_usernames = getattr(settings, 'OPENID_STRICT_USERNAMES', False)
self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False)
self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)
self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False)
self.old_follow_renames = getattr(settings, 'OPENID_FOLLOW_RENAMES', False)
self.old_physical_multifactor = getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False)
self.old_login_render_failure = getattr(settings, 'OPENID_RENDER_FAILURE', None)
self.old_consumer_complete = Consumer.complete
self.old_openid_use_email_for_username = getattr(settings,
'OPENID_USE_EMAIL_FOR_USERNAME', False)
self.old_required_fields = getattr(
settings, 'OPENID_SREG_REQUIRED_FIELDS', [])
settings.OPENID_CREATE_USERS = False
settings.OPENID_STRICT_USERNAMES = False
settings.OPENID_UPDATE_DETAILS_FROM_SREG = False
settings.OPENID_SSO_SERVER_URL = None
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {}
settings.OPENID_USE_AS_ADMIN_LOGIN = False
settings.OPENID_FOLLOW_RENAMES = False
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False
settings.OPENID_SREG_REQUIRED_FIELDS = []
settings.OPENID_USE_EMAIL_FOR_USERNAME = False
def tearDown(self):
settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url
settings.OPENID_CREATE_USERS = self.old_create_users
settings.OPENID_STRICT_USERNAMES = self.old_strict_usernames
settings.OPENID_UPDATE_DETAILS_FROM_SREG = self.old_update_details
settings.OPENID_SSO_SERVER_URL = self.old_sso_server_url
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map
settings.OPENID_USE_AS_ADMIN_LOGIN = self.old_use_as_admin_login
settings.OPENID_FOLLOW_RENAMES = self.old_follow_renames
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = self.old_physical_multifactor
settings.OPENID_RENDER_FAILURE = self.old_login_render_failure
Consumer.complete = self.old_consumer_complete
settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields
settings.OPENID_USE_EMAIL_FOR_USERNAME = self.old_openid_use_email_for_username
setDefaultFetcher(None)
super(RelyingPartyTests, self).tearDown()
def complete(self, openid_response):
"""Complete an OpenID authentication request."""
# The server can generate either a redirect or a form post
# here. For simplicity, force generation of a redirect.
openid_response.whichEncoding = lambda: ENCODE_URL
webresponse = self.provider.server.encodeResponse(openid_response)
self.assertEquals(webresponse.code, 302)
redirect_to = webresponse.headers['location']
self.assertTrue(redirect_to.startswith(
'http://testserver/openid/complete/'))
return self.client.get('/openid/complete/',
dict(cgi.parse_qsl(redirect_to.split('?', 1)[1])))
def test_login(self):
user = User.objects.create_user('someuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# The login form is displayed:
response = self.client.get('/openid/login/')
self.assertTemplateUsed(response, 'openid/login.html')
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
openid_request = self.provider.parseFormPost(response.content)
self.assertEquals(openid_request.mode, 'checkid_setup')
self.assertTrue(openid_request.return_to.startswith(
'http://testserver/openid/complete/'))
# Complete the request. The user is redirected to the next URL.
openid_response = openid_request.answer(True)
response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/')
# And they are now logged in:
response = self.client.get('/getuser/')
self.assertEquals(response.content, 'someuser')
def test_login_with_nonascii_return_to(self):
"""Ensure non-ascii characters can be used for the 'next' arg."""
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': u'/files/ñandú.jpg'.encode('utf-8')})
self.assertContains(response, 'OpenID transaction in progress')
def test_login_no_next(self):
"""Logins with no next parameter redirect to LOGIN_REDIRECT_URL."""
user = User.objects.create_user('someuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
settings.LOGIN_REDIRECT_URL = '/getuser/'
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity'})
self.assertContains(response, 'OpenID transaction in progress')
openid_request = self.provider.parseFormPost(response.content)
self.assertEquals(openid_request.mode, 'checkid_setup')
self.assertTrue(openid_request.return_to.startswith(
'http://testserver/openid/complete/'))
# Complete the request. The user is redirected to the next URL.
openid_response = openid_request.answer(True)
response = self.complete(openid_response)
self.assertRedirects(
response, 'http://testserver' + settings.LOGIN_REDIRECT_URL)
def test_login_sso(self):
settings.OPENID_SSO_SERVER_URL = 'http://example.com/identity'
user = User.objects.create_user('someuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# Requesting the login form immediately begins an
# authentication request.
response = self.client.get('/openid/login/', {'next': '/getuser/'})
self.assertEquals(response.status_code, 200)
self.assertContains(response, 'OpenID transaction in progress')
openid_request = self.provider.parseFormPost(response.content)
self.assertEquals(openid_request.mode, 'checkid_setup')
self.assertTrue(openid_request.return_to.startswith(
'http://testserver/openid/complete/'))
# Complete the request. The user is redirected to the next URL.
openid_response = openid_request.answer(True)
response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/')
# And they are now logged in:
response = self.client.get('/getuser/')
self.assertEquals(response.content, 'someuser')
def test_login_create_users(self):
settings.OPENID_CREATE_USERS = True
# Create a user with the same name as we'll pass back via sreg.
User.objects.create_user('someuser', 'someone@example.com')
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
'email': 'foo@example.com'})
openid_response.addExtension(sreg_response)
response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/')
# And they are now logged in as a new user (they haven't taken
# over the existing "someuser" user).
response = self.client.get('/getuser/')
self.assertEquals(response.content, 'someuser2')
# Check the details of the new user.
user = User.objects.get(username='someuser2')
self.assertEquals(user.first_name, 'Some')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'foo@example.com')
def _do_user_login(self, req_data, resp_data, use_sreg=True, use_pape=None):
openid_request = self._get_login_request(req_data)
openid_response = self._get_login_response(openid_request, resp_data, use_sreg, use_pape)
response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/')
return response
def _get_login_request(self, req_data):
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/', req_data)
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
return openid_request
def _get_login_response(self, openid_request, resp_data, use_sreg, use_pape):
openid_response = openid_request.answer(True)
if use_sreg:
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, resp_data)
openid_response.addExtension(sreg_response)
if use_pape is not None:
policies = [
use_pape
]
pape_response = pape.Response(auth_policies=policies)
openid_response.addExtension(pape_response)
return openid_response
def parse_query_string(self, query_str):
query_items = map(tuple,
[item.split('=') for item in query_str.split('&')])
query = dict(query_items)
return query
def test_login_physical_multifactor_request(self):
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
self.provider.type_uris.append(pape.ns_uri)
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
response = self.client.post('/openid/login/', openid_req)
openid_request = self.provider.parseFormPost(response.content)
request_auth = openid_request.message.getArg(
'http://specs.openid.net/extensions/pape/1.0',
'preferred_auth_policies',
)
self.assertEqual(request_auth, preferred_auth)
def test_login_physical_multifactor_response(self):
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
self.provider.type_uris.append(pape.ns_uri)
def mock_complete(this, request_args, return_to):
request = {'openid.mode': 'checkid_setup',
'openid.trust_root': 'http://localhost/',
'openid.return_to': 'http://localhost/',
'openid.identity': IDENTIFIER_SELECT,
'openid.ns.pape' : pape.ns_uri,
'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE),
}
openid_server = self.provider.server
orequest = openid_server.decodeRequest(request)
response = SuccessResponse(
self.endpoint, orequest.message,
signed_fields=['openid.pape.auth_policies',])
return response
Consumer.complete = mock_complete
user = User.objects.create_user('testuser', 'test@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User',
'email': 'test@example.com'}
response = self._do_user_login(openid_req, openid_resp, use_pape=pape.AUTH_MULTI_FACTOR_PHYSICAL)
query = self.parse_query_string(response.request['QUERY_STRING'])
self.assertTrue('openid.pape.auth_policies' in query)
self.assertEqual(query['openid.pape.auth_policies'],
quote_plus(preferred_auth))
response = self.client.get('/getuser/')
self.assertEqual(response.content, 'testuser')
def test_login_physical_multifactor_not_provided(self):
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
self.provider.type_uris.append(pape.ns_uri)
def mock_complete(this, request_args, return_to):
request = {'openid.mode': 'checkid_setup',
'openid.trust_root': 'http://localhost/',
'openid.return_to': 'http://localhost/',
'openid.identity': IDENTIFIER_SELECT,
'openid.ns.pape' : pape.ns_uri,
'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE),
}
openid_server = self.provider.server
orequest = openid_server.decodeRequest(request)
response = SuccessResponse(
self.endpoint, orequest.message,
signed_fields=['openid.pape.auth_policies',])
return response
Consumer.complete = mock_complete
user = User.objects.create_user('testuser', 'test@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User',
'email': 'test@example.com'}
openid_request = self._get_login_request(openid_req)
openid_response = self._get_login_response(openid_request, openid_req, openid_resp, use_pape=pape.AUTH_NONE)
response_auth = openid_request.message.getArg(
'http://specs.openid.net/extensions/pape/1.0',
'auth_policies',
)
self.assertNotEqual(response_auth, preferred_auth)
response = self.complete(openid_response)
self.assertEquals(403, response.status_code)
self.assertContains(response, '
OpenID failed
', status_code=403)
self.assertContains(response, 'Login requires physical multi-factor authentication.
', status_code=403)
def test_login_physical_multifactor_not_provided_override(self):
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
self.provider.type_uris.append(pape.ns_uri)
# Override the login_failure handler
def mock_login_failure_handler(request, message, status=403,
template_name=None,
exception=None):
self.assertTrue(isinstance(exception, MissingPhysicalMultiFactor))
return HttpResponse('Test Failure Override', status=200)
settings.OPENID_RENDER_FAILURE = mock_login_failure_handler
def mock_complete(this, request_args, return_to):
request = {'openid.mode': 'checkid_setup',
'openid.trust_root': 'http://localhost/',
'openid.return_to': 'http://localhost/',
'openid.identity': IDENTIFIER_SELECT,
'openid.ns.pape' : pape.ns_uri,
'openid.pape.auth_policies': request_args.get('openid.pape.auth_policies', pape.AUTH_NONE),
}
openid_server = self.provider.server
orequest = openid_server.decodeRequest(request)
response = SuccessResponse(
self.endpoint, orequest.message,
signed_fields=['openid.pape.auth_policies',])
return response
Consumer.complete = mock_complete
user = User.objects.create_user('testuser', 'test@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': 'testuser', 'fullname': 'Openid User',
'email': 'test@example.com'}
openid_request = self._get_login_request(openid_req)
openid_response = self._get_login_response(openid_request, openid_req, openid_resp, use_pape=pape.AUTH_NONE)
response_auth = openid_request.message.getArg(
'http://specs.openid.net/extensions/pape/1.0',
'auth_policies',
)
self.assertNotEqual(response_auth, preferred_auth)
# Status code should be 200, since we over-rode the login_failure handler
response = self.complete(openid_response)
self.assertEquals(200, response.status_code)
self.assertContains(response, 'Test Failure Override')
def test_login_without_nickname(self):
settings.OPENID_CREATE_USERS = True
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': '', 'fullname': 'Openid User',
'email': 'foo@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# username defaults to 'openiduser'
self.assertEquals(response.content, 'openiduser')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Openid')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'foo@example.com')
def test_login_without_nickname_with_email_suggestion(self):
settings.OPENID_CREATE_USERS = True
settings.OPENID_USE_EMAIL_FOR_USERNAME = True
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': '', 'fullname': 'Openid User',
'email': 'foo@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# username defaults to a munged version of the email
self.assertEquals(response.content, 'fooexamplecom')
def test_login_duplicate_username_numbering(self):
settings.OPENID_FOLLOW_RENAMES = False
settings.OPENID_CREATE_USERS = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's name we're going to conflict with
user = User.objects.create_user('testuser', 'someone@example.com')
# identity url is for 'renameuser'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which already exists for another identity
openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
'email': 'test@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# Since this username is already taken by someone else, we go through
# the process of adding +i to it, and get testuser2.
self.assertEquals(response.content, 'testuser2')
def test_login_duplicate_username_numbering_with_conflicts(self):
settings.OPENID_FOLLOW_RENAMES = False
settings.OPENID_CREATE_USERS = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's name we're going to conflict with
user = User.objects.create_user('testuser', 'someone@example.com')
user = User.objects.create_user('testuser3', 'someone@example.com')
# identity url is for 'renameuser'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which already exists for another identity
openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
'email': 'test@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# Since this username is already taken by someone else, we go through
# the process of adding +i to it starting with the count of users with
# username starting with 'testuser', of which there are 2. i should
# start at 3, which already exists, so it should skip to 4.
self.assertEquals(response.content, 'testuser4')
def test_login_duplicate_username_numbering_with_holes(self):
settings.OPENID_FOLLOW_RENAMES = False
settings.OPENID_CREATE_USERS = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's name we're going to conflict with
user = User.objects.create_user('testuser', 'someone@example.com')
user = User.objects.create_user('testuser1', 'someone@example.com')
user = User.objects.create_user('testuser6', 'someone@example.com')
user = User.objects.create_user('testuser7', 'someone@example.com')
user = User.objects.create_user('testuser8', 'someone@example.com')
# identity url is for 'renameuser'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which already exists for another identity
openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
'email': 'test@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# Since this username is already taken by someone else, we go through
# the process of adding +i to it starting with the count of users with
# username starting with 'testuser', of which there are 5. i should
# start at 6, and increment until it reaches 9.
self.assertEquals(response.content, 'testuser9')
def test_login_duplicate_username_numbering_with_nonsequential_matches(self):
settings.OPENID_FOLLOW_RENAMES = False
settings.OPENID_CREATE_USERS = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's name we're going to conflict with
user = User.objects.create_user('testuser', 'someone@example.com')
user = User.objects.create_user('testuserfoo', 'someone@example.com')
# identity url is for 'renameuser'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which already exists for another identity
openid_resp = {'nickname': 'testuser', 'fullname': 'Test User',
'email': 'test@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# Since this username is already taken by someone else, we go through
# the process of adding +i to it starting with the count of users with
# username starting with 'testuser', of which there are 2. i should
# start at 3, which will be available.
self.assertEquals(response.content, 'testuser3')
def test_login_follow_rename(self):
settings.OPENID_FOLLOW_RENAMES = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': 'someuser', 'fullname': 'Some User',
'email': 'foo@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# If OPENID_FOLLOW_RENAMES, they are logged in as
# someuser (the passed in nickname has changed the username)
self.assertEquals(response.content, 'someuser')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Some')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'foo@example.com')
def test_login_follow_rename_without_nickname_change(self):
settings.OPENID_FOLLOW_RENAMES = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
settings.OPENID_STRICT_USERNAMES = True
user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': 'testuser', 'fullname': 'Some User',
'email': 'foo@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# Username should not have changed
self.assertEquals(response.content, 'testuser')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Some')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'foo@example.com')
def test_login_follow_rename_conflict(self):
settings.OPENID_FOLLOW_RENAMES = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's name we're going to switch to
user = User.objects.create_user('testuser', 'someone@example.com')
UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity')
# Setup user who is going to try to change username to 'testuser'
renamed_user = User.objects.create_user('renameuser', 'someone@example.com')
UserOpenID.objects.get_or_create(
user=renamed_user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
# identity url is for 'renameuser'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which already exists for another identity
openid_resp = {'nickname': 'testuser', 'fullname': 'Rename User',
'email': 'rename@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser'
# but since that username is already taken by someone else, we go through
# the process of adding +i to it, and get testuser2.
self.assertEquals(response.content, 'testuser2')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Rename')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'rename@example.com')
def test_login_follow_rename_false_onlyonce(self):
settings.OPENID_FOLLOW_RENAMES = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's name we're going to switch to
user = User.objects.create_user('testuser', 'someone@example.com')
UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity')
# Setup user who is going to try to change username to 'testuser'
renamed_user = User.objects.create_user('testuser2000eight', 'someone@example.com')
UserOpenID.objects.get_or_create(
user=renamed_user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
# identity url is for 'testuser2000eight'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which already exists for another identity
openid_resp = {'nickname': 'testuser2', 'fullname': 'Rename User',
'email': 'rename@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser'
# but since that username is already taken by someone else, we go through
# the process of adding +i to it. Even though it looks like the username
# follows the nickname+i scheme, it has non-numbers in the suffix, so
# it's not an auto-generated one. The regular process of renaming to
# 'testuser' has a conflict, so we get +2 at the end.
self.assertEquals(response.content, 'testuser2')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Rename')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'rename@example.com')
def test_login_follow_rename_conflict_onlyonce(self):
settings.OPENID_FOLLOW_RENAMES = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's name we're going to switch to
user = User.objects.create_user('testuser', 'someone@example.com')
UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity')
# Setup user who is going to try to change username to 'testuser'
renamed_user = User.objects.create_user('testuser2000', 'someone@example.com')
UserOpenID.objects.get_or_create(
user=renamed_user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
# identity url is for 'testuser2000'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which already exists for another identity
openid_resp = {'nickname': 'testuser', 'fullname': 'Rename User',
'email': 'rename@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# If OPENID_FOLLOW_RENAMES, attempt to change username to 'testuser'
# but since that username is already taken by someone else, we go through
# the process of adding +i to it. Since the user for this identity url
# already has a name matching that pattern, check if first.
self.assertEquals(response.content, 'testuser2000')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Rename')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'rename@example.com')
def test_login_follow_rename_false_conflict(self):
settings.OPENID_FOLLOW_RENAMES = True
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
# Setup existing user who's username matches the name+i pattern
user = User.objects.create_user('testuser2', 'someone@example.com')
UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
# identity url is for 'testuser2'
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
# but returned username is for 'testuser', which looks like we've done
# a username+1 for them already, but 'testuser' isn't actually taken
openid_resp = {'nickname': 'testuser', 'fullname': 'Same User',
'email': 'same@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
# If OPENID_FOLLOW_RENAMES, username should be changed to 'testuser'
# because it wasn't currently taken
self.assertEquals(response.content, 'testuser')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Same')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'same@example.com')
def test_strict_username_no_nickname(self):
settings.OPENID_CREATE_USERS = True
settings.OPENID_STRICT_USERNAMES = True
settings.OPENID_SREG_REQUIRED_FIELDS = []
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': '', # No nickname
'fullname': 'Some User',
'email': 'foo@example.com'})
openid_response.addExtension(sreg_response)
response = self.complete(openid_response)
# Status code should be 403: Forbidden
self.assertEquals(403, response.status_code)
self.assertContains(response, 'OpenID failed
', status_code=403)
self.assertContains(response, "An attribute required for logging in was not returned "
"(nickname)", status_code=403)
def test_strict_username_no_nickname_override(self):
settings.OPENID_CREATE_USERS = True
settings.OPENID_STRICT_USERNAMES = True
settings.OPENID_SREG_REQUIRED_FIELDS = []
# Override the login_failure handler
def mock_login_failure_handler(request, message, status=403,
template_name=None,
exception=None):
self.assertTrue(isinstance(exception, (RequiredAttributeNotReturned, MissingUsernameViolation)))
return HttpResponse('Test Failure Override', status=200)
settings.OPENID_RENDER_FAILURE = mock_login_failure_handler
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': '', # No nickname
'fullname': 'Some User',
'email': 'foo@example.com'})
openid_response.addExtension(sreg_response)
response = self.complete(openid_response)
# Status code should be 200, since we over-rode the login_failure handler
self.assertEquals(200, response.status_code)
self.assertContains(response, 'Test Failure Override')
def test_strict_username_duplicate_user(self):
settings.OPENID_CREATE_USERS = True
settings.OPENID_STRICT_USERNAMES = True
# Create a user with the same name as we'll pass back via sreg.
user = User.objects.create_user('someuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/different_identity',
display_id='http://example.com/different_identity')
useropenid.save()
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
'email': 'foo@example.com'})
openid_response.addExtension(sreg_response)
response = self.complete(openid_response)
# Status code should be 403: Forbidden
self.assertEquals(403, response.status_code)
self.assertContains(response, 'OpenID failed
', status_code=403)
self.assertContains(response,
"The username (someuser) with which you tried to log in is "
"already in use for a different account.",
status_code=403)
def test_strict_username_duplicate_user_override(self):
settings.OPENID_CREATE_USERS = True
settings.OPENID_STRICT_USERNAMES = True
# Override the login_failure handler
def mock_login_failure_handler(request, message, status=403,
template_name=None,
exception=None):
self.assertTrue(isinstance(exception, DuplicateUsernameViolation))
return HttpResponse('Test Failure Override', status=200)
settings.OPENID_RENDER_FAILURE = mock_login_failure_handler
# Create a user with the same name as we'll pass back via sreg.
user = User.objects.create_user('someuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/different_identity',
display_id='http://example.com/different_identity')
useropenid.save()
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
'email': 'foo@example.com'})
openid_response.addExtension(sreg_response)
response = self.complete(openid_response)
# Status code should be 200, since we over-rode the login_failure handler
self.assertEquals(200, response.status_code)
self.assertContains(response, 'Test Failure Override')
def test_login_requires_sreg_required_fields(self):
# If any required attributes are not included in the response,
# we fail with a forbidden.
settings.OPENID_CREATE_USERS = True
settings.OPENID_SREG_REQUIRED_FIELDS = ('email', 'language')
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': 'foo',
'fullname': 'Some User',
'email': 'foo@example.com'})
openid_response.addExtension(sreg_response)
response = self.complete(openid_response)
# Status code should be 403: Forbidden as we didn't include
# a required field - language.
self.assertContains(response,
"An attribute required for logging in was not returned "
"(language)", status_code=403)
def test_login_update_details(self):
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'}
openid_resp = {'nickname': 'testuser', 'fullname': 'Some User',
'email': 'foo@example.com'}
self._do_user_login(openid_req, openid_resp)
response = self.client.get('/getuser/')
self.assertEquals(response.content, 'testuser')
# The user's full name and email have been updated.
user = User.objects.get(username=response.content)
self.assertEquals(user.first_name, 'Some')
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'foo@example.com')
def test_login_uses_sreg_extra_fields(self):
# The configurable sreg attributes are used in the request.
settings.OPENID_SREG_EXTRA_FIELDS = ('language',)
user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
for field in ('email', 'fullname', 'nickname', 'language'):
self.assertTrue(field in sreg_request)
def test_login_uses_sreg_required_fields(self):
# The configurable sreg attributes are used in the request.
settings.OPENID_SREG_REQUIRED_FIELDS = ('email', 'language')
user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
self.assertEqual(['email', 'language'], sreg_request.required)
self.assertEqual(['fullname', 'nickname'], sreg_request.optional)
def test_login_attribute_exchange(self):
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# Configure the provider to advertise attribute exchange
# protocol and start the authentication process:
self.provider.type_uris.append('http://openid.net/srv/ax/1.0')
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# The resulting OpenID request uses the Attribute Exchange
# extension rather than the Simple Registration extension.
openid_request = self.provider.parseFormPost(response.content)
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
self.assertEqual(sreg_request.required, [])
self.assertEqual(sreg_request.optional, [])
fetch_request = ax.FetchRequest.fromOpenIDRequest(openid_request)
self.assertTrue(fetch_request.has_key(
'http://axschema.org/contact/email'))
self.assertTrue(fetch_request.has_key(
'http://axschema.org/namePerson'))
self.assertTrue(fetch_request.has_key(
'http://axschema.org/namePerson/first'))
self.assertTrue(fetch_request.has_key(
'http://axschema.org/namePerson/last'))
self.assertTrue(fetch_request.has_key(
'http://axschema.org/namePerson/friendly'))
# myOpenID compatibilty attributes:
self.assertTrue(fetch_request.has_key(
'http://schema.openid.net/contact/email'))
self.assertTrue(fetch_request.has_key(
'http://schema.openid.net/namePerson'))
self.assertTrue(fetch_request.has_key(
'http://schema.openid.net/namePerson/friendly'))
# Build up a response including AX data.
openid_response = openid_request.answer(True)
fetch_response = ax.FetchResponse(fetch_request)
fetch_response.addValue(
'http://axschema.org/contact/email', 'foo@example.com')
fetch_response.addValue(
'http://axschema.org/namePerson/first', 'Firstname')
fetch_response.addValue(
'http://axschema.org/namePerson/last', 'Lastname')
fetch_response.addValue(
'http://axschema.org/namePerson/friendly', 'someuser')
openid_response.addExtension(fetch_response)
response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/')
# And they are now logged in as testuser (the passed in
# nickname has not caused the username to change).
response = self.client.get('/getuser/')
self.assertEquals(response.content, 'testuser')
# The user's full name and email have been updated.
user = User.objects.get(username='testuser')
self.assertEquals(user.first_name, 'Firstname')
self.assertEquals(user.last_name, 'Lastname')
self.assertEquals(user.email, 'foo@example.com')
def test_login_teams(self):
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = False
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname',
'otherteam': 'othergroup'}
user = User.objects.create_user('testuser', 'someone@example.com')
group = Group(name='groupname')
group.save()
ogroup = Group(name='othergroup')
ogroup.save()
user.groups.add(ogroup)
user.save()
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request
openid_request = self.provider.parseFormPost(response.content)
openid_response = openid_request.answer(True)
teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request)
teams_response = teams.TeamsResponse.extractResponse(
teams_request, 'teamname,some-other-team')
openid_response.addExtension(teams_response)
response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/')
# And they are now logged in as testuser
response = self.client.get('/getuser/')
self.assertEquals(response.content, 'testuser')
# The user's groups have been updated.
user = User.objects.get(username='testuser')
self.assertTrue(group in user.groups.all())
self.assertTrue(ogroup not in user.groups.all())
def test_login_teams_automapping(self):
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname',
'otherteam': 'othergroup'}
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = True
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST = ['django-group1', 'django-group2']
user = User.objects.create_user('testuser', 'someone@example.com')
group1 = Group(name='django-group1')
group1.save()
group2 = Group(name='django-group2')
group2.save()
group3 = Group(name='django-group3')
group3.save()
user.save()
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'})
self.assertContains(response, 'OpenID transaction in progress')
# Complete the request
openid_request = self.provider.parseFormPost(response.content)
openid_response = openid_request.answer(True)
teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request)
self.assertEqual(group1 in user.groups.all(), False)
self.assertEqual(group2 in user.groups.all(), False)
self.assertTrue(group3 not in user.groups.all())
def test_login_teams_staff_not_defined(self):
delattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS')
user = User.objects.create_user('testuser', 'someone@example.com')
user.is_staff = True
user.save()
self.assertTrue(user.is_staff)
user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team')
self.assertTrue(user.is_staff)
def test_login_teams_staff_assignment(self):
settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('teamname',)
user = User.objects.create_user('testuser', 'someone@example.com')
user.is_staff = False
user.save()
self.assertFalse(user.is_staff)
user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team')
self.assertTrue(user.is_staff)
def test_login_teams_staff_unassignment(self):
settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('different-teamname',)
user = User.objects.create_user('testuser', 'someone@example.com')
user.is_staff = True
user.save()
self.assertTrue(user.is_staff)
user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team')
self.assertFalse(user.is_staff)
def get_openid_authed_user_with_teams(self, user, teams_str):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
# Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity'})
# Complete the request
openid_request = self.provider.parseFormPost(response.content)
openid_response = openid_request.answer(True)
teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request)
teams_response = teams.TeamsResponse.extractResponse(
teams_request, teams_str)
openid_response.addExtension(teams_response)
response = self.complete(openid_response)
return User.objects.get(username=user.username)
def test_login_complete_signals_login(self):
# An oauth_login_complete signal is emitted including the
# request and sreg_response.
user = User.objects.create_user('someuser', 'someone@example.com')
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
useropenid.save()
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity'})
openid_request = self.provider.parseFormPost(response.content)
openid_response = openid_request.answer(True)
# Use a closure to test whether the signal handler was called.
self.signal_handler_called = False
def login_callback(sender, **kwargs):
self.assertTrue(isinstance(
kwargs.get('request', None), HttpRequest))
self.assertTrue(isinstance(
kwargs.get('openid_response', None), SuccessResponse))
self.signal_handler_called = True
openid_login_complete.connect(login_callback)
response = self.complete(openid_response)
self.assertTrue(self.signal_handler_called)
openid_login_complete.disconnect(login_callback)
class HelperFunctionsTest(TestCase):
def test_sanitise_redirect_url(self):
settings.ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = [
"example.com", "example.org"]
# list of URLs and whether they should be passed or not
urls = [
("http://example.com", True),
("http://example.org/", True),
("http://example.org/foo/bar", True),
("http://example.org/foo/bar?baz=quux", True),
("http://example.org:9999/foo/bar?baz=quux", True),
("http://www.example.org/", False),
("http://example.net/foo/bar?baz=quux", False),
("/somewhere/local", True),
("/somewhere/local?url=http://fail.com/bar", True),
# An empty path, as seen when no "next" parameter is passed.
("", False),
("/path with spaces", False),
]
for url, returns_self in urls:
sanitised = sanitise_redirect_url(url)
if returns_self:
self.assertEqual(url, sanitised)
else:
self.assertEqual(settings.LOGIN_REDIRECT_URL, sanitised)
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
django-openid-auth-0.5/django_openid_auth/tests/test_admin.py 0000664 0001750 0001750 00000006322 12120173233 025700 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Tests for the django_openid_auth Admin login form replacement.
"""
import os
import unittest
from django.conf import settings
from django.contrib.auth.models import User, AnonymousUser
settings.OPENID_USE_AS_ADMIN_LOGIN = True
from django_openid_auth import admin
from django.test import TestCase
def create_user(is_staff=False, authenticated=True):
"""
Create and return a user, either the AnonymousUser or a normal Django user,
setting the is_staff attribute if appropriate.
"""
if not authenticated:
return AnonymousUser()
else:
user = User(
username=u'testing', email='testing@example.com',
is_staff=is_staff)
user.set_password(u'test')
user.save()
class SiteAdminTests(TestCase):
"""
TestCase for accessing /admin/ when the django_openid_auth form replacement
is in use.
"""
def test_admin_site_with_openid_login_authenticated_non_staff(self):
"""
If the request has an authenticated user, who is not flagged as a
staff member, then they get a failure response.
"""
create_user()
self.client.login(username='testing', password='test')
response = self.client.get('/admin/')
self.assertTrue('User testing does not have admin access.' in
response.content, 'Missing error message in response')
def test_admin_site_with_openid_login_non_authenticated_user(self):
"""
Unauthenticated users accessing the admin page should be directed to
the OpenID login url.
"""
response = self.client.get('/admin/')
self.assertEqual(302, response.status_code)
self.assertEqual('http://testserver/openid/login/?next=/admin/',
response['Location'])
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
django-openid-auth-0.5/django_openid_auth/tests/__init__.py 0000664 0001750 0001750 00000003325 12120172612 025310 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import unittest
from test_views import *
from test_store import *
from test_auth import *
from test_admin import *
def suite():
suite = unittest.TestSuite()
for name in ['test_auth', 'test_store', 'test_views', 'test_admin']:
mod = __import__('%s.%s' % (__name__, name), {}, {}, ['suite'])
suite.addTest(mod.suite())
return suite
django-openid-auth-0.5/django_openid_auth/tests/test_store.py 0000664 0001750 0001750 00000017727 12120172607 025763 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import time
import unittest
from django.test import TestCase
from openid.association import Association as OIDAssociation
from openid.store.nonce import SKEW
from django_openid_auth.models import Association, Nonce
from django_openid_auth.store import DjangoOpenIDStore
class OpenIDStoreTests(TestCase):
def setUp(self):
super(OpenIDStoreTests, self).setUp()
self.store = DjangoOpenIDStore()
def test_storeAssociation(self):
assoc = OIDAssociation('handle', 'secret', 42, 600, 'HMAC-SHA1')
self.store.storeAssociation('server-url', assoc)
dbassoc = Association.objects.get(
server_url='server-url', handle='handle')
self.assertEquals(dbassoc.server_url, 'server-url')
self.assertEquals(dbassoc.handle, 'handle')
self.assertEquals(dbassoc.secret, 'secret'.encode('base-64'))
self.assertEquals(dbassoc.issued, 42)
self.assertEquals(dbassoc.lifetime, 600)
self.assertEquals(dbassoc.assoc_type, 'HMAC-SHA1')
def test_storeAssociation_update_existing(self):
assoc = OIDAssociation('handle', 'secret', 42, 600, 'HMAC-SHA1')
self.store.storeAssociation('server-url', assoc)
# Now update the association with new information.
assoc = OIDAssociation('handle', 'secret2', 420, 900, 'HMAC-SHA256')
self.store.storeAssociation('server-url', assoc)
dbassoc = Association.objects.get(
server_url='server-url', handle='handle')
self.assertEqual(dbassoc.secret, 'secret2'.encode('base-64'))
self.assertEqual(dbassoc.issued, 420)
self.assertEqual(dbassoc.lifetime, 900)
self.assertEqual(dbassoc.assoc_type, 'HMAC-SHA256')
def test_getAssociation(self):
timestamp = int(time.time())
self.store.storeAssociation(
'server-url', OIDAssociation('handle', 'secret', timestamp, 600,
'HMAC-SHA1'))
assoc = self.store.getAssociation('server-url', 'handle')
self.assertTrue(isinstance(assoc, OIDAssociation))
self.assertEquals(assoc.handle, 'handle')
self.assertEquals(assoc.secret, 'secret')
self.assertEquals(assoc.issued, timestamp)
self.assertEquals(assoc.lifetime, 600)
self.assertEquals(assoc.assoc_type, 'HMAC-SHA1')
def test_getAssociation_unknown(self):
assoc = self.store.getAssociation('server-url', 'unknown')
self.assertEquals(assoc, None)
def test_getAssociation_expired(self):
lifetime = 600
timestamp = int(time.time()) - 2 * lifetime
self.store.storeAssociation(
'server-url', OIDAssociation('handle', 'secret', timestamp,
lifetime, 'HMAC-SHA1'))
# The association is not returned, and is removed from the database.
assoc = self.store.getAssociation('server-url', 'handle')
self.assertEquals(assoc, None)
self.assertRaises(Association.DoesNotExist, Association.objects.get,
server_url='server-url', handle='handle')
def test_getAssociation_no_handle(self):
timestamp = int(time.time())
self.store.storeAssociation(
'server-url', OIDAssociation('handle1', 'secret', timestamp + 1,
600, 'HMAC-SHA1'))
self.store.storeAssociation(
'server-url', OIDAssociation('handle2', 'secret', timestamp,
600, 'HMAC-SHA1'))
# The newest handle is returned.
assoc = self.store.getAssociation('server-url', None)
self.assertNotEquals(assoc, None)
self.assertEquals(assoc.handle, 'handle1')
self.assertEquals(assoc.issued, timestamp + 1)
def test_removeAssociation(self):
timestamp = int(time.time())
self.store.storeAssociation(
'server-url', OIDAssociation('handle', 'secret', timestamp, 600,
'HMAC-SHA1'))
self.assertEquals(
self.store.removeAssociation('server-url', 'handle'), True)
self.assertEquals(
self.store.getAssociation('server-url', 'handle'), None)
def test_removeAssociation_unknown(self):
self.assertEquals(
self.store.removeAssociation('server-url', 'unknown'), False)
def test_useNonce(self):
timestamp = time.time()
# The nonce can only be used once.
self.assertEqual(
self.store.useNonce('server-url', timestamp, 'salt'), True)
self.assertEqual(
self.store.useNonce('server-url', timestamp, 'salt'), False)
self.assertEqual(
self.store.useNonce('server-url', timestamp, 'salt'), False)
def test_useNonce_expired(self):
timestamp = time.time() - 2 * SKEW
self.assertEqual(
self.store.useNonce('server-url', timestamp, 'salt'), False)
def test_useNonce_future(self):
timestamp = time.time() + 2 * SKEW
self.assertEqual(
self.store.useNonce('server-url', timestamp, 'salt'), False)
def test_cleanupNonces(self):
timestamp = time.time()
self.assertEqual(
self.store.useNonce('server1', timestamp, 'salt1'), True)
self.assertEqual(
self.store.useNonce('server2', timestamp, 'salt2'), True)
self.assertEqual(
self.store.useNonce('server3', timestamp, 'salt3'), True)
self.assertEqual(Nonce.objects.count(), 3)
self.assertEqual(
self.store.cleanupNonces(_now=timestamp + 2 * SKEW), 3)
self.assertEqual(Nonce.objects.count(), 0)
# The nonces have now been cleared:
self.assertEqual(
self.store.useNonce('server1', timestamp, 'salt1'), True)
self.assertEqual(
self.store.cleanupNonces(_now=timestamp + 2 * SKEW), 1)
self.assertEqual(
self.store.cleanupNonces(_now=timestamp + 2 * SKEW), 0)
def test_cleanupAssociations(self):
timestamp = int(time.time()) - 100
self.store.storeAssociation(
'server-url', OIDAssociation('handle1', 'secret', timestamp,
50, 'HMAC-SHA1'))
self.store.storeAssociation(
'server-url', OIDAssociation('handle2', 'secret', timestamp,
200, 'HMAC-SHA1'))
self.assertEquals(self.store.cleanupAssociations(), 1)
# The second (non-expired) association is left behind.
self.assertNotEqual(self.store.getAssociation('server-url', 'handle2'),
None)
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
django-openid-auth-0.5/django_openid_auth/tests/urls.py 0000664 0001750 0001750 00000003166 12120172604 024542 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.http import HttpResponse
from django.conf.urls.defaults import *
def get_user(request):
return HttpResponse(request.user.username)
urlpatterns = patterns('',
(r'^getuser/$', get_user),
(r'^openid/', include('django_openid_auth.urls')),
)
django-openid-auth-0.5/django_openid_auth/tests/test_auth.py 0000664 0001750 0001750 00000017042 12120172600 025547 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2010-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import unittest
from django.conf import settings
from django.contrib.auth.models import User
from django.test import TestCase
from django_openid_auth.auth import OpenIDBackend
from openid.consumer.consumer import SuccessResponse
from openid.consumer.discover import OpenIDServiceEndpoint
from openid.message import Message, OPENID2_NS
SREG_NS = "http://openid.net/sreg/1.0"
AX_NS = "http://openid.net/srv/ax/1.0"
class OpenIDBackendTests(TestCase):
def setUp(self):
super(OpenIDBackendTests, self).setUp()
self.backend = OpenIDBackend()
self.old_openid_use_email_for_username = getattr(settings,
'OPENID_USE_EMAIL_FOR_USERNAME', False)
def tearDown(self):
settings.OPENID_USE_EMAIL_FOR_USERNAME = \
self.old_openid_use_email_for_username
def test_extract_user_details_sreg(self):
endpoint = OpenIDServiceEndpoint()
message = Message(OPENID2_NS)
message.setArg(SREG_NS, "nickname", "someuser")
message.setArg(SREG_NS, "fullname", "Some User")
message.setArg(SREG_NS, "email", "foo@example.com")
response = SuccessResponse(
endpoint, message, signed_fields=message.toPostArgs().keys())
data = self.backend._extract_user_details(response)
self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some",
"last_name": "User",
"email": "foo@example.com"})
def make_response_ax(self, schema="http://axschema.org/",
fullname="Some User", nickname="someuser", email="foo@example.com",
first=None, last=None):
endpoint = OpenIDServiceEndpoint()
message = Message(OPENID2_NS)
attributes = [
("nickname", schema + "namePerson/friendly", nickname),
("fullname", schema + "namePerson", fullname),
("email", schema + "contact/email", email),
]
if first:
attributes.append(
("first", "http://axschema.org/namePerson/first", first))
if last:
attributes.append(
("last", "http://axschema.org/namePerson/last", last))
message.setArg(AX_NS, "mode", "fetch_response")
for (alias, uri, value) in attributes:
message.setArg(AX_NS, "type.%s" % alias, uri)
message.setArg(AX_NS, "value.%s" % alias, value)
return SuccessResponse(
endpoint, message, signed_fields=message.toPostArgs().keys())
def test_extract_user_details_ax(self):
response = self.make_response_ax(fullname="Some User",
nickname="someuser", email="foo@example.com")
data = self.backend._extract_user_details(response)
self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some",
"last_name": "User",
"email": "foo@example.com"})
def test_extract_user_details_ax_split_name(self):
# Include fullname too to show that the split data takes
# precedence.
response = self.make_response_ax(
fullname="Bad Data", first="Some", last="User")
data = self.backend._extract_user_details(response)
self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some",
"last_name": "User",
"email": "foo@example.com"})
def test_extract_user_details_ax_broken_myopenid(self):
response = self.make_response_ax(
schema="http://schema.openid.net/", fullname="Some User",
nickname="someuser", email="foo@example.com")
data = self.backend._extract_user_details(response)
self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some",
"last_name": "User",
"email": "foo@example.com"})
def test_update_user_details_long_names(self):
response = self.make_response_ax()
user = User.objects.create_user('someuser', 'someuser@example.com',
password=None)
data = dict(first_name=u"Some56789012345678901234567890123",
last_name=u"User56789012345678901234567890123",
email=u"someotheruser@example.com")
self.backend.update_user_details(user, data, response)
self.assertEqual("Some56789012345678901234567890", user.first_name)
self.assertEqual("User56789012345678901234567890", user.last_name)
def test_extract_user_details_name_with_trailing_space(self):
response = self.make_response_ax(fullname="SomeUser ")
data = self.backend._extract_user_details(response)
self.assertEqual("", data['first_name'])
self.assertEqual("SomeUser", data['last_name'])
def test_extract_user_details_name_with_thin_space(self):
response = self.make_response_ax(fullname=u"Some\u2009User")
data = self.backend._extract_user_details(response)
self.assertEqual("Some", data['first_name'])
self.assertEqual("User", data['last_name'])
def test_preferred_username_email_munging(self):
settings.OPENID_USE_EMAIL_FOR_USERNAME = True
for nick, email, expected in [
('nickcomesfirst', 'foo@example.com', 'nickcomesfirst'),
('', 'foo@example.com', 'fooexamplecom'),
('noemail', '', 'noemail'),
('', '@%.-', 'openiduser'),
('', '', 'openiduser'),
(None, None, 'openiduser')]:
self.assertEqual(expected,
self.backend._get_preferred_username(nick, email))
def test_preferred_username_no_email_munging(self):
for nick, email, expected in [
('nickcomesfirst', 'foo@example.com', 'nickcomesfirst'),
('', 'foo@example.com', 'openiduser'),
('noemail', '', 'noemail'),
('', '@%.-', 'openiduser'),
('', '', 'openiduser'),
(None, None, 'openiduser')]:
self.assertEqual(expected,
self.backend._get_preferred_username(nick, email))
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
django-openid-auth-0.5/django_openid_auth/signals.py 0000664 0001750 0001750 00000003015 12120172574 024052 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import django.dispatch
openid_login_complete = django.dispatch.Signal(providing_args=[
'request', 'openid_response'])
django-openid-auth-0.5/django_openid_auth/admin.py 0000664 0001750 0001750 00000007126 12120173203 023477 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2008-2013 Canonical Ltd.
# Copyright (C) 2010 Dave Walker
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.conf import settings
from django.contrib import admin
from django_openid_auth.models import Nonce, Association, UserOpenID
from django_openid_auth.store import DjangoOpenIDStore
class NonceAdmin(admin.ModelAdmin):
list_display = ('server_url', 'timestamp')
actions = ['cleanup_nonces']
def cleanup_nonces(self, request, queryset):
store = DjangoOpenIDStore()
count = store.cleanupNonces()
self.message_user(request, "%d expired nonces removed" % count)
cleanup_nonces.short_description = "Clean up expired nonces"
admin.site.register(Nonce, NonceAdmin)
class AssociationAdmin(admin.ModelAdmin):
list_display = ('server_url', 'assoc_type')
list_filter = ('assoc_type',)
search_fields = ('server_url',)
actions = ['cleanup_associations']
def cleanup_associations(self, request, queryset):
store = DjangoOpenIDStore()
count = store.cleanupAssociations()
self.message_user(request, "%d expired associations removed" % count)
cleanup_associations.short_description = "Clean up expired associations"
admin.site.register(Association, AssociationAdmin)
class UserOpenIDAdmin(admin.ModelAdmin):
raw_id_fields = ('user',)
list_display = ('user', 'claimed_id')
search_fields = ('claimed_id',)
admin.site.register(UserOpenID, UserOpenIDAdmin)
# Support for allowing openid authentication for /admin (django.contrib.admin)
if getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False):
from django.http import HttpResponseRedirect
from django_openid_auth import views
def _openid_login(self, request, error_message='', extra_context=None):
if request.user.is_authenticated():
if not request.user.is_staff:
return views.default_render_failure(
request, "User %s does not have admin access."
% request.user.username)
assert error_message, "Unknown Error: %s" % error_message
else:
# Redirect to openid login path,
return HttpResponseRedirect(
settings.LOGIN_URL + "?next=" + request.get_full_path())
# Overide the standard admin login form.
admin.sites.AdminSite.login = _openid_login
django-openid-auth-0.5/django_openid_auth/auth.py 0000664 0001750 0001750 00000032662 12120172560 023360 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Glue between OpenID and django.contrib.auth."""
__metaclass__ = type
from django.conf import settings
from django.contrib.auth.models import User, Group
from openid.consumer.consumer import SUCCESS
from openid.extensions import ax, sreg, pape
from django_openid_auth import teams
from django_openid_auth.models import UserOpenID
from django_openid_auth.exceptions import (
IdentityAlreadyClaimed,
DuplicateUsernameViolation,
MissingUsernameViolation,
MissingPhysicalMultiFactor,
RequiredAttributeNotReturned,
)
class OpenIDBackend:
"""A django.contrib.auth backend that authenticates the user based on
an OpenID response."""
supports_object_permissions = False
supports_anonymous_user = True
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
def authenticate(self, **kwargs):
"""Authenticate the user based on an OpenID response."""
# Require that the OpenID response be passed in as a keyword
# argument, to make sure we don't match the username/password
# calling conventions of authenticate.
openid_response = kwargs.get('openid_response')
if openid_response is None:
return None
if openid_response.status != SUCCESS:
return None
user = None
try:
user_openid = UserOpenID.objects.get(
claimed_id__exact=openid_response.identity_url)
except UserOpenID.DoesNotExist:
if getattr(settings, 'OPENID_CREATE_USERS', False):
user = self.create_user_from_openid(openid_response)
else:
user = user_openid.user
if user is None:
return None
if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False):
details = self._extract_user_details(openid_response)
self.update_user_details(user, details, openid_response)
if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False):
pape_response = pape.Response.fromSuccessResponse(openid_response)
if pape_response is None or \
pape.AUTH_MULTI_FACTOR_PHYSICAL not in pape_response.auth_policies:
raise MissingPhysicalMultiFactor()
teams_response = teams.TeamsResponse.fromSuccessResponse(
openid_response)
if teams_response:
self.update_groups_from_teams(user, teams_response)
self.update_staff_status_from_teams(user, teams_response)
return user
def _extract_user_details(self, openid_response):
email = fullname = first_name = last_name = nickname = None
sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response)
if sreg_response:
email = sreg_response.get('email')
fullname = sreg_response.get('fullname')
nickname = sreg_response.get('nickname')
# If any attributes are provided via Attribute Exchange, use
# them in preference.
fetch_response = ax.FetchResponse.fromSuccessResponse(openid_response)
if fetch_response:
# The myOpenID provider advertises AX support, but uses
# attribute names from an obsolete draft of the
# specification. We check for them first so the common
# names take precedence.
email = fetch_response.getSingle(
'http://schema.openid.net/contact/email', email)
fullname = fetch_response.getSingle(
'http://schema.openid.net/namePerson', fullname)
nickname = fetch_response.getSingle(
'http://schema.openid.net/namePerson/friendly', nickname)
email = fetch_response.getSingle(
'http://axschema.org/contact/email', email)
fullname = fetch_response.getSingle(
'http://axschema.org/namePerson', fullname)
first_name = fetch_response.getSingle(
'http://axschema.org/namePerson/first', first_name)
last_name = fetch_response.getSingle(
'http://axschema.org/namePerson/last', last_name)
nickname = fetch_response.getSingle(
'http://axschema.org/namePerson/friendly', nickname)
if fullname and not (first_name or last_name):
# Django wants to store first and last names separately,
# so we do our best to split the full name.
fullname = fullname.strip()
split_names = fullname.rsplit(None, 1)
if len(split_names) == 2:
first_name, last_name = split_names
else:
first_name = u''
last_name = fullname
return dict(email=email, nickname=nickname,
first_name=first_name, last_name=last_name)
def _get_preferred_username(self, nickname, email):
if nickname:
return nickname
if email and getattr(settings, 'OPENID_USE_EMAIL_FOR_USERNAME',
False):
suggestion = ''.join([x for x in email if x.isalnum()])
if suggestion:
return suggestion
return 'openiduser'
def _get_available_username(self, nickname, identity_url):
# If we're being strict about usernames, throw an error if we didn't
# get one back from the provider
if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
if nickname is None or nickname == '':
raise MissingUsernameViolation()
# If we don't have a nickname, and we're not being strict, use a default
nickname = nickname or 'openiduser'
# See if we already have this nickname assigned to a username
try:
user = User.objects.get(username__exact=nickname)
except User.DoesNotExist:
# No conflict, we can use this nickname
return nickname
# Check if we already have nickname+i for this identity_url
try:
user_openid = UserOpenID.objects.get(
claimed_id__exact=identity_url,
user__username__startswith=nickname)
# No exception means we have an existing user for this identity
# that starts with this nickname.
# If they are an exact match, the user already exists and hasn't
# changed their username, so continue to use it
if nickname == user_openid.user.username:
return nickname
# It is possible we've had to assign them to nickname+i already.
oid_username = user_openid.user.username
if len(oid_username) > len(nickname):
try:
# check that it ends with a number
int(oid_username[len(nickname):])
return oid_username
except ValueError:
# username starts with nickname, but isn't nickname+#
pass
except UserOpenID.DoesNotExist:
# No user associated with this identity_url
pass
if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
if User.objects.filter(username__exact=nickname).count() > 0:
raise DuplicateUsernameViolation(
"The username (%s) with which you tried to log in is "
"already in use for a different account." % nickname)
# Pick a username for the user based on their nickname,
# checking for conflicts. Start with number of existing users who's
# username starts with this nickname to avoid having to iterate over
# all of the existing ones.
i = User.objects.filter(username__startswith=nickname).count() + 1
while True:
username = nickname
if i > 1:
username += str(i)
try:
user = User.objects.get(username__exact=username)
except User.DoesNotExist:
break
i += 1
return username
def create_user_from_openid(self, openid_response):
details = self._extract_user_details(openid_response)
required_attrs = getattr(settings, 'OPENID_SREG_REQUIRED_FIELDS', [])
if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
required_attrs.append('nickname')
for required_attr in required_attrs:
if required_attr not in details or not details[required_attr]:
raise RequiredAttributeNotReturned(
"An attribute required for logging in was not "
"returned ({0}).".format(required_attr))
nickname = self._get_preferred_username(details['nickname'],
details['email'])
email = details['email'] or ''
username = self._get_available_username(nickname,
openid_response.identity_url)
user = User.objects.create_user(username, email, password=None)
self.associate_openid(user, openid_response)
self.update_user_details(user, details, openid_response)
return user
def associate_openid(self, user, openid_response):
"""Associate an OpenID with a user account."""
# Check to see if this OpenID has already been claimed.
try:
user_openid = UserOpenID.objects.get(
claimed_id__exact=openid_response.identity_url)
except UserOpenID.DoesNotExist:
user_openid = UserOpenID(
user=user,
claimed_id=openid_response.identity_url,
display_id=openid_response.endpoint.getDisplayIdentifier())
user_openid.save()
else:
if user_openid.user != user:
raise IdentityAlreadyClaimed(
"The identity %s has already been claimed"
% openid_response.identity_url)
return user_openid
def update_user_details(self, user, details, openid_response):
updated = False
if details['first_name']:
user.first_name = details['first_name'][:30]
updated = True
if details['last_name']:
user.last_name = details['last_name'][:30]
updated = True
if details['email']:
user.email = details['email']
updated = True
if getattr(settings, 'OPENID_FOLLOW_RENAMES', False):
user.username = self._get_available_username(details['nickname'], openid_response.identity_url)
updated = True
if updated:
user.save()
def update_groups_from_teams(self, user, teams_response):
teams_mapping_auto = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False)
teams_mapping_auto_blacklist = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST', [])
teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
if teams_mapping_auto:
#ignore teams_mapping. use all django-groups
teams_mapping = dict()
all_groups = Group.objects.exclude(name__in=teams_mapping_auto_blacklist)
for group in all_groups:
teams_mapping[group.name] = group.name
if len(teams_mapping) == 0:
return
current_groups = set(user.groups.filter(
name__in=teams_mapping.values()))
desired_groups = set(Group.objects.filter(
name__in=[teams_mapping[lp_team]
for lp_team in teams_response.is_member
if lp_team in teams_mapping]))
for group in current_groups - desired_groups:
user.groups.remove(group)
for group in desired_groups - current_groups:
user.groups.add(group)
def update_staff_status_from_teams(self, user, teams_response):
if not hasattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS'):
return
staff_teams = getattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS', [])
user.is_staff = False
for lp_team in teams_response.is_member:
if lp_team in staff_teams:
user.is_staff = True
break
user.save()
django-openid-auth-0.5/django_openid_auth/exceptions.py 0000664 0001750 0001750 00000005016 12120172555 024575 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Exception classes thrown by OpenID Authentication and Validation."""
class DjangoOpenIDException(Exception):
pass
class RequiredAttributeNotReturned(DjangoOpenIDException):
pass
class IdentityAlreadyClaimed(DjangoOpenIDException):
def __init__(self, message=None):
if message is None:
self.message = "Another user already exists for your selected OpenID"
else:
self.message = message
class DuplicateUsernameViolation(DjangoOpenIDException):
def __init__(self, message=None):
if message is None:
self.message = "Your desired username is already being used."
else:
self.message = message
class MissingUsernameViolation(DjangoOpenIDException):
def __init__(self, message=None):
if message is None:
self.message = "No nickname given for your account."
else:
self.message = message
class MissingPhysicalMultiFactor(DjangoOpenIDException):
def __init__(self, message=None):
if message is None:
self.message = "Login requires physical multi-factor authentication."
else:
self.message = message
django-openid-auth-0.5/django_openid_auth/views.py 0000664 0001750 0001750 00000031152 12120172553 023547 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import re
import urllib
from urlparse import urlsplit
from django.conf import settings
from django.contrib.auth import (
REDIRECT_FIELD_NAME, authenticate, login as auth_login)
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.template.loader import render_to_string
try:
from django.views.decorators.csrf import csrf_exempt
except ImportError:
from django.contrib.csrf.middleware import csrf_exempt
from openid.consumer.consumer import (
Consumer, SUCCESS, CANCEL, FAILURE)
from openid.consumer.discover import DiscoveryFailure
from openid.extensions import sreg, ax, pape
from django_openid_auth import teams
from django_openid_auth.forms import OpenIDLoginForm
from django_openid_auth.models import UserOpenID
from django_openid_auth.signals import openid_login_complete
from django_openid_auth.store import DjangoOpenIDStore
from django_openid_auth.exceptions import (
RequiredAttributeNotReturned,
DjangoOpenIDException,
)
next_url_re = re.compile('^/[-\w/]+$')
def is_valid_next_url(next):
# When we allow this:
# /openid/?next=/welcome/
# For security reasons we want to restrict the next= bit to being a local
# path, not a complete URL.
return bool(next_url_re.match(next))
def sanitise_redirect_url(redirect_to):
"""Sanitise the redirection URL."""
# Light security check -- make sure redirect_to isn't garbage.
is_valid = True
if not redirect_to or ' ' in redirect_to:
is_valid = False
elif '//' in redirect_to:
# Allow the redirect URL to be external if it's a permitted domain
allowed_domains = getattr(settings,
"ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS", [])
s, netloc, p, q, f = urlsplit(redirect_to)
# allow it if netloc is blank or if the domain is allowed
if netloc:
# a domain was specified. Is it an allowed domain?
if netloc.find(":") != -1:
netloc, _ = netloc.split(":", 1)
if netloc not in allowed_domains:
is_valid = False
# If the return_to URL is not valid, use the default.
if not is_valid:
redirect_to = settings.LOGIN_REDIRECT_URL
return redirect_to
def make_consumer(request):
"""Create an OpenID Consumer object for the given Django request."""
# Give the OpenID library its own space in the session object.
session = request.session.setdefault('OPENID', {})
store = DjangoOpenIDStore()
return Consumer(session, store)
def render_openid_request(request, openid_request, return_to, trust_root=None):
"""Render an OpenID authentication request."""
if trust_root is None:
trust_root = getattr(settings, 'OPENID_TRUST_ROOT',
request.build_absolute_uri('/'))
if openid_request.shouldSendRedirect():
redirect_url = openid_request.redirectURL(
trust_root, return_to)
return HttpResponseRedirect(redirect_url)
else:
form_html = openid_request.htmlMarkup(
trust_root, return_to, form_tag_attrs={'id': 'openid_message'})
return HttpResponse(form_html, content_type='text/html;charset=UTF-8')
def default_render_failure(request, message, status=403,
template_name='openid/failure.html',
exception=None):
"""Render an error page to the user."""
data = render_to_string(
template_name, dict(message=message, exception=exception),
context_instance=RequestContext(request))
return HttpResponse(data, status=status)
def parse_openid_response(request):
"""Parse an OpenID response from a Django request."""
# Short cut if there is no request parameters.
#if len(request.REQUEST) == 0:
# return None
current_url = request.build_absolute_uri()
consumer = make_consumer(request)
return consumer.complete(dict(request.REQUEST.items()), current_url)
def login_begin(request, template_name='openid/login.html',
login_complete_view='openid-complete',
form_class=OpenIDLoginForm,
render_failure=default_render_failure,
redirect_field_name=REDIRECT_FIELD_NAME):
"""Begin an OpenID login request, possibly asking for an identity URL."""
redirect_to = request.REQUEST.get(redirect_field_name, '')
# Get the OpenID URL to try. First see if we've been configured
# to use a fixed server URL.
openid_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)
if openid_url is None:
if request.POST:
login_form = form_class(data=request.POST)
if login_form.is_valid():
openid_url = login_form.cleaned_data['openid_identifier']
else:
login_form = form_class()
# Invalid or no form data:
if openid_url is None:
return render_to_response(template_name, {
'form': login_form,
redirect_field_name: redirect_to
}, context_instance=RequestContext(request))
error = None
consumer = make_consumer(request)
try:
openid_request = consumer.begin(openid_url)
except DiscoveryFailure, exc:
return render_failure(
request, "OpenID discovery error: %s" % (str(exc),), status=500,
exception=exc)
# Request some user details. If the provider advertises support
# for attribute exchange, use that.
if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri):
fetch_request = ax.FetchRequest()
# We mark all the attributes as required, since Google ignores
# optional attributes. We request both the full name and
# first/last components since some providers offer one but not
# the other.
for (attr, alias) in [
('http://axschema.org/contact/email', 'email'),
('http://axschema.org/namePerson', 'fullname'),
('http://axschema.org/namePerson/first', 'firstname'),
('http://axschema.org/namePerson/last', 'lastname'),
('http://axschema.org/namePerson/friendly', 'nickname'),
# The myOpenID provider advertises AX support, but uses
# attribute names from an obsolete draft of the
# specification. We request them for compatibility.
('http://schema.openid.net/contact/email', 'old_email'),
('http://schema.openid.net/namePerson', 'old_fullname'),
('http://schema.openid.net/namePerson/friendly', 'old_nickname')]:
fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True))
openid_request.addExtension(fetch_request)
else:
sreg_required_fields = []
sreg_required_fields.extend(
getattr(settings, 'OPENID_SREG_REQUIRED_FIELDS', []))
sreg_optional_fields = ['email', 'fullname', 'nickname']
sreg_optional_fields.extend(
getattr(settings, 'OPENID_SREG_EXTRA_FIELDS', []))
sreg_optional_fields = [
field for field in sreg_optional_fields if (
not field in sreg_required_fields)]
openid_request.addExtension(
sreg.SRegRequest(optional=sreg_optional_fields,
required=sreg_required_fields))
if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False):
preferred_auth = [
pape.AUTH_MULTI_FACTOR_PHYSICAL,
]
pape_request = pape.Request(preferred_auth_policies=preferred_auth)
openid_request.addExtension(pape_request)
# Request team info
teams_mapping_auto = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False)
teams_mapping_auto_blacklist = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST', [])
launchpad_teams = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
if teams_mapping_auto:
#ignore launchpad teams. use all django-groups
launchpad_teams = dict()
all_groups = Group.objects.exclude(name__in=teams_mapping_auto_blacklist)
for group in all_groups:
launchpad_teams[group.name] = group.name
if launchpad_teams:
openid_request.addExtension(teams.TeamsRequest(launchpad_teams.keys()))
# Construct the request completion URL, including the page we
# should redirect to.
return_to = request.build_absolute_uri(reverse(login_complete_view))
if redirect_to:
if '?' in return_to:
return_to += '&'
else:
return_to += '?'
# Django gives us Unicode, which is great. We must encode URI.
# urllib enforces str. We can't trust anything about the default
# encoding inside str(foo) , so we must explicitly make foo a str.
return_to += urllib.urlencode(
{redirect_field_name: redirect_to.encode("UTF-8")})
return render_openid_request(request, openid_request, return_to)
@csrf_exempt
def login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME,
render_failure=None):
redirect_to = request.REQUEST.get(redirect_field_name, '')
render_failure = render_failure or \
getattr(settings, 'OPENID_RENDER_FAILURE', None) or \
default_render_failure
openid_response = parse_openid_response(request)
if not openid_response:
return render_failure(
request, 'This is an OpenID relying party endpoint.')
if openid_response.status == SUCCESS:
try:
user = authenticate(openid_response=openid_response)
except DjangoOpenIDException, e:
return render_failure(request, e.message, exception=e)
if user is not None:
if user.is_active:
auth_login(request, user)
response = HttpResponseRedirect(sanitise_redirect_url(redirect_to))
# Notify any listeners that we successfully logged in.
openid_login_complete.send(sender=UserOpenID, request=request,
openid_response=openid_response)
return response
else:
return render_failure(request, 'Disabled account')
else:
return render_failure(request, 'Unknown user')
elif openid_response.status == FAILURE:
return render_failure(
request, 'OpenID authentication failed: %s' %
openid_response.message)
elif openid_response.status == CANCEL:
return render_failure(request, 'Authentication cancelled')
else:
assert False, (
"Unknown OpenID response type: %r" % openid_response.status)
def logo(request):
return HttpResponse(
OPENID_LOGO_BASE_64.decode('base64'), mimetype='image/gif'
)
# Logo from http://openid.net/login-bg.gif
# Embedded here for convenience; you should serve this as a static file
OPENID_LOGO_BASE_64 = """
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
Fzk0lpcjIQA7
"""
django-openid-auth-0.5/django_openid_auth/__init__.py 0000664 0001750 0001750 00000002621 12120172550 024145 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
django-openid-auth-0.5/django_openid_auth/store.py 0000664 0001750 0001750 00000011326 12120172546 023551 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import base64
import time
from openid.association import Association as OIDAssociation
from openid.store.interface import OpenIDStore
from openid.store.nonce import SKEW
from django_openid_auth.models import Association, Nonce
class DjangoOpenIDStore(OpenIDStore):
def __init__(self):
self.max_nonce_age = 6 * 60 * 60 # Six hours
def storeAssociation(self, server_url, association):
try:
assoc = Association.objects.get(
server_url=server_url, handle=association.handle)
except Association.DoesNotExist:
assoc = Association(
server_url=server_url,
handle=association.handle,
secret=base64.encodestring(association.secret),
issued=association.issued,
lifetime=association.lifetime,
assoc_type=association.assoc_type)
else:
assoc.secret = base64.encodestring(association.secret)
assoc.issued = association.issued
assoc.lifetime = association.lifetime
assoc.assoc_type = association.assoc_type
assoc.save()
def getAssociation(self, server_url, handle=None):
assocs = []
if handle is not None:
assocs = Association.objects.filter(
server_url=server_url, handle=handle)
else:
assocs = Association.objects.filter(server_url=server_url)
associations = []
expired = []
for assoc in assocs:
association = OIDAssociation(
assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
assoc.lifetime, assoc.assoc_type
)
if association.getExpiresIn() == 0:
expired.append(assoc)
else:
associations.append((association.issued, association))
for assoc in expired:
assoc.delete()
if not associations:
return None
associations.sort()
return associations[-1][1]
def removeAssociation(self, server_url, handle):
assocs = list(Association.objects.filter(
server_url=server_url, handle=handle))
assocs_exist = len(assocs) > 0
for assoc in assocs:
assoc.delete()
return assocs_exist
def useNonce(self, server_url, timestamp, salt):
if abs(timestamp - time.time()) > SKEW:
return False
try:
ononce = Nonce.objects.get(
server_url__exact=server_url,
timestamp__exact=timestamp,
salt__exact=salt)
except Nonce.DoesNotExist:
ononce = Nonce(
server_url=server_url,
timestamp=timestamp,
salt=salt)
ononce.save()
return True
return False
def cleanupNonces(self, _now=None):
if _now is None:
_now = int(time.time())
expired = Nonce.objects.filter(timestamp__lt=_now - SKEW)
count = expired.count()
if count:
expired.delete()
return count
def cleanupAssociations(self):
now = int(time.time())
expired = Association.objects.extra(
where=['issued + lifetime < %d' % now])
count = expired.count()
if count:
expired.delete()
return count
django-openid-auth-0.5/django_openid_auth/models.py 0000664 0001750 0001750 00000004464 12120172544 023703 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.contrib.auth.models import User
from django.db import models
class Nonce(models.Model):
server_url = models.CharField(max_length=2047)
timestamp = models.IntegerField()
salt = models.CharField(max_length=40)
def __unicode__(self):
return u"Nonce: %s, %s" % (self.server_url, self.salt)
class Association(models.Model):
server_url = models.TextField(max_length=2047)
handle = models.CharField(max_length=255)
secret = models.TextField(max_length=255) # Stored base64 encoded
issued = models.IntegerField()
lifetime = models.IntegerField()
assoc_type = models.TextField(max_length=64)
def __unicode__(self):
return u"Association: %s, %s" % (self.server_url, self.handle)
class UserOpenID(models.Model):
user = models.ForeignKey(User)
claimed_id = models.TextField(max_length=2047, unique=True)
display_id = models.TextField(max_length=2047)
django-openid-auth-0.5/django_openid_auth/urls.py 0000664 0001750 0001750 00000003240 12120172542 023372 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.conf.urls.defaults import *
urlpatterns = patterns('django_openid_auth.views',
url(r'^login/$', 'login_begin', name='openid-login'),
url(r'^complete/$', 'login_complete', name='openid-complete'),
url(r'^logo.gif$', 'logo', name='openid-logo'),
)
django-openid-auth-0.5/django_openid_auth/teams.py 0000664 0001750 0001750 00000033653 12120172536 023534 0 ustar anthony anthony 0000000 0000000 # Launchpad OpenID Teams Extension support for python-openid
#
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Team membership support for Launchpad.
The primary form of communication between the RP and Launchpad is an
OpenID authentication request. Our solution is to piggyback a team
membership test onto this interaction.
As part of an OpenID authentication request, the RP includes the
following fields:
openid.ns.lp:
An OpenID 2.0 namespace URI for the extension. It is not strictly
required for 1.1 requests, but including it is good for forward
compatibility.
It must be set to: http://ns.launchpad.net/2007/openid-teams
openid.lp.query_membership:
A comma separated list of Launchpad team names that the RP is
interested in.
As part of the positive assertion OpenID response, the following field
will be provided:
openid.ns.lp:
(as above)
openid.lp.is_member:
A comma separated list of teams that the user is actually a member
of. The list may be limited to those teams mentioned in the
request.
This field must be included in the response signature in order to
be considered valid (as the response is bounced through the user's
web browser, an unsigned value could be modified).
@since: 2.1.1
"""
from openid.message import registerNamespaceAlias, \
NamespaceAliasRegistrationError
from openid.extension import Extension
from openid import oidutil
try:
basestring #pylint:disable-msg=W0104
except NameError:
# For Python 2.2
basestring = (str, unicode) #pylint:disable-msg=W0622
__all__ = [
'TeamsRequest',
'TeamsResponse',
'ns_uri',
'supportsTeams',
]
ns_uri = 'http://ns.launchpad.net/2007/openid-teams'
try:
registerNamespaceAlias(ns_uri, 'lp')
except NamespaceAliasRegistrationError, e:
oidutil.log('registerNamespaceAlias(%r, %r) failed: %s' % (ns_uri,
'lp', str(e),))
def supportsTeams(endpoint):
"""Does the given endpoint advertise support for Launchpad Teams?
@param endpoint: The endpoint object as returned by OpenID discovery
@type endpoint: openid.consumer.discover.OpenIDEndpoint
@returns: Whether an lp type was advertised by the endpoint
@rtype: bool
"""
return endpoint.usesExtension(ns_uri)
class TeamsNamespaceError(ValueError):
"""The Launchpad teams namespace was not found and could not
be created using the expected name (there's another extension
using the name 'lp')
This is not I{illegal}, for OpenID 2, although it probably
indicates a problem, since it's not expected that other extensions
will re-use the alias that is in use for OpenID 1.
If this is an OpenID 1 request, then there is no recourse. This
should not happen unless some code has modified the namespaces for
the message that is being processed.
"""
def getTeamsNS(message):
"""Extract the Launchpad teams namespace URI from the given
OpenID message.
@param message: The OpenID message from which to parse Launchpad
teams. This may be a request or response message.
@type message: C{L{openid.message.Message}}
@returns: the lp namespace URI for the supplied message. The
message may be modified to define a Launchpad teams
namespace.
@rtype: C{str}
@raise ValueError: when using OpenID 1 if the message defines
the 'lp' alias to be something other than a Launchpad
teams type.
"""
# See if there exists an alias for the Launchpad teams type.
alias = message.namespaces.getAlias(ns_uri)
if alias is None:
# There is no alias, so try to add one. (OpenID version 1)
try:
message.namespaces.addAlias(ns_uri, 'lp')
except KeyError, why:
# An alias for the string 'lp' already exists, but it's
# defined for something other than Launchpad teams
raise TeamsNamespaceError(why[0])
# we know that ns_uri defined, because it's defined in the
# else clause of the loop as well, so disable the warning
return ns_uri #pylint:disable-msg=W0631
class TeamsRequest(Extension):
"""An object to hold the state of a Launchpad teams request.
@ivar query_membership: A comma separated list of Launchpad team
names that the RP is interested in.
@type required: [str]
@group Consumer: requestField, requestTeams, getExtensionArgs, addToOpenIDRequest
@group Server: fromOpenIDRequest, parseExtensionArgs
"""
ns_alias = 'lp'
def __init__(self, query_membership=None, lp_ns_uri=ns_uri):
"""Initialize an empty Launchpad teams request"""
Extension.__init__(self)
self.query_membership = []
self.ns_uri = lp_ns_uri
if query_membership:
self.requestTeams(query_membership)
# Assign getTeamsNS to a static method so that it can be
# overridden for testing.
_getTeamsNS = staticmethod(getTeamsNS)
def fromOpenIDRequest(cls, request):
"""Create a Launchpad teams request that contains the
fields that were requested in the OpenID request with the
given arguments
@param request: The OpenID request
@type request: openid.server.CheckIDRequest
@returns: The newly created Launchpad teams request
@rtype: C{L{TeamsRequest}}
"""
self = cls()
# Since we're going to mess with namespace URI mapping, don't
# mutate the object that was passed in.
message = request.message.copy()
self.ns_uri = self._getTeamsNS(message)
args = message.getArgs(self.ns_uri)
self.parseExtensionArgs(args)
return self
fromOpenIDRequest = classmethod(fromOpenIDRequest)
def parseExtensionArgs(self, args, strict=False):
"""Parse the unqualified Launchpad teams request
parameters and add them to this object.
This method is essentially the inverse of
C{L{getExtensionArgs}}. This method restores the serialized
Launchpad teams request fields.
If you are extracting arguments from a standard OpenID
checkid_* request, you probably want to use C{L{fromOpenIDRequest}},
which will extract the lp namespace and arguments from the
OpenID request. This method is intended for cases where the
OpenID server needs more control over how the arguments are
parsed than that method provides.
>>> args = message.getArgs(ns_uri)
>>> request.parseExtensionArgs(args)
@param args: The unqualified Launchpad teams arguments
@type args: {str:str}
@param strict: Whether requests with fields that are not
defined in the Launchpad teams specification should be
tolerated (and ignored)
@type strict: bool
@returns: None; updates this object
"""
items = args.get('query_membership')
if items:
for team_name in items.split(','):
try:
self.requestTeam(team_name, strict)
except ValueError:
if strict:
raise
def allRequestedTeams(self):
"""A list of all of the Launchpad teams that were
requested.
@rtype: [str]
"""
return self.query_membership
def wereTeamsRequested(self):
"""Have any Launchpad teams been requested?
@rtype: bool
"""
return bool(self.allRequestedTeams())
def __contains__(self, team_name):
"""Was this team in the request?"""
return team_name in self.query_membership
def requestTeam(self, team_name, strict=False):
"""Request the specified team from the OpenID user
@param team_name: the unqualified Launchpad team name
@type team_name: str
@param strict: whether to raise an exception when a team is
added to a request more than once
@raise ValueError: when strict is set and the team was
requested more than once
"""
if strict:
if team_name in self.query_membership:
raise ValueError('That team has already been requested')
else:
if team_name in self.query_membership:
return
self.query_membership.append(team_name)
def requestTeams(self, query_membership, strict=False):
"""Add the given list of teams to the request
@param query_membership: The Launchpad teams request
@type query_membership: [str]
@raise ValueError: when a team requested is not a string
or strict is set and a team was requested more than once
"""
if isinstance(query_membership, basestring):
raise TypeError('Teams should be passed as a list of '
'strings (not %r)' % (type(query_membership),))
for team_name in query_membership:
self.requestTeam(team_name, strict=strict)
def getExtensionArgs(self):
"""Get a dictionary of unqualified Launchpad teams
arguments representing this request.
This method is essentially the inverse of
C{L{parseExtensionArgs}}. This method serializes the Launchpad
teams request fields.
@rtype: {str:str}
"""
args = {}
if self.query_membership:
args['query_membership'] = ','.join(self.query_membership)
return args
class TeamsResponse(Extension):
"""Represents the data returned in a Launchpad teams response
inside of an OpenID C{id_res} response. This object will be
created by the OpenID server, added to the C{id_res} response
object, and then extracted from the C{id_res} message by the
Consumer.
@ivar data: The Launchpad teams data, an array.
@ivar ns_uri: The URI under which the Launchpad teams data was
stored in the response message.
@group Server: extractResponse
@group Consumer: fromSuccessResponse
@group Read-only dictionary interface: keys, iterkeys, items, iteritems,
__iter__, get, __getitem__, keys, has_key
"""
ns_alias = 'lp'
def __init__(self, is_member=None, lp_ns_uri=ns_uri):
Extension.__init__(self)
if is_member is None:
self.is_member = []
else:
self.is_member = is_member
self.ns_uri = lp_ns_uri
def addTeam(self, team_name):
if team_name not in self.is_member:
self.is_member.append(team_name)
def extractResponse(cls, request, is_member_str):
"""Take a C{L{TeamsRequest}} and a list of Launchpad
team values and create a C{L{TeamsResponse}}
object containing that data.
@param request: The Launchpad teams request object
@type request: TeamsRequest
@param is_member: The Launchpad teams data for this
response, as a list of strings.
@type is_member: {str:str}
@returns: a Launchpad teams response object
@rtype: TeamsResponse
"""
self = cls()
self.ns_uri = request.ns_uri
self.is_member = is_member_str.split(',')
return self
extractResponse = classmethod(extractResponse)
# Assign getTeamsNS to a static method so that it can be
# overridden for testing
_getTeamsNS = staticmethod(getTeamsNS)
def fromSuccessResponse(cls, success_response, signed_only=True):
"""Create a C{L{TeamsResponse}} object from a successful OpenID
library response
(C{L{openid.consumer.consumer.SuccessResponse}}) response
message
@param success_response: A SuccessResponse from consumer.complete()
@type success_response: C{L{openid.consumer.consumer.SuccessResponse}}
@param signed_only: Whether to process only data that was
signed in the id_res message from the server.
@type signed_only: bool
@rtype: TeamsResponse
@returns: A Launchpad teams response containing the data
that was supplied with the C{id_res} response.
"""
self = cls()
self.ns_uri = self._getTeamsNS(success_response.message)
if signed_only:
args = success_response.getSignedNS(self.ns_uri)
else:
args = success_response.message.getArgs(self.ns_uri)
if "is_member" in args:
is_member_str = args["is_member"]
self.is_member = is_member_str.split(',')
#self.is_member = args["is_member"]
return self
fromSuccessResponse = classmethod(fromSuccessResponse)
def getExtensionArgs(self):
"""Get the fields to put in the Launchpad teams namespace
when adding them to an id_res message.
@see: openid.extension
"""
ns_args = {'is_member': ','.join(self.is_member),}
return ns_args
django-openid-auth-0.5/django_openid_auth/templates/ 0000775 0001750 0001750 00000000000 12120431074 024030 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/django_openid_auth/templates/openid/ 0000775 0001750 0001750 00000000000 12120431074 025306 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/django_openid_auth/templates/openid/login.html 0000664 0001750 0001750 00000002434 12017423116 027312 0 ustar anthony anthony 0000000 0000000 {% load i18n %}
Sign in with your OpenID
Sign in with your OpenID
{% if form.errors %}
{% trans "Please correct errors below:" %}
{% if form.openid_identifier.errors %}
{{ form.openid_identifier.errors|join:", " }}
{% endif %}
{% if form.next.errors %}
{{ form.next.errors|join:", " }}
{% endif %}
{% endif %}
django-openid-auth-0.5/django_openid_auth/templates/openid/failure.html 0000664 0001750 0001750 00000000346 12017423116 027631 0 ustar anthony anthony 0000000 0000000
OpenID failed
OpenID failed
{{ message|escape }}
django-openid-auth-0.5/django_openid_auth/management/ 0000775 0001750 0001750 00000000000 12120431074 024146 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/django_openid_auth/management/__init__.py 0000664 0001750 0001750 00000002554 12120172533 026267 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
django-openid-auth-0.5/django_openid_auth/management/commands/ 0000775 0001750 0001750 00000000000 12120431074 025747 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/django_openid_auth/management/commands/__init__.py 0000664 0001750 0001750 00000002554 12120172531 030066 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
django-openid-auth-0.5/django_openid_auth/management/commands/openid_cleanup.py 0000664 0001750 0001750 00000003233 12120172526 031313 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.core.management.base import NoArgsCommand
from django_openid_auth.store import DjangoOpenIDStore
class Command(NoArgsCommand):
help = 'Clean up stale OpenID associations and nonces'
def handle_noargs(self, **options):
store = DjangoOpenIDStore()
store.cleanup()
django-openid-auth-0.5/django_openid_auth/forms.py 0000664 0001750 0001750 00000006776 12120172517 023556 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django import forms
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.models import Group
from django.utils.translation import ugettext as _
from django.conf import settings
from openid.yadis import xri
def teams_new_unicode(self):
"""
Replacement for Group.__unicode__()
Calls original method to chain results
"""
name = self.unicode_before_teams()
teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
group_teams = [t for t in teams_mapping if teams_mapping[t] == self.name]
if len(group_teams) > 0:
return "%s -> %s" % (name, ", ".join(group_teams))
else:
return name
Group.unicode_before_teams = Group.__unicode__
Group.__unicode__ = teams_new_unicode
class UserChangeFormWithTeamRestriction(UserChangeForm):
"""
Extends UserChangeForm to add teams awareness to the user admin form
"""
def clean_groups(self):
data = self.cleaned_data['groups']
teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
known_teams = teams_mapping.values()
user_groups = self.instance.groups.all()
for group in data:
if group.name in known_teams and group not in user_groups:
raise forms.ValidationError("""The group %s is mapped to an
external team. You cannot assign it manually.""" % group.name)
return data
UserAdmin.form = UserChangeFormWithTeamRestriction
class OpenIDLoginForm(forms.Form):
openid_identifier = forms.CharField(
max_length=255,
widget=forms.TextInput(attrs={'class': 'required openid'}))
def clean_openid_identifier(self):
if 'openid_identifier' in self.cleaned_data:
openid_identifier = self.cleaned_data['openid_identifier']
if xri.identifierScheme(openid_identifier) == 'XRI' and getattr(
settings, 'OPENID_DISALLOW_INAMES', False
):
raise forms.ValidationError(_('i-names are not supported'))
return self.cleaned_data['openid_identifier']
django-openid-auth-0.5/setup.py 0000664 0001750 0001750 00000006332 12120172653 017734 0 ustar anthony anthony 0000000 0000000 #!/usr/bin/env python
# django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2009-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""OpenID integration for django.contrib.auth
A library that can be used to add OpenID support to Django applications.
The library integrates with Django's built in authentication system, so
most applications require minimal changes to support OpenID llogin. The
library also includes the following features:
* Basic user details are transferred from the OpenID server via the
Simple Registration extension or Attribute Exchange extension.
* can be configured to use a fixed OpenID server URL, for use in SSO.
* supports the launchpad.net teams extension to get team membership
info.
"""
from distutils.core import setup
description, long_description = __doc__.split('\n\n', 1)
VERSION = '0.5'
setup(
name='django-openid-auth',
version=VERSION,
author='Canonical Ltd',
description=description,
long_description=long_description,
license='BSD',
platforms=['any'],
url='https://launchpad.net/django-openid-auth',
download_url=('http://launchpad.net/django-openid-auth/trunk/%s/+download'
'/django-openid-auth-%s.tar.gz' % (VERSION, VERSION)),
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules'
],
packages=[
'django_openid_auth',
'django_openid_auth.management',
'django_openid_auth.management.commands',
'django_openid_auth.tests',
],
package_data={
'django_openid_auth': ['templates/openid/*.html'],
},
provides=['django_openid_auth'],
requires=['django (>=1.1.2)', 'openid (>=2.2.0)'],
)
django-openid-auth-0.5/PKG-INFO 0000664 0001750 0001750 00000002634 12120431074 017313 0 ustar anthony anthony 0000000 0000000 Metadata-Version: 1.1
Name: django-openid-auth
Version: 0.5
Summary: OpenID integration for django.contrib.auth
Home-page: https://launchpad.net/django-openid-auth
Author: Canonical Ltd
Author-email: UNKNOWN
License: BSD
Download-URL: http://launchpad.net/django-openid-auth/trunk/0.5/+download/django-openid-auth-0.5.tar.gz
Description: A library that can be used to add OpenID support to Django applications.
The library integrates with Django's built in authentication system, so
most applications require minimal changes to support OpenID llogin. The
library also includes the following features:
* Basic user details are transferred from the OpenID server via the
Simple Registration extension or Attribute Exchange extension.
* can be configured to use a fixed OpenID server URL, for use in SSO.
* supports the launchpad.net teams extension to get team membership
info.
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires: django (>=1.1.2)
Requires: openid (>=2.2.0)
Provides: django_openid_auth
django-openid-auth-0.5/MANIFEST.in 0000664 0001750 0001750 00000000274 12017423116 017755 0 ustar anthony anthony 0000000 0000000 include Makefile
include MANIFEST.in
include LICENSE.txt
include README.txt
include TODO.txt
recursive-include django_openid_auth/templates *.html
recursive-include example_consumer *.py
django-openid-auth-0.5/Makefile 0000664 0001750 0001750 00000000461 12017423116 017655 0 ustar anthony anthony 0000000 0000000
check:
PYTHONPATH=$(shell pwd) python example_consumer/manage.py test \
--verbosity=2 django_openid_auth
run-example-consumer:
PYTHONPATH=$(shell pwd) python example_consumer/manage.py syncdb
PYTHONPATH=$(shell pwd) python example_consumer/manage.py runserver
.PHONY: check run-example-consumer
django-openid-auth-0.5/LICENSE.txt 0000664 0001750 0001750 00000002433 12017423116 020041 0 ustar anthony anthony 0000000 0000000 Copyright (C) 2007 Simon Willison
Copyright (C) 2008-2010 Canonical Ltd.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
django-openid-auth-0.5/example_consumer/ 0000775 0001750 0001750 00000000000 12120431074 021557 5 ustar anthony anthony 0000000 0000000 django-openid-auth-0.5/example_consumer/manage.py 0000775 0001750 0001750 00000001042 12017423116 023364 0 ustar anthony anthony 0000000 0000000 #!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)
django-openid-auth-0.5/example_consumer/views.py 0000664 0001750 0001750 00000004353 12120172500 023267 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.utils.html import escape
def index(request):
s = ['']
if request.user.is_authenticated():
s.append('You are signed in as %s (%s)' % (
escape(request.user.username),
escape(request.user.get_full_name())))
s.append(' | Sign out')
else:
s.append('Sign in with OpenID')
s.append('
')
s.append('This requires authentication
')
return HttpResponse('\n'.join(s))
def next_works(request):
return HttpResponse('?next= bit works. Home')
@login_required
def require_authentication(request):
return HttpResponse('This page requires authentication')
django-openid-auth-0.5/example_consumer/__init__.py 0000664 0001750 0001750 00000002620 12120172476 023700 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
django-openid-auth-0.5/example_consumer/urls.py 0000664 0001750 0001750 00000003406 12120172474 023127 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.conf.urls.defaults import *
from django.contrib import admin
import views
admin.autodiscover()
urlpatterns = patterns('',
(r'^$', views.index),
(r'^openid/', include('django_openid_auth.urls')),
(r'^logout/$', 'django.contrib.auth.views.logout'),
(r'^private/$', views.require_authentication),
(r'^admin/', include(admin.site.urls)),
)
django-openid-auth-0.5/example_consumer/settings.py 0000664 0001750 0001750 00000012543 12120406430 023774 0 ustar anthony anthony 0000000 0000000 # django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 2007 Simon Willison
# Copyright (C) 2008-2013 Canonical Ltd.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Django settings for example project.
import django
django_version = django.get_version()
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
MANAGERS = ADMINS
if django_version >= "1.2":
csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'sqlite.db'
}
}
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
else:
csrf_middleware = 'django.contrib.csrf.middleware.CsrfViewMiddleware'
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
)
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
# Local time zone for this installation. Choices can be found here:
# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
# although not all variations may be possible on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
# http://blogs.law.harvard.edu/tech/stories/storyReader$15
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT.
# Example: "http://media.lawrence.com"
MEDIA_URL = ''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
# Make this unique, and don't share it with anybody.
SECRET_KEY = '34958734985734985734985798437'
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
csrf_middleware,
)
ROOT_URLCONF = 'example_consumer.urls'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'django_openid_auth',
)
AUTHENTICATION_BACKENDS = (
'django_openid_auth.auth.OpenIDBackend',
'django.contrib.auth.backends.ModelBackend',
)
# Should users be created when new OpenIDs are used to log in?
OPENID_CREATE_USERS = True
# When logging in again, should we overwrite user details based on
# data received via Simple Registration?
OPENID_UPDATE_DETAILS_FROM_SREG = True
# If set, always use this as the identity URL rather than asking the
# user. This only makes sense if it is a server URL.
OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/'
# Tell django.contrib.auth to use the OpenID signin URLs.
LOGIN_URL = '/openid/login/'
LOGIN_REDIRECT_URL = '/'
# Should django_auth_openid be used to sign into the admin interface?
OPENID_USE_AS_ADMIN_LOGIN = False
django-openid-auth-0.5/TODO.txt 0000664 0001750 0001750 00000000000 12017423116 017510 0 ustar anthony anthony 0000000 0000000