]> git.parisson.com Git - django-social-auth.git/commitdiff
Merge remote-tracking branch 'upstream/master'
authorStas Kravets <krvss@mail.ru>
Wed, 2 May 2012 09:35:25 +0000 (13:35 +0400)
committerStas Kravets <krvss@mail.ru>
Wed, 2 May 2012 09:35:25 +0000 (13:35 +0400)
Conflicts:
README.rst
example/local_settings.py.template
example/templates/done.html
social_auth/backends/contrib/vkontakte.py
social_auth/backends/contrib/yandex.py
social_auth/backends/facebook.py
social_auth/views.py

13 files changed:
1  2 
README.rst
doc/configuration.rst
example/local_settings.py.template
example/settings.py
example/templates/done.html
social_auth/backends/__init__.py
social_auth/backends/contrib/mailru.py
social_auth/backends/contrib/odnoklassniki.py
social_auth/backends/contrib/vkontakte.py
social_auth/backends/contrib/yandex.py
social_auth/backends/facebook.py
social_auth/backends/google.py
social_auth/views.py

diff --cc README.rst
index 0ceeb32d8999e777a5b01e372da03cbd4dc0d9e1,188810d23ff762ec6c76e27e47da55af325cec7b..5ba902199d6cd32fb35e040f60f8cf60e82a2325
@@@ -967,11 -1092,27 +1095,26 @@@ Mailing lis
  Join to `django-social-auth discussion list`_ and bring any questions or suggestions
  that would improve this application. Convore_ discussion group is deprecated since
  the service is going to be shut down on April 1st.
- If defining a custom user model, do not import social_auth from any models.py
- that would finally import from the models.py that defines your User class or it
- will make your project fail with a recursive import because social_auth uses
- get_model() to retrieve your User.
--
+ South users
+ ^^^^^^^^^^^
+ South_ users should add this rule to enable migrations::
+     try:
+         import south
+         from south.modelsinspector import add_introspection_rules
+         add_introspection_rules([], ["^social_auth\.fields\.JSONField"])
+     except:
+         pass
+ Custom User model
+ ^^^^^^^^^^^^^^^^^
+ If defining a custom user model, do not import ``social_auth`` from any
+ ``models.py`` that would finally import from the ``models.py`` that defines
+ your ``User`` class or it will make your project fail with a recursive import
+ because ``social_auth`` uses ``get_model()`` to retrieve your User.
+ Third party backends
+ ^^^^^^^^^^^^^^^^^^^^
  There's an ongoing movement to create a list of third party backends on
  djangopackages.com_, so, if somebody doesn't want it's backend in the
  ``contrib`` directory but still wants to share, just split it in a separated
Simple merge
index bfbb541656643ed2e0768bffa2c7064a182c1f15,09585d52eb702e587d3587180810ecd531cc1251..bcb8157b3c481e8557decc2963a96468f13cbdf8
@@@ -31,9 -21,8 +33,11 @@@ GITHUB_APP_ID                     = '
  GITHUB_API_SECRET                 = ''
  FOURSQUARE_CONSUMER_KEY           = ''
  FOURSQUARE_CONSUMER_SECRET        = ''
 +YANDEX_OAUTH2_CLIENT_KEY          = ''
 +YANDEX_OAUTH2_CLIENT_SECRET       = ''
 +YANDEX_OAUTH2_API_URL             = 'https://api-yaru.yandex.ru/me/' # http://api.moikrug.ru/v1/my/ for Moi Krug
+ VK_APP_ID                         = ''
+ VK_API_SECRET                     = ''
  
  SOCIAL_AUTH_PIPELINE = (
      'social_auth.backends.pipeline.social.social_auth_user',
index a6a26ac3439210a1c08e679bdcd247925c70d5c5,a9d30b16e67050a0dbd0a75f056a2619809cc254..351c033b3f76b72bc52046d2c0bbacbcf2bcc7a3
@@@ -75,8 -75,13 +75,11 @@@ AUTHENTICATION_BACKENDS = 
      'social_auth.backends.google.GoogleBackend',
      'social_auth.backends.yahoo.YahooBackend',
      'social_auth.backends.contrib.linkedin.LinkedinBackend',
+     'social_auth.backends.contrib.skyrock.SkyrockBackend',
      'social_auth.backends.contrib.flickr.FlickrBackend',
      'social_auth.backends.contrib.instagram.InstagramBackend',
 -    'social_auth.backends.contrib.vkontakte.VkontakteBackend',
+     'social_auth.backends.contrib.github.GithubBackend',
 -    'social_auth.backends.contrib.yandex.YaruBackend',
+     'social_auth.backends.contrib.yandex.YandexBackend',
      'social_auth.backends.OpenIDBackend',
      'social_auth.backends.contrib.livejournal.LiveJournalBackend',
      'social_auth.backends.browserid.BrowserIDBackend',
Simple merge
index 0080361a9477ed75ecd0c7d10f0d0c3c2d496892,0f71154bffa2c3de7fb4707384b59ae4d10eeb7e..73c8fadf21ee3072888e509e9f83881b2e948a34
@@@ -17,8 -17,8 +17,8 @@@ from openid.consumer.consumer import Co
  from openid.consumer.discover import DiscoveryFailure
  from openid.extensions import sreg, ax
  
--from oauth2 import Consumer as OAuthConsumer, Token, Request as OAuthRequest, \
--                   SignatureMethod_HMAC_SHA1
++from oauth2 import Consumer as OAuthConsumer, Token, Request as OAuthRequest,\
++    SignatureMethod_HMAC_SHA1
  
  from django.db import models
  from django.contrib.auth import authenticate
@@@ -26,13 -26,13 +26,13 @@@ from django.contrib.auth.backends impor
  from django.utils import simplejson
  from django.utils.importlib import import_module
  
--from social_auth.utils import setting, log, model_to_ctype, ctype_to_model, \
--                              clean_partial_pipeline
++from social_auth.utils import setting, log, model_to_ctype, ctype_to_model,\
++    clean_partial_pipeline
  from social_auth.store import DjangoOpenIDStore
--from social_auth.backends.exceptions import StopPipeline, AuthException, \
--                                            AuthFailed, AuthCanceled, \
--                                            AuthUnknownError, AuthTokenError, \
--                                            AuthMissingParameter
++from social_auth.backends.exceptions import StopPipeline, AuthException,\
++    AuthFailed, AuthCanceled,\
++    AuthUnknownError, AuthTokenError,\
++    AuthMissingParameter
  
  
  if setting('SOCIAL_AUTH_USER_MODEL'):
@@@ -55,7 -55,7 +55,7 @@@ AX_SCHEMA_ATTRS = 
      ('http://axschema.org/namePerson/first', 'first_name'),
      ('http://axschema.org/namePerson/last', 'last_name'),
      ('http://axschema.org/namePerson/friendly', 'nickname'),
--]
++    ]
  SREG_ATTR = [
      ('email', 'email'),
      ('fullname', 'fullname'),
@@@ -69,14 -69,14 +69,14 @@@ SESSION_NAME = 'openid
  USERNAME = 'username'
  
  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',
--           ))
++    '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):
@@@ -258,7 -258,7 +258,7 @@@ class OpenIDBackend(SocialAuthBackend)
              resp = sreg.SRegResponse.fromSuccessResponse(response)
              if resp:
                  values.update((alias, resp.get(name) or '')
--                                    for name, alias in sreg_names)
++                    for name, alias in sreg_names)
  
          # Use Attribute Exchange attributes if provided
          if ax_names:
          # update values using SimpleRegistration or AttributeExchange
          # values
          values.update(self.values_from_response(response,
--                                                SREG_ATTR,
--                                                OLD_AX_ATTRS + \
--                                                AX_SCHEMA_ATTRS))
++            SREG_ATTR,
++            OLD_AX_ATTRS +\
++            AX_SCHEMA_ATTRS))
  
          fullname = values.get('fullname') or ''
          first_name = values.get('first_name') or ''
  
          values.update({'fullname': fullname, 'first_name': first_name,
                         'last_name': last_name,
--                       USERNAME: values.get(USERNAME) or \
--                                   (first_name.title() + last_name.title())})
++                       USERNAME: values.get(USERNAME) or\
++                                 (first_name.title() + last_name.title())})
          return values
  
      def extra_data(self, user, uid, response, details):
@@@ -351,7 -351,7 +351,7 @@@ class BaseAuth(object)
              'backend': self.AUTH_BACKEND.name,
              'args': tuple(map(model_to_ctype, args)),
              'kwargs': dict((key, model_to_ctype(val))
--                                for key, val in kwargs.iteritems())
++                for key, val in kwargs.iteritems())
          }
  
      def from_session_dict(self, entry, *args, **kwargs):
  
          kwargs = kwargs.copy()
          kwargs.update((key, ctype_to_model(val))
--                            for key, val in entry['kwargs'].iteritems())
++            for key, val in entry['kwargs'].iteritems())
          return (entry['next'], args, kwargs)
  
      def continue_pipeline(self, *args, **kwargs):
@@@ -425,10 -434,10 +434,10 @@@ class OpenIdAuth(BaseAuth)
      def auth_html(self):
          """Return auth HTML returned by service"""
          openid_request = self.setup_request(self.auth_extra_arguments())
-         return_to = self.request.build_absolute_uri(self.redirect)
+         return_to = self.build_absolute_uri(self.redirect)
          form_tag = {'id': 'openid_message'}
          return openid_request.htmlMarkup(self.trust_root(), return_to,
--                                         form_tag_attrs=form_tag)
++            form_tag_attrs=form_tag)
  
      def trust_root(self):
          """Return trust-root option"""
      def continue_pipeline(self, *args, **kwargs):
          """Continue previous halted pipeline"""
          response = self.consumer().complete(dict(self.data.items()),
-                                             self.request.build_absolute_uri())
 -                                            self.build_absolute_uri())
++            self.build_absolute_uri())
          kwargs.update({
              'auth': self,
              'response': response,
      def auth_complete(self, *args, **kwargs):
          """Complete auth process"""
          response = self.consumer().complete(dict(self.data.items()),
-                                             self.request.build_absolute_uri())
 -                                            self.build_absolute_uri())
++            self.build_absolute_uri())
          if not response:
              raise AuthException(self, 'OpenID relying party endpoint')
          elif response.status == SUCCESS:
              # Mark all attributes as required, Google ignores optional ones
              for attr, alias in (AX_SCHEMA_ATTRS + OLD_AX_ATTRS):
                  fetch_request.add(ax.AttrInfo(attr, alias=alias,
--                                              required=True))
++                    required=True))
          else:
              fetch_request = sreg.SRegRequest(optional=dict(SREG_ATTR).keys())
          openid_request.addExtension(fetch_request)
      def consumer(self):
          """Create an OpenID Consumer object for the given Django request."""
          return Consumer(self.request.session.setdefault(SESSION_NAME, {}),
--                        DjangoOpenIDStore())
++            DjangoOpenIDStore())
  
      @property
      def uses_redirect(self):
@@@ -521,7 -532,20 +532,20 @@@ class BaseOAuth(BaseAuth)
      def __init__(self, request, redirect):
          """Init method"""
          super(BaseOAuth, self).__init__(request, redirect)
-         self.redirect_uri = self.request.build_absolute_uri(self.redirect)
+         self.redirect_uri = self.build_absolute_uri(self.redirect)
+     def get_key_and_secret(self):
+         """Return tuple with Consumer Key and Consumer Secret for current
+         service provider. Must return (key, secret), order *must* be respected.
+         """
 -        return setting(self.SETTINGS_KEY_NAME), \
++        return setting(self.SETTINGS_KEY_NAME),\
+                setting(self.SETTINGS_SECRET_NAME)
+     @classmethod
+     def enabled(cls):
+         """Return backend enabled status by checking basic settings"""
 -        return setting(cls.SETTINGS_KEY_NAME) and \
++        return setting(cls.SETTINGS_KEY_NAME) and\
+                setting(cls.SETTINGS_SECRET_NAME)
  
  
  class ConsumerBasedOAuth(BaseOAuth):
      def unauthorized_token(self):
          """Return request for unauthorized token (first stage)"""
          request = self.oauth_request(token=None, url=self.REQUEST_TOKEN_URL,
--                             extra_params=self.request_token_extra_arguments())
++            extra_params=self.request_token_extra_arguments())
          response = self.fetch_response(request)
          return Token.from_string(response)
  
      def oauth_authorization_request(self, token):
          """Generate OAuth request to authorize token."""
          return OAuthRequest.from_token_and_callback(token=token,
--                                        callback=self.redirect_uri,
--                                        http_url=self.AUTHORIZATION_URL,
--                                        parameters=self.auth_extra_arguments())
++            callback=self.redirect_uri,
++            http_url=self.AUTHORIZATION_URL,
++            parameters=self.auth_extra_arguments())
  
      def oauth_request(self, token, url, extra_params=None):
          """Generate OAuth request, setups callback url"""
          if 'oauth_verifier' in self.data:
              params['oauth_verifier'] = self.data['oauth_verifier']
          request = OAuthRequest.from_consumer_and_token(self.consumer,
--                                                       token=token,
--                                                       http_url=url,
--                                                       parameters=params)
++            token=token,
++            http_url=url,
++            parameters=params)
          request.sign_request(SignatureMethod_HMAC_SHA1(), self.consumer, token)
          return request
  
@@@ -680,9 -689,10 +689,10 @@@ class BaseOAuth2(BaseOAuth)
                    'client_id': client_id,
                    'client_secret': client_secret,
                    'redirect_uri': self.redirect_uri}
-         headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+         headers = {'Content-Type': 'application/x-www-form-urlencoded',
 -                    'Accept': 'application/json'}
++                   'Accept': 'application/json'}
          request = Request(self.ACCESS_TOKEN_URL, data=urlencode(params),
--                          headers=headers)
++            headers=headers)
  
          try:
              response = simplejson.loads(urlopen(request).read())
@@@ -785,4 -789,4 +789,4 @@@ def get_backend(name, *args, **kwargs)
  
  BACKENDS = {
      'openid': OpenIdAuth
--}
++}
index 3f8e5dd830906516e6217c2520f3e7c898a56047,0000000000000000000000000000000000000000..5e4ba15b4f0fee2120b0901ea247bacc9673f2e6
mode 100644,000000..100644
--- /dev/null
@@@ -1,100 -1,0 +1,100 @@@
-     def user_data(self, access_token):
 +"""
 +Mail.ru OAuth2 support
 +
 +Take a look to http://api.mail.ru/docs/guides/oauth/
 +
 +You need to register OAuth site here:
 +http://api.mail.ru/sites/my/add
 +
 +Then update your settings values using registration information
 +
 +"""
 +
 +import logging
 +logger = logging.getLogger(__name__)
 +
 +from django.conf import settings
 +from django.utils import simplejson
 +
 +from urllib import urlencode, unquote
 +from urllib2 import Request, urlopen, HTTPError
 +from hashlib import md5
 +
 +from social_auth.backends import OAuthBackend, BaseOAuth2, USERNAME
 +
 +MAILRU_API_URL       = 'http://www.appsmail.ru/platform/api'
 +MAILRU_OAUTH2_SCOPE  = ['']
 +
 +EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
 +
 +class MailruBackend(OAuthBackend):
 +    """Mail.ru authentication backend"""
 +    name = 'mailru-oauth2'
 +    EXTRA_DATA = [('refresh_token', 'refresh_token'),
 +                  ('expires_in', EXPIRES_NAME)]
 +
 +    def get_user_id(self, details, response):
 +        """Return user unique id provided by Mail.ru"""
 +        return int(response['uid'])
 +    
 +    def get_user_details(self, response):
 +        """Return user details from Mail.ru request"""
 +        values = { USERNAME: unquote(response['nick']), 'email': unquote(response['email']),
 +                  'first_name': unquote(response['first_name']), 'last_name': unquote(response['last_name'])}
 +        
 +        if values['first_name'] and values['last_name']:
 +            values['fullname'] = "%s %s" % (values['first_name'], values['last_name'])
 +        return values
 +    
 +
 +class MailruOAuth2(BaseOAuth2):
 +    """Mail.ru OAuth2 support"""
 +    AUTH_BACKEND = MailruBackend
 +    AUTHORIZATION_URL = 'https://connect.mail.ru/oauth/authorize'
 +    ACCESS_TOKEN_URL = 'https://connect.mail.ru/oauth/token'
 +    SETTINGS_KEY_NAME = 'MAILRU_OAUTH2_CLIENT_KEY'
 +    SETTINGS_SECRET_NAME = 'MAILRU_OAUTH2_CLIENT_SECRET'
 +
 +    def get_scope(self):
 +        return MAILRU_OAUTH2_SCOPE + getattr(settings, 'MAILRU_OAUTH2_EXTRA_SCOPE', [])
 +
 +    def auth_complete(self, *args, **kwargs):
 +        try:
 +            auth_result = super(MailruOAuth2, self).auth_complete(*args, **kwargs)
 +        except HTTPError: # Mail.ru returns HTTPError 400 if cancelled
 +            raise ValueError('Authentication cancelled')
 +
 +        return auth_result
 +
++    def user_data(self, access_token, *args, **kwargs):
 +        """Return user data from Mail.ru REST API"""
 +        data = {'method': 'users.getInfo', 'session_key': access_token}
 +        return mailru_api(data)[0]
 +
 +def mailru_sig(data):
 +    """ Calculates signature of request data """
 +    
 +    param_list = sorted(list(item + '=' + data[item] for item in data))
 +    
 +    return md5(''.join(param_list) + settings.MAILRU_OAUTH2_CLIENT_SECRET).hexdigest() 
 +    
 +def mailru_api(data):
 +    """ Calls Mail.ru REST API method
 +        http://api.mail.ru/docs/guides/restapi/
 +    """
 +    data.update({'app_id': settings.MAILRU_OAUTH2_CLIENT_KEY, 'secure': '1'})
 +    data['sig'] = mailru_sig(data)
 +    
 +    params = urlencode(data)
 +    request = Request(MAILRU_API_URL, params)
 +    try:
 +        return simplejson.loads(urlopen(request).read())
 +    except (TypeError, KeyError, IOError, ValueError, IndexError):
 +        logger.error('Could not load data from Mail.ru.', exc_info=True, extra=dict(data=params))
 +        return None
 +    
 +
 +# Backend definition
 +BACKENDS = {
 +    'mailru-oauth2': MailruOAuth2
 +}
index 09a35e69c20334148834269dc4adebd9009f5395,0000000000000000000000000000000000000000..fdd19673039f3382fb4f5c49d092a7a07d1a3dd7
mode 100644,000000..100644
--- /dev/null
@@@ -1,92 -1,0 +1,93 @@@
-     def user_data(self, access_token):
 +"""
 +Odnoklassniki.ru OAuth2 support
 +
 +Take a look to http://dev.odnoklassniki.ru/wiki/display/ok/The+OAuth+2.0+Protocol
 +
 +You need to register OAuth application here:
 +http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=13992188
 +
 +Then setup your application according manual and use information from registration
 +mail to set settings values
 +
 +"""
 +
 +import logging
 +logger = logging.getLogger(__name__)
 +
 +from django.conf import settings
 +from django.utils import simplejson
 +
 +from urllib import urlencode, unquote
 +from urllib2 import Request, urlopen
 +from hashlib import md5
 +
 +from social_auth.backends import OAuthBackend, BaseOAuth2, USERNAME
 +
 +ODNOKLASSNIKI_API_URL       = 'http://api.odnoklassniki.ru/fb.do'
 +ODNOKLASSNIKI_OAUTH2_SCOPE  = [''] # Enough for authentication
 +
 +EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
 +
 +class OdnoklassnikiBackend(OAuthBackend):
 +    """Odnoklassniki authentication backend"""
 +    name = 'odnoklassniki'
 +    EXTRA_DATA = [('refresh_token', 'refresh_token'),
 +                  ('expires_in', EXPIRES_NAME)]
 +
 +    def get_user_id(self, details, response):
 +        """Return user unique id provided by Odnoklassniki"""
 +        return int(response['uid'])
 +    
 +    def get_user_details(self, response):
 +        """Return user details from Odnoklassniki request"""
++        import pdb; pdb.set_trace()
 +        values = { USERNAME: response['uid'], 'email': '', 'fullname': unquote(response['name']),
 +                  'first_name': unquote(response['first_name']), 'last_name': unquote(response['last_name'])}
 +        return values
 +    
 +
 +class OdnoklassnikiOAuth2(BaseOAuth2):
 +    """Odnoklassniki OAuth2 support"""
 +    AUTH_BACKEND = OdnoklassnikiBackend
 +    AUTHORIZATION_URL = 'http://www.odnoklassniki.ru/oauth/authorize'
 +    ACCESS_TOKEN_URL = 'http://api.odnoklassniki.ru/oauth/token.do'
 +    SETTINGS_KEY_NAME = 'ODNOKLASSNIKI_OAUTH2_CLIENT_KEY'
 +    SETTINGS_SECRET_NAME = 'ODNOKLASSNIKI_OAUTH2_CLIENT_SECRET'
 +
 +    def get_scope(self):
 +        return ODNOKLASSNIKI_OAUTH2_SCOPE + getattr(settings, 'ODNOKLASSNIKI_OAUTH2_EXTRA_SCOPE', [])
 +
++    def user_data(self, access_token, *args, **kwargs):
 +        """Return user data from Odnoklassniki REST API"""
 +        data = {'access_token': access_token, 'method': 'users.getCurrentUser'}
 +        return odnoklassniki_api(data)
 +
 +def odnoklassniki_sig(data):
 +    """ Calculates signature of request data
 +        access_token value must be included """
 +        
 +    suffix = md5(data['access_token'] + settings.ODNOKLASSNIKI_OAUTH2_CLIENT_SECRET).hexdigest()
 +    
 +    check_list = sorted(list(item + '=' + data[item] for item in data if item != 'access_token'))
 +    
 +    return md5(''.join(check_list) + suffix).hexdigest() 
 +    
 +def odnoklassniki_api(data):
 +    """ Calls Odnoklassniki REST API method
 +        http://dev.odnoklassniki.ru/wiki/display/ok/Odnoklassniki+Rest+API
 +    """
 +    data.update({'application_key': settings.ODNOKLASSNIKI_OAUTH2_APP_KEY, 'format': 'JSON'})
 +    data['sig'] = odnoklassniki_sig(data)
 +
 +    params = urlencode(data)
 +    request = Request(ODNOKLASSNIKI_API_URL + '?' + params)
 +    try:
 +        return simplejson.loads(urlopen(request).read())
 +    except (TypeError, KeyError, IOError, ValueError, IndexError):
 +        logger.error('Could not load data from Odnoklassniki.', exc_info=True, extra=dict(data=params))
 +        return None
 +
 +# Backend definition
 +BACKENDS = {
 +    'odnoklassniki': OdnoklassnikiOAuth2
 +}
index 670bd7f35cb23c09b56d1f5381b913563674f291,0f91faba6bd6455ea31da25a5d77ac84e28177e3..c48c580f37b3e75971eebb27d3972ed0844dc40a
  """
 -Vkontakte OAuth support.
 +VKontakte OpenAPI and OAuth 2.0 support.
  
 +This contribution adds support for VKontakte OpenAPI and OAuth 2.0 service in the form
 +www.vkontakte.ru. Username is retrieved from the identity returned by server.
  """
 -from urllib import urlencode, urlopen
  
 +import logging
 +logger = logging.getLogger(__name__)
 +
 +from django.conf import settings
 +from django.contrib.auth import authenticate
  from django.utils import simplejson
  
 -from social_auth.utils import setting
 -from social_auth.backends import BaseOAuth2, OAuthBackend, USERNAME
 +from urllib import urlencode, unquote
 +from urllib2 import Request, urlopen, HTTPError
 +from hashlib import md5
 +from time import time
 +
 +from social_auth.backends import SocialAuthBackend, OAuthBackend, BaseAuth, BaseOAuth2, USERNAME
  
 +VKONTAKTE_API_URL        = 'https://api.vkontakte.ru/method/'
 +VKONTAKTE_SERVER_API_URL = 'http://api.vkontakte.ru/api.php'
 +VKONTAKTE_API_VERSION    = '3.0'
  
 -# Vkontakte configuration
 -VK_AUTHORIZATION_URL = 'http://oauth.vk.com/authorize'
 -VK_ACCESS_TOKEN_URL = 'https://oauth.vk.com/access_token'
 -VK_USER_DATA_URL = 'https://api.vk.com/method/users.get'
 -VK_SERVER = 'vk.com'
 -VK_DEFAULT_DATA = 'first_name,last_name,screen_name,nickname'
 +VKONTAKTE_OAUTH2_SCOPE  = [''] # Enough for authentication
  
 +EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
 +USE_APP_AUTH = getattr(settings, 'VKONTAKTE_APP_AUTH', False)
 +LOCAL_HTML = getattr(settings, 'VKONTAKTE_LOCAL_HTML', 'vkontakte.html')
  
 -class VkontakteBackend(OAuthBackend):
 -    """Vkontakte OAuth authentication backend"""
 +class VKontakteBackend(SocialAuthBackend):
 +    """VKontakte authentication backend"""
      name = 'vkontakte'
 -    EXTRA_DATA = [
 -        ('id', 'id'),
 -        ('expires', setting('SOCIAL_AUTH_EXPIRATION', 'expires'))
 -    ]
  
      def get_user_id(self, details, response):
 -        "OAuth providers return an unique user id in response"""
 -        return response['user_id']
 +        """Return user unique id provided by VKontakte"""
 +        return int(response.GET['id'])
  
      def get_user_details(self, response):
 -        """Return user details from Vkontakte account"""
 -        print response
 -        return {USERNAME: response.get('screen_name'),
 -                'email':  '',
 -                'first_name': response.get('first_name'),
 -                'last_name': response.get('last_name')}
 -
 -
 -class VkontakteAuth(BaseOAuth2):
 -    """Vkontakte OAuth mechanism"""
 -    AUTHORIZATION_URL = VK_AUTHORIZATION_URL
 -    ACCESS_TOKEN_URL = VK_ACCESS_TOKEN_URL
 -    SERVER_URL = VK_SERVER
 -    AUTH_BACKEND = VkontakteBackend
 -    SETTINGS_KEY_NAME = 'VK_APP_ID'
 -    SETTINGS_SECRET_NAME = 'VK_API_SECRET'
 -
 -    def user_data(self, access_token, response, *args, **kwargs):
 -        """Loads user data from service"""
 -        fields = VK_DEFAULT_DATA
 -        if setting('VK_EXTRA_DATA'):
 -            fields += ',' + setting('VK_EXTRA_DATA')
 -
 -        params = {'access_token': access_token,
 -                  'fields': fields,
 -                  'uids': response.get('user_id')}
 -
 -        url = VK_USER_DATA_URL + '?' + urlencode(params)
 +        """Return user details from VKontakte request"""
 +        nickname = unquote(response.GET['nickname'])
 +        values = { USERNAME: response.GET['id'] if len(nickname) == 0 else nickname, 'email': '', 'fullname': '',
 +                  'first_name': unquote(response.GET['first_name']), 'last_name': unquote(response.GET['last_name'])}
 +        return values
  
 -        try:
 -            return simplejson.load(urlopen(url)).get('response')[0]
 -        except (ValueError, IndexError):
 -            return None
 +
 +class VKontakteOAuth2Backend(OAuthBackend):
 +    """VKontakteOAuth2 authentication backend"""
 +    name = 'vkontakte-oauth2'
 +    EXTRA_DATA = [('expires_in', EXPIRES_NAME)]
 +
 +    def get_user_id(self, details, response):
 +        """Return user unique id provided by VKontakte"""
 +        return int(response['user_id'])
 +
 +    def get_user_details(self, response):
 +        """Return user details from VKontakte request"""
 +        values = { USERNAME: str(response['user_id']), 'email': ''}
 +
 +        details = response['response']
 +        user_name = details.get('user_name')
 +
 +        if user_name:
 +            values['fullname'] = unquote(user_name)
 +
 +            if ' ' in values['fullname']:
 +                values['first_name'], values['last_name'] = values['fullname'].split()
 +            else:
 +                values['first_name'] = values['fullname']
 +
 +        if 'last_name' in details:
 +            values['last_name'] = unquote(details['last_name'])
 +
 +        if 'first_name' in details:
 +            values['first_name'] = unquote(details['first_name'])
 +
 +        return values
 +
 +
 +class VKontakteAuth(BaseAuth):
 +    """VKontakte OpenAPI authorization mechanism"""
 +    AUTH_BACKEND = VKontakteBackend
 +    APP_ID = settings.VKONTAKTE_APP_ID
 +
 +    def auth_html(self):
 +        """Returns local VK authentication page, not necessary for VK to authenticate """
 +        from django.core.urlresolvers import reverse
 +        from django.template import RequestContext, loader
 +
 +        dict = { 'VK_APP_ID'      : self.APP_ID,
 +                 'VK_COMPLETE_URL': self.redirect }
 +
 +        vk_template = loader.get_template(LOCAL_HTML)
 +        context = RequestContext(self.request, dict)
 +
 +        return vk_template.render(context)
 +
 +    def auth_complete(self, *args, **kwargs):
 +        """Performs check of authentication in VKontakte, returns User if succeeded"""
 +        app_cookie = 'vk_app_' + self.APP_ID
 +
 +        if not 'id' in self.request.GET or not app_cookie in self.request.COOKIES:
 +            raise ValueError('VKontakte authentication is not completed')
 +
 +        cookie_dict = dict(item.split('=') for item in self.request.COOKIES[app_cookie].split('&'))
 +        check_str = ''.join([item + '=' + cookie_dict[item] for item in ['expire', 'mid', 'secret', 'sid']])
 +
 +        hash = md5(check_str + settings.VKONTAKTE_APP_SECRET).hexdigest()
 +
 +        if hash != cookie_dict['sig'] or int(cookie_dict['expire']) < time() :
 +            raise ValueError('VKontakte authentication failed: invalid hash')
 +        else:
 +            kwargs.update({'response': self.request, self.AUTH_BACKEND.name: True})
 +            return authenticate(*args, **kwargs)
 +
 +    @property
 +    def uses_redirect(self):
 +        """VKontakte does not require visiting server url in order
 +        to do authentication, so auth_xxx methods are not needed to be called.
 +        Their current implementation is just an example"""
 +        return False
 +
 +
 +class VKontakteOAuth2(BaseOAuth2):
 +    """VKontakte OAuth2 support"""
 +    AUTH_BACKEND = VKontakteOAuth2Backend
 +    AUTHORIZATION_URL = 'http://api.vkontakte.ru/oauth/authorize'
 +    ACCESS_TOKEN_URL = ' https://api.vkontakte.ru/oauth/access_token'
 +    SETTINGS_KEY_NAME = 'VKONTAKTE_APP_ID'
 +    SETTINGS_SECRET_NAME = 'VKONTAKTE_APP_SECRET'
  
      def get_scope(self):
 -        """Return list with needed access scope"""
 -        # Look at http://vk.com/developers.php?oid=-1&p=%D0%9F%D1%80%D0%B0%D0%B2%D0%B0_%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0_%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9
 -        return setting('VK_EXTRA_SCOPE', [])
 +        return VKONTAKTE_OAUTH2_SCOPE + getattr(settings, 'VKONTAKTE_OAUTH2_EXTRA_SCOPE', [])
 +
 +    def auth_complete(self, *args, **kwargs):
 +        if USE_APP_AUTH:
 +            stop, app_auth = self.application_auth()
 +
 +            if app_auth:
 +                return app_auth
 +
 +            if stop:
 +                return None
 +
 +        try:
 +            auth_result = super(VKontakteOAuth2, self).auth_complete(*args, **kwargs)
 +        except HTTPError: # VKontakte returns HTTPError 400 if cancelled
 +            raise ValueError('Authentication cancelled')
 +
 +        return auth_result
 +
-     def user_data(self, access_token):
++    def user_data(self, access_token, *args, **kwargs):
 +        """Return user data from VKontakte API"""
 +        data = {'access_token': access_token }
 +
 +        return vkontakte_api('getUserInfoEx', data)
 +
 +    def user_profile(self, user_id, access_token = None):
 +        data = {'uids': user_id, 'fields': 'photo'}
 +
 +        if access_token:
 +            data['access_token'] = access_token
 +
 +        profiles = vkontakte_api('getProfiles', data).get('response', None)
 +
 +        return profiles[0] if profiles else None
 +
 +    def is_app_user(self, user_id, access_token = None):
 +        """Returns app usage flag from VKontakte API"""
 +
 +        data = {'uid': user_id}
 +
 +        if access_token:
 +            data['access_token'] = access_token
 +
 +        return vkontakte_api('isAppUser', data).get('response', 0)
 +
 +    def application_auth(self):
 +        required_params = ('is_app_user', 'viewer_id', 'access_token', 'api_id', )
 +
 +        for param in required_params:
 +            if not param in self.request.REQUEST:
 +                return (False, None,)
 +
 +        auth_key = self.request.REQUEST.get('auth_key')
 +
 +        # Verify signature, if present
 +        if auth_key:
 +            check_key = md5(self.request.REQUEST.get('api_id') + '_' + self.request.REQUEST.get('viewer_id') + '_' + \
 +                            USE_APP_AUTH['key']).hexdigest()
 +            if check_key != auth_key:
 +                raise ValueError('VKontakte authentication failed: invalid auth key')
 +
 +        user_check = USE_APP_AUTH.get('user_mode', 0)
 +        user_id = self.request.REQUEST.get('viewer_id')
 +
 +        if user_check:
 +            is_user = self.request.REQUEST.get('is_app_user') if user_check == 1 else self.is_app_user(user_id)
 +
 +            if not int(is_user):
 +                return (True, None,)
 +
 +        data = {'response': self.user_profile(user_id), 'user_id': user_id}
 +
 +        return (True, authenticate(**{'response': data, self.AUTH_BACKEND.name: True}))
 +
 +
 +def vkontakte_api(method, data):
 +    """ Calls VKontakte OpenAPI method
 +        http://vkontakte.ru/apiclub,
 +        http://vkontakte.ru/pages.php?o=-1&p=%C2%FB%EF%EE%EB%ED%E5%ED%E8%E5%20%E7%E0%EF%F0%EE%F1%EE%E2%20%EA%20API
 +    """
 +
 +    # We need to perform server-side call if no access_token
 +    if not 'access_token' in data:
 +        if not 'v' in data:
 +            data['v'] = VKONTAKTE_API_VERSION
 +
 +        if not 'api_id' in data:
 +            data['api_id'] = USE_APP_AUTH.get('id') if USE_APP_AUTH else settings.VKONTAKTE_APP_ID
 +
 +        data['method'] = method
 +        data['format'] = 'json'
 +
 +        url = VKONTAKTE_SERVER_API_URL
 +        secret = USE_APP_AUTH.get('key') if USE_APP_AUTH else settings.VKONTAKTE_APP_SECRET
 +
 +        param_list = sorted(list(item + '=' + data[item] for item in data))
 +        data['sig'] = md5(''.join(param_list) + secret).hexdigest()
 +    else:
 +        url = VKONTAKTE_API_URL + method
 +
 +    params = urlencode(data)
 +    api_request = Request(url + '?' + params)
 +    try:
 +        return simplejson.loads(urlopen(api_request).read())
 +    except (TypeError, KeyError, IOError, ValueError, IndexError):
 +        logger.error('Could not load data from VKontakte.', exc_info=True, extra=dict(data=params))
 +        return None
  
  
  # Backend definition
index f5bcb667c994dd1fc671361312b554df7d542fed,24414c63a14fe4628c16c5ae5f53a1f8d5213ff5..6cffc9d588c638f9aa8186a3564418ced7612b39
@@@ -75,57 -42,46 +75,57 @@@ class YandexAuth(OpenIdAuth)
      AUTH_BACKEND = YandexBackend
  
      def openid_url(self):
 -        """Return Google OpenID service url"""
 -        return YANDEX_OPENID_URL
 +        """Returns Yandex authentication URL"""
 +        if YANDEX_USER_FIELD not in self.data:
 +            return YANDEX_OID_2_URL
 +        else:
 +            return YANDEX_URL % self.data[YANDEX_USER_FIELD]
 +
 +
 +class YandexOAuth2(BaseOAuth2):
 +    """Yandex OAuth2 support
 +       See http://api.yandex.ru/oauth/doc/dg/concepts/About.xml for details"""
 +    AUTH_BACKEND = YandexOAuth2Backend
 +    AUTHORIZATION_URL = 'https://oauth.yandex.ru/authorize'
 +    ACCESS_TOKEN_URL = 'https://oauth.yandex.ru/token'
 +    SETTINGS_KEY_NAME = 'YANDEX_OAUTH2_CLIENT_KEY'
 +    SETTINGS_SECRET_NAME = 'YANDEX_OAUTH2_CLIENT_SECRET'
 +
 +    def get_scope(self):
 +        return [] # Yandex does not allow custom scope
 +
 +    def auth_complete(self, *args, **kwargs):
 +        try:
 +            auth_result = super(YandexOAuth2, self).auth_complete(*args, **kwargs)
 +        except HTTPError: # Returns HTTPError 400 if cancelled
 +            raise ValueError('Authentication cancelled')
  
 -class YaruBackend(OAuthBackend):
 -    """Yandex OAuth authentication backend"""
 -    name = 'yaru'
 -    EXTRA_DATA = [
 -        ('id', 'id'),
 -        ('expires', setting('SOCIAL_AUTH_EXPIRATION', 'expires'))
 -    ]
 +        return auth_result
 +
-     def user_data(self, access_token):
++    def user_data(self, access_token, *args, **kwargs):
 +        """Return user data from Yandex REST API specified in settings"""
 +        params = urlencode({'text': 1, 'format': 'xml'})
 +        request = Request(settings.YANDEX_OAUTH2_API_URL + '?' + params, headers={'Authorization': "OAuth " + access_token })
  
 -    def get_user_details(self, response):
 -        """Return user details from Vkontakte account"""
 -        return { USERNAME: get_username_from_url(response.get('links')),
 -                 'email':  response.get('email'),
 -                 'first_name': response.get('name'),
 -               } 
 -
 -
 -class YaruAuth(BaseOAuth2):
 -    """Yandex OAuth mechanism"""
 -    AUTHORIZATION_URL = YANDEX_AUTHORIZATION_URL
 -    ACCESS_TOKEN_URL = YANDEX_ACCESS_TOKEN_URL
 -    SERVER_URL = YANDEX_SERVER
 -    AUTH_BACKEND = YaruBackend 
 -    SETTINGS_KEY_NAME = 'YANDEX_APP_ID'
 -    SETTINGS_SECRET_NAME = 'YANDEX_API_SECRET'
 -
 -    def user_data(self, access_token, response, *args, **kwargs):
 -        """Loads user data from service"""
 -        params = {'oauth_token': access_token,
 -                  'format': 'json',
 -                 }
 -        headers = {'Content-Type': 'application/x-yaru+json; type=person',
 -                    'Accept': 'application/x-yaru+json'}
 -
 -        url = YANDEX_USER_ID_URL + '?' + urlencode(params)
          try:
 -            return simplejson.load(urlopen(url))
 -        except (ValueError, IndexError):
 +            reply = urlopen(request).read()
 +            dom = xml.dom.minidom.parseString(reply)
 +
 +            id = getNodeText(dom, "id")
 +            if "/" in id:
 +                id = id.split("/")[-1]
 +
 +            name = getNodeText(dom, "name")
 +
 +            links = getNodesWithAttribute(dom, "link", {"rel": "userpic"})
 +            if not links:
 +                userpic = getNodeText(dom, "Portrait")
 +            else:
 +                userpic = links[0].getAttribute("href") if links else ""
 +
 +            return {"id": id, "name": name, "userpic": userpic, "access_token": access_token}
 +        except (TypeError, KeyError, IOError, ValueError, IndexError):
 +            logger.error('Could not load data from Yandex.', exc_info=True, extra=dict(data=params))
              return None
  
  
index b7abee67c460cd78badd084afe7f9d5fdccd490e,b1fbbbcdb2843350b922b9931cb2b8721528a9de..06d69d93d80c8326b08baa16f0950bd702809deb
@@@ -88,51 -85,7 +89,59 @@@ class FacebookAuth(BaseOAuth2)
  
      def auth_complete(self, *args, **kwargs):
          """Completes loging process, must return user instance"""
 -        if 'code' not in self.data:
 +        access_token = None
 +        expires = None
 +
 +        if 'code' in self.data:
 +            url = ACCESS_TOKEN + urlencode({
 +                'client_id': setting('FACEBOOK_APP_ID'),
 +                'redirect_uri': self.redirect_uri,
 +                'client_secret': setting('FACEBOOK_API_SECRET'),
 +                'code': self.data['code']
 +            })
 +            try:
 +                response = cgi.parse_qs(urlopen(url).read())
 +            except HTTPError:
 +                raise AuthFailed(self, 'There was an error authenticating the app')
 +
 +            access_token = response['access_token'][0]
 +            if 'expires' in response:
 +                    expires = response['expires'][0]
 +
 +        if 'signed_request' in self.data:
 +            response = load_signed_request(self.data.get('signed_request'))
 +
 +            if response is not None:
 +                access_token = response.get('access_token') or response.get('oauth_token') \
 +                               or self.data.get('access_token')
 +
 +                if 'expires' in response:
 +                    expires = response['expires']
 +
 +        if access_token:
 +            data = self.user_data(access_token)
 +
-             if data is not None:
-                 data['access_token'] = access_token
-                 # expires will not be part of response if offline access
-                 # premission was requested
-                 if expires:
-                     data['expires'] = response['expires'][0]
++            if not isinstance(data, dict):
++                # From time to time Facebook responds back a JSON with just False
++                # as value, the reason is still unknown, but since the data is
++                # needed (it contains the user ID used to identify the account on
++                # further logins), this app cannot allow it to continue with the
++                # auth process.
++                raise AuthUnknownError(self, 'An error ocurred while retrieving '\
++                                         'users Facebook data')
++
++            data['access_token'] = access_token
++            # expires will not be part of response if offline access
++            # premission was requested
++            if expires:
++                data['expires'] = response['expires'][0]
 +
 +            kwargs.update({'auth': self,
 +                           'response': data,
 +                           self.AUTH_BACKEND.name: True})
 +
 +            return authenticate(*args, **kwargs)
 +        else:
              if self.data.get('error') == 'access_denied':
                  raise AuthCanceled(self)
              else:
Simple merge
index 48958884f7b71d7157525984d81996863af535bb,13bd3308004ad3dd894f997eeeb954ecba5e033e..f9ff07f8eb2197752ece30f30371044bbfb31883
@@@ -13,75 -9,16 +9,16 @@@ from django.http import HttpResponseRed
  from django.contrib.auth import login, REDIRECT_FIELD_NAME
  from django.contrib.auth.decorators import login_required
  from django.contrib import messages
- from django.utils.importlib import import_module
  from django.views.decorators.csrf import csrf_exempt
  
- from social_auth.backends import get_backend
- from social_auth.utils import sanitize_redirect, setting, log, \
 -from social_auth.utils import sanitize_redirect, setting, \
--                              backend_setting, clean_partial_pipeline
++from social_auth.utils import sanitize_redirect, setting,\
++    backend_setting, clean_partial_pipeline
+ from social_auth.decorators import dsa_view
  
- from social_auth.backends.exceptions import AuthFailed, AuthException
  
--DEFAULT_REDIRECT = setting('SOCIAL_AUTH_LOGIN_REDIRECT_URL') or \
++DEFAULT_REDIRECT = setting('SOCIAL_AUTH_LOGIN_REDIRECT_URL') or\
                     setting('LOGIN_REDIRECT_URL')
  LOGIN_ERROR_URL = setting('LOGIN_ERROR_URL', setting('LOGIN_URL'))
- RAISE_EXCEPTIONS = setting('SOCIAL_AUTH_RAISE_EXCEPTIONS', setting('DEBUG'))
- PROCESS_EXCEPTIONS = setting('SOCIAL_AUTH_PROCESS_EXCEPTIONS',
-                              'social_auth.utils.log_exceptions_to_messages')
- def dsa_view(redirect_name=None):
-     """Decorate djangos-social-auth views. Will check and retrieve backend
-     or return HttpResponseServerError if backend is not found.
-         redirect_name parameter is used to build redirect URL used by backend.
-     """
-     def dec(func):
-         @wraps(func)
-         def wrapper(request, backend, *args, **kwargs):
-             if redirect_name:
-                 redirect = reverse(redirect_name, args=(backend,))
-             else:
-                 redirect = request.path
-             backend = get_backend(backend, request, redirect)
-             if not backend:
-                 return HttpResponseServerError('Incorrect authentication ' + \
-                                                'service')
-             try:
-                 return func(request, backend, *args, **kwargs)
-             except AuthException, e:
-                 backend_name = backend.AUTH_BACKEND.name
-                 if 'django.contrib.messages' in setting('INSTALLED_APPS'):
-                     from django.contrib.messages.api import error
-                     error(request, unicode(e), extra_tags=backend_name)
-                 else:
-                     log('warn', 'Messages framework not in place, some '+
-                                 'errors have not been shown to the user.')
-                 url = setting('SOCIAL_AUTH_BACKEND_ERROR_URL', LOGIN_ERROR_URL)
-                 return HttpResponseRedirect(url)
-             except Exception, e:  # some error ocurred
-                 if RAISE_EXCEPTIONS:
-                     raise
-                 log('error', unicode(e), exc_info=True, extra={
-                     'request': request
-                 })
-                 mod, func_name = PROCESS_EXCEPTIONS.rsplit('.', 1)
-                 try:
-                     process = getattr(import_module(mod), func_name,
-                                       lambda *args: None)
-                 except ImportError:
-                     pass
-                 else:
-                     process(request, backend, e)
-                 url = backend_setting(backend, 'SOCIAL_AUTH_BACKEND_ERROR_URL',
-                                       LOGIN_ERROR_URL)
-                 return HttpResponseRedirect(url)
-         return wrapper
-     return dec
  
  
  @dsa_view(setting('SOCIAL_AUTH_COMPLETE_URL_NAME', 'socialauth_complete'))
@@@ -121,8 -51,8 +51,8 @@@ def associate_complete(request, backend
          return user
      else:
          url = backend_setting(backend,
--                              'SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL') or \
--              redirect_value or \
++            'SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL') or\
++              redirect_value or\
                DEFAULT_REDIRECT
      return HttpResponseRedirect(url)
  
  def disconnect(request, backend, association_id=None):
      """Disconnects given backend from current logged in user."""
      backend.disconnect(request.user, association_id)
--    url = request.REQUEST.get(REDIRECT_FIELD_NAME, '') or \
--          backend_setting(backend, 'SOCIAL_AUTH_DISCONNECT_REDIRECT_URL') or \
++    url = request.REQUEST.get(REDIRECT_FIELD_NAME, '') or\
++          backend_setting(backend, 'SOCIAL_AUTH_DISCONNECT_REDIRECT_URL') or\
            DEFAULT_REDIRECT
      return HttpResponseRedirect(url)
  
@@@ -156,7 -86,7 +86,7 @@@ def auth_process(request, backend)
          return HttpResponseRedirect(backend.auth_url())
      else:
          return HttpResponse(backend.auth_html(),
--                            content_type='text/html;charset=UTF-8')
++            content_type='text/html;charset=UTF-8')
  
  
  def complete_process(request, backend, *args, **kwargs):
              # in authenticate process
              social_user = user.social_user
              if redirect_value:
--                request.session[REDIRECT_FIELD_NAME] = redirect_value or \
++                request.session[REDIRECT_FIELD_NAME] = redirect_value or\
                                                         DEFAULT_REDIRECT
  
              if setting('SOCIAL_AUTH_SESSION_EXPIRATION', True):
  
              # store last login backend name in session
              key = setting('SOCIAL_AUTH_LAST_LOGIN',
--                          'social_auth_last_login_backend')
++                'social_auth_last_login_backend')
              request.session[key] = social_user.provider
  
              # Remove possible redirect URL from session, if this is a new
              # account, send him to the new-users-page if defined.
              new_user_redirect = backend_setting(backend,
--                                           'SOCIAL_AUTH_NEW_USER_REDIRECT_URL')
++                'SOCIAL_AUTH_NEW_USER_REDIRECT_URL')
              if new_user_redirect and getattr(user, 'is_new', False):
                  url = new_user_redirect
              else:
--                url = redirect_value or \
++                url = redirect_value or\
                        backend_setting(backend,
--                                      'SOCIAL_AUTH_LOGIN_REDIRECT_URL') or \
++                          'SOCIAL_AUTH_LOGIN_REDIRECT_URL') or\
                        DEFAULT_REDIRECT
          else:
              url = backend_setting(backend, 'SOCIAL_AUTH_INACTIVE_USER_URL',
--                                  LOGIN_ERROR_URL)
++                LOGIN_ERROR_URL)
      else:
          msg = setting('LOGIN_ERROR_MESSAGE', None)
          if msg:
@@@ -224,8 -154,9 +154,9 @@@ def auth_complete(request, backend, use
      if request.session.get(name):
          data = request.session.pop(name)
          idx, args, kwargs = backend.from_session_dict(data, user=user,
--                                                      request=request,
--                                                      *args, **kwargs)
++            request=request,
++            *args, **kwargs)
          return backend.continue_pipeline(pipeline_index=idx, *args, **kwargs)
      else:
-         return backend.auth_complete(user=user, request=request, *args, **kwargs)
+         return backend.auth_complete(user=user, request=request, *args,
 -                                     **kwargs)
++            **kwargs)