mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-15 05:53:00 +00:00
Removed user model flags, scope-guarded server model with new scopes
This commit is contained in:
@@ -79,23 +79,32 @@ class APIHandler(BaseHandler):
|
||||
% req_scope,
|
||||
)
|
||||
|
||||
def has_access(orm_resource, kind):
|
||||
def has_access_to(orm_resource, kind):
|
||||
"""
|
||||
param orm_resource: User or Service or Group
|
||||
param kind: 'users' or 'services' or 'groups'
|
||||
param orm_resource: User or Service or Group or spawner
|
||||
param kind: 'user' or 'service' or 'group' or 'server'.
|
||||
`kind` could probably be derived from `orm_resource`, problem is Jupyterhub.users.User
|
||||
"""
|
||||
if sub_scope == scopes.Scope.ALL:
|
||||
return True
|
||||
else:
|
||||
found_resource = orm_resource.name in sub_scope[kind]
|
||||
if not found_resource: # Try group-based access
|
||||
if 'group' in sub_scope and kind == 'user':
|
||||
if kind == 'server':
|
||||
# First check if we have access to user info
|
||||
user_name = orm_resource.user.name
|
||||
found_resource = user_name in sub_scope['user']
|
||||
if not found_resource:
|
||||
# Now check for specific servers:
|
||||
server_format = f"{orm_resource.user / orm_resource.name}"
|
||||
found_resource = server_format in sub_scope[kind]
|
||||
elif 'group' in sub_scope and kind == 'user':
|
||||
group_names = {group.name for group in orm_resource.groups}
|
||||
user_in_group = bool(group_names & set(sub_scope['group']))
|
||||
found_resource = user_in_group
|
||||
return found_resource
|
||||
|
||||
return has_access
|
||||
return has_access_to
|
||||
|
||||
def get_current_user_cookie(self):
|
||||
"""Override get_user_cookie to check Referer header"""
|
||||
@@ -168,17 +177,24 @@ class APIHandler(BaseHandler):
|
||||
|
||||
def server_model(self, spawner, include_state=False):
|
||||
"""Get the JSON model for a Spawner"""
|
||||
return {
|
||||
server_scope = 'read:users:servers'
|
||||
server_state_scope = 'admin:users:server_state'
|
||||
model = {
|
||||
'name': spawner.name,
|
||||
'last_activity': isoformat(spawner.orm_spawner.last_activity),
|
||||
'started': isoformat(spawner.orm_spawner.started),
|
||||
'pending': spawner.pending,
|
||||
'ready': spawner.ready,
|
||||
'state': spawner.get_state() if include_state else None,
|
||||
'url': url_path_join(spawner.user.url, spawner.name, '/'),
|
||||
'user_options': spawner.user_options,
|
||||
'progress_url': spawner._progress_url,
|
||||
}
|
||||
# First check users, then servers
|
||||
if server_state_scope in self.parsed_scopes:
|
||||
scope_filter = self.get_scope_filter(server_state_scope)
|
||||
if scope_filter(spawner, kind='server'):
|
||||
model['state'] = spawner.get_state()
|
||||
return model
|
||||
|
||||
def token_model(self, token):
|
||||
"""Get the JSON model for an APIToken"""
|
||||
@@ -220,13 +236,7 @@ class APIHandler(BaseHandler):
|
||||
model.update(extra)
|
||||
return model
|
||||
|
||||
def user_model(
|
||||
self,
|
||||
user,
|
||||
include_servers=False,
|
||||
include_server_state=False,
|
||||
include_auth_state=False,
|
||||
):
|
||||
def user_model(self, user):
|
||||
"""Get the JSON model for a User object"""
|
||||
if isinstance(user, orm.User):
|
||||
user = self.users[user.id]
|
||||
@@ -258,8 +268,8 @@ class APIHandler(BaseHandler):
|
||||
'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'},
|
||||
'admin:users:auth_state': {'kind', 'name', 'auth_state'},
|
||||
'admin:users:server_state': {'kind', 'name', 'servers', 'server_state'},
|
||||
}
|
||||
self.log.debug(
|
||||
"Asking for user model of %s with scopes [%s]",
|
||||
@@ -273,21 +283,16 @@ 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:
|
||||
if 'servers' in allowed_keys:
|
||||
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_server_state
|
||||
)
|
||||
servers[name] = self.server_model(spawner)
|
||||
return model
|
||||
|
||||
def group_model(self, group):
|
||||
@@ -308,11 +313,8 @@ 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
|
||||
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}
|
||||
|
@@ -104,9 +104,7 @@ class UserListAPIHandler(APIHandler):
|
||||
data = []
|
||||
for u in query:
|
||||
if post_filter is None or post_filter(u):
|
||||
user_model = self.user_model(
|
||||
u, include_servers=True, include_server_state=True
|
||||
)
|
||||
user_model = self.user_model(u)
|
||||
if user_model:
|
||||
data.append(user_model)
|
||||
self.write(json.dumps(data))
|
||||
@@ -188,16 +186,15 @@ def admin_or_self(method):
|
||||
|
||||
class UserAPIHandler(APIHandler):
|
||||
@needs_scope(
|
||||
'read:users'
|
||||
) # Todo: Add the same list of scopes as at UserListAPIHandler
|
||||
'read:users',
|
||||
'read:users:name',
|
||||
'reda:users:servers',
|
||||
'read:users:groups',
|
||||
'read:users:activity',
|
||||
)
|
||||
async def get(self, user_name):
|
||||
user = self.find_user(user_name)
|
||||
model = self.user_model(
|
||||
user,
|
||||
include_servers=True,
|
||||
include_server_state=True,
|
||||
include_auth_state=True,
|
||||
)
|
||||
model = self.user_model(user)
|
||||
# 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
|
||||
|
@@ -90,7 +90,7 @@ def get_scope_hierarchy():
|
||||
'read:users:servers',
|
||||
],
|
||||
'users:tokens': ['read:users:tokens'],
|
||||
'admin:users': ['read:users:auth_state', 'read:users:server_state'],
|
||||
'admin:users': ['admin:users:auth_state', 'admin:users:server_state'],
|
||||
'admin:users:servers': None,
|
||||
'groups': ['read:groups'],
|
||||
'admin:groups': None,
|
||||
|
@@ -159,6 +159,7 @@ def fill_user(model):
|
||||
model.setdefault('created', TIMESTAMP)
|
||||
model.setdefault('last_activity', TIMESTAMP)
|
||||
model.setdefault('servers', {})
|
||||
model.setdefault('auth_state', '')
|
||||
return model
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user