]> git.parisson.com Git - django-social-auth.git/commitdiff
Merge remote-tracking branch 'upstream/master'; Fixing VKontakte bad exception declar...
authorStas Kravets <krvss@mail.ru>
Tue, 23 Aug 2011 09:46:19 +0000 (13:46 +0400)
committerStas Kravets <krvss@mail.ru>
Tue, 23 Aug 2011 09:46:19 +0000 (13:46 +0400)
Conflicts:
social_auth/backends/facebook.py

1  2 
example/local_settings.py.template
social_auth/backends/__init__.py
social_auth/backends/contrib/vkontakte.py
social_auth/backends/facebook.py

index fdae6edebeec1e8ea9da5b7bf03df4ec4cf35e8c,8b52a3b7bce763a3bf80d0523b20105b6a9fb086..f1049967722f170b371b3333547468b020da5232
@@@ -13,17 -13,7 +13,19 @@@ SOCIAL_AUTH_FORCE_RANDOM_USERNAME = Fal
  SOCIAL_AUTH_DEFAULT_USERNAME      = 'socialauth_user'
  SOCIAL_AUTH_COMPLETE_URL_NAME     = 'socialauth_complete'
  LOGIN_ERROR_URL                   = '/login/error/'
 +VKONTAKTE_APP_ID                  = ''
 +VKONTAKTE_APP_SECRET              = ''
 +# Usage for applications auth: {'key': application_key, 'user_mode': 0 (default) | 1 (check) | 2 (online check) }
 +# 0 means is_app_user request parameter is ignored, 1 - must be = 1, 2 - checked via VK API request (useful when user
 +# connects to your application on app page and you reload the iframe)
 +VKONTAKTE_APP_AUTH                = None
 +ODNOKLASSNIKI_OAUTH2_CLIENT_KEY   = ''
 +ODNOKLASSNIKI_OAUTH2_APP_KEY      = ''
 +ODNOKLASSNIKI_OAUTH2_CLIENT_SECRET = ''
 +MAILRU_OAUTH2_CLIENT_KEY                = ''
 +MAILRU_OAUTH2_APP_KEY                   = ''
 +MAILRU_OAUTH2_CLIENT_SECRET       = ''
  #SOCIAL_AUTH_USER_MODEL            = 'app.CustomUser'
  SOCIAL_AUTH_ERROR_KEY             = 'socialauth_error'
 -GITHUB_API_SECRET                 = ''
+ GITHUB_APP_ID                     = ''
++GITHUB_API_SECRET                 = ''
Simple merge
index 7c17a246da3b7f499fc36de9a528f183440400a7,0000000000000000000000000000000000000000..74c2f09f0ee50ed0c11d91a8aa1587b5db9465f6
mode 100644,000000..100644
--- /dev/null
@@@ -1,245 -1,0 +1,245 @@@
-                 raise('VKontakte authentication failed: invalid auth key')
 +"""
 +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 django.conf import settings
 +from django.contrib.auth import authenticate
 +from django.utils import simplejson
 +
 +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_LOCAL_HTML  = 'vkontakte.html'
 +
 +VKONTAKTE_API_URL        = 'https://api.vkontakte.ru/method/'
 +VKONTAKTE_SERVER_API_URL = 'http://api.vkontakte.ru/api.php'
 +VKONTAKTE_API_VERSION    = '3.0'
 +
 +VKONTAKTE_OAUTH2_SCOPE  = [''] # Enough for authentication
 +
 +EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
 +USE_APP_AUTH = getattr(settings, 'VKONTAKTE_APP_AUTH', False)
 +
 +class VKontakteBackend(SocialAuthBackend):
 +    """VKontakte authentication backend"""
 +    name = 'vkontakte'
 +
 +    def get_user_id(self, details, response):
 +        """Return user unique id provided by VKontakte"""
 +        return int(response.GET['id'])
 +    
 +    def get_user_details(self, response):
 +        """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
 +
 +
 +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': reverse(settings.SOCIAL_AUTH_COMPLETE_URL_NAME, args=[VKontakteBackend.name]) }
 +        
 +        vk_template = loader.get_template(VKONTAKTE_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 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):
 +        """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):
 +        return None
 +
 +
 +# Backend definition
 +BACKENDS = {
 +    'vkontakte': VKontakteAuth,
 +    'vkontakte-oauth2': VKontakteOAuth2
 +}
 +    
index 070f7c5505eaaeeb3af199504878fa85e55884a8,c01fda70182c860e16c0f9fdb89e37bd90c7ea88..0afd11c06db26bb38232e5e5b86aa68b5a0765e8
@@@ -12,11 -12,8 +12,12 @@@ By default account id and token expirat
  field, check OAuthBackend class for details on how to extend it.
  """
  import cgi
- import urllib
+ from urllib import urlencode
+ from urllib2 import urlopen
 +import base64
 +import hmac
 +import hashlib
 +import time
  
  from django.conf import settings
  from django.utils import simplejson
@@@ -58,51 -55,18 +59,50 @@@ class FacebookAuth(BaseOAuth)
                  'redirect_uri': self.redirect_uri}
          if hasattr(settings, 'FACEBOOK_EXTENDED_PERMISSIONS'):
              args['scope'] = ','.join(settings.FACEBOOK_EXTENDED_PERMISSIONS)
-         return FACEBOOK_AUTHORIZATION_URL + '?' + urllib.urlencode(args)
+         return FACEBOOK_AUTHORIZATION_URL + '?' + urlencode(args)
  
      def auth_complete(self, *args, **kwargs):
 +        access_token = None
 +        expires = None
 +        
          """Returns user, might be logged in"""
 +        
          if 'code' in self.data:
              url = FACEBOOK_ACCESS_TOKEN_URL + '?' + \
-                   urllib.urlencode({'client_id': settings.FACEBOOK_APP_ID,
+                   urlencode({'client_id': settings.FACEBOOK_APP_ID,
                                  'redirect_uri': self.redirect_uri,
                                  'client_secret': settings.FACEBOOK_API_SECRET,
                                  'code': self.data['code']})
-                   
-             response = cgi.parse_qs(urllib.urlopen(url).read())            
+             response = cgi.parse_qs(urlopen(url).read())
              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')
 +            
 +                if 'expires' in response:
 +                    expires = response['expires']
 +
 +        if 'session_key' in self.data:
 +            params=['secret', 'uid', 'session_key', 'access_token', 'expires', 'base_domain']
 +            params_dict = dict([(p, self.data[p]) for p in params])
 +
 +            sorted = params_dict.items()
 +            sorted.sort(key=lambda x:x[0])
 +            
 +            check_str = ''.join(["%s=%s"%(x[0], x[1]) for x in sorted]) + settings.FACEBOOK_API_SECRET
 +            expected_sig = hashlib.md5(check_str).hexdigest()
 +            sig = self.data['sig']
 +
 +            if sig == expected_sig:
 +                access_token = params_dict['access_token']
 +                expires = params_dict['expires']
 +
 +        if access_token:
              data = self.user_data(access_token)
              if data is not None:
                  if 'error' in data: