From e16f211abbb4b140399e082f3f8f9dbf78205d5b Mon Sep 17 00:00:00 2001 From: Stas Kravets Date: Wed, 4 Jan 2012 20:03:26 +0400 Subject: [PATCH] Yandex OAuth2 support for Ya.ru and Moi Krug added --- example/local_settings.py.template | 3 + example/settings.py | 1 + social_auth/backends/contrib/yandex.py | 103 ++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/example/local_settings.py.template b/example/local_settings.py.template index 26aeaae..7e928f1 100644 --- a/example/local_settings.py.template +++ b/example/local_settings.py.template @@ -31,3 +31,6 @@ 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 \ No newline at end of file diff --git a/example/settings.py b/example/settings.py index 537d821..797e7df 100644 --- a/example/settings.py +++ b/example/settings.py @@ -80,6 +80,7 @@ AUTHENTICATION_BACKENDS = ( 'social_auth.backends.contrib.livejournal.LiveJournalBackend', 'social_auth.backends.contrib.vkontakte.VKontakteBackend', 'social_auth.backends.contrib.yandex.YandexBackend', + 'social_auth.backends.contrib.yandex.YandexOAuth2Backend', 'social_auth.backends.contrib.odnoklassniki.OdnoklassnikiBackend', 'social_auth.backends.contrib.vkontakte.VKontakteOAuth2Backend', 'social_auth.backends.contrib.mailru.MailruBackend', diff --git a/social_auth/backends/contrib/yandex.py b/social_auth/backends/contrib/yandex.py index fee08b8..8030e5b 100644 --- a/social_auth/backends/contrib/yandex.py +++ b/social_auth/backends/contrib/yandex.py @@ -11,7 +11,13 @@ logger = logging.getLogger(__name__) import urlparse -from social_auth.backends import OpenIDBackend, OpenIdAuth, USERNAME +from urllib import urlencode, unquote +from urllib2 import Request, urlopen, HTTPError + +from django.conf import settings +import xml.dom.minidom + +from social_auth.backends import OpenIDBackend, OpenIdAuth, USERNAME, OAuthBackend, BaseOAuth2 # Yandex conf @@ -19,6 +25,7 @@ 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') class YandexBackend(OpenIDBackend): """Yandex OpenID authentication backend""" @@ -36,6 +43,33 @@ class YandexBackend(OpenIDBackend): 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} + + return values + + class YandexAuth(OpenIdAuth): """Yandex OpenID authentication""" AUTH_BACKEND = YandexBackend @@ -46,8 +80,73 @@ class YandexAuth(OpenIdAuth): 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') + + return auth_result + + def user_data(self, access_token): + """Return user data from Yandex REST API specified in settings""" + params = urlencode({'oauth_token': access_token, 'text': 1, 'format': 'xml'}) + request = Request(settings.YANDEX_OAUTH2_API_URL + '?' + params) + + try: + dom = xml.dom.minidom.parseString(urlopen(request).read()) + + id = getNodeText(dom, "id") + if "/" in id: + id = id.split("/")[-1] + + name = getNodeText(dom, "name") + + links = getNodesWithAttribute(dom, "link", {"rel": "userpic"}) + 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 + + +def getNodeText(dom, nodeName): + nodelist = dom.getElementsByTagName(nodeName)[0].childNodes + + rc = [] + for node in nodelist: + if node.nodeType == node.TEXT_NODE: + rc.append(node.data) + return ''.join(rc) + +def getNodesWithAttribute(dom, nodeName, attrDict): + nodelist = dom.getElementsByTagName(nodeName) + found = [] + + for node in nodelist: + for key, value in attrDict.items(): + if node.hasAttribute(key) and node.getAttribute(key) == value: + found.append(node) + + return found + # Backend definition BACKENDS = { 'yandex': YandexAuth, + 'yandex-oauth2': YandexOAuth2 } -- 2.39.5