Commit cfb82d35 by Ricardo Kirkner

Add http://ns.login.ubuntu.com/2013/validation/account to the list of requested…

Add http://ns.login.ubuntu.com/2013/validation/account to the list of requested attributes in the Attribute Exchange request, and store the result in UserOpenID.
parents 0ba276cf dda73b1d
...@@ -198,3 +198,28 @@ information, by setting the following setting: ...@@ -198,3 +198,28 @@ information, by setting the following setting:
Otherwise, and by default, if the server omits nick information and a user is Otherwise, and by default, if the server omits nick information and a user is
created it'll receive a username 'openiduser' + a number. created it'll receive a username 'openiduser' + a number.
Consider also the OPENID_STRICT_USERNAMES setting (see ``Require a valid nickname``) Consider also the OPENID_STRICT_USERNAMES setting (see ``Require a valid nickname``)
== Specify Valid Account Verification Schemes ==
When using OpenID Attribute Exchange, the attribute URI
http://ns.login.ubuntu.com/2013/validation/account is included in the request.
OpenID Providers that support this extension can reply with a token
representing what measures they have taken to validate the e-mail address
included in the response. To change the list of schemes acceptable for your
purposes you can change the setting:
OPENID_VALID_VERIFICATION_SCHEMES = {
None: (),
'http://example.com/': ('token_via_email',),
}
The element with the None key specifies a list of verification schemes that
will be accepted as trusted from OpenID Providers that we haven't explicitly
configured. These are, almost by definition, untrusted, so it is strongly
recommended that this list remain empty. Verified accounts will be granted the
django_openid_auth.account_verified permission, which can be checked using
user.has_perm() and the perms RequestContext attribute in the normal way.
N.B. Users of the South migration framework will need to provide a data
migration to create the permission when upgrading django-openid-auth, due to a
known issue in South. See http://south.aeracode.org/ticket/211 for details.
...@@ -33,7 +33,7 @@ __metaclass__ = type ...@@ -33,7 +33,7 @@ __metaclass__ = type
import re import re
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group, Permission
from openid.consumer.consumer import SUCCESS from openid.consumer.consumer import SUCCESS
from openid.extensions import ax, sreg, pape from openid.extensions import ax, sreg, pape
...@@ -47,6 +47,7 @@ from django_openid_auth.exceptions import ( ...@@ -47,6 +47,7 @@ from django_openid_auth.exceptions import (
RequiredAttributeNotReturned, RequiredAttributeNotReturned,
) )
class OpenIDBackend: class OpenIDBackend:
"""A django.contrib.auth backend that authenticates the user based on """A django.contrib.auth backend that authenticates the user based on
an OpenID response.""" an OpenID response."""
...@@ -79,7 +80,8 @@ class OpenIDBackend: ...@@ -79,7 +80,8 @@ class OpenIDBackend:
claimed_id__exact=openid_response.identity_url) claimed_id__exact=openid_response.identity_url)
except UserOpenID.DoesNotExist: except UserOpenID.DoesNotExist:
if getattr(settings, 'OPENID_CREATE_USERS', False): if getattr(settings, 'OPENID_CREATE_USERS', False):
user = self.create_user_from_openid(openid_response) user, user_openid = self.create_user_from_openid(
openid_response)
else: else:
user = user_openid.user user = user_openid.user
...@@ -88,7 +90,7 @@ class OpenIDBackend: ...@@ -88,7 +90,7 @@ class OpenIDBackend:
if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False): if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False):
details = self._extract_user_details(openid_response) details = self._extract_user_details(openid_response)
self.update_user_details(user, details, openid_response) self.update_user_details(user_openid, details, openid_response)
if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False): if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False):
pape_response = pape.Response.fromSuccessResponse(openid_response) pape_response = pape.Response.fromSuccessResponse(openid_response)
...@@ -122,6 +124,7 @@ class OpenIDBackend: ...@@ -122,6 +124,7 @@ class OpenIDBackend:
def _extract_user_details(self, openid_response): def _extract_user_details(self, openid_response):
email = fullname = first_name = last_name = nickname = None email = fullname = first_name = last_name = nickname = None
verified = 'no'
sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response) sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response)
if sreg_response: if sreg_response:
email = sreg_response.get('email') email = sreg_response.get('email')
...@@ -152,6 +155,8 @@ class OpenIDBackend: ...@@ -152,6 +155,8 @@ class OpenIDBackend:
'http://axschema.org/namePerson/last', last_name) 'http://axschema.org/namePerson/last', last_name)
nickname = fetch_response.getSingle( nickname = fetch_response.getSingle(
'http://axschema.org/namePerson/friendly', nickname) 'http://axschema.org/namePerson/friendly', nickname)
verified = fetch_response.getSingle(
'http://ns.login.ubuntu.com/2013/validation/account', verified)
if fullname and not (first_name or last_name): if fullname and not (first_name or last_name):
# Django wants to store first and last names separately, # Django wants to store first and last names separately,
...@@ -164,14 +169,20 @@ class OpenIDBackend: ...@@ -164,14 +169,20 @@ class OpenIDBackend:
first_name = u'' first_name = u''
last_name = fullname last_name = fullname
return dict(email=email, nickname=nickname, verification_scheme_map = getattr(
settings, 'OPENID_VALID_VERIFICATION_SCHEMES', {})
valid_schemes = verification_scheme_map.get(
openid_response.endpoint.server_url,
verification_scheme_map.get(None, ()))
verified = (verified in valid_schemes)
return dict(email=email, nickname=nickname, account_verified=verified,
first_name=first_name, last_name=last_name) first_name=first_name, last_name=last_name)
def _get_preferred_username(self, nickname, email): def _get_preferred_username(self, nickname, email):
if nickname: if nickname:
return nickname return nickname
if email and getattr(settings, 'OPENID_USE_EMAIL_FOR_USERNAME', if email and getattr(settings, 'OPENID_USE_EMAIL_FOR_USERNAME', False):
False):
suggestion = ''.join([x for x in email if x.isalnum()]) suggestion = ''.join([x for x in email if x.isalnum()])
if suggestion: if suggestion:
return suggestion return suggestion
...@@ -264,10 +275,10 @@ class OpenIDBackend: ...@@ -264,10 +275,10 @@ class OpenIDBackend:
openid_response.identity_url) openid_response.identity_url)
user = User.objects.create_user(username, email, password=None) user = User.objects.create_user(username, email, password=None)
self.associate_openid(user, openid_response) user_openid = self.associate_openid(user, openid_response)
self.update_user_details(user, details, openid_response) self.update_user_details(user_openid, details, openid_response)
return user return user, user_openid
def associate_openid(self, user, openid_response): def associate_openid(self, user, openid_response):
"""Associate an OpenID with a user account.""" """Associate an OpenID with a user account."""
...@@ -289,7 +300,8 @@ class OpenIDBackend: ...@@ -289,7 +300,8 @@ class OpenIDBackend:
return user_openid return user_openid
def update_user_details(self, user, details, openid_response): def update_user_details(self, user_openid, details, openid_response):
user = user_openid.user
updated = False updated = False
if details['first_name']: if details['first_name']:
user.first_name = details['first_name'][:30] user.first_name = details['first_name'][:30]
...@@ -303,6 +315,11 @@ class OpenIDBackend: ...@@ -303,6 +315,11 @@ class OpenIDBackend:
if getattr(settings, 'OPENID_FOLLOW_RENAMES', False): if getattr(settings, 'OPENID_FOLLOW_RENAMES', False):
user.username = self._get_available_username(details['nickname'], openid_response.identity_url) user.username = self._get_available_username(details['nickname'], openid_response.identity_url)
updated = True updated = True
account_verified = details.get('account_verified', None)
if (account_verified is not None and
user_openid.account_verified != account_verified):
user_openid.account_verified = account_verified
user_openid.save()
if updated: if updated:
user.save() user.save()
...@@ -348,4 +365,3 @@ class OpenIDBackend: ...@@ -348,4 +365,3 @@ class OpenIDBackend:
break break
user.save() user.save()
...@@ -27,7 +27,10 @@ ...@@ -27,7 +27,10 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
from django.contrib.auth.models import User from django.contrib.auth.models import (
Permission,
User,
)
from django.db import models from django.db import models
...@@ -56,3 +59,27 @@ class UserOpenID(models.Model): ...@@ -56,3 +59,27 @@ class UserOpenID(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User)
claimed_id = models.TextField(max_length=2047, unique=True) claimed_id = models.TextField(max_length=2047, unique=True)
display_id = models.TextField(max_length=2047) display_id = models.TextField(max_length=2047)
account_verified = models.BooleanField(default=False)
class Meta:
permissions = (
('account_verified', 'The OpenID has been verified'),
)
def _get_permission(self):
return Permission.objects.get(codename='account_verified')
def save(self, force_insert=False, force_update=False, using=None):
permission = self._get_permission()
perm_label = '%s.%s' % (permission.content_type.app_label,
permission.codename)
if self.account_verified and not self.user.has_perm(perm_label):
self.user.user_permissions.add(permission)
elif not self.account_verified and self.user.has_perm(perm_label):
self.user.user_permissions.remove(permission)
super(UserOpenID, self).save(force_insert, force_update, using)
def delete(self, using=None):
permission = self._get_permission()
self.user.user_permissions.remove(permission)
super(UserOpenID, self).delete(using)
...@@ -35,7 +35,8 @@ from test_admin import * ...@@ -35,7 +35,8 @@ from test_admin import *
def suite(): def suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
for name in ['test_auth', 'test_store', 'test_views', 'test_admin']: for name in ['test_auth', 'test_models', 'test_store', 'test_views',
'test_admin']:
mod = __import__('%s.%s' % (__name__, name), {}, {}, ['suite']) mod = __import__('%s.%s' % (__name__, name), {}, {}, ['suite'])
suite.addTest(mod.suite()) suite.addTest(mod.suite())
return suite return suite
...@@ -33,6 +33,7 @@ from django.contrib.auth.models import Group, User ...@@ -33,6 +33,7 @@ from django.contrib.auth.models import Group, User
from django.test import TestCase from django.test import TestCase
from django_openid_auth.auth import OpenIDBackend from django_openid_auth.auth import OpenIDBackend
from django_openid_auth.models import UserOpenID
from django_openid_auth.teams import ns_uri as TEAMS_NS from django_openid_auth.teams import ns_uri as TEAMS_NS
from openid.consumer.consumer import SuccessResponse from openid.consumer.consumer import SuccessResponse
from openid.consumer.discover import OpenIDServiceEndpoint from openid.consumer.discover import OpenIDServiceEndpoint
...@@ -72,6 +73,7 @@ class OpenIDBackendTests(TestCase): ...@@ -72,6 +73,7 @@ class OpenIDBackendTests(TestCase):
'first_name': 'Some', 'first_name': 'Some',
'last_name': 'User', 'last_name': 'User',
'email': 'foo@example.com', 'email': 'foo@example.com',
'account_verified': False,
} }
data = { data = {
'nickname': expected['nickname'], 'nickname': expected['nickname'],
...@@ -107,14 +109,17 @@ class OpenIDBackendTests(TestCase): ...@@ -107,14 +109,17 @@ class OpenIDBackendTests(TestCase):
return response return response
def make_response_ax(self, schema="http://axschema.org/", def make_response_ax(self, schema="http://axschema.org/",
fullname="Some User", nickname="someuser", email="foo@example.com", fullname="Some User", nickname="someuser", email="foo@example.com",
first=None, last=None): first=None, last=None, verified=False):
endpoint = OpenIDServiceEndpoint() endpoint = OpenIDServiceEndpoint()
message = Message(OPENID2_NS) message = Message(OPENID2_NS)
attributes = [ attributes = [
("nickname", schema + "namePerson/friendly", nickname), ("nickname", schema + "namePerson/friendly", nickname),
("fullname", schema + "namePerson", fullname), ("fullname", schema + "namePerson", fullname),
("email", schema + "contact/email", email), ("email", schema + "contact/email", email),
("account_verified",
"http://ns.login.ubuntu.com/2013/validation/account",
"token_via_email" if verified else "no")
] ]
if first: if first:
attributes.append( attributes.append(
...@@ -139,7 +144,8 @@ class OpenIDBackendTests(TestCase): ...@@ -139,7 +144,8 @@ class OpenIDBackendTests(TestCase):
self.assertEqual(data, {"nickname": "someuser", self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some", "first_name": "Some",
"last_name": "User", "last_name": "User",
"email": "foo@example.com"}) "email": "foo@example.com",
"account_verified": False})
def test_extract_user_details_ax_split_name(self): def test_extract_user_details_ax_split_name(self):
# Include fullname too to show that the split data takes # Include fullname too to show that the split data takes
...@@ -152,7 +158,8 @@ class OpenIDBackendTests(TestCase): ...@@ -152,7 +158,8 @@ class OpenIDBackendTests(TestCase):
self.assertEqual(data, {"nickname": "someuser", self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some", "first_name": "Some",
"last_name": "User", "last_name": "User",
"email": "foo@example.com"}) "email": "foo@example.com",
"account_verified": False})
def test_extract_user_details_ax_broken_myopenid(self): def test_extract_user_details_ax_broken_myopenid(self):
response = self.make_response_ax( response = self.make_response_ax(
...@@ -164,21 +171,71 @@ class OpenIDBackendTests(TestCase): ...@@ -164,21 +171,71 @@ class OpenIDBackendTests(TestCase):
self.assertEqual(data, {"nickname": "someuser", self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some", "first_name": "Some",
"last_name": "User", "last_name": "User",
"email": "foo@example.com"}) "email": "foo@example.com",
"account_verified": False})
def test_update_user_details_long_names(self): def test_update_user_details_long_names(self):
response = self.make_response_ax() response = self.make_response_ax()
user = User.objects.create_user('someuser', 'someuser@example.com', user = User.objects.create_user('someuser', 'someuser@example.com',
password=None) password=None)
user_openid, created = UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity',
account_verified=False)
data = dict(first_name=u"Some56789012345678901234567890123", data = dict(first_name=u"Some56789012345678901234567890123",
last_name=u"User56789012345678901234567890123", last_name=u"User56789012345678901234567890123",
email=u"someotheruser@example.com") email=u"someotheruser@example.com", account_verified=False)
self.backend.update_user_details(user, data, response) self.backend.update_user_details(user_openid, data, response)
self.assertEqual("Some56789012345678901234567890", user.first_name) self.assertEqual("Some56789012345678901234567890", user.first_name)
self.assertEqual("User56789012345678901234567890", user.last_name) self.assertEqual("User56789012345678901234567890", user.last_name)
def make_user(self, username='someuser', email='someuser@example.com',
password=None):
user = User.objects.create_user(username, email, password=password)
return user
def make_user_openid(self, user=None,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity'):
if user is None:
user = self.make_user()
user_openid, created = UserOpenID.objects.get_or_create(
user=user, claimed_id=claimed_id, display_id=display_id)
return user_openid
def _test_account_verified(self, user_openid, verified, expected):
# set user's verification status
user_openid.account_verified = verified
# get a response including verification status
response = self.make_response_ax()
data = dict(first_name=u"Some56789012345678901234567890123",
last_name=u"User56789012345678901234567890123",
email=u"someotheruser@example.com", account_verified=expected)
self.backend.update_user_details(user_openid, data, response)
# refresh object from the database
user_openid = UserOpenID.objects.get(pk=user_openid.pk)
# check the verification status
self.assertEqual(user_openid.account_verified, expected)
self.assertEqual(user_openid.user.has_perm(
'django_openid_auth.account_verified'), expected)
def test_update_user_openid_unverified(self):
user_openid = self.make_user_openid()
for verified in (False, True):
self._test_account_verified(user_openid, verified, expected=False)
def test_update_user_openid_verified(self):
user_openid = self.make_user_openid()
for verified in (False, True):
self._test_account_verified(user_openid, verified, expected=True)
def test_extract_user_details_name_with_trailing_space(self): def test_extract_user_details_name_with_trailing_space(self):
response = self.make_response_ax(fullname="SomeUser ") response = self.make_response_ax(fullname="SomeUser ")
......
# django-openid-auth - OpenID integration for django.contrib.auth
#
# Copyright (C) 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.contrib.auth.models import User
from django.test import TestCase
from django_openid_auth.models import UserOpenID
class UserOpenIDModelTestCase(TestCase):
def test_create_useropenid(self):
user = User.objects.create_user('someuser', 'someuser@example.com',
password=None)
user_openid, created = UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity',
account_verified=False)
self.assertEqual('someuser', user_openid.user.username)
self.assertEqual(
user_openid.claimed_id, 'http://example.com/existing_identity')
self.assertEqual(
user_openid.display_id, 'http://example.com/existing_identity')
self.assertFalse(user_openid.account_verified)
self.assertFalse(
User.objects.get(username='someuser').has_perm(
'django_openid_auth.account_verified'))
def test_create_verified_useropenid(self):
user = User.objects.create_user('someuser', 'someuser@example.com',
password=None)
user_openid, created = UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity',
account_verified=True)
self.assertEqual('someuser', user_openid.user.username)
self.assertEqual(
user_openid.claimed_id, 'http://example.com/existing_identity')
self.assertEqual(
user_openid.display_id, 'http://example.com/existing_identity')
self.assertTrue(user_openid.account_verified)
self.assertTrue(
User.objects.get(username='someuser').has_perm(
'django_openid_auth.account_verified'))
def test_delete_verified_useropenid(self):
user = User.objects.create_user('someuser', 'someuser@example.com',
password=None)
user_openid, created = UserOpenID.objects.get_or_create(
user=user,
claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity',
account_verified=True)
self.assertTrue(
User.objects.get(username='someuser').has_perm(
'django_openid_auth.account_verified'))
user_openid.delete()
self.assertFalse(
User.objects.get(username='someuser').has_perm(
'django_openid_auth.account_verified'))
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
...@@ -32,7 +32,7 @@ import unittest ...@@ -32,7 +32,7 @@ import unittest
from urllib import quote_plus from urllib import quote_plus
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group, Permission
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.test import TestCase from django.test import TestCase
from openid.consumer.consumer import Consumer, SuccessResponse from openid.consumer.consumer import Consumer, SuccessResponse
...@@ -48,7 +48,7 @@ from openid.message import IDENTIFIER_SELECT ...@@ -48,7 +48,7 @@ from openid.message import IDENTIFIER_SELECT
from django_openid_auth import teams from django_openid_auth import teams
from django_openid_auth.models import UserOpenID from django_openid_auth.models import UserOpenID
from django_openid_auth.views import ( from django_openid_auth.views import (
sanitise_redirect_url, sanitise_redirect_url,
make_consumer, make_consumer,
) )
from django_openid_auth.signals import openid_login_complete from django_openid_auth.signals import openid_login_complete
...@@ -62,6 +62,7 @@ from django_openid_auth.exceptions import ( ...@@ -62,6 +62,7 @@ from django_openid_auth.exceptions import (
ET = importElementTree() ET = importElementTree()
class StubOpenIDProvider(HTTPFetcher): class StubOpenIDProvider(HTTPFetcher):
def __init__(self, base_url): def __init__(self, base_url):
...@@ -152,7 +153,7 @@ class DummyDjangoRequest(object): ...@@ -152,7 +153,7 @@ class DummyDjangoRequest(object):
def build_absolute_uri(self): def build_absolute_uri(self):
return self.META['SCRIPT_NAME'] + self.request_path return self.META['SCRIPT_NAME'] + self.request_path
def _combined_request(self): def _combined_request(self):
request = {} request = {}
request.update(self.POST) request.update(self.POST)
...@@ -175,22 +176,35 @@ class RelyingPartyTests(TestCase): ...@@ -175,22 +176,35 @@ class RelyingPartyTests(TestCase):
self.server = Server(DjangoOpenIDStore(), op_endpoint=server_url) self.server = Server(DjangoOpenIDStore(), op_endpoint=server_url)
setDefaultFetcher(self.provider, wrap_exceptions=False) setDefaultFetcher(self.provider, wrap_exceptions=False)
self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/') self.old_login_redirect_url = getattr(
self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False) settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/')
self.old_strict_usernames = getattr(settings, 'OPENID_STRICT_USERNAMES', False) self.old_create_users = getattr(
self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False) settings, 'OPENID_CREATE_USERS', False)
self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None) self.old_strict_usernames = getattr(
self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) settings, 'OPENID_STRICT_USERNAMES', False)
self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False) self.old_update_details = getattr(
self.old_follow_renames = getattr(settings, 'OPENID_FOLLOW_RENAMES', False) settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False)
self.old_physical_multifactor = getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False) self.old_sso_server_url = getattr(
self.old_login_render_failure = getattr(settings, 'OPENID_RENDER_FAILURE', None) settings, 'OPENID_SSO_SERVER_URL', None)
self.old_consumer_complete = Consumer.complete self.old_teams_map = getattr(
self.old_openid_use_email_for_username = getattr(settings, 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_openid_use_email_for_username = getattr(
settings,
'OPENID_USE_EMAIL_FOR_USERNAME', False) 'OPENID_USE_EMAIL_FOR_USERNAME', False)
self.old_required_fields = getattr( self.old_required_fields = getattr(
settings, 'OPENID_SREG_REQUIRED_FIELDS', []) settings, 'OPENID_SREG_REQUIRED_FIELDS', [])
self.old_valid_verification_schemes = getattr(
settings, 'OPENID_VALID_VERIFICATION_SCHEMES', {})
self.old_consumer_complete = Consumer.complete
settings.OPENID_CREATE_USERS = False settings.OPENID_CREATE_USERS = False
settings.OPENID_STRICT_USERNAMES = False settings.OPENID_STRICT_USERNAMES = False
...@@ -202,6 +216,7 @@ class RelyingPartyTests(TestCase): ...@@ -202,6 +216,7 @@ class RelyingPartyTests(TestCase):
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False
settings.OPENID_SREG_REQUIRED_FIELDS = [] settings.OPENID_SREG_REQUIRED_FIELDS = []
settings.OPENID_USE_EMAIL_FOR_USERNAME = False settings.OPENID_USE_EMAIL_FOR_USERNAME = False
settings.OPENID_VALID_VERIFICATION_SCHEMES = {}
def tearDown(self): def tearDown(self):
settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url
...@@ -212,11 +227,15 @@ class RelyingPartyTests(TestCase): ...@@ -212,11 +227,15 @@ class RelyingPartyTests(TestCase):
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map
settings.OPENID_USE_AS_ADMIN_LOGIN = self.old_use_as_admin_login settings.OPENID_USE_AS_ADMIN_LOGIN = self.old_use_as_admin_login
settings.OPENID_FOLLOW_RENAMES = self.old_follow_renames settings.OPENID_FOLLOW_RENAMES = self.old_follow_renames
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = self.old_physical_multifactor settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = (
self.old_physical_multifactor)
settings.OPENID_RENDER_FAILURE = self.old_login_render_failure settings.OPENID_RENDER_FAILURE = self.old_login_render_failure
Consumer.complete = self.old_consumer_complete Consumer.complete = self.old_consumer_complete
settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields settings.OPENID_SREG_REQUIRED_FIELDS = self.old_required_fields
settings.OPENID_USE_EMAIL_FOR_USERNAME = self.old_openid_use_email_for_username settings.OPENID_USE_EMAIL_FOR_USERNAME = (
self.old_openid_use_email_for_username)
settings.OPENID_VALID_VERIFICATION_SCHEMES = (
self.old_valid_verification_schemes)
setDefaultFetcher(None) setDefaultFetcher(None)
super(RelyingPartyTests, self).tearDown() super(RelyingPartyTests, self).tearDown()
...@@ -230,8 +249,9 @@ class RelyingPartyTests(TestCase): ...@@ -230,8 +249,9 @@ class RelyingPartyTests(TestCase):
self.assertEquals(webresponse.code, 302) self.assertEquals(webresponse.code, 302)
redirect_to = webresponse.headers['location'] redirect_to = webresponse.headers['location']
self.assertTrue(redirect_to.startswith( self.assertTrue(redirect_to.startswith(
'http://testserver/openid/complete/')) 'http://testserver/openid/complete/'))
return self.client.get('/openid/complete/', return self.client.get(
'/openid/complete/',
dict(cgi.parse_qsl(redirect_to.split('?', 1)[1]))) dict(cgi.parse_qsl(redirect_to.split('?', 1)[1])))
def test_login(self): def test_login(self):
...@@ -239,7 +259,8 @@ class RelyingPartyTests(TestCase): ...@@ -239,7 +259,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
# The login form is displayed: # The login form is displayed:
...@@ -279,7 +300,8 @@ class RelyingPartyTests(TestCase): ...@@ -279,7 +300,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
settings.LOGIN_REDIRECT_URL = '/getuser/' settings.LOGIN_REDIRECT_URL = '/getuser/'
...@@ -304,7 +326,8 @@ class RelyingPartyTests(TestCase): ...@@ -304,7 +326,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
# Requesting the login form immediately begins an # Requesting the login form immediately begins an
...@@ -361,9 +384,11 @@ class RelyingPartyTests(TestCase): ...@@ -361,9 +384,11 @@ class RelyingPartyTests(TestCase):
self.assertEquals(user.last_name, 'User') self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'foo@example.com') self.assertEquals(user.email, 'foo@example.com')
def _do_user_login(self, req_data, resp_data, use_sreg=True, use_pape=None): def _do_user_login(self, req_data, resp_data, use_sreg=True,
use_pape=None):
openid_request = self._get_login_request(req_data) openid_request = self._get_login_request(req_data)
openid_response = self._get_login_response(openid_request, resp_data, use_sreg, use_pape) openid_response = self._get_login_response(
openid_request, resp_data, use_sreg, use_pape)
response = self.complete(openid_response) response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/') self.assertRedirects(response, 'http://testserver/getuser/')
return response return response
...@@ -378,7 +403,8 @@ class RelyingPartyTests(TestCase): ...@@ -378,7 +403,8 @@ class RelyingPartyTests(TestCase):
openid_request = self.provider.parseFormPost(response.content) openid_request = self.provider.parseFormPost(response.content)
return openid_request return openid_request
def _get_login_response(self, openid_request, resp_data, use_sreg, use_pape): def _get_login_response(self, openid_request, resp_data, use_sreg,
use_pape):
openid_response = openid_request.answer(True) openid_response = openid_request.answer(True)
if use_sreg: if use_sreg:
...@@ -404,7 +430,7 @@ class RelyingPartyTests(TestCase): ...@@ -404,7 +430,7 @@ class RelyingPartyTests(TestCase):
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = True
preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL preferred_auth = pape.AUTH_MULTI_FACTOR_PHYSICAL
self.provider.type_uris.append(pape.ns_uri) self.provider.type_uris.append(pape.ns_uri)
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
'next': '/getuser/'} 'next': '/getuser/'}
response = self.client.post('/openid/login/', openid_req) response = self.client.post('/openid/login/', openid_req)
...@@ -441,7 +467,8 @@ class RelyingPartyTests(TestCase): ...@@ -441,7 +467,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -453,7 +480,7 @@ class RelyingPartyTests(TestCase): ...@@ -453,7 +480,7 @@ class RelyingPartyTests(TestCase):
query = self.parse_query_string(response.request['QUERY_STRING']) query = self.parse_query_string(response.request['QUERY_STRING'])
self.assertTrue('openid.pape.auth_policies' in query) self.assertTrue('openid.pape.auth_policies' in query)
self.assertEqual(query['openid.pape.auth_policies'], self.assertEqual(query['openid.pape.auth_policies'],
quote_plus(preferred_auth)) quote_plus(preferred_auth))
response = self.client.get('/getuser/') response = self.client.get('/getuser/')
...@@ -482,10 +509,11 @@ class RelyingPartyTests(TestCase): ...@@ -482,10 +509,11 @@ class RelyingPartyTests(TestCase):
Consumer.complete = mock_complete Consumer.complete = mock_complete
user = User.objects.create_user('testuser', 'test@example.com') user = User.objects.create_user('testuser', 'test@example.com')
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -537,10 +565,11 @@ class RelyingPartyTests(TestCase): ...@@ -537,10 +565,11 @@ class RelyingPartyTests(TestCase):
Consumer.complete = mock_complete Consumer.complete = mock_complete
user = User.objects.create_user('testuser', 'test@example.com') user = User.objects.create_user('testuser', 'test@example.com')
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -694,7 +723,8 @@ class RelyingPartyTests(TestCase): ...@@ -694,7 +723,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -722,7 +752,8 @@ class RelyingPartyTests(TestCase): ...@@ -722,7 +752,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -749,14 +780,16 @@ class RelyingPartyTests(TestCase): ...@@ -749,14 +780,16 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create( UserOpenID.objects.get_or_create(
user=user, user=user,
claimed_id='http://example.com/existing_identity', claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity') display_id='http://example.com/existing_identity',
account_verified=False)
# Setup user who is going to try to change username to 'testuser' # Setup user who is going to try to change username to 'testuser'
renamed_user = User.objects.create_user('renameuser', 'someone@example.com') renamed_user = User.objects.create_user('renameuser', 'someone@example.com')
UserOpenID.objects.get_or_create( UserOpenID.objects.get_or_create(
user=renamed_user, user=renamed_user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
# identity url is for 'renameuser' # identity url is for 'renameuser'
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -786,14 +819,16 @@ class RelyingPartyTests(TestCase): ...@@ -786,14 +819,16 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create( UserOpenID.objects.get_or_create(
user=user, user=user,
claimed_id='http://example.com/existing_identity', claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity') display_id='http://example.com/existing_identity',
account_verified=False)
# Setup user who is going to try to change username to 'testuser' # Setup user who is going to try to change username to 'testuser'
renamed_user = User.objects.create_user('testuser2000eight', 'someone@example.com') renamed_user = User.objects.create_user('testuser2000eight', 'someone@example.com')
UserOpenID.objects.get_or_create( UserOpenID.objects.get_or_create(
user=renamed_user, user=renamed_user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
# identity url is for 'testuser2000eight' # identity url is for 'testuser2000eight'
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -826,14 +861,16 @@ class RelyingPartyTests(TestCase): ...@@ -826,14 +861,16 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create( UserOpenID.objects.get_or_create(
user=user, user=user,
claimed_id='http://example.com/existing_identity', claimed_id='http://example.com/existing_identity',
display_id='http://example.com/existing_identity') display_id='http://example.com/existing_identity',
account_verified=False)
# Setup user who is going to try to change username to 'testuser' # Setup user who is going to try to change username to 'testuser'
renamed_user = User.objects.create_user('testuser2000', 'someone@example.com') renamed_user = User.objects.create_user('testuser2000', 'someone@example.com')
UserOpenID.objects.get_or_create( UserOpenID.objects.get_or_create(
user=renamed_user, user=renamed_user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
# identity url is for 'testuser2000' # identity url is for 'testuser2000'
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -864,7 +901,8 @@ class RelyingPartyTests(TestCase): ...@@ -864,7 +901,8 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create( UserOpenID.objects.get_or_create(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
# identity url is for 'testuser2' # identity url is for 'testuser2'
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -927,7 +965,7 @@ class RelyingPartyTests(TestCase): ...@@ -927,7 +965,7 @@ class RelyingPartyTests(TestCase):
self.assertTrue(isinstance(exception, (RequiredAttributeNotReturned, MissingUsernameViolation))) self.assertTrue(isinstance(exception, (RequiredAttributeNotReturned, MissingUsernameViolation)))
return HttpResponse('Test Failure Override', status=200) return HttpResponse('Test Failure Override', status=200)
settings.OPENID_RENDER_FAILURE = mock_login_failure_handler settings.OPENID_RENDER_FAILURE = mock_login_failure_handler
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
response = self.client.post('/openid/login/', response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity', {'openid_identifier': 'http://example.com/identity',
...@@ -945,7 +983,7 @@ class RelyingPartyTests(TestCase): ...@@ -945,7 +983,7 @@ class RelyingPartyTests(TestCase):
'email': 'foo@example.com'}) 'email': 'foo@example.com'})
openid_response.addExtension(sreg_response) openid_response.addExtension(sreg_response)
response = self.complete(openid_response) response = self.complete(openid_response)
# Status code should be 200, since we over-rode the login_failure handler # Status code should be 200, since we over-rode the login_failure handler
self.assertEquals(200, response.status_code) self.assertEquals(200, response.status_code)
self.assertContains(response, 'Test Failure Override') self.assertContains(response, 'Test Failure Override')
...@@ -958,7 +996,8 @@ class RelyingPartyTests(TestCase): ...@@ -958,7 +996,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/different_identity', claimed_id='http://example.com/different_identity',
display_id='http://example.com/different_identity') display_id='http://example.com/different_identity',
account_verified=False)
useropenid.save() useropenid.save()
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
...@@ -1003,7 +1042,8 @@ class RelyingPartyTests(TestCase): ...@@ -1003,7 +1042,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/different_identity', claimed_id='http://example.com/different_identity',
display_id='http://example.com/different_identity') display_id='http://example.com/different_identity',
account_verified=False)
useropenid.save() useropenid.save()
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
...@@ -1022,7 +1062,7 @@ class RelyingPartyTests(TestCase): ...@@ -1022,7 +1062,7 @@ class RelyingPartyTests(TestCase):
'email': 'foo@example.com'}) 'email': 'foo@example.com'})
openid_response.addExtension(sreg_response) openid_response.addExtension(sreg_response)
response = self.complete(openid_response) response = self.complete(openid_response)
# Status code should be 200, since we over-rode the login_failure handler # Status code should be 200, since we over-rode the login_failure handler
self.assertEquals(200, response.status_code) self.assertEquals(200, response.status_code)
self.assertContains(response, 'Test Failure Override') self.assertContains(response, 'Test Failure Override')
...@@ -1062,7 +1102,8 @@ class RelyingPartyTests(TestCase): ...@@ -1062,7 +1102,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity', openid_req = {'openid_identifier': 'http://example.com/identity',
...@@ -1087,7 +1128,8 @@ class RelyingPartyTests(TestCase): ...@@ -1087,7 +1128,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
...@@ -1107,7 +1149,8 @@ class RelyingPartyTests(TestCase): ...@@ -1107,7 +1149,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
...@@ -1121,7 +1164,8 @@ class RelyingPartyTests(TestCase): ...@@ -1121,7 +1164,8 @@ class RelyingPartyTests(TestCase):
self.assertEqual(['email', 'language'], sreg_request.required) self.assertEqual(['email', 'language'], sreg_request.required)
self.assertEqual(['fullname', 'nickname'], sreg_request.optional) self.assertEqual(['fullname', 'nickname'], sreg_request.optional)
def test_login_attribute_exchange(self): def check_login_attribute_exchange(self, validation_type, is_verified,
request_account_verified=True):
settings.OPENID_UPDATE_DETAILS_FROM_SREG = True settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
user = User.objects.create_user('testuser', 'someone@example.com') user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID( useropenid = UserOpenID(
...@@ -1163,6 +1207,11 @@ class RelyingPartyTests(TestCase): ...@@ -1163,6 +1207,11 @@ class RelyingPartyTests(TestCase):
'http://schema.openid.net/namePerson')) 'http://schema.openid.net/namePerson'))
self.assertTrue(fetch_request.has_key( self.assertTrue(fetch_request.has_key(
'http://schema.openid.net/namePerson/friendly')) 'http://schema.openid.net/namePerson/friendly'))
# Account verification:
self.assertEqual(
fetch_request.has_key(
'http://ns.login.ubuntu.com/2013/validation/account'),
request_account_verified)
# Build up a response including AX data. # Build up a response including AX data.
openid_response = openid_request.answer(True) openid_response = openid_request.answer(True)
...@@ -1175,12 +1224,17 @@ class RelyingPartyTests(TestCase): ...@@ -1175,12 +1224,17 @@ class RelyingPartyTests(TestCase):
'http://axschema.org/namePerson/last', 'Lastname') 'http://axschema.org/namePerson/last', 'Lastname')
fetch_response.addValue( fetch_response.addValue(
'http://axschema.org/namePerson/friendly', 'someuser') 'http://axschema.org/namePerson/friendly', 'someuser')
if validation_type is not None:
fetch_response.addValue(
'http://ns.login.ubuntu.com/2013/validation/account',
validation_type)
openid_response.addExtension(fetch_response) openid_response.addExtension(fetch_response)
response = self.complete(openid_response) response = self.complete(openid_response)
self.assertRedirects(response, 'http://testserver/getuser/') self.assertRedirects(response, 'http://testserver/getuser/')
# And they are now logged in as testuser (the passed in # And they are now logged in as testuser (the passed in
# nickname has not caused the username to change). # nickname has not caused the username to change), because
# settings.OPENID_FOLLOW_RENAMES is False.
response = self.client.get('/getuser/') response = self.client.get('/getuser/')
self.assertEquals(response.content, 'testuser') self.assertEquals(response.content, 'testuser')
...@@ -1189,6 +1243,56 @@ class RelyingPartyTests(TestCase): ...@@ -1189,6 +1243,56 @@ class RelyingPartyTests(TestCase):
self.assertEquals(user.first_name, 'Firstname') self.assertEquals(user.first_name, 'Firstname')
self.assertEquals(user.last_name, 'Lastname') self.assertEquals(user.last_name, 'Lastname')
self.assertEquals(user.email, 'foo@example.com') self.assertEquals(user.email, 'foo@example.com')
# So have the user's permissions
self.assertEqual(
user.has_perm('django_openid_auth.account_verified'), is_verified)
# And the verified status of their UserOpenID
user_openid = UserOpenID.objects.get(user=user)
self.assertEqual(user_openid.account_verified, is_verified)
def test_login_attribute_exchange_with_verification(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
self.provider.endpoint_url: ('token_via_email',),
}
self.check_login_attribute_exchange('token_via_email',
is_verified=True)
def test_login_attribute_exchange_without_verification(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
self.provider.endpoint_url: ('token_via_email',),
}
self.check_login_attribute_exchange(None, is_verified=False)
def test_login_attribute_exchange_without_account_verified(self):
# don't request account_verified attribute in AX request (as there are
# no valid verificatation schemes defined)
# and check account verification status is left unmodified
# (it's set to False by default for a new user)
self.check_login_attribute_exchange(None, is_verified=False,
request_account_verified=False)
def test_login_attribute_exchange_unrecognised_verification(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
self.provider.endpoint_url: ('token_via_email',),
}
self.check_login_attribute_exchange('unrecognised_scheme',
is_verified=False)
def test_login_attribute_exchange_different_default_verification(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
None: ('token_via_email', 'sms'),
'http://otherprovider/': ('unrecognised_scheme',),
}
self.check_login_attribute_exchange('unrecognised_scheme',
is_verified=False)
def test_login_attribute_exchange_matched_default_verification(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
None: ('token_via_email',),
'http://otherprovider/': ('unrecognised_scheme',),
}
self.check_login_attribute_exchange('token_via_email',
is_verified=True)
def test_login_teams(self): def test_login_teams(self):
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = False settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = False
...@@ -1197,14 +1301,21 @@ class RelyingPartyTests(TestCase): ...@@ -1197,14 +1301,21 @@ class RelyingPartyTests(TestCase):
user = User.objects.create_user('testuser', 'someone@example.com') user = User.objects.create_user('testuser', 'someone@example.com')
group = Group(name='groupname') group = Group(name='groupname')
group.save() group.save()
# Django creates the add_nonce permission by default
group.permissions.add(
Permission.objects.get(codename='add_nonce'))
ogroup = Group(name='othergroup') ogroup = Group(name='othergroup')
ogroup.save() ogroup.save()
# Django creates the add_useropenid permission by default
ogroup.permissions.add(
Permission.objects.get(codename='add_useropenid'))
user.groups.add(ogroup) user.groups.add(ogroup)
user.save() user.save()
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=True)
useropenid.save() useropenid.save()
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
...@@ -1248,7 +1359,8 @@ class RelyingPartyTests(TestCase): ...@@ -1248,7 +1359,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
...@@ -1300,7 +1412,8 @@ class RelyingPartyTests(TestCase): ...@@ -1300,7 +1412,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
# Posting in an identity URL begins the authentication request: # Posting in an identity URL begins the authentication request:
...@@ -1324,7 +1437,8 @@ class RelyingPartyTests(TestCase): ...@@ -1324,7 +1437,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID( useropenid = UserOpenID(
user=user, user=user,
claimed_id='http://example.com/identity', claimed_id='http://example.com/identity',
display_id='http://example.com/identity') display_id='http://example.com/identity',
account_verified=False)
useropenid.save() useropenid.save()
response = self.client.post('/openid/login/', response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity'}) {'openid_identifier': 'http://example.com/identity'})
...@@ -1345,7 +1459,7 @@ class RelyingPartyTests(TestCase): ...@@ -1345,7 +1459,7 @@ class RelyingPartyTests(TestCase):
self.assertTrue(self.signal_handler_called) self.assertTrue(self.signal_handler_called)
openid_login_complete.disconnect(login_callback) openid_login_complete.disconnect(login_callback)
class HelperFunctionsTest(TestCase): class HelperFunctionsTest(TestCase):
def test_sanitise_redirect_url(self): def test_sanitise_redirect_url(self):
settings.ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = [ settings.ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = [
......
...@@ -56,7 +56,6 @@ from django_openid_auth.models import UserOpenID ...@@ -56,7 +56,6 @@ from django_openid_auth.models import UserOpenID
from django_openid_auth.signals import openid_login_complete from django_openid_auth.signals import openid_login_complete
from django_openid_auth.store import DjangoOpenIDStore from django_openid_auth.store import DjangoOpenIDStore
from django_openid_auth.exceptions import ( from django_openid_auth.exceptions import (
RequiredAttributeNotReturned,
DjangoOpenIDException, DjangoOpenIDException,
) )
...@@ -170,7 +169,6 @@ def login_begin(request, template_name='openid/login.html', ...@@ -170,7 +169,6 @@ def login_begin(request, template_name='openid/login.html',
redirect_field_name: redirect_to redirect_field_name: redirect_to
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
error = None
consumer = make_consumer(request) consumer = make_consumer(request)
try: try:
openid_request = consumer.begin(openid_url) openid_request = consumer.begin(openid_url)
...@@ -181,25 +179,40 @@ def login_begin(request, template_name='openid/login.html', ...@@ -181,25 +179,40 @@ def login_begin(request, template_name='openid/login.html',
# Request some user details. If the provider advertises support # Request some user details. If the provider advertises support
# for attribute exchange, use that. # for attribute exchange, use that.
if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri): endpoint = openid_request.endpoint
if endpoint.supportsType(ax.AXMessage.ns_uri):
fetch_request = ax.FetchRequest() fetch_request = ax.FetchRequest()
# We mark all the attributes as required, since Google ignores # We mark all the attributes as required, since Google ignores
# optional attributes. We request both the full name and # optional attributes. We request both the full name and
# first/last components since some providers offer one but not # first/last components since some providers offer one but not
# the other. # the other.
for (attr, alias) in [ for (attr, alias) in [
('http://axschema.org/contact/email', 'email'), ('http://axschema.org/contact/email', 'email'),
('http://axschema.org/namePerson', 'fullname'), ('http://axschema.org/namePerson', 'fullname'),
('http://axschema.org/namePerson/first', 'firstname'), ('http://axschema.org/namePerson/first', 'firstname'),
('http://axschema.org/namePerson/last', 'lastname'), ('http://axschema.org/namePerson/last', 'lastname'),
('http://axschema.org/namePerson/friendly', 'nickname'), ('http://axschema.org/namePerson/friendly', 'nickname'),
# The myOpenID provider advertises AX support, but uses # The myOpenID provider advertises AX support, but uses
# attribute names from an obsolete draft of the # attribute names from an obsolete draft of the
# specification. We request them for compatibility. # specification. We request them for compatibility.
('http://schema.openid.net/contact/email', 'old_email'), ('http://schema.openid.net/contact/email', 'old_email'),
('http://schema.openid.net/namePerson', 'old_fullname'), ('http://schema.openid.net/namePerson', 'old_fullname'),
('http://schema.openid.net/namePerson/friendly', 'old_nickname')]: ('http://schema.openid.net/namePerson/friendly',
'old_nickname')]:
fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True)) fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True))
# conditionally require account_verified attribute
verification_scheme_map = getattr(
settings, 'OPENID_VALID_VERIFICATION_SCHEMES', {})
valid_schemes = verification_scheme_map.get(
endpoint.server_url, verification_scheme_map.get(None, ()))
if valid_schemes:
# there are valid schemes configured for this endpoint, so
# request account_verified status
fetch_request.add(ax.AttrInfo(
'http://ns.login.ubuntu.com/2013/validation/account',
alias='account_verified', required=True))
openid_request.addExtension(fetch_request) openid_request.addExtension(fetch_request)
else: else:
sreg_required_fields = [] sreg_required_fields = []
...@@ -214,7 +227,7 @@ def login_begin(request, template_name='openid/login.html', ...@@ -214,7 +227,7 @@ def login_begin(request, template_name='openid/login.html',
openid_request.addExtension( openid_request.addExtension(
sreg.SRegRequest(optional=sreg_optional_fields, sreg.SRegRequest(optional=sreg_optional_fields,
required=sreg_required_fields)) required=sreg_required_fields))
if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False): if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False):
preferred_auth = [ preferred_auth = [
pape.AUTH_MULTI_FACTOR_PHYSICAL, pape.AUTH_MULTI_FACTOR_PHYSICAL,
...@@ -271,7 +284,7 @@ def login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME, ...@@ -271,7 +284,7 @@ def login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME,
user = authenticate(openid_response=openid_response) user = authenticate(openid_response=openid_response)
except DjangoOpenIDException, e: except DjangoOpenIDException, e:
return render_failure(request, e.message, exception=e) return render_failure(request, e.message, exception=e)
if user is not None: if user is not None:
if user.is_active: if user.is_active:
auth_login(request, user) auth_login(request, user)
......
...@@ -133,6 +133,14 @@ OPENID_CREATE_USERS = True ...@@ -133,6 +133,14 @@ OPENID_CREATE_USERS = True
# data received via Simple Registration? # data received via Simple Registration?
OPENID_UPDATE_DETAILS_FROM_SREG = True OPENID_UPDATE_DETAILS_FROM_SREG = True
# Map of OpenID Provider base URLs to recognised account verification schemes
# returned in response to a http://ns.login.ubuntu.com/2013/validation/account
# request. Use None as the key in place of a URL to specify verification
# schemes that will be trusted from unknown OpenID Providers (not recommended).
OPENID_VALID_VERIFICATION_SCHEMES = {
None: (),
}
# If set, always use this as the identity URL rather than asking the # If set, always use this as the identity URL rather than asking the
# user. This only makes sense if it is a server URL. # user. This only makes sense if it is a server URL.
OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/' OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment