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:
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``)
== 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
import re
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.extensions import ax, sreg, pape
......@@ -47,6 +47,7 @@ from django_openid_auth.exceptions import (
RequiredAttributeNotReturned,
)
class OpenIDBackend:
"""A django.contrib.auth backend that authenticates the user based on
an OpenID response."""
......@@ -79,7 +80,8 @@ class OpenIDBackend:
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)
user, user_openid = self.create_user_from_openid(
openid_response)
else:
user = user_openid.user
......@@ -88,7 +90,7 @@ class OpenIDBackend:
if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False):
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):
pape_response = pape.Response.fromSuccessResponse(openid_response)
......@@ -122,6 +124,7 @@ class OpenIDBackend:
def _extract_user_details(self, openid_response):
email = fullname = first_name = last_name = nickname = None
verified = 'no'
sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response)
if sreg_response:
email = sreg_response.get('email')
......@@ -152,6 +155,8 @@ class OpenIDBackend:
'http://axschema.org/namePerson/last', last_name)
nickname = fetch_response.getSingle(
'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):
# Django wants to store first and last names separately,
......@@ -164,14 +169,20 @@ class OpenIDBackend:
first_name = u''
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)
def _get_preferred_username(self, nickname, email):
if nickname:
return nickname
if email and getattr(settings, 'OPENID_USE_EMAIL_FOR_USERNAME',
False):
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
......@@ -264,10 +275,10 @@ class OpenIDBackend:
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)
user_openid = self.associate_openid(user, openid_response)
self.update_user_details(user_openid, details, openid_response)
return user
return user, user_openid
def associate_openid(self, user, openid_response):
"""Associate an OpenID with a user account."""
......@@ -289,7 +300,8 @@ class OpenIDBackend:
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
if details['first_name']:
user.first_name = details['first_name'][:30]
......@@ -303,6 +315,11 @@ class OpenIDBackend:
if getattr(settings, 'OPENID_FOLLOW_RENAMES', False):
user.username = self._get_available_username(details['nickname'], openid_response.identity_url)
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:
user.save()
......@@ -348,4 +365,3 @@ class OpenIDBackend:
break
user.save()
......@@ -27,7 +27,10 @@
# 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.contrib.auth.models import (
Permission,
User,
)
from django.db import models
......@@ -56,3 +59,27 @@ class UserOpenID(models.Model):
user = models.ForeignKey(User)
claimed_id = models.TextField(max_length=2047, unique=True)
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 *
def suite():
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'])
suite.addTest(mod.suite())
return suite
......@@ -33,6 +33,7 @@ from django.contrib.auth.models import Group, User
from django.test import TestCase
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 openid.consumer.consumer import SuccessResponse
from openid.consumer.discover import OpenIDServiceEndpoint
......@@ -72,6 +73,7 @@ class OpenIDBackendTests(TestCase):
'first_name': 'Some',
'last_name': 'User',
'email': 'foo@example.com',
'account_verified': False,
}
data = {
'nickname': expected['nickname'],
......@@ -108,13 +110,16 @@ class OpenIDBackendTests(TestCase):
def make_response_ax(self, schema="http://axschema.org/",
fullname="Some User", nickname="someuser", email="foo@example.com",
first=None, last=None):
first=None, last=None, verified=False):
endpoint = OpenIDServiceEndpoint()
message = Message(OPENID2_NS)
attributes = [
("nickname", schema + "namePerson/friendly", nickname),
("fullname", schema + "namePerson", fullname),
("email", schema + "contact/email", email),
("account_verified",
"http://ns.login.ubuntu.com/2013/validation/account",
"token_via_email" if verified else "no")
]
if first:
attributes.append(
......@@ -139,7 +144,8 @@ class OpenIDBackendTests(TestCase):
self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some",
"last_name": "User",
"email": "foo@example.com"})
"email": "foo@example.com",
"account_verified": False})
def test_extract_user_details_ax_split_name(self):
# Include fullname too to show that the split data takes
......@@ -152,7 +158,8 @@ class OpenIDBackendTests(TestCase):
self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some",
"last_name": "User",
"email": "foo@example.com"})
"email": "foo@example.com",
"account_verified": False})
def test_extract_user_details_ax_broken_myopenid(self):
response = self.make_response_ax(
......@@ -164,21 +171,71 @@ class OpenIDBackendTests(TestCase):
self.assertEqual(data, {"nickname": "someuser",
"first_name": "Some",
"last_name": "User",
"email": "foo@example.com"})
"email": "foo@example.com",
"account_verified": False})
def test_update_user_details_long_names(self):
response = self.make_response_ax()
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)
data = dict(first_name=u"Some56789012345678901234567890123",
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("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):
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
from urllib import quote_plus
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.test import TestCase
from openid.consumer.consumer import Consumer, SuccessResponse
......@@ -62,6 +62,7 @@ from django_openid_auth.exceptions import (
ET = importElementTree()
class StubOpenIDProvider(HTTPFetcher):
def __init__(self, base_url):
......@@ -175,22 +176,35 @@ class RelyingPartyTests(TestCase):
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,
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_openid_use_email_for_username = getattr(
settings,
'OPENID_USE_EMAIL_FOR_USERNAME', False)
self.old_required_fields = getattr(
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_STRICT_USERNAMES = False
......@@ -202,6 +216,7 @@ class RelyingPartyTests(TestCase):
settings.OPENID_PHYSICAL_MULTIFACTOR_REQUIRED = False
settings.OPENID_SREG_REQUIRED_FIELDS = []
settings.OPENID_USE_EMAIL_FOR_USERNAME = False
settings.OPENID_VALID_VERIFICATION_SCHEMES = {}
def tearDown(self):
settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url
......@@ -212,11 +227,15 @@ class RelyingPartyTests(TestCase):
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_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
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)
super(RelyingPartyTests, self).tearDown()
......@@ -231,7 +250,8 @@ class RelyingPartyTests(TestCase):
redirect_to = webresponse.headers['location']
self.assertTrue(redirect_to.startswith(
'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])))
def test_login(self):
......@@ -239,7 +259,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
# The login form is displayed:
......@@ -279,7 +300,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
settings.LOGIN_REDIRECT_URL = '/getuser/'
......@@ -304,7 +326,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
# Requesting the login form immediately begins an
......@@ -361,9 +384,11 @@ class RelyingPartyTests(TestCase):
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):
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)
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
......@@ -378,7 +403,8 @@ class RelyingPartyTests(TestCase):
openid_request = self.provider.parseFormPost(response.content)
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)
if use_sreg:
......@@ -441,7 +467,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -485,7 +512,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -540,7 +568,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -694,7 +723,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -722,7 +752,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -749,14 +780,16 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create(
user=user,
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'
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')
display_id='http://example.com/identity',
account_verified=False)
# identity url is for 'renameuser'
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -786,14 +819,16 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create(
user=user,
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'
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')
display_id='http://example.com/identity',
account_verified=False)
# identity url is for 'testuser2000eight'
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -826,14 +861,16 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create(
user=user,
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'
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')
display_id='http://example.com/identity',
account_verified=False)
# identity url is for 'testuser2000'
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -864,7 +901,8 @@ class RelyingPartyTests(TestCase):
UserOpenID.objects.get_or_create(
user=user,
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'
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -958,7 +996,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
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()
# Posting in an identity URL begins the authentication request:
......@@ -1003,7 +1042,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
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()
# Posting in an identity URL begins the authentication request:
......@@ -1062,7 +1102,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
openid_req = {'openid_identifier': 'http://example.com/identity',
......@@ -1087,7 +1128,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
# Posting in an identity URL begins the authentication request:
......@@ -1107,7 +1149,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
# Posting in an identity URL begins the authentication request:
......@@ -1121,7 +1164,8 @@ class RelyingPartyTests(TestCase):
self.assertEqual(['email', 'language'], sreg_request.required)
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
user = User.objects.create_user('testuser', 'someone@example.com')
useropenid = UserOpenID(
......@@ -1163,6 +1207,11 @@ class RelyingPartyTests(TestCase):
'http://schema.openid.net/namePerson'))
self.assertTrue(fetch_request.has_key(
'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.
openid_response = openid_request.answer(True)
......@@ -1175,12 +1224,17 @@ class RelyingPartyTests(TestCase):
'http://axschema.org/namePerson/last', 'Lastname')
fetch_response.addValue(
'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)
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).
# nickname has not caused the username to change), because
# settings.OPENID_FOLLOW_RENAMES is False.
response = self.client.get('/getuser/')
self.assertEquals(response.content, 'testuser')
......@@ -1189,6 +1243,56 @@ class RelyingPartyTests(TestCase):
self.assertEquals(user.first_name, 'Firstname')
self.assertEquals(user.last_name, 'Lastname')
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):
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = False
......@@ -1197,14 +1301,21 @@ class RelyingPartyTests(TestCase):
user = User.objects.create_user('testuser', 'someone@example.com')
group = Group(name='groupname')
group.save()
# Django creates the add_nonce permission by default
group.permissions.add(
Permission.objects.get(codename='add_nonce'))
ogroup = Group(name='othergroup')
ogroup.save()
# Django creates the add_useropenid permission by default
ogroup.permissions.add(
Permission.objects.get(codename='add_useropenid'))
user.groups.add(ogroup)
user.save()
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=True)
useropenid.save()
# Posting in an identity URL begins the authentication request:
......@@ -1248,7 +1359,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
# Posting in an identity URL begins the authentication request:
......@@ -1300,7 +1412,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
# Posting in an identity URL begins the authentication request:
......@@ -1324,7 +1437,8 @@ class RelyingPartyTests(TestCase):
useropenid = UserOpenID(
user=user,
claimed_id='http://example.com/identity',
display_id='http://example.com/identity')
display_id='http://example.com/identity',
account_verified=False)
useropenid.save()
response = self.client.post('/openid/login/',
{'openid_identifier': 'http://example.com/identity'})
......
......@@ -56,7 +56,6 @@ 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,
)
......@@ -170,7 +169,6 @@ def login_begin(request, template_name='openid/login.html',
redirect_field_name: redirect_to
}, context_instance=RequestContext(request))
error = None
consumer = make_consumer(request)
try:
openid_request = consumer.begin(openid_url)
......@@ -181,7 +179,8 @@ def login_begin(request, template_name='openid/login.html',
# Request some user details. If the provider advertises support
# 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()
# We mark all the attributes as required, since Google ignores
# optional attributes. We request both the full name and
......@@ -198,8 +197,22 @@ def login_begin(request, template_name='openid/login.html',
# 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')]:
('http://schema.openid.net/namePerson/friendly',
'old_nickname')]:
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)
else:
sreg_required_fields = []
......
......@@ -133,6 +133,14 @@ OPENID_CREATE_USERS = True
# data received via Simple Registration?
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
# user. This only makes sense if it is a server URL.
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