mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 15:33:02 +00:00
Replaced auth_state and server_state with scopes
This commit is contained in:
@@ -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}
|
||||
|
@@ -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))
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user