diff --git a/README.rst b/README.rst index 32f75a47..b868f031 100644 --- a/README.rst +++ b/README.rst @@ -190,11 +190,14 @@ Configuration Final user name will have an integer suffix in case it's already taken. -- OAuth_ authentication will store access_token by default, set this value - to False to avoid such behavior:: +- Backends will store extra values from response by default, set this to False + to avoid such behavior:: SOCIAL_AUTH_EXTRA_DATA = False + Also more extra values will be stored if defined, details about this setting + are listed below on OpenId and OAuth sections. + - It's possible to override the used User model if needed:: SOCIAL_AUTH_USER_MODEL = 'myapp.CustomUser' @@ -255,6 +258,24 @@ OpenId_ support is simpler to implement than OAuth_. Google and Yahoo providers are supported by default, others are supported by POST method providing endpoint URL. +OpenId_ backends can store extra data in UserSocialAuth.extra_data field +by defining a set of values names to retrieve from any of the used schemas, +pettributeExchange and SimpleRegistration. As their keywords differ we need +two settings. + +Settings is per backend, so we have two possible values for each one. Name +is dynamically checked using uppercase backend name as prefix:: + + _SREG_EXTRA_DATA + _AX_EXTRA_DATA + +Example:: + + GOOGLE_SREG_EXTRA_DATA = [(..., ...)] + GOOGLE_AX_EXTRA_DATA = [(..., ...)] + +Settings must be a list of tuples mapping value name in response and value +alias used to store. ----- OAuth @@ -266,6 +287,20 @@ but provides the option for unregistered applications. Check next sections for details. +OAuth_ backends also can store extra data in UserSocialAuth.extra_data field +by defining a set of values names to retrieve from service response. + +Settings is per backend and it's name is dynamically checked using uppercase +backend name as prefix:: + + _EXTRA_DATA + +Example:: + + FACEBOOK_EXTRA_DATA = [(..., ...)] + +Settings must be a list of tuples mapping value name in response and value +alias used to store. ------- Twitter diff --git a/social_auth/backends/__init__.py b/social_auth/backends/__init__.py index 15e0b1df..ca13e8b1 100644 --- a/social_auth/backends/__init__.py +++ b/social_auth/backends/__init__.py @@ -50,7 +50,11 @@ ('http://axschema.org/namePerson/last', 'last_name'), ('http://axschema.org/namePerson/friendly', 'nickname'), ] -SREG_ATTR = ['email', 'fullname', 'nickname'] +SREG_ATTR = [ + ('email', 'email'), + ('fullname', 'fullname'), + ('nickname', 'nickname') +] OPENID_ID_FIELD = 'openid_identifier' SESSION_NAME = 'openid' @@ -221,14 +225,33 @@ def get_user(self, user_id): class OAuthBackend(SocialAuthBackend): - """OAuth authentication backend base class""" + """OAuth authentication backend base class. + + EXTRA_DATA defines a set of name that will be stored in + extra_data field. It must be a list of tuples with + name and alias. + + Also settings will be inspected to get more values names that should be + stored on extra_data field. Setting name is created from current backend + name (all uppercase) plus _EXTRA_DATA. + + access_token is always stored. + """ + EXTRA_DATA = None + def get_user_id(self, details, response): "OAuth providers return an unique user id in response""" return response['id'] def extra_data(self, user, uid, response, details): - """Return access_token to store in extra_data field""" - return response.get('access_token', '') + """Return access_token and extra defined names to store in + extra_data field""" + data = {'access_token': response.get('access_token', '')} + name = self.name.replace('-', '_').upper() + names = self.EXTRA_DATA or [] + \ + getattr(settings, name + '_EXTRA_DATA', []) + data.update((alias, response.get(name)) for name, alias in names) + return data class OpenIDBackend(SocialAuthBackend): @@ -239,21 +262,41 @@ def get_user_id(self, details, response): """Return user unique id provided by service""" return response.identity_url + def values_from_response(self, response, sreg_names=None, ax_names=None): + """Return values from SimpleRegistration response or + AttributeExchange response if present. + + @sreg_names and @ax_names must be a list of name and aliases + for such name. The alias will be used as mapping key. + """ + values = {} + + # Use Simple Registration attributes if provided + if sreg_names: + resp = sreg.SRegResponse.fromSuccessResponse(response) + if resp: + values.update((alias, resp.get(name) or '') + for name, alias in sreg_names) + + # Use Attribute Exchange attributes if provided + if ax_names: + resp = ax.FetchResponse.fromSuccessResponse(response) + if resp: + values.update((alias.replace('old_', ''), + resp.getSingle(src, '')) + for src, alias in ax_names) + return values + def get_user_details(self, response): """Return user details from an OpenID request""" values = {USERNAME: '', 'email': '', 'fullname': '', 'first_name': '', 'last_name': ''} - - resp = sreg.SRegResponse.fromSuccessResponse(response) - if resp: - values.update((name, resp.get(name) or values.get(name) or '') - for name in ('email', 'fullname', 'nickname')) - - # Use Attribute Exchange attributes if provided - resp = ax.FetchResponse.fromSuccessResponse(response) - if resp: - values.update((alias.replace('old_', ''), resp.getSingle(src, '')) - for src, alias in OLD_AX_ATTRS + AX_SCHEMA_ATTRS) + # update values using SimpleRegistration or AttributeExchange + # values + values.update(self.values_from_response(response, + SREG_ATTR, + OLD_AX_ATTRS + \ + AX_SCHEMA_ATTRS)) fullname = values.get('fullname') or '' first_name = values.get('first_name') or '' @@ -273,6 +316,23 @@ def get_user_details(self, response): (first_name.title() + last_name.title())}) return values + def extra_data(self, user, uid, response, details): + """Return defined extra data names to store in extra_data field. + Settings will be inspected to get more values names that should be + stored on extra_data field. Setting name is created from current + backend name (all uppercase) plus _SREG_EXTRA_DATA and + _AX_EXTRA_DATA because values can be returned by SimpleRegistration + or AttributeExchange schemas. + + Both list must be a value name and an alias mapping similar to + SREG_ATTR, OLD_AX_ATTRS or AX_SCHEMA_ATTRS + """ + name = self.name.replace('-', '_').upper() + sreg_names = getattr(settings, name + '_SREG_EXTRA_DATA', None) + ax_names = getattr(settings, name + '_AX_EXTRA_DATA', None) + data = self.values_from_response(response, ax_names, sreg_names) + return data + class BaseAuth(object): """Base authentication class, new authenticators should subclass @@ -367,7 +427,7 @@ def setup_request(self): fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True)) else: - fetch_request = sreg.SRegRequest(optional=SREG_ATTR) + fetch_request = sreg.SRegRequest(optional=dict(SREG_ATTR).keys()) openid_request.addExtension(fetch_request) return openid_request diff --git a/social_auth/backends/facebook.py b/social_auth/backends/facebook.py index 6ed37525..a8784e91 100644 --- a/social_auth/backends/facebook.py +++ b/social_auth/backends/facebook.py @@ -7,6 +7,9 @@ Extended permissions are supported by defining FACEBOOK_EXTENDED_PERMISSIONS setting, it must be a list of values to request. + +By default account id and token expiration time are stored in extra_data +field, check OAuthBackend class for details on how to extend it. """ import cgi import urllib @@ -28,6 +31,8 @@ class FacebookBackend(OAuthBackend): """Facebook OAuth authentication backend""" name = 'facebook' + # Default extra data to store + EXTRA_DATA = [('id', 'id'), ('expires', 'expires')] def get_user_details(self, response): """Return user details from Facebook account""" diff --git a/social_auth/backends/twitter.py b/social_auth/backends/twitter.py index a4ca92d3..aaed2c95 100644 --- a/social_auth/backends/twitter.py +++ b/social_auth/backends/twitter.py @@ -7,6 +7,9 @@ values. User screen name is used to generate username. + +By default account id is stored in extra_data field, check OAuthBackend +class for details on how to extend it. """ from django.utils import simplejson @@ -26,6 +29,7 @@ class TwitterBackend(OAuthBackend): """Twitter OAuth authentication backend""" name = 'twitter' + EXTRA_DATA = [('id', 'id')] def get_user_details(self, response): """Return user details from Twitter account""" diff --git a/social_auth/models.py b/social_auth/models.py index 9ef0e162..749f0c5f 100644 --- a/social_auth/models.py +++ b/social_auth/models.py @@ -4,6 +4,8 @@ from django.db import models from django.conf import settings +from social_auth.fields import JSONField + # If User class is overridden, it *must* provide the following fields, # or it won't be playing nicely with django.contrib.auth module: # @@ -36,7 +38,7 @@ class UserSocialAuth(models.Model): user = models.ForeignKey(User, related_name='social_auth') provider = models.CharField(max_length=32) uid = models.CharField(max_length=255) - extra_data = models.TextField(default='', blank=True) + extra_data = JSONField(blank=True) class Meta: """Meta data"""