diff --git a/jupyterhub/apihandlers/auth.py b/jupyterhub/apihandlers/auth.py index f8cbcecc..cf812ba5 100644 --- a/jupyterhub/apihandlers/auth.py +++ b/jupyterhub/apihandlers/auth.py @@ -18,7 +18,11 @@ class TokenAPIHandler(APIHandler): orm_token = orm.APIToken.find(self.db, token) if orm_token is None: raise web.HTTPError(404) - self.write(json.dumps(self.user_model(self.users[orm_token.user]))) + if orm_token.user: + model = self.user_model(self.users[orm_token.user]) + elif orm_token.service: + model = self.service_model(orm_token.service) + self.write(json.dumps(model)) @gen.coroutine def post(self): diff --git a/jupyterhub/apihandlers/base.py b/jupyterhub/apihandlers/base.py index 492cc2d9..dd705196 100644 --- a/jupyterhub/apihandlers/base.py +++ b/jupyterhub/apihandlers/base.py @@ -106,7 +106,15 @@ class APIHandler(BaseHandler): """Get the JSON model for a Group object""" return { 'name': group.name, - 'users': [ u.name for u in group.users ] + 'users': [ u.name for u in group.users ], + } + + def service_model(self, service): + """Get the JSON model for a Service object""" + return { + 'kind': 'service', + 'name': service.name, + 'admin': service.admin, } _user_model_types = { @@ -152,6 +160,7 @@ class APIHandler(BaseHandler): if not isinstance(groupname, str): raise web.HTTPError(400, ("group names must be str, not %r", type(groupname))) + def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Headers', 'accept, content-type') self.finish() diff --git a/jupyterhub/tests/test_services_auth.py b/jupyterhub/tests/test_services_auth.py index f613ff6a..4cf91d7e 100644 --- a/jupyterhub/tests/test_services_auth.py +++ b/jupyterhub/tests/test_services_auth.py @@ -1,4 +1,6 @@ +from binascii import hexlify import json +import os from queue import Queue import sys from threading import Thread @@ -217,7 +219,8 @@ def test_hub_authenticated(request): assert auth.login_url in r.headers['Location'] -def test_service_cookie_auth(app, mockservice_url): +def test_hubauth_cookie(app, mockservice_url): + """Test HubAuthenticated service with user cookies""" cookies = app.login_user('badger') r = requests.get(public_url(app, mockservice_url) + '/whoami/', cookies=cookies) r.raise_for_status() @@ -230,7 +233,8 @@ def test_service_cookie_auth(app, mockservice_url): } -def test_service_token_auth(app, mockservice_url): +def test_hubauth_token(app, mockservice_url): + """Test HubAuthenticated service with user API tokens""" u = add_user(app.db, name='river') token = u.new_api_token() app.db.commit() @@ -267,3 +271,44 @@ def test_service_token_auth(app, mockservice_url): path = urlparse(location).path assert path.endswith('/hub/login') + +def test_hubauth_service_token(app, mockservice_url, io_loop): + """Test HubAuthenticated service with service API tokens""" + + token = hexlify(os.urandom(5)).decode('utf8') + name = 'test-api-service' + app.service_tokens[token] = name + io_loop.run_sync(app.init_api_tokens) + + # token in Authorization header + r = requests.get(public_url(app, mockservice_url) + '/whoami/', + headers={ + 'Authorization': 'token %s' % token, + }) + r.raise_for_status() + reply = r.json() + assert reply == { + 'kind': 'service', + 'name': name, + 'admin': False, + } + + # token in ?token parameter + r = requests.get(public_url(app, mockservice_url) + '/whoami/?token=%s' % token) + r.raise_for_status() + reply = r.json() + assert reply == { + 'kind': 'service', + 'name': name, + 'admin': False, + } + + r = requests.get(public_url(app, mockservice_url) + '/whoami/?token=no-such-token', + allow_redirects=False, + ) + assert r.status_code == 302 + assert 'Location' in r.headers + location = r.headers['Location'] + path = urlparse(location).path + assert path.endswith('/hub/login') +