mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-14 21:43:01 +00:00
Refactored scope names and updated docs to reflect this
This commit is contained in:
@@ -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
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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'],
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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']
|
||||
}
|
||||
]
|
||||
|
@@ -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
|
||||
]
|
||||
}
|
||||
|
@@ -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",
|
||||
"...",
|
||||
]
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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")
|
||||
|
@@ -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
|
||||
|
@@ -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)))
|
||||
|
@@ -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)))
|
||||
|
@@ -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:
|
||||
|
@@ -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(
|
||||
|
@@ -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}
|
||||
|
||||
|
@@ -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 user’s authentication state.'},
|
||||
'admin:auth_state': {'description': 'Read a user’s 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"
|
||||
|
@@ -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),
|
||||
|
@@ -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()
|
||||
|
@@ -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',
|
||||
]
|
||||
)
|
||||
|
@@ -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'],
|
||||
},
|
||||
|
@@ -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,
|
||||
),
|
||||
|
@@ -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,
|
||||
),
|
||||
|
@@ -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),
|
||||
|
Reference in New Issue
Block a user