Refactored scope names and updated docs to reflect this

This commit is contained in:
0mar
2021-06-15 12:07:07 +02:00
parent 5789806cf7
commit 7a3b237bb3
23 changed files with 180 additions and 161 deletions

View File

@@ -17,35 +17,37 @@ securityDefinitions:
flow: accessCode
authorizationUrl: "/hub/api/oauth2/authorize" # what are the absolute URIs here? is oauth2 correct here or shall we use just authorizations?
tokenUrl: "/hub/api/oauth2/token"
scopes:
scopes: # Todo: Generate based on scope table
(noscope): Allows only to identify the requesting entity
self: Metascope, grants access to user's own resources; resolves to (no scope) for services.
all: Metascope, valid for tokens only. Grants access to everything that the token's owning entity can do.
all: Metascope, valid for tokens only. Grants access to all resources of the token-owning entity.
admin:users: Grants read, write, create and delete access to users and their authentication state but not their servers or tokens.
admin:users:auth_state: Grants access to users' authentication state only.
admin:auth_state: Grants access to users' authentication state only.
users: Grants read and write permissions to users' models apart from servers, tokens and authentication state.
users:activity: Grants access to read and post users' activity only.
users:activity!user=username: Update a single user's activity (example horizontal filter).
read:users: Read-only access to users' models apart from servers, tokens and authentication state.
read:users!user=username: As above limited to a specific user (example horizontal filter).
read:users:name: Read-only access to users' names.
read:users:roles: Read-only access to a list of users' roles names.
read:users:name: Read-only access to user names.
read:roles:users: Read-only access to user role assignments.
read:users:groups: Read-only access to a list of users' group names.
read:users:activity: Read-only access to users' activity.
read:users:activity!group=groupname: Read-only access to specific group's users' activity (example horizontal filter).
admin:users:servers: Grants read, start/stop, create and delete permissions to users' servers and their state.
admin:users:server_state: Grants access to servers' state only.
users:servers: Allows for starting/stopping users' servers in addition to read access to their models. Does not include the server state.
users:servers!server=servername: Limits the above to a specific server (example horizontal filter).
read:users:servers: Read-only access to users' server models. Does not include the server state.
users:tokens: Grants read, write, create and delete permissions to users' tokens.
read:users:tokens: Read-only access to users' tokens.
admin:servers: Grants read, start/stop, create and delete permissions to users' servers and their state.
admin:server_state: Grants access to servers' state only.
servers: Allows for starting/stopping users' servers in addition to read access to their models. Does not include the server state.
servers!server=servername: Limits the above to a specific server (example horizontal filter).
read:servers: Read-only access to users' server models. Does not include the server state.
tokens: Grants read, write, create and delete permissions to users' tokens.
read:tokens: Read-only access to users' tokens.
admin:groups: Grants read, write, create and delete access to groups.
groups: Grants read and write permissions to groups, including adding/removing users to/from groups.
read:roles:groups: Read-only access to group roles assignments
groups!group=groupname: As above limited to a specific group only (example horizontal filter)
read:groups: Read-only access to groups.
read:services: Read-only access to service models.
read:services:name: Read-only access to service names.
read:services:roles: Read-only access to a list of service roles names.
read:roles:services: Read-only access to a list of service roles names.
read:hub: Read-only access to detailed information about JupyterHub.
proxy: Allows for obtaining information about the proxy's routing table, for syncing the Hub with proxy and notifying the Hub about a new proxy.
shutdown: Grants access to shutdown the Hub.
@@ -126,8 +128,10 @@ paths:
- read:users:name
- read:users:groups
- read:users:activity
- read:users:servers
#TODO: add admin:users:auth_state/server_state?
- read:servers
- read:roles:users
- admin:auth_state
- admin:server_state
parameters:
- name: state
in: query
@@ -203,9 +207,10 @@ paths:
- read:users:name
- read:users:groups
- read:users:activity
- read:users:servers
- admin:users:auth_state
- admin:users:server_state
- read:servers
- read:roles:users
- admin:auth_state
- admin:server_state
parameters:
- name: name
description: username
@@ -340,7 +345,7 @@ paths:
summary: Start a user's single-user notebook server
security:
- oauth2:
- users:servers
- servers
parameters:
- name: name
description: username
@@ -369,7 +374,7 @@ paths:
summary: Stop a user's server
security:
- oauth2:
- users:servers
- servers
parameters:
- name: name
description: username
@@ -386,7 +391,7 @@ paths:
summary: Start a user's single-user named-server notebook server
security:
- oauth2:
- users:servers
- servers
parameters:
- name: name
description: username
@@ -420,7 +425,7 @@ paths:
summary: Stop a user's named-server
security:
- oauth2:
- users:servers
- servers
parameters:
- name: name
description: username
@@ -460,7 +465,7 @@ paths:
summary: List tokens for the user
security:
- oauth2:
- read:users:tokens
- read:tokens
responses:
"200":
description: The list of tokens
@@ -476,7 +481,7 @@ paths:
summary: Create a new token for the user
security:
- oauth2:
- users:tokens
- tokens
parameters:
- name: token_params
in: body
@@ -519,7 +524,7 @@ paths:
summary: Get the model for a token by id
security:
- oauth2:
- read:users:tokens
- read:tokens
responses:
"200":
description: The info for the new token
@@ -529,7 +534,7 @@ paths:
summary: Delete (revoke) a token by id
security:
- oauth2:
- users:tokens
- tokens
responses:
"204":
description: The token has been deleted
@@ -542,9 +547,10 @@ paths:
- read:users:name
- read:users:groups
- read:users:activity
- read:users:servers
- admin:users:auth_state
- admin:users:server_state
- read:servers
- read:roles:users
- admin:auth_state
- admin:server_state
responses:
"200":
description: The authenticated user's model is returned.
@@ -556,6 +562,8 @@ paths:
security:
- oauth2:
- read:groups
- read:groups:name
- read:roles:groups
parameters:
- name: offset
in: query
@@ -586,6 +594,8 @@ paths:
security:
- oauth2:
- read:groups
- read:groups:name
- read:roles:groups
parameters:
- name: name
description: group name
@@ -688,6 +698,8 @@ paths:
security:
- oauth2:
- read:services
- read:services:name
- read:roles:services
responses:
"200":
description: The service list
@@ -701,6 +713,8 @@ paths:
security:
- oauth2:
- read:services
- read:services:name
- read:roles:services
parameters:
- name: name
description: service name
@@ -790,7 +804,7 @@ paths:
accepts passwords (e.g. not OAuth).
security:
- oauth2:
- users:tokens # minrk: this is a deprecated alias to POST /users/{name}/tokens, either remove it or use the same scope
- tokens
parameters:
- name: credentials
in: body
@@ -817,7 +831,7 @@ paths:
summary: Identify a user or service from an API token
security:
- oauth2:
- read:users:tokens # minrk: is it really necessary to have a scope for this, or use self handler for token whoami?
- (noscope)
parameters:
- name: token
in: path

View File

@@ -17,7 +17,7 @@ To remedy situations like this, JupyterHub is transitioning to an RBAC system. B
## Definitions
**Scopes** are specific permissions used to evaluate API requests. For example: the API endpoint `users/servers`, which enables starting or stopping user servers, is guarded by the scope `users:servers`.
**Scopes** are specific permissions used to evaluate API requests. For example: the API endpoint `users/servers`, which enables starting or stopping user servers, is guarded by the scope `servers`.
Scopes are not directly assigned to requesters. Rather, when a client performs an API call, their access will be evaluated based on their assigned roles.

View File

@@ -55,7 +55,7 @@ c.JupyterHub.load_roles = [
{
'name': 'server-rights',
'description': 'Allows parties to start and stop user servers',
'scopes': ['users:servers'],
'scopes': ['servers'],
'users': ['alice', 'bob'],
'services': ['idle-culler'],
'groups': ['admin-group'],

View File

@@ -7,7 +7,7 @@ A scope has a syntax-based design that reveals which resources it provides acces
## Scope conventions
- `<resource>` \
The `<resource>` scopes, such as `users` or `groups`, grant read and write permissions to the resource itself and all its sub-resources. E.g., the scope `users:servers` is included within the scope `users`.
The top-level `<resource>` scopes, such as `users` or `groups`, grant read and write permissions to the resource itself as well as its sub-resources. For example, the scope `users:activity` is included in the scope `users`.
+++
- `read:<resource>` \
@@ -19,7 +19,7 @@ A scope has a syntax-based design that reveals which resources it provides acces
+++
- `<resource>:<subresource>` \
The {ref}`vertically filtered <vertical-filtering-target>` scopes provide access to a subset of the information granted by the `<resource>` scope. E.g., the scope `users:servers` allows for accessing user servers only.
The {ref}`vertically filtered <vertical-filtering-target>` scopes provide access to a subset of the information granted by the `<resource>` scope. E.g., the scope `users:activity` only provides permission to post user activity.
+++
- `<resource>!<object>=<objectname>` \
@@ -42,8 +42,8 @@ Metascopes do not follow the general scope syntax. Instead, a metascope resolves
Access to the user's own resources and subresources is covered by metascope `self`. This metascope includes the user's model, activity, servers and tokens. For example, `self` for a user named "gerard" includes:
- `users!user=gerard` where the `users` scope provides access to the full user model and activity. The filter restricts this access to the user's own resources.
- `users:servers!user=gerard` which grants the user access to their own servers without being able to create/delete any.
- `users:tokens!user=gerard` which allows the user to access, request and delete their own tokens.
- `servers!user=gerard` which grants the user access to their own servers without being able to create/delete any.
- `tokens!user=gerard` which allows the user to access, request and delete their own tokens.
The `self` scope is only valid for user entities. In other cases (e.g., for services) it resolves to an empty set of scopes.

View File

@@ -38,7 +38,7 @@ Below follows a short tutorial on how to add a cull-idle service in the RBAC sys
{
"name": "idle-culler",
"description": "Culls idle servers",
"scopes": ["read:users:name", "read:users:activity", "users:servers"],
"scopes": ["read:users:name", "read:users:activity", "servers"],
"services": ["idle-culler"],
}
]
@@ -48,7 +48,7 @@ Below follows a short tutorial on how to add a cull-idle service in the RBAC sys
Note that in the RBAC system the `admin` field in the `idle-culler` service definition is omitted. Instead, the `idle-culler` role provides the service with only the permissions it needs.
If the optional actions of deleting the idle servers and/or removing inactive users are desired, **change the following scopes** in the `idle-culler` role definition:
- `users:servers` to `admin:users:servers` for deleting servers
- `servers` to `admin:servers` for deleting servers
- `read:users:name`, `read:users:activity` to `admin:users` for deleting users.
```
@@ -65,8 +65,8 @@ A service capable of creating/removing users and launching multiple servers shou
The scopes required to access the API enpoints:
1. `admin:users`
2. `users:servers`
3. `admin:users:servers`
2. `servers`
3. `admin:servers`
From the above, the role definition is:
@@ -77,7 +77,7 @@ c.JupyterHub.load_roles = [
{
"name": "api-launcher",
"description": "Manages servers",
"scopes": ["admin:users", "admin:users:servers"],
"scopes": ["admin:users", "admin:servers"],
"services": [<service_name>]
}
]
@@ -117,7 +117,7 @@ c.JupyterHub.load_roles = [
{
'name': 'teacher',
'description': 'Allows for accessing information about teacher group members and starting/stopping their servers',
'scopes': [ 'read:users!group=class-B', 'users:servers!group=class-B'],
'scopes': [ 'read:users!group=class-B', 'servers!group=class-B'],
'users': ['johan']
}
]

View File

@@ -91,7 +91,7 @@ c.JupyterHub.load_roles = [
"name": "idle-culler",
"scopes": [
"read:users:activity", # read user last_activity
"users:servers", # start and stop servers
"servers", # start and stop servers
# 'admin:users' # needed if culling idle users as well
]
}

View File

@@ -45,7 +45,7 @@ $ curl -X GET http://127.0.0.1:8000/services/fastapi/me \
"servers": null,
"scopes": [
"access:services",
"access:users:servers!user=test-user",
"access:servers!user=test-user",
"...",
]
}

View File

@@ -43,19 +43,19 @@ $ curl -H "Authorization: token 8630bbd8ef064c48b22c7f122f0cd8ad" http://127.0.0
],
"scopes": [
"access:services",
"access:users:servers!user=test",
"access:servers!user=test",
"read:users!user=test",
"read:users:activity!user=test",
"read:users:groups!user=test",
"read:users:name!user=test",
"read:users:servers!user=test",
"read:users:tokens!user=test",
"read:servers!user=test",
"read:tokens!user=test",
"users!user=test",
"users:activity!user=test",
"users:groups!user=test",
"users:name!user=test",
"users:servers!user=test",
"users:tokens!user=test"
"servers!user=test",
"tokens!user=test"
],
"server": null
}

View File

@@ -258,7 +258,7 @@ class OAuthAuthorizeHandler(OAuthHandler, BaseHandler):
# check for access to target resource
if client.spawner:
scope_filter = self.get_scope_filter("access:users:servers")
scope_filter = self.get_scope_filter("access:servers")
allowed = scope_filter(client.spawner, kind='server')
elif client.service:
scope_filter = self.get_scope_filter("access:services")

View File

@@ -143,7 +143,7 @@ class APIHandler(BaseHandler):
'user_options': spawner.user_options,
'progress_url': spawner._progress_url,
}
scope_filter = self.get_scope_filter('admin:users:server_state')
scope_filter = self.get_scope_filter('admin:server_state')
if scope_filter(spawner, kind='server'):
model['state'] = spawner.get_state()
return model
@@ -219,9 +219,9 @@ class APIHandler(BaseHandler):
'read:users:name': {'kind', 'name', 'admin'},
'read:users:groups': {'kind', 'name', 'groups'},
'read:users:activity': {'kind', 'name', 'last_activity'},
'read:users:servers': {'kind', 'name', 'servers'},
'read:users:roles': {'kind', 'name', 'roles', 'admin'},
'admin:users:auth_state': {'kind', 'name', 'auth_state'},
'read:servers': {'kind', 'name', 'servers'},
'read:roles:users': {'kind', 'name', 'roles', 'admin'},
'admin:auth_state': {'kind', 'name', 'auth_state'},
}
self.log.debug(
"Asking for user model of %s with scopes [%s]",
@@ -237,7 +237,7 @@ class APIHandler(BaseHandler):
model['pending'] = user.spawners[''].pending
servers = model['servers'] = {}
scope_filter = self.get_scope_filter('read:users:servers')
scope_filter = self.get_scope_filter('read:servers')
for name, spawner in user.spawners.items():
# include 'active' servers, not just ready
# (this includes pending events)
@@ -258,7 +258,7 @@ class APIHandler(BaseHandler):
access_map = {
'read:groups': {'kind', 'name', 'users'},
'read:groups:name': {'kind', 'name'},
'read:groups:roles': {'kind', 'name', 'roles'},
'read:roles:groups': {'kind', 'name', 'roles'},
}
model = self._filter_model(model, access_map, group, 'group')
return model
@@ -290,7 +290,7 @@ class APIHandler(BaseHandler):
'display',
},
'read:services:name': {'kind', 'name', 'admin'},
'read:services:roles': {'kind', 'name', 'roles', 'admin'},
'read:roles:services': {'kind', 'name', 'roles', 'admin'},
}
model = self._filter_model(model, access_map, service, 'service')
return model

View File

@@ -34,7 +34,7 @@ class _GroupAPIHandler(APIHandler):
class GroupListAPIHandler(_GroupAPIHandler):
@needs_scope('read:groups', 'read:groups:name', 'read:groups:roles')
@needs_scope('read:groups', 'read:groups:name', 'read:roles:groups')
def get(self):
"""List groups"""
query = self.db.query(orm.Group)
@@ -77,7 +77,7 @@ class GroupListAPIHandler(_GroupAPIHandler):
class GroupAPIHandler(_GroupAPIHandler):
"""View and modify groups by name"""
@needs_scope('read:groups', 'read:groups:name', 'read:groups:roles')
@needs_scope('read:groups', 'read:groups:name', 'read:roles:groups')
def get(self, group_name):
group = self.find_group(group_name)
self.write(json.dumps(self.group_model(group)))

View File

@@ -11,7 +11,7 @@ from .base import APIHandler
class ServiceListAPIHandler(APIHandler):
@needs_scope('read:services', 'read:services:name', 'read:services:roles')
@needs_scope('read:services', 'read:services:name', 'read:roles:services')
def get(self):
data = {}
for name, service in self.services.items():
@@ -22,7 +22,7 @@ class ServiceListAPIHandler(APIHandler):
class ServiceAPIHandler(APIHandler):
@needs_scope('read:services', 'read:services:name', 'read:services:roles')
@needs_scope('read:services', 'read:services:name', 'read:roles:services')
def get(self, service_name):
service = self.services[service_name]
self.write(json.dumps(self.service_model(service)))

View File

@@ -75,10 +75,10 @@ class UserListAPIHandler(APIHandler):
@needs_scope(
'read:users',
'read:users:name',
'read:users:servers',
'read:servers',
'read:users:groups',
'read:users:activity',
'read:users:roles',
'read:roles:users',
)
def get(self):
state_filter = self.get_argument("state", None)
@@ -195,10 +195,10 @@ class UserAPIHandler(APIHandler):
@needs_scope(
'read:users',
'read:users:name',
'read:users:servers',
'read:servers',
'read:users:groups',
'read:users:activity',
'read:users:roles',
'read:roles:users',
)
async def get(self, user_name):
user = self.find_user(user_name)
@@ -297,7 +297,7 @@ class UserAPIHandler(APIHandler):
class UserTokenListAPIHandler(APIHandler):
"""API endpoint for listing/creating tokens"""
@needs_scope('read:users:tokens')
@needs_scope('read:tokens')
def get(self, user_name):
"""Get tokens for a given user"""
user = self.find_user(user_name)
@@ -352,7 +352,7 @@ class UserTokenListAPIHandler(APIHandler):
self._resolve_roles_and_scopes()
user = self.find_user(user_name)
kind = 'user' if isinstance(requester, User) else 'service'
scope_filter = self.get_scope_filter('users:tokens')
scope_filter = self.get_scope_filter('tokens')
if user is None or not scope_filter(user, kind):
raise web.HTTPError(
403,
@@ -417,7 +417,7 @@ class UserTokenAPIHandler(APIHandler):
raise web.HTTPError(404, "Token not found %s", orm_token)
return orm_token
@needs_scope('read:users:tokens')
@needs_scope('read:tokens')
def get(self, user_name, token_id):
""""""
user = self.find_user(user_name)
@@ -426,7 +426,7 @@ class UserTokenAPIHandler(APIHandler):
token = self.find_token_by_id(user, token_id)
self.write(json.dumps(self.token_model(token)))
@needs_scope('users:tokens')
@needs_scope('tokens')
def delete(self, user_name, token_id):
"""Delete a token"""
user = self.find_user(user_name)
@@ -451,7 +451,7 @@ class UserTokenAPIHandler(APIHandler):
class UserServerAPIHandler(APIHandler):
"""Start and stop single-user servers"""
@needs_scope('users:servers')
@needs_scope('servers')
async def post(self, user_name, server_name=''):
user = self.find_user(user_name)
if server_name:
@@ -496,7 +496,7 @@ class UserServerAPIHandler(APIHandler):
self.set_header('Content-Type', 'text/plain')
self.set_status(status)
@needs_scope('users:servers')
@needs_scope('servers')
async def delete(self, user_name, server_name=''):
user = self.find_user(user_name)
options = self.get_json_body()
@@ -569,7 +569,7 @@ class UserAdminAccessAPIHandler(APIHandler):
This handler sets the necessary cookie for an admin to login to a single-user server.
"""
@needs_scope('users:servers')
@needs_scope('servers')
def post(self, user_name):
self.log.warning(
"Deprecated in JupyterHub 0.8."
@@ -625,7 +625,7 @@ class SpawnProgressAPIHandler(APIHandler):
await asyncio.wait([self._finish_future], timeout=self.keepalive_interval)
@needs_scope('read:users:servers')
@needs_scope('read:servers')
async def get(self, user_name, server_name=''):
self.set_header('Cache-Control', 'no-cache')
if server_name is None:

View File

@@ -456,7 +456,7 @@ class AdminHandler(BaseHandler):
@web.authenticated
@needs_scope('users')
@needs_scope('admin:users')
@needs_scope('admin:users:servers')
@needs_scope('admin:servers')
async def get(self):
auth_state = await self.current_user.get_auth_state()
html = await self.render_template(

View File

@@ -33,16 +33,16 @@ def get_default_roles():
'description': 'Elevated privileges (can do anything)',
'scopes': [
'admin:users',
'admin:users:servers',
'users:tokens',
'admin:servers',
'tokens',
'admin:groups',
'read:services',
'read:hub',
'proxy',
'shutdown',
'access:services',
'access:users:servers',
'read:services:roles',
'access:servers',
'read:roles',
],
},
{
@@ -50,7 +50,7 @@ def get_default_roles():
'description': 'Post activity only',
'scopes': [
'users:activity!user',
'access:users:servers!user',
'access:servers!user',
],
},
{
@@ -70,9 +70,10 @@ def expand_self_scope(name):
users:name
users:groups
users:activity
users:servers
users:tokens
access:users:servers
tokens
servers
access:servers
Arguments:
name (str): user name
@@ -87,11 +88,11 @@ def expand_self_scope(name):
'read:users:groups',
'users:activity',
'read:users:activity',
'users:servers',
'read:users:servers',
'users:tokens',
'read:users:tokens',
'access:users:servers',
'servers',
'read:servers',
'tokens',
'read:tokens',
'access:servers',
]
return {"{}!user={}".format(scope, name) for scope in scope_list}

View File

@@ -34,9 +34,9 @@ scope_definitions = {
},
'admin:users': {
'description': 'Read, write, create and delete users and their authentication state, not including their servers or tokens.',
'subscopes': ['admin:users:auth_state', 'users', 'read:users:roles'],
'subscopes': ['admin:auth_state', 'users', 'read:roles:users'],
},
'admin:users:auth_state': {'description': 'Read a users authentication state.'},
'admin:auth_state': {'description': 'Read a users authentication state.'},
'users': {
'description': 'Read and write permissions to user models (excluding servers, tokens and authentication state).',
'subscopes': ['read:users', 'users:activity'],
@@ -52,32 +52,38 @@ scope_definitions = {
'read:users:name': {'description': 'Read names of users.'},
'read:users:groups': {'description': 'Read users group membership.'},
'read:users:activity': {'description': 'Read time of last user activity.'},
'read:users:roles': {'description': 'Read users role assignments.'},
'read:roles': {
'description': 'Read role assignments.',
'subscopes': ['read:roles:users', 'read:roles:services', 'read:roles:groups'],
},
'read:roles:users': {'description': 'Read user role assignments.'},
'read:roles:services': {'description': 'Read service role assignments.'},
'read:roles:groups': {'description': 'Read group role assignments.'},
'users:activity': {
'description': 'Update time of last user activity.',
'subscopes': ['read:users:activity'],
},
'admin:users:servers': {
'admin:servers': {
'description': 'Read, start, stop, create and delete user servers and their state.',
'subscopes': ['admin:users:server_state', 'users:servers'],
'subscopes': ['admin:server_state', 'servers'],
},
'admin:users:server_state': {'description': 'Read and write users server state.'},
'users:servers': {
'admin:server_state': {'description': 'Read and write users server state.'},
'servers': {
'description': 'Start and stop user servers.',
'subscopes': ['read:users:servers'],
'subscopes': ['read:servers'],
},
'read:users:servers': {
'read:servers': {
'description': 'Read users names and their server models (excluding the server state).',
'subscopes': ['read:users:name'],
},
'users:tokens': {
'tokens': {
'description': 'Read, write, create and delete user tokens.',
'subscopes': ['read:users:tokens'],
'subscopes': ['read:tokens'],
},
'read:users:tokens': {'description': 'Read user tokens.'},
'read:tokens': {'description': 'Read user tokens.'},
'admin:groups': {
'description': 'Read and write group information, create and delete groups.',
'subscopes': ['groups', 'read:groups:roles'],
'subscopes': ['groups', 'read:roles:groups'],
},
'groups': {
'description': 'Read and write group information, including adding/removing users to/from groups.',
@@ -88,15 +94,13 @@ scope_definitions = {
'subscopes': ['read:groups:name'],
},
'read:groups:name': {'description': 'Read group names.'},
'read:groups:roles': {'description': 'Read group role assignments.'},
'read:services': {
'description': 'Read service models.',
'subscopes': ['read:services:name'],
},
'read:services:name': {'description': 'Read service names.'},
'read:services:roles': {'description': 'Read service role assignments.'},
'read:hub': {'description': 'Read detailed information about the Hub.'},
'access:users:servers': {
'access:servers': {
'description': 'Access user servers via API or browser.',
},
'access:services': {
@@ -279,7 +283,7 @@ def get_scopes_for(orm_object):
spawner = orm_object.oauth_client.spawner
if spawner:
token_scopes.add(
f"access:users:servers!server={spawner.user.name}/{spawner.name}"
f"access:servers!server={spawner.user.name}/{spawner.name}"
)
else:
service = orm_object.oauth_client.service
@@ -388,7 +392,7 @@ def parse_scopes(scope_list):
"""
Parses scopes and filters in something akin to JSON style
For instance, scope list ["users", "groups!group=foo", "users:servers!server=user/bar", "users:servers!server=user/baz"]
For instance, scope list ["users", "groups!group=foo", "servers!server=user/bar", "servers!server=user/baz"]
would lead to scope model
{
"users":scope.ALL,
@@ -397,7 +401,7 @@ def parse_scopes(scope_list):
"alice"
]
},
"users:servers":{
"servers":{
"server":[
"user/bar",
"user/baz"

View File

@@ -835,8 +835,8 @@ class HubAuthenticated:
which in turn is set by $JUPYTERHUB_OAUTH_SCOPES
Default values include:
- 'access:services', 'access:services!service={service_name}' for services
- 'access:users:servers', 'access:users:servers!user={user}',
'access:users:servers!server={user}/{server_name}'
- 'access:servers', 'access:servers!user={user}',
'access:servers!server={user}/{server_name}'
for single-user servers
If hub_scopes is not used (e.g. JupyterHub 1.x),

View File

@@ -222,8 +222,8 @@ class Spawner(LoggingConfigurable):
@default("oauth_scopes")
def _default_oauth_scopes(self):
return [
f"access:users:servers!server={self.user.name}/{self.name}",
f"access:users:servers!user={self.user.name}",
f"access:servers!server={self.user.name}/{self.name}",
f"access:servers!user={self.user.name}",
]
handler = Any()

View File

@@ -956,7 +956,7 @@ async def test_oauth_page_scope_appearance(
[
'self',
'read:users!user=gawain',
'read:users:tokens',
'read:tokens',
'read:groups!group=mythos',
]
)

View File

@@ -180,13 +180,13 @@ def test_orm_roles_delete_cascade(db):
['admin:users'],
{
'admin:users',
'admin:users:auth_state',
'admin:auth_state',
'users',
'read:users',
'users:activity',
'read:users:name',
'read:users:groups',
'read:users:roles',
'read:roles:users',
'read:users:activity',
},
),
@@ -210,32 +210,32 @@ def test_orm_roles_delete_cascade(db):
'read:users:activity',
},
),
(['read:users:servers'], {'read:users:servers', 'read:users:name'}),
(['read:servers'], {'read:servers', 'read:users:name'}),
(
['admin:groups'],
{
'admin:groups',
'groups',
'read:groups',
'read:groups:roles',
'read:roles:groups',
'read:groups:name',
},
),
(
['admin:groups', 'read:users:servers'],
['admin:groups', 'read:servers'],
{
'admin:groups',
'groups',
'read:groups',
'read:groups:roles',
'read:roles:groups',
'read:groups:name',
'read:users:servers',
'read:servers',
'read:users:name',
},
),
(
['users:tokens!group=hobbits'],
{'users:tokens!group=hobbits', 'read:users:tokens!group=hobbits'},
['tokens!group=hobbits'],
{'tokens!group=hobbits', 'read:tokens!group=hobbits'},
),
],
)
@@ -398,7 +398,7 @@ async def test_delete_roles(db, role_type, rolename, response_type, response):
'users',
'users!user=charlie',
'admin:groups',
'read:users:tokens',
'read:tokens',
],
},
'existing',
@@ -501,8 +501,8 @@ async def test_load_roles_services(tmpdir, request):
'scopes': [
'read:users:name',
'read:users:activity',
'read:users:servers',
'users:servers',
'read:servers',
'servers',
],
'services': ['idle-culler'],
},

View File

@@ -79,9 +79,9 @@ def test_scope_multiple_filters():
def test_scope_parse_server_name():
handler = get_handler_with_scopes(
['users:servers!server=maeby/server1', 'read:users!user=maeby']
['servers!server=maeby/server1', 'read:users!user=maeby']
)
assert _check_scope_access(handler, 'users:servers', user='maeby', server='server1')
assert _check_scope_access(handler, 'servers', user='maeby', server='server1')
class MockAPIHandler:
@@ -99,7 +99,7 @@ class MockAPIHandler:
def user_thing(self, user_name):
return True
@needs_scope('users:servers')
@needs_scope('servers')
def server_thing(self, user_name, server_name):
return True
@@ -140,18 +140,18 @@ def mock_handler():
(['users!user=george'], 'user_thing', ('fake_user',), False),
(['users!user=george'], 'user_thing', ('oscar',), False),
(['users!user=george', 'users!user=oscar'], 'user_thing', ('oscar',), True),
(['users:servers'], 'server_thing', ('user1', 'server_1'), True),
(['users:servers'], 'server_thing', ('user1', ''), True),
(['users:servers'], 'server_thing', ('user1', None), True),
(['servers'], 'server_thing', ('user1', 'server_1'), True),
(['servers'], 'server_thing', ('user1', ''), True),
(['servers'], 'server_thing', ('user1', None), True),
(
['users:servers!server=maeby/bluth'],
['servers!server=maeby/bluth'],
'server_thing',
('maeby', 'bluth'),
True,
),
(['users:servers!server=maeby/bluth'], 'server_thing', ('gob', 'bluth'), False),
(['servers!server=maeby/bluth'], 'server_thing', ('gob', 'bluth'), False),
(
['users:servers!server=maeby/bluth'],
['servers!server=maeby/bluth'],
'server_thing',
('maybe', 'bluth2'),
False,
@@ -488,17 +488,17 @@ async def test_metascope_all_expansion(app, create_user_with_scopes):
@mark.parametrize(
"scopes, can_stop ,num_servers, keys_in, keys_out",
[
(['read:users:servers!user=almond'], False, 2, {'name'}, {'state'}),
(['read:servers!user=almond'], False, 2, {'name'}, {'state'}),
(['admin:users', 'read:users'], False, 0, set(), set()),
(
['read:users:servers!group=nuts', 'users:servers'],
['read:servers!group=nuts', 'servers'],
True,
2,
{'name'},
{'state'},
),
(
['admin:users:server_state', 'read:users:servers'],
['admin:server_state', 'read:servers'],
False,
2,
{'name', 'state'},
@@ -506,8 +506,8 @@ async def test_metascope_all_expansion(app, create_user_with_scopes):
),
(
[
'read:users:servers!server=almond/bianca',
'admin:users:server_state!server=almond/bianca',
'read:servers!server=almond/bianca',
'admin:server_state!server=almond/bianca',
],
False,
1,
@@ -634,8 +634,8 @@ async def test_server_state_access(
),
(
'no_intersection_user_server',
['users:servers!user=y'],
['users:servers!server=x'],
['servers!user=y'],
['servers!server=x'],
set(),
),
(
@@ -704,7 +704,7 @@ async def test_resolve_token_permissions(
},
),
(
{'read:services:roles', 'read:services:name'},
{'read:roles:services', 'read:services:name'},
{'name', 'kind', 'roles', 'admin'},
),
({'read:services:name'}, {'name', 'kind', 'admin'}),
@@ -734,7 +734,7 @@ async def test_service_model_filtering(
},
),
(
{'read:groups:roles', 'read:groups:name'},
{'read:roles:groups', 'read:groups:name'},
{'name', 'kind', 'roles'},
),
({'read:groups:name'}, {'name', 'kind'}),
@@ -761,7 +761,7 @@ async def test_group_model_filtering(
async def test_roles_access(app, create_service_with_scopes, create_user_with_scopes):
user = add_user(app.db, name='miranda')
read_user = create_user_with_scopes('read:users:roles')
read_user = create_user_with_scopes('read:roles:users')
r = await api_request(
app, 'users', user.name, headers=auth_header(app.db, read_user.name)
)
@@ -822,15 +822,15 @@ async def test_roles_access(app, create_service_with_scopes, create_user_with_sc
),
# resolves server under user, without warning
(
set(["read:users:servers!user=abc"]),
set(["read:users:servers!server=abc/xyz"]),
set(["read:users:servers!server=abc/xyz"]),
set(["read:servers!user=abc"]),
set(["read:servers!server=abc/xyz"]),
set(["read:servers!server=abc/xyz"]),
False,
),
# user->server, no match
(
set(["read:users:servers!user=abc"]),
set(["read:users:servers!server=abcd/xyz"]),
set(["read:servers!user=abc"]),
set(["read:servers!server=abcd/xyz"]),
set([]),
False,
),

View File

@@ -128,7 +128,7 @@ async def test_hubauth_token(app, mockservice_url, create_user_with_scopes):
),
(
[
"access:users:servers!user=$service",
"access:servers!user=$service",
],
False,
),

View File

@@ -17,18 +17,18 @@ from .utils import AsyncSession
@pytest.mark.parametrize(
"access_scopes, server_name, expect_success",
[
(["access:users:servers!group=$group"], "", True),
(["access:users:servers!group=other-group"], "", False),
(["access:users:servers"], "", True),
(["access:users:servers"], "named", True),
(["access:users:servers!user=$user"], "", True),
(["access:users:servers!user=$user"], "named", True),
(["access:users:servers!server=$server"], "", True),
(["access:users:servers!server=$server"], "named-server", True),
(["access:users:servers!server=$user/other"], "", False),
(["access:users:servers!server=$user/other"], "some-name", False),
(["access:users:servers!user=$other"], "", False),
(["access:users:servers!user=$other"], "named", False),
(["access:servers!group=$group"], "", True),
(["access:servers!group=other-group"], "", False),
(["access:servers"], "", True),
(["access:servers"], "named", True),
(["access:servers!user=$user"], "", True),
(["access:servers!user=$user"], "named", True),
(["access:servers!server=$server"], "", True),
(["access:servers!server=$server"], "named-server", True),
(["access:servers!server=$user/other"], "", False),
(["access:servers!server=$user/other"], "some-name", False),
(["access:servers!user=$other"], "", False),
(["access:servers!user=$other"], "named", False),
(["access:services"], "", False),
(["self"], "named", False),
([], "", False),