import logging
logger = logging.getLogger(__name__)
-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 urllib import urlencode
+from urllib2 import urlopen
from hashlib import md5
from time import time
from social_auth.backends import SocialAuthBackend, OAuthBackend, BaseAuth, BaseOAuth2, USERNAME
from social_auth.utils import setting
+# Vkontakte configuration
+VK_AUTHORIZATION_URL = 'http://oauth.vk.com/authorize'
+VK_ACCESS_TOKEN_URL = 'https://oauth.vk.com/access_token'
+VK_SERVER = 'vk.com'
+VK_DEFAULT_DATA = ['first_name','last_name','screen_name','nickname', 'photo']
+
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)
-LOCAL_HTML = getattr(settings, 'VKONTAKTE_LOCAL_HTML', 'vkontakte.html')
+USE_APP_AUTH = setting('VKONTAKTE_APP_AUTH', False)
+LOCAL_HTML = setting('VKONTAKTE_LOCAL_HTML', 'vkontakte.html')
class VKontakteBackend(SocialAuthBackend):
- """VKontakte authentication backend"""
+ """VKontakte OpenAPI authentication backend"""
name = 'vkontakte'
def get_user_id(self, details, response):
def get_user_details(self, response):
"""Return user details from VKontakte request"""
- nickname = unquote(response.GET['nickname'])
+ nickname = 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'])
-
+ 'first_name': response.GET['first_name'], 'last_name': response.GET['last_name']}
return values
class VKontakteAuth(BaseAuth):
"""VKontakte OpenAPI authorization mechanism"""
AUTH_BACKEND = VKontakteBackend
- APP_ID = settings.VKONTAKTE_APP_ID
+ APP_ID = setting('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,
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()
+ hash = md5(check_str + setting('VKONTAKTE_APP_SECRET')).hexdigest()
if hash != cookie_dict['sig'] or int(cookie_dict['expire']) < time() :
raise ValueError('VKontakte authentication failed: invalid hash')
return False
+class VKontakteOAuth2Backend(OAuthBackend):
+ """VKontakteOAuth2 authentication backend"""
+ name = 'vkontakte-oauth2'
+
+ 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']
+
+ def get_user_details(self, response):
+ """Return user details from Vkontakte account"""
+ return {USERNAME: response.get('screen_name'),
+ 'email': '',
+ 'first_name': response.get('first_name'),
+ 'last_name': response.get('last_name')}
+
+
class VKontakteOAuth2(BaseOAuth2):
- """VKontakte OAuth2 support"""
+ """Vkontakte OAuth mechanism"""
+ AUTHORIZATION_URL = VK_AUTHORIZATION_URL
+ ACCESS_TOKEN_URL = VK_ACCESS_TOKEN_URL
+ SERVER_URL = VK_SERVER
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'
+ SETTINGS_KEY_NAME = 'VK_APP_ID'
+ SETTINGS_SECRET_NAME = 'VK_API_SECRET'
+ # 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
+ SCOPE_VAR_NAME = 'VK_EXTRA_SCOPE'
def get_scope(self):
- return setting('VKONTAKTE_OAUTH2_EXTRA_SCOPE', [])
+ return setting(VKontakteOAuth2.SCOPE_VAR_NAME) or setting('VKONTAKTE_OAUTH2_EXTRA_SCOPE')
+
+ def user_data(self, access_token, response, *args, **kwargs):
+ """Loads user data from service"""
+ fields = ','.join(VK_DEFAULT_DATA + setting('VK_EXTRA_DATA',[]))
+ params = {'access_token': access_token,
+ 'fields': fields,
+ 'uids': response.get('user_id')}
+
+ data = vkontakte_api('users.get', params)
+
+ if data:
+ data = data.get('response')[0]
+ data['user_photo'] = data.get('photo') # Backward compatibility
+
+ return data
+
+
+class VKontakteAppAuth(VKontakteOAuth2):
+ """VKontakte Application Authentication support"""
def auth_complete(self, *args, **kwargs):
if USE_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, *args, **kwargs):
- """Return user data from VKontakte API"""
- data = {'access_token': access_token }
-
- return vkontakte_api('getUserInfoEx', data)
+ return super(VKontakteAppAuth, self).auth_complete(*args, **kwargs)
def user_profile(self, user_id, access_token = None):
data = {'uids': user_id, 'fields': 'photo'}
# 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()
+ check_key = md5('_'.join([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')
return (True, authenticate(**{'response': data, self.AUTH_BACKEND.name: True}))
+def _api_get_val_fun(name, conf):
+ if USE_APP_AUTH:
+ return USE_APP_AUTH.get(name)
+ else:
+ return setting(conf)
+
+
def vkontakte_api(method, data):
""" Calls VKontakte OpenAPI method
http://vkontakte.ru/apiclub,
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['api_id'] = _api_get_val_fun('id','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
+ secret = _api_get_val_fun('key','VKONTAKTE_APP_SECRET')
param_list = sorted(list(item + '=' + data[item] for item in data))
data['sig'] = md5(''.join(param_list) + secret).hexdigest()
url = VKONTAKTE_API_URL + method
params = urlencode(data)
- api_request = Request(url + '?' + params)
+ url += '?' + params
try:
- return simplejson.loads(urlopen(api_request).read())
+ return simplejson.load(urlopen(url))
except (TypeError, KeyError, IOError, ValueError, IndexError):
- logger.error('Could not load data from VKontakte.', exc_info=True, extra=dict(data=params))
+ logger.error('Could not load data from VKontakte.', exc_info=True, extra=dict(data=data))
return None
-
# Backend definition
BACKENDS = {
'vkontakte': VKontakteAuth,
"""
-Yandex OpenID support.
+Yandex OpenID and OAuth2 support.
This contribution adds support for Yandex.ru OpenID service in the form
openid.yandex.ru/user. Username is retrieved from the identity url.
If username is not specified, OpenID 2.0 url used for authentication.
"""
import logging
-logger = logging.getLogger(__name__)
+from django.utils import simplejson
-import urlparse
+logger = logging.getLogger(__name__)
from urllib import urlencode, unquote
-from urllib2 import Request, urlopen, HTTPError
+from urllib2 import urlopen
+from urlparse import urlparse, urlsplit
+
+from social_auth.backends import OpenIDBackend, OpenIdAuth, USERNAME,\
+ OAuthBackend, BaseOAuth2
-from django.conf import settings
-import xml.dom.minidom
+from social_auth.utils import setting
-from social_auth.backends import OpenIDBackend, OpenIdAuth, USERNAME, OAuthBackend, BaseOAuth2
+# Yandex configuration
+YANDEX_AUTHORIZATION_URL = 'https://oauth.yandex.ru/authorize'
+YANDEX_ACCESS_TOKEN_URL = 'https://oauth.yandex.ru/token'
+YANDEX_SERVER = 'oauth.yandex.ru'
+YANDEX_OPENID_URL = 'http://openid.yandex.ru'
-# Yandex conf
-YANDEX_URL = 'http://openid.yandex.ru/%s'
-YANDEX_USER_FIELD = 'openid_ya_user'
-YANDEX_OID_2_URL = 'http://yandex.ru'
-EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
+def get_username_from_url(links):
+ try:
+ host = urlparse(links.get('www')).hostname
+ return host.split('.')[0]
+ except (IndexError, AttributeError):
+ return None
+
class YandexBackend(OpenIDBackend):
"""Yandex OpenID authentication backend"""
name = 'yandex'
+ def get_user_id(self, details, response):
+ return details['email'] or response.identity_url
+
def get_user_details(self, response):
"""Generate username from identity url"""
values = super(YandexBackend, self).get_user_details(response)
values[USERNAME] = values.get(USERNAME) or\
- urlparse.urlsplit(response.identity_url)\
+ urlsplit(response.identity_url)\
.path.strip('/')
- values['email'] = values.get('email') or ''
-
- return values
-
-
-class YandexOAuth2Backend(OAuthBackend):
- """Yandex OAuth2 authentication backend"""
- name = 'yandex-oauth2'
-
- def get_user_id(self, details, response):
- """Return user unique id provided by Yandex"""
- return int(response['id'])
-
- def get_user_details(self, response):
- """Return user details from Yandex request"""
-
- name = unquote(response['name'])
- first_name = ''
- last_name = ''
-
- if ' ' in name:
- last_name, first_name = name.split(' ')
- name = first_name
- else:
- first_name = name
-
- values = { USERNAME: name, 'email': '',
- 'first_name': first_name, 'last_name': last_name}
+ values['email'] = values.get('email', '')
return values
def openid_url(self):
"""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'
+ return YANDEX_OPENID_URL
- 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 get_user_details(self, response):
+ name = response['name']
+ last_name = ''
- 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 })
+ if ' ' in name:
+ names = name.split(' ')
+ last_name = names[0]
+ first_name = names[1]
+ else:
+ first_name = name
+ """Return user details from Yandex account"""
+ return { USERNAME: get_username_from_url(response.get('links')),
+ 'email': response.get('email', ''),
+ 'first_name': first_name, 'last_name': last_name,
+ }
+
+
+class YaruAuth(BaseOAuth2):
+ """Yandex Ya.ru OAuth mechanism"""
+ AUTHORIZATION_URL = YANDEX_AUTHORIZATION_URL
+ ACCESS_TOKEN_URL = YANDEX_ACCESS_TOKEN_URL
+ AUTH_BACKEND = YaruBackend
+ SERVER_URL = YANDEX_SERVER
+ SETTINGS_KEY_NAME = 'YANDEX_APP_ID'
+ SETTINGS_SECRET_NAME = 'YANDEX_API_SECRET'
+
+ def get_api_url(self):
+ return 'https://api-yaru.yandex.ru/me/'
+
+ def user_data(self, access_token, response, *args, **kwargs):
+ """Loads user data from service"""
+ params = {'oauth_token': access_token,
+ 'format': 'json',
+ 'text': 1,
+ }
+
+ url = self.get_api_url() + '?' + urlencode(params)
try:
- 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):
+ return simplejson.load(urlopen(url))
+ except (ValueError, IndexError):
logger.error('Could not load data from Yandex.', exc_info=True, extra=dict(data=params))
return None
-def getNodeText(dom, nodeName):
- node = dom.getElementsByTagName(nodeName)
+class YandexOAuth2Backend(YaruBackend):
+ """Legacy Yandex OAuth2 authentication backend"""
+ name = 'yandex-oauth2'
+
+
+class YandexOAuth2(YaruAuth):
+ """Yandex Ya.ru/Moi Krug OAuth mechanism"""
+ AUTH_BACKEND = YandexOAuth2Backend
- if node:
- nodelist = node[0].childNodes
- else:
- return ''
+ def get_api_url(self):
+ return setting('YANDEX_OAUTH2_API_URL')
- rc = []
- for node in nodelist:
- if node.nodeType == node.TEXT_NODE:
- rc.append(node.data)
+ def user_data(self, access_token, response, *args, **kwargs):
+ reply = super(YandexOAuth2, self).user_data(access_token, response, args, kwargs)
- return ''.join(rc)
+ if reply:
+ if isinstance(reply, list) and len(reply) >= 1:
+ reply = reply[0]
-def getNodesWithAttribute(dom, nodeName, attrDict):
- nodelist = dom.getElementsByTagName(nodeName)
- found = []
+ if 'links' in reply:
+ userpic = reply['links'].get('avatar')
+ elif 'avatar' in reply:
+ userpic = reply['avatar'].get('Portrait')
- for node in nodelist:
- for key, value in attrDict.items():
- if node.hasAttribute(key):
- if value and node.getAttribute(key) != value:
- continue
- found.append(node)
+ reply.update({"id":reply["id"].split("/")[-1], "access_token": access_token, "userpic": userpic or ''})
- return found
+ return reply
# Backend definition
BACKENDS = {
'yandex': YandexAuth,
+ 'yaru': YaruAuth,
'yandex-oauth2': YandexOAuth2
}