From c5c44f6dbedbed3f29e9e7d0ee1627743825a9de Mon Sep 17 00:00:00 2001 From: 0mar Date: Fri, 26 Mar 2021 13:47:01 +0100 Subject: [PATCH] Replaced auth_state and server_state with scopes --- jupyterhub/apihandlers/base.py | 34 +++++++++++++++++++++++++++++---- jupyterhub/apihandlers/users.py | 14 +++++++++----- jupyterhub/roles.py | 3 +-- jupyterhub/tests/test_roles.py | 29 ---------------------------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/jupyterhub/apihandlers/base.py b/jupyterhub/apihandlers/base.py index a8e94ecb..ddf1d1b8 100644 --- a/jupyterhub/apihandlers/base.py +++ b/jupyterhub/apihandlers/base.py @@ -220,7 +220,13 @@ class APIHandler(BaseHandler): model.update(extra) return model - def user_model(self, user, include_servers=False, include_state=False): + def user_model( + self, + user, + include_servers=False, + include_server_state=False, + include_auth_state=False, + ): """Get the JSON model for a User object""" if isinstance(user, orm.User): user = self.users[user.id] @@ -234,13 +240,26 @@ class APIHandler(BaseHandler): 'pending': None, 'created': isoformat(user.created), 'last_activity': isoformat(user.last_activity), + 'auth_state': '', # placeholder, filled in later } access_map = { - 'read:users': set(model.keys()), # All available components + 'read:users': { + 'kind', + 'name', + 'admin', + 'roles', + 'groups', + 'server', + 'pending', + 'created', + 'last_activity', + }, 'read:users:name': {'kind', 'name'}, 'read:users:groups': {'kind', 'name', 'groups'}, 'read:users:activity': {'kind', 'name', 'last_activity'}, 'read:users:servers': {'kind', 'name', 'servers'}, + 'read:users:auth_state': {'kind', 'name', 'auth_state'}, + 'read:users:server_state': {'kind', 'name', 'server_state'}, } self.log.debug( "Asking for user model of %s with scopes [%s]", @@ -254,18 +273,20 @@ class APIHandler(BaseHandler): if scope_filter(user, kind='user'): allowed_keys |= access_map[scope] model = {key: model[key] for key in allowed_keys if key in model} + if not include_auth_state: + model.pop("auth_state", None) if model: + include_server_state &= 'server_state' in allowed_keys if '' in user.spawners and 'pending' in allowed_keys: model['pending'] = user.spawners[''].pending if include_servers and 'servers' in allowed_keys: - # Todo: Replace include_state with scope (read|admin):users:auth_state servers = model['servers'] = {} for name, spawner in user.spawners.items(): # include 'active' servers, not just ready # (this includes pending events) if spawner.active: servers[name] = self.server_model( - spawner, include_state=include_state + spawner, include_state=include_server_state ) return model @@ -287,6 +308,11 @@ class APIHandler(BaseHandler): scope_filter = self.get_scope_filter(req_scope) if scope_filter(service, kind='service'): model['roles'] = [r.name for r in service.roles] + model[ + 'admin' + ] = ( + service.admin + ) # todo: Remove once we replace admin flag with role check return model _user_model_types = {'name': str, 'admin': bool, 'groups': list, 'auth_state': dict} diff --git a/jupyterhub/apihandlers/users.py b/jupyterhub/apihandlers/users.py index f2f8efc7..dea4bd6d 100644 --- a/jupyterhub/apihandlers/users.py +++ b/jupyterhub/apihandlers/users.py @@ -105,7 +105,7 @@ class UserListAPIHandler(APIHandler): for u in query: if post_filter is None or post_filter(u): user_model = self.user_model( - u, include_servers=True, include_state=True + u, include_servers=True, include_server_state=True ) if user_model: data.append(user_model) @@ -187,18 +187,22 @@ def admin_or_self(method): class UserAPIHandler(APIHandler): - @needs_scope('read:users') + @needs_scope( + 'read:users' + ) # Todo: Add the same list of scopes as at UserListAPIHandler async def get(self, user_name): user = self.find_user(user_name) model = self.user_model( - user, include_servers=True, include_state=self.current_user.admin + user, + include_servers=True, + include_server_state=True, + include_auth_state=True, ) # auth state will only be shown if the requester is an admin # this means users can't see their own auth state unless they # are admins, Hub admins often are also marked as admins so they # will see their auth state but normal users won't - requester = self.current_user - if requester.admin: + if 'auth_state' in model: model['auth_state'] = await user.get_auth_state() self.write(json.dumps(model)) diff --git a/jupyterhub/roles.py b/jupyterhub/roles.py index e16f1a94..93221eb5 100644 --- a/jupyterhub/roles.py +++ b/jupyterhub/roles.py @@ -93,10 +93,9 @@ def get_scope_hierarchy(): 'read:users:groups', 'read:users:activity', 'read:users:servers', - 'read:users:auth_state', ], 'users:tokens': ['read:users:tokens'], - 'admin:users': None, + 'admin:users': ['read:users:auth_state', 'read:users:server_state'], 'admin:users:servers': None, 'groups': ['read:groups'], 'admin:groups': None, diff --git a/jupyterhub/tests/test_roles.py b/jupyterhub/tests/test_roles.py index 8ac16c99..c300f43d 100644 --- a/jupyterhub/tests/test_roles.py +++ b/jupyterhub/tests/test_roles.py @@ -421,32 +421,3 @@ async def test_get_new_token_via_api(app, headers, role_list, status): # verify deletion r = await api_request(app, 'users/user/tokens', token_id) assert r.status_code == 404 - - -@mark.role -@mark.parametrize( - "kind, has_user_scopes", - [ - ('users', True), - ('services', False), - ], -) -async def test_self_expansion(app, kind, has_user_scopes): - Class = orm.get_class(kind) - orm_obj = Class(name=f'test_{kind}') - app.db.add(orm_obj) - app.db.commit() - test_role = orm.Role(name='test_role', scopes=['self']) - orm_obj.roles.append(test_role) - # test expansion of user/service scopes - scopes = roles.expand_roles_to_scopes(orm_obj) - assert bool(scopes) == has_user_scopes - - # test expansion of token scopes - orm_obj.new_api_token() - print(orm_obj.api_tokens[0]) - token_scopes = scopes.get_scopes_for(orm_obj.api_tokens[0]) - print(token_scopes) - assert bool(token_scopes) == has_user_scopes - app.db.delete(orm_obj) - app.db.delete(test_role)