diff --git a/jupyterhub/apihandlers/auth.py b/jupyterhub/apihandlers/auth.py index aa395edd..b8b3b727 100644 --- a/jupyterhub/apihandlers/auth.py +++ b/jupyterhub/apihandlers/auth.py @@ -5,14 +5,20 @@ from datetime import datetime import json -from urllib.parse import quote +from urllib.parse import ( + parse_qsl, + quote, + urlencode, + urlparse, + urlunparse, +) -from oauth2.web.tornado import OAuth2Handler +from oauthlib import oauth2 from tornado import web from .. import orm from ..user import User -from ..utils import token_authenticated +from ..utils import token_authenticated, compare_token from .base import BaseHandler, APIHandler @@ -98,24 +104,149 @@ class CookieAPIHandler(APIHandler): self.write(json.dumps(self.user_model(user))) -class OAuthHandler(BaseHandler, OAuth2Handler): +class OAuthHandler(BaseHandler): + def extract_oauth_params(self): + """extract oauthlib params from a request + + Returns: + + (uri, http_method, body, headers) + """ + return ( + self.make_absolute_redirect_uri(self.request.uri), + self.request.method, + self.request.body, + self.request.headers, + ) + + def make_absolute_redirect_uri(self, uri): + """Make absolute redirect URIs + + internal redirect uris, e.g. `/user/foo/oauth_handler` + are allowed in jupyterhub, but oauthlib prohibits them. + Add `$HOST` header to redirect_uri to make them acceptable. + """ + redirect_uri = self.get_argument('redirect_uri') + if not redirect_uri or not redirect_uri.startswith('/'): + return uri + # make absolute local redirects full URLs + # to satisfy oauthlib's absolute URI requirement + redirect_uri = self.request.protocol + "://" + self.request.headers['Host'] + redirect_uri + parsed_url = urlparse(uri) + query_list = parse_qsl(parsed_url.query, keep_blank_values=True) + for idx, item in enumerate(query_list): + if item[0] == 'redirect_uri': + query_list[idx] = ('redirect_uri', redirect_uri) + break + + return urlunparse( + urlparse(uri) + ._replace(query=urlencode(query_list)) + ) + + +class OAuthAuthorizeHandler(OAuthHandler): """Implement OAuth provider handlers OAuth2Handler sets `self.provider` in initialize, but we are already passing the Provider object via settings. """ - @property - def provider(self): - return self.settings['oauth_provider'] - def initialize(self): - pass + @web.authenticated + def get(self): + # You need to define extract_params and make sure it does not + # include file like objects waiting for input. In Django this + # is request.META['wsgi.input'] and request.META['wsgi.errors'] + uri, http_method, body, headers = self.extract_oauth_params() + + try: + scopes, credentials = self.oauth_provider.validate_authorization_request( + uri, http_method, body, headers) + + if scopes == ['identify']: + pass + client_id = 'hmmm' + # You probably want to render a template instead. + self.write('