[snowy: 24/26] Fetch user attributes from sreg and ax for openid registration
- From: Sanford Armstrong <sharm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [snowy: 24/26] Fetch user attributes from sreg and ax for openid registration
- Date: Tue, 22 Jun 2010 20:59:01 +0000 (UTC)
commit 4f8cb9365b3f2c4c22500398303d05cb0392202b
Author: Leon Handreke <leon handreke gmail com>
Date: Sat May 29 23:17:11 2010 +0200
Fetch user attributes from sreg and ax for openid registration
[django_openid_auth] Upgrade to newest version that supports attribute exchange
accounts/forms.py | 1 +
accounts/views.py | 20 ++++--
lib/django_openid_auth/auth.py | 113 +++++++++++++++++++---------
lib/django_openid_auth/tests/test_views.py | 80 ++++++++++++++++++--
lib/django_openid_auth/views.py | 15 ++++-
5 files changed, 177 insertions(+), 52 deletions(-)
---
diff --git a/accounts/forms.py b/accounts/forms.py
index 8ebd945..1134800 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -43,6 +43,7 @@ class RegistrationFormUniqueUser(RegistrationFormUniqueEmail):
self.fields['username'].label = _(u'Username')
self.fields['username'].help_text = _(u'Maximum of 30 characters in length.')
+ self.fields['username'].error_messages['invalid'] = _(u'Usernames may not contain special characters.')
self.fields['email'].label = _(u'Email address')
diff --git a/accounts/views.py b/accounts/views.py
index bd88e89..d8e7d46 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -30,14 +30,22 @@ from django.conf import settings
from snowy.accounts.models import UserProfile
from snowy.accounts.forms import InternationalizationForm, OpenIDRegistrationFormUniqueUser
+from django_openid_auth import auth
+
def openid_registration(request, template_name='registration/registration_form.html'):
+ registration_form = OpenIDRegistrationFormUniqueUser(request.POST or None)
+
try:
openid_response = request.session['openid_response']
- # do sreg magic here
except KeyError:
return HttpResponseNotAllowed(_(u'No openid_response object for this session!'))
- registration_form = OpenIDRegistrationFormUniqueUser(request.POST or None)
+ try:
+ attributes = auth._extract_user_details(openid_response)
+ registration_form.fields['username'].initial = attributes['nickname']
+ registration_form.fields['email'].initial = attributes['email']
+ except KeyError:
+ pass
if registration_form.is_valid():
user = authenticate(openid_response=openid_response,
@@ -47,13 +55,13 @@ def openid_registration(request, template_name='registration/registration_form.h
del request.session['openid_response']
if user is not None:
- email = registration_form.cleaned_data.get('email', '')
+ email = registration_form.cleaned_data.get('email')
if email:
user.email = email
- display_name = registration_form.cleaned_data.get('display_name', '')
- if display_name:
- user.get_profile().display_name = display_name
+ #display_name = registration_form.cleaned_data.get('display_name')
+ #if display_name:
+ # user.get_profile().display_name = display_name
user.save()
user.get_profile().save()
diff --git a/lib/django_openid_auth/auth.py b/lib/django_openid_auth/auth.py
index 1670e02..220f20c 100644
--- a/lib/django_openid_auth/auth.py
+++ b/lib/django_openid_auth/auth.py
@@ -33,7 +33,7 @@ __metaclass__ = type
from django.conf import settings
from django.contrib.auth.models import User, Group
from openid.consumer.consumer import SUCCESS
-from openid.extensions import sreg
+from openid.extensions import ax, sreg
from django_openid_auth import teams
from django_openid_auth.models import UserOpenID
@@ -71,17 +71,13 @@ class OpenIDBackend:
user_openid = UserOpenID.objects.get(
claimed_id__exact=openid_response.identity_url)
except UserOpenID.DoesNotExist:
- if kwargs.get('create_user'):
- if kwargs.get('username'):
- user = self.create_user_from_openid(openid_response,
- username=kwargs.get('username'))
- else:
- return None
+ if kwargs.get('create_user') and kwargs.get('username'):
+ user = self.create_user_from_openid(openid_response,
+ username=kwargs.get('username'))
+ elif getattr(settings, 'OPENID_CREATE_USERS', False):
+ user = self.create_user_from_openid(openid_response)
else:
- if getattr(settings, 'OPENID_CREATE_USERS', False):
- user = self.create_user_from_openid(openid_response)
- else:
- return None
+ return None
else:
user = user_openid.user
@@ -89,10 +85,8 @@ class OpenIDBackend:
return None
if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False):
- sreg_response = sreg.SRegResponse.fromSuccessResponse(
- openid_response)
- if sreg_response:
- self.update_user_details_from_sreg(user, sreg_response)
+ details = _extract_user_details(openid_response)
+ self.update_user_details(user, details)
teams_response = teams.TeamsResponse.fromSuccessResponse(
openid_response)
@@ -102,27 +96,29 @@ class OpenIDBackend:
return user
def create_user_from_openid(self, openid_response, username='snowyuser', email=''):
- sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response)
+ nickname = username
+ #email = details['email'] or ''
+
# Pick a username for the user based on their nickname,
# checking for conflicts.
i = 1
while True:
- # use the name nickname here so we don't interfere with the username argument
- nickname = username
+ username = nickname
if i > 1:
- nickname += str(i)
+ username += str(i)
try:
- User.objects.get(username__exact=nickname)
+ User.objects.get(username__exact=username)
except User.DoesNotExist:
break
i += 1
- user = User.objects.create_user(nickname, email, password=None)
+ user = User.objects.create_user(username, email, password=None)
user.get_profile().openid_user = True
user.get_profile().save()
- if sreg_response:
- self.update_user_details_from_sreg(user, sreg_response)
+ if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False):
+ details = _extract_user_details(openid_response)
+ self.update_user_details(user, details)
self.associate_openid(user, openid_response)
return user
@@ -147,20 +143,20 @@ class OpenIDBackend:
return user_openid
- def update_user_details_from_sreg(self, user, sreg_response):
- fullname = sreg_response.get('fullname')
- if fullname:
- # Do our best here ...
- if ' ' in fullname:
- user.first_name, user.last_name = fullname.rsplit(None, 1)
- else:
- user.first_name = u''
- user.last_name = fullname
-
- email = sreg_response.get('email')
- if email:
- user.email = email
- user.save()
+ def update_user_details(self, user, details):
+ updated = False
+ if details['first_name']:
+ user.first_name = details['first_name']
+ updated = True
+ if details['last_name']:
+ user.last_name = details['last_name']
+ updated = True
+ if details['email']:
+ user.email = details['email']
+ updated = True
+
+ if updated:
+ user.save()
def update_groups_from_teams(self, user, teams_response):
teams_mapping_auto = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False)
@@ -186,3 +182,46 @@ class OpenIDBackend:
user.groups.remove(group)
for group in desired_groups - current_groups:
user.groups.add(group)
+
+# to be used outside of the backend
+def _extract_user_details(openid_response):
+ email = fullname = first_name = last_name = nickname = None
+ sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response)
+ if sreg_response:
+ email = sreg_response.get('email')
+ fullname = sreg_response.get('fullname')
+ nickname = sreg_response.get('nickname')
+
+ # If any attributes are provided via Attribute Exchange, use
+ # them in preference.
+ fetch_response = ax.FetchResponse.fromSuccessResponse(openid_response)
+ if fetch_response:
+ email = fetch_response.getSingle(
+ 'http://axschema.org/contact/email', email)
+ fullname = fetch_response.getSingle(
+ 'http://axschema.org/namePerson', fullname)
+ first_name = fetch_response.getSingle(
+ 'http://axschema.org/namePerson/first', first_name)
+ last_name = fetch_response.getSingle(
+ 'http://axschema.org/namePerson/last', last_name)
+ nickname = fetch_response.getSingle(
+ 'http://axschema.org/namePerson/friendly', nickname)
+
+ if fullname and not (first_name or last_name):
+ # Django wants to store first and last names separately,
+ # so we do our best to split the full name.
+ if ' ' in fullname:
+ first_name, last_name = fullname.rsplit(None, 1)
+ else:
+ first_name = u''
+ last_name = fullname
+
+ if (first_name and last_name) and not fullname:
+ fullname = last_name + " " + last_name
+
+ if (first_name and last_name) and not nickname:
+ nickname = (first_name + last_name).lower()
+
+ return dict(email=email, nickname=nickname,
+ first_name=first_name, last_name=last_name,
+ fullname=fullname)
diff --git a/lib/django_openid_auth/tests/test_views.py b/lib/django_openid_auth/tests/test_views.py
index 0c6c546..26cd87c 100644
--- a/lib/django_openid_auth/tests/test_views.py
+++ b/lib/django_openid_auth/tests/test_views.py
@@ -34,7 +34,7 @@ import unittest
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.test import TestCase
-from openid.extensions.sreg import SRegRequest, SRegResponse
+from openid.extensions import ax, sreg
from openid.fetchers import (
HTTPFetcher, HTTPFetchingError, HTTPResponse, setDefaultFetcher)
from openid.oidutil import importElementTree
@@ -58,10 +58,12 @@ class StubOpenIDProvider(HTTPFetcher):
self.endpoint_url = base_url + 'endpoint'
self.server = Server(self.store, self.endpoint_url)
self.last_request = None
+ self.type_uris = ['http://specs.openid.net/auth/2.0/signon']
def fetch(self, url, body=None, headers=None):
if url == self.identity_url:
- # Serve an XRDS document directly, which is the
+ # Serve an XRDS document directly, pointing at our endpoint.
+ type_uris = ['<Type>%s</Type>' % uri for uri in self.type_uris]
return HTTPResponse(
url, 200, {'content-type': 'application/xrds+xml'}, """\
<?xml version="1.0"?>
@@ -70,13 +72,13 @@ class StubOpenIDProvider(HTTPFetcher):
xmlns:xrds="xri://$xrds">
<XRD>
<Service priority="0">
- <Type>http://specs.openid.net/auth/2.0/signon</Type>
+ %s
<URI>%s</URI>
<LocalID>%s</LocalID>
</Service>
</XRD>
</xrds:XRDS>
-""" % (self.endpoint_url, self.localid_url))
+""" % ('\n'.join(type_uris), self.endpoint_url, self.localid_url))
elif url.startswith(self.endpoint_url):
# Gather query parameters
query = {}
@@ -260,9 +262,9 @@ class RelyingPartyTests(TestCase):
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
- sreg_request = SRegRequest.fromOpenIDRequest(openid_request)
+ sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
- sreg_response = SRegResponse.extractResponse(
+ sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
'email': 'foo example com'})
openid_response.addExtension(sreg_response)
@@ -298,9 +300,9 @@ class RelyingPartyTests(TestCase):
# Complete the request, passing back some simple registration
# data. The user is redirected to the next URL.
openid_request = self.provider.parseFormPost(response.content)
- sreg_request = SRegRequest.fromOpenIDRequest(openid_request)
+ sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
openid_response = openid_request.answer(True)
- sreg_response = SRegResponse.extractResponse(
+ sreg_response = sreg.SRegResponse.extractResponse(
sreg_request, {'nickname': 'someuser', 'fullname': 'Some User',
'email': 'foo example com'})
openid_response.addExtension(sreg_response)
@@ -318,6 +320,68 @@ class RelyingPartyTests(TestCase):
self.assertEquals(user.last_name, 'User')
self.assertEquals(user.email, 'foo example com')
+ def test_login_attribute_exchange(self):
+ settings.OPENID_UPDATE_DETAILS_FROM_SREG = True
+ user = User.objects.create_user('testuser', 'someone example com')
+ useropenid = UserOpenID(
+ user=user,
+ claimed_id='http://example.com/identity',
+ display_id='http://example.com/identity')
+ useropenid.save()
+
+ # Configure the provider to advertise attribute exchange
+ # protocol and start the authentication process:
+ self.provider.type_uris.append('http://openid.net/srv/ax/1.0')
+ response = self.client.post('/openid/login/',
+ {'openid_identifier': 'http://example.com/identity',
+ 'next': '/getuser/'})
+ self.assertContains(response, 'OpenID transaction in progress')
+
+ # The resulting OpenID request uses the Attribute Exchange
+ # extension rather than the Simple Registration extension.
+ openid_request = self.provider.parseFormPost(response.content)
+ sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
+ self.assertEqual(sreg_request.required, [])
+ self.assertEqual(sreg_request.optional, [])
+
+ fetch_request = ax.FetchRequest.fromOpenIDRequest(openid_request)
+ self.assertTrue(fetch_request.has_key(
+ 'http://axschema.org/contact/email'))
+ self.assertTrue(fetch_request.has_key(
+ 'http://axschema.org/namePerson'))
+ self.assertTrue(fetch_request.has_key(
+ 'http://axschema.org/namePerson/first'))
+ self.assertTrue(fetch_request.has_key(
+ 'http://axschema.org/namePerson/last'))
+ self.assertTrue(fetch_request.has_key(
+ 'http://axschema.org/namePerson/friendly'))
+
+ # Build up a response including AX data.
+ openid_response = openid_request.answer(True)
+ fetch_response = ax.FetchResponse(fetch_request)
+ fetch_response.addValue(
+ 'http://axschema.org/contact/email', 'foo example com')
+ fetch_response.addValue(
+ 'http://axschema.org/namePerson/first', 'Firstname')
+ fetch_response.addValue(
+ 'http://axschema.org/namePerson/last', 'Lastname')
+ fetch_response.addValue(
+ 'http://axschema.org/namePerson/friendly', 'someuser')
+ openid_response.addExtension(fetch_response)
+ response = self.complete(openid_response)
+ self.assertRedirects(response, 'http://testserver/getuser/')
+
+ # And they are now logged in as testuser (the passed in
+ # nickname has not caused the username to change).
+ response = self.client.get('/getuser/')
+ self.assertEquals(response.content, 'testuser')
+
+ # The user's full name and email have been updated.
+ user = User.objects.get(username='testuser')
+ self.assertEquals(user.first_name, 'Firstname')
+ self.assertEquals(user.last_name, 'Lastname')
+ self.assertEquals(user.email, 'foo example com')
+
def test_login_teams(self):
settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname',
'otherteam': 'othergroup'}
diff --git a/lib/django_openid_auth/views.py b/lib/django_openid_auth/views.py
index cc22aed..195da01 100644
--- a/lib/django_openid_auth/views.py
+++ b/lib/django_openid_auth/views.py
@@ -44,7 +44,7 @@ from django.template.loader import render_to_string
from openid.consumer.consumer import (
Consumer, SUCCESS, CANCEL, FAILURE)
from openid.consumer.discover import DiscoveryFailure
-from openid.extensions import sreg
+from openid.extensions import sreg, ax
from django_openid_auth import teams
from django_openid_auth.forms import OpenIDLoginForm
@@ -164,6 +164,19 @@ def login_begin(request, template_name='openid/login.html',
request, "OpenID discovery error: %s" % (str(exc),), status=500)
# Request some user details.
+ fetch_request = ax.FetchRequest()
+ # We mark all the attributes as required, since Google ignores
+ # optional attributes. We request both the full name and
+ # first/last components since some providers offer one but not
+ # the other.
+ for (attr, alias) in [
+ ('http://axschema.org/contact/email', 'email'),
+ ('http://axschema.org/namePerson', 'fullname'),
+ ('http://axschema.org/namePerson/first', 'firstname'),
+ ('http://axschema.org/namePerson/last', 'lastname'),
+ ('http://axschema.org/namePerson/friendly', 'nickname')]:
+ fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True))
+ openid_request.addExtension(fetch_request)
openid_request.addExtension(
sreg.SRegRequest(optional=['email', 'fullname', 'nickname']))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]