Commit 7e1edd43 by James Tait

Enable restricting acceptable account verification schemes per provider.

parent e035fb2f
...@@ -193,6 +193,12 @@ representing what measures they have taken to validate the e-mail address ...@@ -193,6 +193,12 @@ 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 included in the response. To change the list of schemes acceptable for your
purposes you can change the setting: purposes you can change the setting:
OPENID_VALID_VERIFICATION_SCHEMES = ( OPENID_VALID_VERIFICATION_SCHEMES = {
'token_via_email', 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.
...@@ -45,6 +45,7 @@ from django_openid_auth.exceptions import ( ...@@ -45,6 +45,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."""
...@@ -163,8 +164,12 @@ class OpenIDBackend: ...@@ -163,8 +164,12 @@ class OpenIDBackend:
first_name = u'' first_name = u''
last_name = fullname last_name = fullname
verified = verified in getattr( verification_scheme_map = getattr(
settings, 'OPENID_VALID_VERIFICATION_SCHEMES', ()) 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, 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)
...@@ -172,8 +177,7 @@ class OpenIDBackend: ...@@ -172,8 +177,7 @@ class OpenIDBackend:
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
......
...@@ -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):
...@@ -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):
...@@ -1226,14 +1246,37 @@ class RelyingPartyTests(TestCase): ...@@ -1226,14 +1246,37 @@ class RelyingPartyTests(TestCase):
self.assertEqual(user_openid.account_verified, is_verified) self.assertEqual(user_openid.account_verified, is_verified)
def test_login_attribute_exchange_with_validation(self): def test_login_attribute_exchange_with_validation(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
self.provider.endpoint_url: ('token_via_email',),
}
self._test_login_attribute_exchange('token_via_email', True) self._test_login_attribute_exchange('token_via_email', True)
def test_login_attribute_exchange_without_validation(self): def test_login_attribute_exchange_without_validation(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
self.provider.endpoint_url: ('token_via_email',),
}
self._test_login_attribute_exchange(None, False) self._test_login_attribute_exchange(None, False)
def test_login_attribute_exchange_unrecognised_validation(self): def test_login_attribute_exchange_unrecognised_validation(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
self.provider.endpoint_url: ('token_via_email',),
}
self._test_login_attribute_exchange('unrecognised_scheme', False)
def test_login_attribute_exchange_different_default_validation(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
None: ('token_via_email', 'sms'),
'http://otherprovider/': ('unrecognised_scheme',),
}
self._test_login_attribute_exchange('unrecognised_scheme', False) self._test_login_attribute_exchange('unrecognised_scheme', False)
def test_login_attribute_exchange_matched_default_validation(self):
settings.OPENID_VALID_VERIFICATION_SCHEMES = {
None: ('token_via_email',),
'http://otherprovider/': ('unrecognised_scheme',),
}
self._test_login_attribute_exchange('token_via_email', 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
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname', settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname',
......
...@@ -133,11 +133,13 @@ OPENID_CREATE_USERS = True ...@@ -133,11 +133,13 @@ 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
# List of recognised account verification schemes returned in response to a # Map of OpenID Provider base URLs to recognised account verification schemes
# http://ns.login.ubuntu.com/2013/validation/account request # returned in response to a http://ns.login.ubuntu.com/2013/validation/account
OPENID_VALID_VERIFICATION_SCHEMES = ( # request. Use None as the key in place of a URL to specify verification
'token_via_email', # 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.
......
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