Tornado Github Login Handler and Github OAuth Mixin. Since I cloned FacebookOAuth2Mixin, some of are unnecessary or unrelated to Github. Like fields (scope)
#!/usr/bin/env python
# coding:utf-8
import functools
import json
import urlparse
import tornado.auth
import tornado.concurrent
import tornado.escape
import tornado.gen
import tornado.httpclient
import tornado.httputil
import tornado.web
class GithubOAuth2Mixin(tornado.auth.OAuth2Mixin):
"""Github authentication using the new Graph API and OAuth2."""
_OAUTH_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token?"
_OAUTH_AUTHORIZE_URL = "https://github.com/login/oauth/authorize?"
_OAUTH_NO_CALLBACKS = False
_API_URL = "https://api.github.com"
# noinspection PyProtectedMember,PyIncorrectDocstring
@tornado.auth._auth_return_future
def get_authenticated_user(self, redirect_uri, client_id, client_secret,
code, callback, extra_fields=None):
http = self.get_auth_http_client()
args = {
"redirect_uri": redirect_uri,
"code": code,
"client_id": client_id,
"client_secret": client_secret,
}
fields = {'id', 'name', 'first_name', 'last_name', 'locale', 'picture', 'link'}
if extra_fields:
fields.update(extra_fields)
http.fetch(self._oauth_request_token_url(**args),
functools.partial(self._on_access_token, redirect_uri, client_id,
client_secret, callback, fields))
# noinspection PyUnusedLocal
def _on_access_token(self, redirect_uri, client_id, client_secret,
future, fields, response):
if response.error:
future.set_exception(tornado.auth.AuthError('Github auth error: %s' % str(response)))
return
args = urlparse.parse_qs(tornado.escape.native_str(response.body))
session = {
"access_token": args["access_token"][-1],
"expires": args.get("expires")
}
self.github_request(
path="/me",
callback=functools.partial(
self._on_get_user_info, future, session, fields),
access_token=session["access_token"],
fields=",".join(fields)
)
# noinspection PyMethodMayBeStatic
def _on_get_user_info(self, future, session, fields, user):
if user is None:
future.set_result(None)
return
fieldmap = {}
for field in fields:
fieldmap[field] = user.get(field)
fieldmap.update({"access_token": session["access_token"], "session_expires": session.get("expires")})
future.set_result(fieldmap)
# noinspection PyProtectedMember,PyIncorrectDocstring
@tornado.auth._auth_return_future
def github_request(self, path, callback, access_token=None,
post_args=None, **args):
url = self._API_URL + path
oauth_future = self.oauth2_request(url, access_token=access_token,
post_args=post_args, **args)
tornado.concurrent.chain_future(oauth_future, callback)
# noinspection PyAbstractClass
class GithubLoginHandler(tornado.web.RequestHandler, GithubOAuth2Mixin):
def get_current_user(self):
if self.get_secure_cookie("user_github", None):
return json.loads(self.get_secure_cookie("user_github"))
@tornado.gen.coroutine
def get(self):
if self.get_argument('code', False):
user = yield self.get_authenticated_user(
redirect_uri='http://16b3166a.ngrok.io/auth/github',
client_id=self.settings["github_oauth"]["key"],
client_secret=self.settings["github_oauth"]["secret"],
code=self.get_argument("code"))
cookie = json.dumps(user, separators=(',', ':'))
self.set_secure_cookie('user_github', cookie)
self.redirect(self.get_query_argument("next", "/404"))
else:
yield self.authorize_redirect(
redirect_uri='http://16b3166a.ngrok.io/auth/github',
client_id=self.settings['github_oauth']['key'],
scope=['profile', 'email'],
response_type='code',
extra_params={'approval_prompt': 'auto'})