guneysus
3/15/2016 - 12:09 PM

Tornado Github Login Handler and Github OAuth Mixin. Since I cloned FacebookOAuth2Mixin, some of are unnecessary or unrelated to Github. Lik

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'})