add whoami-oauth-basic example

implements oauth without inheriting from HubOAuthenticated

should be easier to parse for users with alternate oauth implementations
This commit is contained in:
Min RK
2018-02-28 14:10:02 +01:00
parent a58bea6d93
commit 8b3c2fa12f
4 changed files with 161 additions and 1 deletions

View File

@@ -0,0 +1,131 @@
"""Basic implementation of OAuth without any inheritance
Implements OAuth handshake directly
so all URLs and requests should be in one place
"""
import json
import os
import sys
from urllib.parse import urlencode, urlparse
from tornado.auth import OAuth2Mixin
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
from tornado.httputil import url_concat
from tornado.ioloop import IOLoop
from tornado import log
from tornado import web
class JupyterHubLoginHandler(web.RequestHandler):
"""Login Handler
this handler both begins and ends the OAuth process
"""
async def token_for_code(self, code):
"""Complete OAuth by requesting an access token for an oauth code"""
params = dict(
client_id=self.settings['client_id'],
client_secret=self.settings['api_token'],
grant_type='authorization_code',
code=code,
redirect_uri=self.settings['redirect_uri'],
)
req = HTTPRequest(self.settings['token_url'], method='POST',
body=urlencode(params).encode('utf8'),
headers={
'Content-Type': 'application/x-www-form-urlencoded',
},
)
response = await AsyncHTTPClient().fetch(req)
data = json.loads(response.body.decode('utf8', 'replace'))
return data['access_token']
async def get(self):
code = self.get_argument('code', None)
if code:
# code is set, we are the oauth callback
# complete oauth
token = await self.token_for_code(code)
# login successful, set cookie and redirect back to home
self.set_secure_cookie('whoami-oauth-token', token)
self.redirect('/')
else:
# we are the login handler,
# begin oauth process which will come back later with an
# authorization_code
self.redirect(url_concat(
self.settings['authorize_url'],
dict(
redirect_uri=self.settings['redirect_uri'],
client_id=self.settings['client_id'],
response_type='code',
)
))
class WhoAmIHandler(web.RequestHandler):
"""Serve the JSON model for the authenticated user"""
def get_current_user(self):
"""The login handler stored a jupyterhub API token
in a cookie
"""
btoken = self.get_secure_cookie('whoami-oauth-token')
if btoken:
return btoken.decode('ascii')
async def user_for_token(self, token):
"""Retrieve the user for a given token, via /hub/api/user"""
req = HTTPRequest(
self.settings['user_url'],
headers={
'Authorization': f'token {token}'
},
)
response = await AsyncHTTPClient().fetch(req)
return json.loads(response.body.decode('utf8', 'replace'))
@web.authenticated
async def get(self):
user_token = self.get_current_user()
user_model = await self.user_for_token(user_token)
self.set_header('content-type', 'application/json')
self.write(json.dumps(user_model, indent=1, sort_keys=True))
def main():
log.enable_pretty_logging()
# construct OAuth URLs from jupyterhub base URL
hub_api = os.environ['JUPYTERHUB_URL'].rstrip('/') + '/hub/api'
authorize_url = hub_api + '/oauth2/authorize'
token_url = hub_api + '/oauth2/token'
user_url = hub_api + '/user'
app = web.Application([
('/oauth_callback', JupyterHubLoginHandler),
('/', WhoAmIHandler),
],
login_url='/oauth_callback',
cookie_secret=os.urandom(32),
api_token=os.environ['JUPYTERHUB_API_TOKEN'],
client_id=os.environ['JUPYTERHUB_CLIENT_ID'],
redirect_uri=os.environ['JUPYTERHUB_SERVICE_URL'].rstrip('/') + '/oauth_callback',
authorize_url=authorize_url,
token_url=token_url,
user_url=user_url,
)
url = urlparse(os.environ['JUPYTERHUB_SERVICE_URL'])
log.app_log.info("Running basic whoami service on %s",
os.environ['JUPYTERHUB_SERVICE_URL'])
app.listen(url.port, url.hostname)
IOLoop.current().start()
if __name__ == '__main__':
main()