mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 23:42:59 +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)
|
model.update(extra)
|
||||||
return model
|
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"""
|
"""Get the JSON model for a User object"""
|
||||||
if isinstance(user, orm.User):
|
if isinstance(user, orm.User):
|
||||||
user = self.users[user.id]
|
user = self.users[user.id]
|
||||||
@@ -234,13 +240,26 @@ class APIHandler(BaseHandler):
|
|||||||
'pending': None,
|
'pending': None,
|
||||||
'created': isoformat(user.created),
|
'created': isoformat(user.created),
|
||||||
'last_activity': isoformat(user.last_activity),
|
'last_activity': isoformat(user.last_activity),
|
||||||
|
'auth_state': '', # placeholder, filled in later
|
||||||
}
|
}
|
||||||
access_map = {
|
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:name': {'kind', 'name'},
|
||||||
'read:users:groups': {'kind', 'name', 'groups'},
|
'read:users:groups': {'kind', 'name', 'groups'},
|
||||||
'read:users:activity': {'kind', 'name', 'last_activity'},
|
'read:users:activity': {'kind', 'name', 'last_activity'},
|
||||||
'read:users:servers': {'kind', 'name', 'servers'},
|
'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(
|
self.log.debug(
|
||||||
"Asking for user model of %s with scopes [%s]",
|
"Asking for user model of %s with scopes [%s]",
|
||||||
@@ -254,18 +273,20 @@ class APIHandler(BaseHandler):
|
|||||||
if scope_filter(user, kind='user'):
|
if scope_filter(user, kind='user'):
|
||||||
allowed_keys |= access_map[scope]
|
allowed_keys |= access_map[scope]
|
||||||
model = {key: model[key] for key in allowed_keys if key in model}
|
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:
|
if model:
|
||||||
|
include_server_state &= 'server_state' in allowed_keys
|
||||||
if '' in user.spawners and 'pending' in allowed_keys:
|
if '' in user.spawners and 'pending' in allowed_keys:
|
||||||
model['pending'] = user.spawners[''].pending
|
model['pending'] = user.spawners[''].pending
|
||||||
if include_servers and 'servers' in allowed_keys:
|
if include_servers and 'servers' in allowed_keys:
|
||||||
# Todo: Replace include_state with scope (read|admin):users:auth_state
|
|
||||||
servers = model['servers'] = {}
|
servers = model['servers'] = {}
|
||||||
for name, spawner in user.spawners.items():
|
for name, spawner in user.spawners.items():
|
||||||
# include 'active' servers, not just ready
|
# include 'active' servers, not just ready
|
||||||
# (this includes pending events)
|
# (this includes pending events)
|
||||||
if spawner.active:
|
if spawner.active:
|
||||||
servers[name] = self.server_model(
|
servers[name] = self.server_model(
|
||||||
spawner, include_state=include_state
|
spawner, include_state=include_server_state
|
||||||
)
|
)
|
||||||
return model
|
return model
|
||||||
|
|
||||||
@@ -287,6 +308,11 @@ class APIHandler(BaseHandler):
|
|||||||
scope_filter = self.get_scope_filter(req_scope)
|
scope_filter = self.get_scope_filter(req_scope)
|
||||||
if scope_filter(service, kind='service'):
|
if scope_filter(service, kind='service'):
|
||||||
model['roles'] = [r.name for r in service.roles]
|
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
|
return model
|
||||||
|
|
||||||
_user_model_types = {'name': str, 'admin': bool, 'groups': list, 'auth_state': dict}
|
_user_model_types = {'name': str, 'admin': bool, 'groups': list, 'auth_state': dict}
|
||||||
|
@@ -105,7 +105,7 @@ class UserListAPIHandler(APIHandler):
|
|||||||
for u in query:
|
for u in query:
|
||||||
if post_filter is None or post_filter(u):
|
if post_filter is None or post_filter(u):
|
||||||
user_model = self.user_model(
|
user_model = self.user_model(
|
||||||
u, include_servers=True, include_state=True
|
u, include_servers=True, include_server_state=True
|
||||||
)
|
)
|
||||||
if user_model:
|
if user_model:
|
||||||
data.append(user_model)
|
data.append(user_model)
|
||||||
@@ -187,18 +187,22 @@ def admin_or_self(method):
|
|||||||
|
|
||||||
|
|
||||||
class UserAPIHandler(APIHandler):
|
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):
|
async def get(self, user_name):
|
||||||
user = self.find_user(user_name)
|
user = self.find_user(user_name)
|
||||||
model = self.user_model(
|
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
|
# auth state will only be shown if the requester is an admin
|
||||||
# this means users can't see their own auth state unless they
|
# this means users can't see their own auth state unless they
|
||||||
# are admins, Hub admins often are also marked as admins so they
|
# are admins, Hub admins often are also marked as admins so they
|
||||||
# will see their auth state but normal users won't
|
# will see their auth state but normal users won't
|
||||||
requester = self.current_user
|
if 'auth_state' in model:
|
||||||
if requester.admin:
|
|
||||||
model['auth_state'] = await user.get_auth_state()
|
model['auth_state'] = await user.get_auth_state()
|
||||||
self.write(json.dumps(model))
|
self.write(json.dumps(model))
|
||||||
|
|
||||||
|
@@ -93,10 +93,9 @@ def get_scope_hierarchy():
|
|||||||
'read:users:groups',
|
'read:users:groups',
|
||||||
'read:users:activity',
|
'read:users:activity',
|
||||||
'read:users:servers',
|
'read:users:servers',
|
||||||
'read:users:auth_state',
|
|
||||||
],
|
],
|
||||||
'users:tokens': ['read:users:tokens'],
|
'users:tokens': ['read:users:tokens'],
|
||||||
'admin:users': None,
|
'admin:users': ['read:users:auth_state', 'read:users:server_state'],
|
||||||
'admin:users:servers': None,
|
'admin:users:servers': None,
|
||||||
'groups': ['read:groups'],
|
'groups': ['read:groups'],
|
||||||
'admin:groups': None,
|
'admin:groups': None,
|
||||||
|
@@ -421,32 +421,3 @@ async def test_get_new_token_via_api(app, headers, role_list, status):
|
|||||||
# verify deletion
|
# verify deletion
|
||||||
r = await api_request(app, 'users/user/tokens', token_id)
|
r = await api_request(app, 'users/user/tokens', token_id)
|
||||||
assert r.status_code == 404
|
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