Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote-tracking branch 'upstream/master'

Conflicts:
	social_auth/backends/__init__.py
  • Loading branch information...
commit 2c6fbec9d5740b428e782dd18e6ac37861ac5fc8 2 parents 15aba05 + a9cfde5
@krvss krvss authored
View
45 README.rst
@@ -42,6 +42,8 @@ credentials, some features are:
* `Linkedin OAuth`_
* `Foursquare OAuth2`_
* `GitHub OAuth`_
+ * `Dropbox OAuth`_
+ * `Flickr OAuth`_
- Basic user data population and signaling, to allows custom fields values
from providers response
@@ -114,6 +116,8 @@ Configuration
'social_auth.backends.contrib.orkut.OrkutBackend',
'social_auth.backends.contrib.foursquare.FoursquareBackend',
'social_auth.backends.contrib.github.GithubBackend',
+ 'social_auth.backends.contrib.dropbox.DropboxBackend',
+ 'social_auth.backends.contrib.flickr.FlickrBackend',
'social_auth.backends.OpenIDBackend',
'django.contrib.auth.backends.ModelBackend',
)
@@ -154,6 +158,10 @@ Configuration
FOURSQUARE_CONSUMER_SECRET = ''
GITHUB_APP_ID = ''
GITHUB_API_SECRET = ''
+ DROPBOX_APP_ID = ''
+ DROPBOX_API_SECRET = ''
+ FLICKR_APP_ID = ''
+ FLICKR_API_SECRET = ''
- Setup login URLs::
@@ -562,6 +570,32 @@ GitHub works similar to Facebook (OAuth).
-------
+Dropbox
+-------
+Dropbox uses OAuth v1.0 for authentication.
+
+- Register a new application at `Dropbox Developers`_, and
+
+- fill ``App Key`` and ``App Secret`` values in the settings::
+
+ DROPBOX_APP_ID = ''
+ DROPBOX_API_SECRET = ''
+
+
+------
+Flickr
+------
+Flickr uses OAuth v1.0 for authentication.
+
+- Register a new application at the `Flickr App Garden`_, and
+
+- fill ``Key`` and ``Secret`` values in the settings::
+
+ FLICKR_APP_ID = ''
+ FLICKR_API_SECRET = ''
+
+
+-------
Testing
-------
To test the app just run::
@@ -676,6 +710,12 @@ Attributions to whom deserves:
- GitHub support
+- danielgtaylor_ (Daniel G. Taylor)
+
+ - Dropbox support
+ - Flickr support
+ - Provider name context processor
+
----------
Copyrights
----------
@@ -749,3 +789,8 @@ Base work is copyrighted by:
.. _GitHub OAuth: http://developer.github.com/v3/oauth/
.. _GitHub Developers: https://github.com/account/applications/new
.. _djangopackages.com: http://djangopackages.com/grids/g/social-auth-backends/
+.. _Dropbox OAuth: https://www.dropbox.com/developers_beta/reference/api
+.. _Dropbox Developers: https://www.dropbox.com/developers/apps
+.. _Flickr OAuth: http://www.flickr.com/services/api/
+.. _Flickr App Garden: http://www.flickr.com/services/apps/create/
+.. _danielgtaylor: https://github.com/danielgtaylor
View
1  example/settings.py
@@ -84,7 +84,6 @@
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
- 'django.core.context_processors.static',
'django.contrib.messages.context_processors.messages',
'social_auth.context_processors.social_auth_by_type_backends',
)
View
2  social_auth/__init__.py
@@ -2,5 +2,5 @@
Django-social-auth application, allows OpenId or OAuth user
registration/authentication just adding a few configurations.
"""
-version = (0, 5, 13)
+version = (0, 6, 0)
__version__ = '.'.join(map(str, version))
View
222 social_auth/backends/__init__.py
@@ -27,18 +27,15 @@
SignatureMethod_HMAC_SHA1
from django.conf import settings
-from django.core.exceptions import MultipleObjectsReturned
from django.contrib.auth import authenticate
from django.contrib.auth.backends import ModelBackend
from django.utils import simplejson
from django.utils.importlib import import_module
-from django.db.utils import IntegrityError
from social_auth.models import UserSocialAuth
from social_auth.store import DjangoOpenIDStore
-from social_auth.signals import pre_update, socialauth_registered, \
- socialauth_not_registered
-from social_auth.utils import sanitize_log_data
+from social_auth.signals import pre_update, socialauth_registered
+from social_auth.backends.exceptions import StopPipeline
# OpenID configuration
@@ -84,6 +81,15 @@ def _setting(name, default=None):
DEFAULT_USERNAME = _setting('SOCIAL_AUTH_DEFAULT_USERNAME')
CHANGE_SIGNAL_ONLY = _setting('SOCIAL_AUTH_CHANGE_SIGNAL_ONLY', False)
UUID_LENGHT = _setting('SOCIAL_AUTH_UUID_LENGTH', 16)
+PIPELINE = _setting('SOCIAL_AUTH_PIPELINE', (
+ 'social_auth.backends.pipeline.social.social_auth_user',
+ 'social_auth.backends.pipeline.associate.associate_by_email',
+ 'social_auth.backends.pipeline.user.get_username',
+ 'social_auth.backends.pipeline.user.create_user',
+ 'social_auth.backends.pipeline.social.associate_user',
+ 'social_auth.backends.pipeline.social.load_extra_data',
+ 'social_auth.backends.pipeline.user.update_user_details',
+ ))
class SocialAuthBackend(ModelBackend):
@@ -108,168 +114,50 @@ def authenticate(self, *args, **kwargs):
response = kwargs.get('response')
details = self.get_user_details(response)
uid = self.get_user_id(details, response)
- is_new = False
user = kwargs.get('user')
-
- try:
- social_user = self.get_social_auth_user(uid)
- except UserSocialAuth.DoesNotExist:
- if user is None: # new user
- if not CREATE_USERS or not kwargs.get('create_user', True):
- # Send signal for cases where tracking failed registering
- # is useful.
- socialauth_not_registered.send(sender=self.__class__,
- uid=uid,
- response=response,
- details=details)
- return None
-
- email = details.get('email')
- if email and ASSOCIATE_BY_MAIL:
- # try to associate accounts registered with the same email
- # address, only if it's a single object. ValueError is
- # raised if multiple objects are returned
- try:
- user = User.objects.get(email=email)
- except MultipleObjectsReturned:
- raise ValueError('Not unique email address supplied')
- except User.DoesNotExist:
- user = None
- if not user:
- username = self.username(details)
- logger.debug('Creating new user with username %s and email %s',
- username, sanitize_log_data(email))
- user = User.objects.create_user(username=username,
- email=email)
- is_new = True
-
+ request = kwargs.get('request')
+
+ # Pipeline:
+ # Arguments:
+ # request, backend, social_user, uid, response, details
+ # user, is_new, args, kwargs
+ kwargs = kwargs.copy()
+ kwargs.update({
+ 'backend': self,
+ 'request': request,
+ 'uid': uid,
+ 'user': user,
+ 'social_user': None,
+ 'response': response,
+ 'details': details,
+ 'is_new': False,
+ })
+ for name in PIPELINE:
+ mod_name, func_name = name.rsplit('.', 1)
try:
- social_user = self.associate_auth(user, uid, response, details)
- except IntegrityError:
- # Protect for possible race condition, those bastard with FTL
- # clicking capabilities
- social_user = self.get_social_auth_user(uid)
-
- # Raise ValueError if this account was registered by another user.
- if user and user != social_user.user:
- raise ValueError('Account already in use.', social_user)
- user = social_user.user
-
- # Flag user "new" status
- setattr(user, 'is_new', is_new)
-
- # Update extra_data storage, unless disabled by setting
- if LOAD_EXTRA_DATA:
- extra_data = self.extra_data(user, uid, response, details)
- if extra_data and social_user.extra_data != extra_data:
- social_user.extra_data = extra_data
- social_user.save()
-
- user.social_user = social_user
-
- # Update user account data.
- self.update_user_details(user, response, details, is_new)
- return user
-
- def username(self, details):
- """Return an unique username, if SOCIAL_AUTH_FORCE_RANDOM_USERNAME
- setting is True, then username will be a random USERNAME_MAX_LENGTH
- chars uuid generated hash
- """
- def mk_uuid():
- """Return hash from unique string"""
- return uuid4().get_hex()
-
- if FORCE_RANDOM_USERNAME:
- username = mk_uuid()
- elif details.get(USERNAME):
- username = details[USERNAME]
- elif DEFAULT_USERNAME:
- username = DEFAULT_USERNAME
- if callable(username):
- username = username()
- else:
- username = mk_uuid()
-
- short_username = username[:USERNAME_MAX_LENGTH - UUID_LENGHT]
- final_username = None
-
- while True:
- final_username = USERNAME_FIXER(username)[:USERNAME_MAX_LENGTH]
-
- try:
- User.objects.get(username=final_username)
- except User.DoesNotExist:
- break
+ mod = import_module(mod_name)
+ except ImportError:
+ logger.exception('Error importing pipeline %s', name)
else:
- # User with same username already exists, generate a unique
- # username for current user using username as base but adding
- # a unique hash at the end. Original username is cut to avoid
- # the field max_length.
- username = short_username + mk_uuid()[:UUID_LENGHT]
-
- return final_username
-
- def associate_auth(self, user, uid, response, details):
- """Associate a Social Auth with an user account."""
- return UserSocialAuth.objects.create(user=user, uid=uid,
- provider=self.name)
+ pipeline = getattr(mod, func_name, None)
+ if callable(pipeline):
+ try:
+ kwargs.update(pipeline(*args, **kwargs) or {})
+ except StopPipeline:
+ break
+
+ social_user = kwargs.get('social_user')
+ if social_user:
+ # define user.social_user attribute to track current social
+ # account
+ user = social_user.user
+ user.social_user = social_user
+ return user
def extra_data(self, user, uid, response, details):
"""Return default blank user extra data"""
return ''
- def update_user_details(self, user, response, details, is_new=False):
- """Update user details with (maybe) new data. Username is not
- changed if associating a new credential."""
- changed = False # flag to track changes
-
- # check if values update should be left to signals handlers only
- if not CHANGE_SIGNAL_ONLY:
- logger.debug('Updating user details for user %s', user,
- extra=dict(data=details))
-
- for name, value in details.iteritems():
- # do not update username, it was already generated by
- # self.username(...) and loaded in given instance
- if name != USERNAME and value and value != getattr(user, name,
- None):
- setattr(user, name, value)
- changed = True
-
- # Fire a pre-update signal sending current backend instance,
- # user instance (created or retrieved from database), service
- # response and processed details.
- #
- # Also fire socialauth_registered signal for newly registered
- # users.
- #
- # Signal handlers must return True or False to signal instance
- # changes. Send method returns a list of tuples with receiver
- # and it's response.
- signal_response = lambda (receiver, response): response
-
- kwargs = {'sender': self.__class__, 'user': user,
- 'response': response, 'details': details}
- changed |= any(filter(signal_response, pre_update.send(**kwargs)))
-
- # Fire socialauth_registered signal on new user registration
- if is_new:
- changed |= any(filter(signal_response,
- socialauth_registered.send(**kwargs)))
-
- if changed:
- user.save()
-
- def get_social_auth_user(self, uid):
- """Return social auth user instance for given uid for current
- backend.
-
- Raise DoesNotExist exception if no entry.
- """
- return UserSocialAuth.objects.select_related('user')\
- .get(provider=self.name, uid=str(uid))
-
def get_user_id(self, details, response):
"""Must return a unique ID from values returned on details"""
raise NotImplementedError('Implement in subclass')
@@ -284,13 +172,6 @@ def get_user_details(self, response):
"""
raise NotImplementedError('Implement in subclass')
- def get_user(self, user_id):
- """Return user instance for @user_id"""
- try:
- return User.objects.get(pk=user_id)
- except User.DoesNotExist:
- return None
-
class OAuthBackend(SocialAuthBackend):
"""OAuth authentication backend base class.
@@ -325,15 +206,6 @@ class OpenIDBackend(SocialAuthBackend):
"""Generic OpenID authentication backend"""
name = 'openid'
- def get_social_auth_user(self, uid):
- """Return social auth user instance for given uid. OpenId uses
- identity_url to identify the user in a unique way and that value
- identifies the provider too.
-
- Riase DoesNotExist exception if no entry.
- """
- return UserSocialAuth.objects.select_related('user').get(uid=uid)
-
def get_user_id(self, details, response):
"""Return user unique id provided by service"""
return response.identity_url
View
72 social_auth/backends/contrib/dropbox.py
@@ -0,0 +1,72 @@
+"""
+Dropbox OAuth support.
+
+This contribution adds support for Dropbox OAuth service. The settings
+DROPBOX_APP_ID and DROPBOX_API_SECRET must be defined with the values
+given by Dropbox application registration process.
+
+By default account id and token expiration time are stored in extra_data
+field, check OAuthBackend class for details on how to extend it.
+"""
+from django.conf import settings
+from django.utils import simplejson
+
+from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, USERNAME
+
+# Dropbox configuration
+DROPBOX_SERVER = 'dropbox.com'
+DROPBOX_API = 'api.%s' % DROPBOX_SERVER
+DROPBOX_REQUEST_TOKEN_URL = 'https://%s/1/oauth/request_token' % DROPBOX_API
+DROPBOX_AUTHORIZATION_URL = 'https://www.%s/1/oauth/authorize' % DROPBOX_SERVER
+DROPBOX_ACCESS_TOKEN_URL = 'https://%s/1/oauth/access_token' % DROPBOX_API
+EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
+
+
+class DropboxBackend(OAuthBackend):
+ """Dropbox OAuth authentication backend"""
+ name = 'dropbox'
+ # Default extra data to store
+ EXTRA_DATA = [('id', 'id'), ('expires', EXPIRES_NAME)]
+
+ def get_user_details(self, response):
+ """Return user details from Dropbox account"""
+ return {USERNAME: response.get('uid'),
+ 'email': response.get('email'),
+ 'first_name': response.get('display_name')}
+
+ def get_user_id(self, details, response):
+ """OAuth providers return an unique user id in response"""
+ # Dropbox uses a uid parameter instead of id like most others...
+ return response['uid']
+
+class DropboxAuth(ConsumerBasedOAuth):
+ """Dropbox OAuth authentication mechanism"""
+ AUTHORIZATION_URL = DROPBOX_AUTHORIZATION_URL
+ REQUEST_TOKEN_URL = DROPBOX_REQUEST_TOKEN_URL
+ ACCESS_TOKEN_URL = DROPBOX_ACCESS_TOKEN_URL
+ SERVER_URL = DROPBOX_API
+ AUTH_BACKEND = DropboxBackend
+ SETTINGS_KEY_NAME = 'DROPBOX_APP_ID'
+ SETTINGS_SECRET_NAME = 'DROPBOX_API_SECRET'
+
+ def user_data(self, access_token):
+ """Loads user data from service"""
+ url = 'https://' + DROPBOX_API + '/1/account/info'
+ request = self.oauth_request(access_token, url)
+ response = self.fetch_response(request)
+ try:
+ return simplejson.loads(response)
+ except ValueError:
+ return None
+
+ @classmethod
+ def enabled(cls):
+ """Return backend enabled status by checking basic settings"""
+ return all(hasattr(settings, name) for name in
+ ('DROPBOX_APP_ID',
+ 'DROPBOX_API_SECRET'))
+
+# Backend definition
+BACKENDS = {
+ 'dropbox': DropboxAuth,
+}
View
80 social_auth/backends/contrib/flickr.py
@@ -0,0 +1,80 @@
+"""
+Flickr OAuth support.
+
+This contribution adds support for Flickr OAuth service. The settings
+FLICKR_APP_ID and FLICKR_API_SECRET must be defined with the values
+given by Flickr application registration process.
+
+By default account id, username and token expiration time are stored in
+extra_data field, check OAuthBackend class for details on how to extend it.
+"""
+try:
+ from urlparse import parse_qs
+ parse_qs # placate pyflakes
+except ImportError:
+ # fall back for Python 2.5
+ from cgi import parse_qs
+
+from django.conf import settings
+from django.utils import simplejson
+
+from oauth2 import Token
+from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, USERNAME
+
+# Flickr configuration
+FLICKR_SERVER = 'http://www.flickr.com/services'
+FLICKR_REQUEST_TOKEN_URL = '%s/oauth/request_token' % FLICKR_SERVER
+FLICKR_AUTHORIZATION_URL = '%s/oauth/authorize' % FLICKR_SERVER
+FLICKR_ACCESS_TOKEN_URL = '%s/oauth/access_token' % FLICKR_SERVER
+EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
+
+
+class FlickrBackend(OAuthBackend):
+ """Flickr OAuth authentication backend"""
+ name = 'flickr'
+ # Default extra data to store
+ EXTRA_DATA = [('id', 'id'), ('username', 'username'), ('expires', EXPIRES_NAME)]
+
+ def get_user_details(self, response):
+ """Return user details from Flickr account"""
+ print response
+ return {USERNAME: response.get('id'),
+ 'email': '',
+ 'first_name': response.get('fullname')}
+
+class FlickrAuth(ConsumerBasedOAuth):
+ """Flickr OAuth authentication mechanism"""
+ AUTHORIZATION_URL = FLICKR_AUTHORIZATION_URL
+ REQUEST_TOKEN_URL = FLICKR_REQUEST_TOKEN_URL
+ ACCESS_TOKEN_URL = FLICKR_ACCESS_TOKEN_URL
+ SERVER_URL = FLICKR_SERVER
+ AUTH_BACKEND = FlickrBackend
+ SETTINGS_KEY_NAME = 'FLICKR_APP_ID'
+ SETTINGS_SECRET_NAME = 'FLICKR_API_SECRET'
+
+ def access_token(self, token):
+ """Return request for access token value"""
+ # Flickr is a bit different - it passes user information along with
+ # the access token, so temporarily store it to vie the user_data
+ # method easy access later in the flow!
+ request = self.oauth_request(token, self.ACCESS_TOKEN_URL)
+ response = self.fetch_response(request)
+ token = Token.from_string(response)
+ params = parse_qs(response)
+ token.user_nsid = params['user_nsid'][0]
+ token.fullname = params['fullname'][0]
+ token.username = params['username'][0]
+ return token
+
+ def user_data(self, access_token):
+ """Loads user data from service"""
+ return {
+ 'id': access_token.user_nsid,
+ 'username': access_token.username,
+ 'fullname': access_token.fullname,
+ }
+
+# Backend definition
+BACKENDS = {
+ 'flickr': FlickrAuth,
+}
View
7 social_auth/backends/contrib/yandex.py
@@ -6,6 +6,9 @@
If username is not specified, OpenID 2.0 url used for authentication.
"""
+import logging
+logger = logging.getLogger(__name__)
+
import urlparse
from social_auth.backends import OpenIDBackend, OpenIdAuth, USERNAME
@@ -27,7 +30,9 @@ def get_user_details(self, response):
values[USERNAME] = values.get(USERNAME) or \
urlparse.urlsplit(response.identity_url)\
.path.strip('/')
-
+
+ values['email'] = values.get('email') or ''
+
return values
View
5 social_auth/backends/exceptions.py
@@ -0,0 +1,5 @@
+class StopPipeline(Exception):
+ """Stop pipeline process exception.
+ Raise this exception to stop the rest of the pipeline process.
+ """
+ pass
View
23 social_auth/backends/pipeline/__init__.py
@@ -0,0 +1,23 @@
+"""Django-Social-Auth Pipeline.
+
+Pipelines must return a dictionary with values that will be passed as parameter
+to next pipeline item. Pipelines must take **kwargs parameters to avoid
+failure. At some point a pipeline entry must create a UserSocialAuth instance
+and load it to the output if the user logged in correctly.
+"""
+import warnings
+
+from django.conf import settings
+
+from social_auth.models import User
+
+
+USERNAME = 'username'
+USERNAME_MAX_LENGTH = User._meta.get_field(USERNAME).max_length
+
+
+def warn_setting(name, func_name):
+ """Warn about deprecated settings."""
+ if hasattr(settings, name):
+ msg = '%s is deprecated, disable or override "%s" pipeline instead'
+ warnings.warn(msg % (name, func_name))
View
23 social_auth/backends/pipeline/associate.py
@@ -0,0 +1,23 @@
+from django.conf import settings
+from django.core.exceptions import MultipleObjectsReturned
+
+from social_auth.models import User
+from social_auth.backends.pipeline import warn_setting
+
+
+def associate_by_email(details, *args, **kwargs):
+ """Return user entry with same email address as one returned on details."""
+ email = details.get('email')
+
+ warn_setting('SOCIAL_AUTH_ASSOCIATE_BY_MAIL', 'associate_by_email')
+
+ if email and getattr(settings, 'SOCIAL_AUTH_ASSOCIATE_BY_MAIL', False):
+ # try to associate accounts registered with the same email address,
+ # only if it's a single object. ValueError is raised if multiple
+ # objects are returned
+ try:
+ return {'user': User.objects.get(email=email)}
+ except MultipleObjectsReturned:
+ raise ValueError('Not unique email address.')
+ except User.DoesNotExist:
+ pass
View
55 social_auth/backends/pipeline/social.py
@@ -0,0 +1,55 @@
+from django.conf import settings
+from django.db.utils import IntegrityError
+
+from social_auth.models import User, UserSocialAuth
+from social_auth.backends.pipeline import warn_setting
+
+
+def social_auth_user(backend, uid, user=None, *args, **kwargs):
+ """Return UserSocialAuth account for backend/uid pair or None if it
+ doesn't exists.
+
+ Raise ValueError if UserSocialAuth entry belongs to another user.
+ """
+ try:
+ social_user = UserSocialAuth.objects.select_related('user')\
+ .get(provider=backend.name,
+ uid=uid)
+ except UserSocialAuth.DoesNotExist:
+ social_user = None
+
+ if user and social_user and social_user.user != user:
+ raise ValueError('Account already in use.', social_user)
+ return {'social_user': social_user}
+
+
+def associate_user(backend, user, uid, social_user=None, *args, **kwargs):
+ """Associate user social account with user instance."""
+ if social_user:
+ return None
+
+ try:
+ social = UserSocialAuth.objects.create(user=user, uid=uid,
+ provider=backend.name)
+ except IntegrityError:
+ # Protect for possible race condition, those bastard with FTL
+ # clicking capabilities, check issue #131:
+ # https://github.com/omab/django-social-auth/issues/131
+ return social_auth_user(backend, uid, user, social_user=social_user,
+ *args, **kwargs)
+ else:
+ return {'social_user': social}
+
+
+def load_extra_data(backend, details, response, social_user, uid, user,
+ *args, **kwargs):
+ """Load extra data from provider and store it on current UserSocialAuth
+ extra_data field.
+ """
+ warn_setting('SOCIAL_AUTH_EXTRA_DATA', 'load_extra_data')
+
+ if getattr(settings, 'SOCIAL_AUTH_EXTRA_DATA', True):
+ extra_data = backend.extra_data(user, uid, response, details)
+ if extra_data and social_user.extra_data != extra_data:
+ social_user.extra_data = extra_data
+ social_user.save()
View
121 social_auth/backends/pipeline/user.py
@@ -0,0 +1,121 @@
+from uuid import uuid4
+
+from django.conf import settings
+
+from social_auth.models import User
+from social_auth.backends.pipeline import USERNAME, USERNAME_MAX_LENGTH, warn_setting
+from social_auth.signals import socialauth_not_registered, \
+ socialauth_registered, \
+ pre_update
+
+
+def get_username(details, user=None, *args, **kwargs):
+ """Return an username for new user. Return current user username
+ if user was given.
+ """
+ if user:
+ return {'username': user.username}
+
+ warn_setting('SOCIAL_AUTH_FORCE_RANDOM_USERNAME', 'get_username')
+ warn_setting('SOCIAL_AUTH_DEFAULT_USERNAME', 'get_username')
+ warn_setting('SOCIAL_AUTH_UUID_LENGTH', 'get_username')
+ warn_setting('SOCIAL_AUTH_USERNAME_FIXER', 'get_username')
+
+ if getattr(settings, 'SOCIAL_AUTH_FORCE_RANDOM_USERNAME', False):
+ username = uuid4().get_hex()
+ elif details.get(USERNAME):
+ username = details[USERNAME]
+ elif settings.hasattr('SOCIAL_AUTH_DEFAULT_USERNAME'):
+ username = settings.SOCIAL_AUTH_DEFAULT_USERNAME
+ if callable(username):
+ username = username()
+ else:
+ username = uuid4().get_hex()
+
+ uuid_lenght = getattr(settings, 'SOCIAL_AUTH_UUID_LENGTH', 16)
+ username_fixer = getattr(settings, 'SOCIAL_AUTH_USERNAME_FIXER',
+ lambda u: u)
+
+ short_username = username[:USERNAME_MAX_LENGTH - uuid_lenght]
+ final_username = None
+
+ while True:
+ final_username = username_fixer(username)[:USERNAME_MAX_LENGTH]
+
+ try:
+ User.objects.get(username=final_username)
+ except User.DoesNotExist:
+ break
+ else:
+ # User with same username already exists, generate a unique
+ # username for current user using username as base but adding
+ # a unique hash at the end. Original username is cut to avoid
+ # the field max_length.
+ username = short_username + uuid4().get_hex()[:uuid_lenght]
+ return {'username': final_username}
+
+
+def create_user(backend, details, response, uid, username, user=None, *args,
+ **kwargs):
+ """Create user. Depends on get_username pipeline."""
+ if user:
+ return {'user': user}
+ if not username:
+ return None
+
+ warn_setting('SOCIAL_AUTH_CREATE_USERS', 'create_user')
+
+ if not getattr(settings, 'SOCIAL_AUTH_CREATE_USERS', True):
+ # Send signal for cases where tracking failed registering is useful.
+ socialauth_not_registered.send(sender=backend.__class__,
+ uid=uid,
+ response=response,
+ details=details)
+ return None
+
+ email = details.get('email')
+ return {
+ 'user': User.objects.create_user(username=username, email=email),
+ 'is_new': True
+ }
+
+
+def update_user_details(backend, details, response, user, is_new=False, *args, **kwargs):
+ """Update user details using data from provider."""
+ changed = False # flag to track changes
+
+ warn_setting('SOCIAL_AUTH_CHANGE_SIGNAL_ONLY', 'update_user_details')
+
+ # check if values update should be left to signals handlers only
+ if not getattr(settings, 'SOCIAL_AUTH_CHANGE_SIGNAL_ONLY', False):
+ for name, value in details.iteritems():
+ # do not update username, it was already generated
+ if name == USERNAME:
+ continue
+ if value and value != getattr(user, name, None):
+ setattr(user, name, value)
+ changed = True
+
+ # Fire a pre-update signal sending current backend instance,
+ # user instance (created or retrieved from database), service
+ # response and processed details.
+ #
+ # Also fire socialauth_registered signal for newly registered
+ # users.
+ #
+ # Signal handlers must return True or False to signal instance
+ # changes. Send method returns a list of tuples with receiver
+ # and it's response.
+ signal_response = lambda (receiver, response): response
+ signal_kwargs = {'sender': backend.__class__, 'user': user,
+ 'response': response, 'details': details}
+
+ changed |= any(filter(signal_response, pre_update.send(**signal_kwargs)))
+
+ # Fire socialauth_registered signal on new user registration
+ if is_new:
+ changed |= any(filter(signal_response,
+ socialauth_registered.send(**signal_kwargs)))
+
+ if changed:
+ user.save()
View
18 social_auth/context_processors.py
@@ -26,6 +26,24 @@ def social_auth_by_type_backends(request):
return {'social_auth': data}
+def social_auth_by_name_backends(request):
+ """Load Social Auth current user data to context.
+ Will add a social_auth object whose attribute names are the names of each
+ provider, e.g. social_auth.facebook would be the facebook association or
+ None, depending on the logged in user's current associations. Providers
+ with a hyphen have the hyphen replaced with an underscore, e.g.
+ google-oauth2 becomes google_oauth2 when referenced in templates.
+ """
+ keys = BACKENDS.keys()
+ accounts = dict(zip(keys, [None] * len(keys)))
+
+ if isinstance(request.user, User) and request.user.is_authenticated():
+ for associated in request.user.social_auth.all():
+ accounts[associated.provider.replace('-', '_')] = associated
+
+ return {'social_auth': accounts}
+
+
def backends_data(user):
"""Return backends data for given user.
View
8 social_auth/views.py
@@ -70,12 +70,9 @@ def wrapper(request, backend, *args, **kwargs):
logger.error(unicode(e), exc_info=True,
extra=dict(request=request))
- # Why!?
- msg = str(e)
-
if 'django.contrib.messages' in settings.INSTALLED_APPS:
from django.contrib.messages.api import error
- error(request, msg, extra_tags=backend_name)
+ error(request, unicode(e), extra_tags=backend_name)
else:
logger.warn('Messages framework not in place, some '+
'errors have not been shown to the user.')
@@ -185,4 +182,5 @@ def auth_complete(request, backend, user=None, *args, **kwargs):
"""Complete auth process. Return authenticated user or None."""
if user and not user.is_authenticated():
user = None
- return backend.auth_complete(user=user, *args, **kwargs)
+ kwargs.update({'user': user, 'request': request})
+ return backend.auth_complete(*args, **kwargs)
Please sign in to comment.
Something went wrong with that request. Please try again.