From: Stas Kravets Date: Tue, 23 Aug 2011 09:46:19 +0000 (+0400) Subject: Merge remote-tracking branch 'upstream/master'; Fixing VKontakte bad exception declar... X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=623626850aad94b0670b4f070b7e3b1df5be2122;p=django-social-auth.git Merge remote-tracking branch 'upstream/master'; Fixing VKontakte bad exception declaration Conflicts: social_auth/backends/facebook.py --- 623626850aad94b0670b4f070b7e3b1df5be2122 diff --cc example/local_settings.py.template index fdae6ed,8b52a3b..f104996 --- a/example/local_settings.py.template +++ b/example/local_settings.py.template @@@ -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_APP_ID = '' -GITHUB_API_SECRET = '' ++GITHUB_API_SECRET = '' diff --cc social_auth/backends/contrib/vkontakte.py index 7c17a24,0000000..74c2f09 mode 100644,000000..100644 --- a/social_auth/backends/contrib/vkontakte.py +++ b/social_auth/backends/contrib/vkontakte.py @@@ -1,245 -1,0 +1,245 @@@ +""" +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('VKontakte authentication failed: invalid 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 +} + diff --cc social_auth/backends/facebook.py index 070f7c5,c01fda7..0afd11c --- a/social_auth/backends/facebook.py +++ b/social_auth/backends/facebook.py @@@ -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: