diff --git a/jupyterhub/apihandlers/auth.py b/jupyterhub/apihandlers/auth.py index 99ab0a2e..86a0413a 100644 --- a/jupyterhub/apihandlers/auth.py +++ b/jupyterhub/apihandlers/auth.py @@ -18,14 +18,14 @@ from .base import BaseHandler, APIHandler class TokenAPIHandler(APIHandler): @token_authenticated - def get(self, token): + async def get(self, token): orm_token = orm.APIToken.find(self.db, token) if orm_token is None: orm_token = orm.OAuthAccessToken.find(self.db, token) if orm_token is None: raise web.HTTPError(404) if orm_token.user: - model = self.user_model(self.users[orm_token.user]) + model = await self.user_model(self.users[orm_token.user]) elif orm_token.service: model = self.service_model(orm_token.service) else: @@ -71,13 +71,13 @@ class TokenAPIHandler(APIHandler): api_token = user.new_api_token(note=note) self.write(json.dumps({ 'token': api_token, - 'user': self.user_model(user), + 'user': await self.user_model(user), })) class CookieAPIHandler(APIHandler): @token_authenticated - def get(self, cookie_name, cookie_value=None): + async def get(self, cookie_name, cookie_value=None): cookie_name = quote(cookie_name, safe='') if cookie_value is None: self.log.warning("Cookie values in request body is deprecated, use `/cookie_name/cookie_value`") @@ -87,12 +87,12 @@ class CookieAPIHandler(APIHandler): user = self._user_for_cookie(cookie_name, cookie_value) if user is None: raise web.HTTPError(404) - self.write(json.dumps(self.user_model(user))) + self.write(json.dumps(await self.user_model(user))) class OAuthHandler(BaseHandler, OAuth2Handler): """Implement OAuth provider handlers - + OAuth2Handler sets `self.provider` in initialize, but we are already passing the Provider object via settings. """ diff --git a/jupyterhub/apihandlers/base.py b/jupyterhub/apihandlers/base.py index cacad69a..f43665a2 100644 --- a/jupyterhub/apihandlers/base.py +++ b/jupyterhub/apihandlers/base.py @@ -71,7 +71,7 @@ class APIHandler(BaseHandler): self.log.error("Couldn't parse JSON", exc_info=True) raise web.HTTPError(400, 'Invalid JSON in body of request') return model - + def write_error(self, status_code, **kwargs): """Write JSON errors instead of HTML""" exc_info = kwargs.get('exc_info') @@ -94,7 +94,7 @@ class APIHandler(BaseHandler): 'message': message or status_message, })) - def user_model(self, user): + async def user_model(self, user): """Get the JSON model for a User object""" if isinstance(user, orm.User): user = self.users[user.id] @@ -107,6 +107,7 @@ class APIHandler(BaseHandler): 'server': user.url if user.running else None, 'pending': None, 'last_activity': user.last_activity.isoformat(), + 'auth_state': await user.get_auth_state(), } if '' in user.spawners: model['pending'] = user.spawners[''].pending or None @@ -151,7 +152,7 @@ class APIHandler(BaseHandler): def _check_model(self, model, model_types, name): """Check a model provided by a REST API request - + Args: model (dict): user-provided model model_types (dict): dict of key:type used to validate types and keys diff --git a/jupyterhub/apihandlers/users.py b/jupyterhub/apihandlers/users.py index 6ac53ddc..11e01a3b 100644 --- a/jupyterhub/apihandlers/users.py +++ b/jupyterhub/apihandlers/users.py @@ -17,20 +17,20 @@ class SelfAPIHandler(APIHandler): Based on the authentication info. Acts as a 'whoami' for auth tokens. """ - def get(self): + async def get(self): user = self.get_current_user() if user is None: # whoami can be accessed via oauth token user = self.get_current_user_oauth_token() if user is None: raise web.HTTPError(403) - self.write(json.dumps(self.user_model(user))) + self.write(json.dumps(await self.user_model(user))) class UserListAPIHandler(APIHandler): @admin_only - def get(self): - data = [ self.user_model(u) for u in self.db.query(orm.User) ] + async def get(self): + data = [ await self.user_model(u) for u in self.db.query(orm.User) ] self.write(json.dumps(data)) @admin_only @@ -44,7 +44,7 @@ class UserListAPIHandler(APIHandler): # admin is set for all users # to create admin and non-admin users requires at least two API requests admin = data.get('admin', False) - + to_create = [] invalid_names = [] for name in usernames: @@ -83,7 +83,7 @@ class UserListAPIHandler(APIHandler): else: created.append(user) - self.write(json.dumps([ self.user_model(u) for u in created ])) + self.write(json.dumps([ await self.user_model(u) for u in created ])) self.set_status(201) @@ -95,7 +95,7 @@ def admin_or_self(method): raise web.HTTPError(403) if not (current.name == name or current.admin): raise web.HTTPError(403) - + # raise 404 if not found if not self.find_user(name): raise web.HTTPError(404) @@ -104,10 +104,12 @@ def admin_or_self(method): class UserAPIHandler(APIHandler): + #@gen.coroutine @admin_or_self - def get(self, name): + async def get(self, name): user = self.find_user(name) - self.write(json.dumps(self.user_model(user))) + user = await self.user_model(user) + self.write(json.dumps(user)) @admin_only async def post(self, name): @@ -131,7 +133,7 @@ class UserAPIHandler(APIHandler): self.users.delete(user) raise web.HTTPError(400, "Failed to create user: %s" % name) - self.write(json.dumps(self.user_model(user))) + self.write(json.dumps(await self.user_model(user))) self.set_status(201) @admin_only @@ -155,7 +157,7 @@ class UserAPIHandler(APIHandler): self.set_status(204) @admin_only - def patch(self, name): + async def patch(self, name): user = self.find_user(name) if user is None: raise web.HTTPError(404) @@ -168,7 +170,7 @@ class UserAPIHandler(APIHandler): for key, value in data.items(): setattr(user, key, value) self.db.commit() - self.write(json.dumps(self.user_model(user))) + self.write(json.dumps(await self.user_model(user))) class UserServerAPIHandler(APIHandler): diff --git a/jupyterhub/tests/test_api.py b/jupyterhub/tests/test_api.py index b28453bc..239a27ea 100644 --- a/jupyterhub/tests/test_api.py +++ b/jupyterhub/tests/test_api.py @@ -201,6 +201,7 @@ def test_get_users(app): 'admin': True, 'server': None, 'pending': None, + 'auth_state': None, }, { 'kind': 'user', @@ -209,6 +210,7 @@ def test_get_users(app): 'admin': False, 'server': None, 'pending': None, + 'auth_state': None, } ] @@ -280,6 +282,7 @@ def test_get_user(app): 'admin': False, 'server': None, 'pending': None, + 'auth_state': None, } @@ -593,7 +596,7 @@ def test_spawn_limit(app, no_patience, slow_spawn, request): user.spawner._start_future = Future() r = yield api_request(app, 'users', name, 'server', method='post') assert r.status_code == 429 - + # allow ykka to start users[0].spawner._start_future.set_result(None) # wait for ykka to finish diff --git a/jupyterhub/tests/test_named_servers.py b/jupyterhub/tests/test_named_servers.py index b1f84a62..a2511cd5 100644 --- a/jupyterhub/tests/test_named_servers.py +++ b/jupyterhub/tests/test_named_servers.py @@ -36,6 +36,7 @@ def test_default_server(app, named_servers): 'kind': 'user', 'admin': False, 'pending': None, + 'auth_state': None, 'server': user.url, 'servers': { '': { @@ -63,6 +64,7 @@ def test_default_server(app, named_servers): 'pending': None, 'server': None, 'servers': {}, + 'auth_state': None, } @@ -99,6 +101,7 @@ def test_create_named_server(app, named_servers): 'kind': 'user', 'admin': False, 'pending': None, + 'auth_state': None, 'server': None, 'servers': { servername: { @@ -136,6 +139,7 @@ def test_delete_named_server(app, named_servers): 'kind': 'user', 'admin': False, 'pending': None, + 'auth_state': None, 'server': None, 'servers': { name: {