Generate REST API scope descriptions from source code

This commit is contained in:
0mar
2021-06-15 13:49:24 +02:00
parent 7a3b237bb3
commit ceed989e77
3 changed files with 145 additions and 64 deletions

View File

@@ -17,40 +17,53 @@ securityDefinitions:
flow: accessCode flow: accessCode
authorizationUrl: "/hub/api/oauth2/authorize" # what are the absolute URIs here? is oauth2 correct here or shall we use just authorizations? 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" tokenUrl: "/hub/api/oauth2/token"
scopes: # Todo: Generate based on scope table scopes: # Generated based on scope table in jupyterhub/scopes.py
(noscope): Allows only to identify the requesting entity (no_scope): Identify the owner of the requesting entity.
self: Metascope, grants access to user's own resources; resolves to (no scope) for services. self:
all: Metascope, valid for tokens only. Grants access to all resources of the token-owning entity. The users own resources _(metascope for users, resolves to (no_scope)
admin:users: Grants read, write, create and delete access to users and their authentication state but not their servers or tokens. for services)_
admin:auth_state: Grants access to users' authentication state only. all: Everything that the token-owning entity can access _(metascope for tokens)_
users: Grants read and write permissions to users' models apart from servers, tokens and authentication state. admin:users:
users:activity: Grants access to read and post users' activity only. Read, write, create and delete users and their authentication state,
users:activity!user=username: Update a single user's activity (example horizontal filter). not including their servers or tokens.
read:users: Read-only access to users' models apart from servers, tokens and authentication state. admin:auth_state: Read a users authentication state.
read:users!user=username: As above limited to a specific user (example horizontal filter). users:
read:users:name: Read-only access to user names. Read and write permissions to user models (excluding servers, tokens
read:roles:users: Read-only access to user role assignments. and authentication state).
read:users:groups: Read-only access to a list of users' group names. read:users:
read:users:activity: Read-only access to users' activity. Read user models (excluding including servers, tokens and authentication
read:users:activity!group=groupname: Read-only access to specific group's users' activity (example horizontal filter). state).
admin:servers: Grants read, start/stop, create and delete permissions to users' servers and their state. read:users:name: Read names of users.
admin:server_state: Grants access to servers' state only. read:users:groups: Read users group membership.
servers: Allows for starting/stopping users' servers in addition to read access to their models. Does not include the server state. read:users:activity: Read time of last user activity.
servers!server=servername: Limits the above to a specific server (example horizontal filter). read:roles: Read role assignments.
read:servers: Read-only access to users' server models. Does not include the server state. read:roles:users: Read user role assignments.
tokens: Grants read, write, create and delete permissions to users' tokens. read:roles:services: Read service role assignments.
read:tokens: Read-only access to users' tokens. read:roles:groups: Read group role assignments.
admin:groups: Grants read, write, create and delete access to groups. users:activity: Update time of last user activity.
groups: Grants read and write permissions to groups, including adding/removing users to/from groups. admin:servers: Read, start, stop, create and delete user servers and their state.
read:roles:groups: Read-only access to group roles assignments admin:server_state: Read and write users server state.
groups!group=groupname: As above limited to a specific group only (example horizontal filter) servers: Start and stop user servers.
read:groups: Read-only access to groups. read:servers:
read:services: Read-only access to service models. Read users names and their server models (excluding the server
read:services:name: Read-only access to service names. state).
read:roles:services: Read-only access to a list of service roles names. tokens: Read, write, create and delete user tokens.
read:hub: Read-only access to detailed information about JupyterHub. read:tokens: Read user tokens.
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. admin:groups: Read and write group information, create and delete groups.
shutdown: Grants access to shutdown the Hub. groups:
Read and write group information, including adding/removing users to/from
groups.
read:groups: Read group models.
read:groups:name: Read group names.
read:services: Read service models.
read:services:name: Read service names.
read:hub: Read detailed information about the Hub.
access:servers: Access user servers via API or browser.
access:services: Access services via API or browser.
proxy:
Read information about the proxys routing table, sync the Hub with the
proxy and notify the Hub about a new proxy.
shutdown: Shutdown the hub.
security: # global security, do we want to keep only the apiKey (token: []), change to only oauth2 (with scope self) or have both (either can be used)? security: # global security, do we want to keep only the apiKey (token: []), change to only oauth2 (with scope self) or have both (either can be used)?
- token: [] - token: []
- oauth2: - oauth2:
@@ -106,7 +119,9 @@ paths:
properties: properties:
class: class:
type: string type: string
description: The Python class currently active for JupyterHub Authentication description:
The Python class currently active for JupyterHub
Authentication
version: version:
type: string type: string
description: The version of the currently active Authenticator description: The version of the currently active Authenticator
@@ -115,7 +130,9 @@ paths:
properties: properties:
class: class:
type: string type: string
description: The Python class currently active for spawning single-user notebook servers description:
The Python class currently active for spawning single-user
notebook servers
version: version:
type: string type: string
description: The version of the currently active Spawner description: The version of the currently active Spawner
@@ -253,16 +270,22 @@ paths:
- name: body - name: body
in: body in: body
required: true required: true
description: Updated user info. At least one key to be updated (name or admin) is required. description:
Updated user info. At least one key to be updated (name or admin)
is required.
schema: schema:
type: object type: object
properties: properties:
name: name:
type: string type: string
description: the new name (optional, if another key is updated i.e. admin) description:
the new name (optional, if another key is updated i.e.
admin)
admin: admin:
type: boolean type: boolean
description: update admin (optional, if another key is updated i.e. name) description:
update admin (optional, if another key is updated i.e.
name)
responses: responses:
"200": "200":
description: The updated user info description: The updated user info
@@ -285,9 +308,9 @@ paths:
/users/{name}/activity: /users/{name}/activity:
post: post:
summary: Notify Hub of activity for a given user. summary: Notify Hub of activity for a given user.
description: Notify the Hub of activity by the user, description:
e.g. accessing a service or (more likely) Notify the Hub of activity by the user, e.g. accessing a service
actively using a server. or (more likely) actively using a server.
security: security:
- oauth2: - oauth2:
- users:activity - users:activity
@@ -369,7 +392,9 @@ paths:
"201": "201":
description: The user's notebook server has started description: The user's notebook server has started
"202": "202":
description: The user's notebook server has not yet started, but has been requested description:
The user's notebook server has not yet started, but has been
requested
delete: delete:
summary: Stop a user's server summary: Stop a user's server
security: security:
@@ -385,7 +410,9 @@ paths:
"204": "204":
description: The user's notebook server has stopped description: The user's notebook server has stopped
"202": "202":
description: The user's notebook server has not yet stopped as it is taking a while to stop description:
The user's notebook server has not yet stopped as it is taking
a while to stop
/users/{name}/servers/{server_name}: /users/{name}/servers/{server_name}:
post: post:
summary: Start a user's single-user named-server notebook server summary: Start a user's single-user named-server notebook server
@@ -420,7 +447,9 @@ paths:
"201": "201":
description: The user's notebook named-server has started description: The user's notebook named-server has started
"202": "202":
description: The user's notebook named-server has not yet started, but has been requested description:
The user's notebook named-server has not yet started, but has
been requested
delete: delete:
summary: Stop a user's named-server summary: Stop a user's named-server
security: security:
@@ -453,7 +482,9 @@ paths:
"204": "204":
description: The user's notebook named-server has stopped description: The user's notebook named-server has stopped
"202": "202":
description: The user's notebook named-server has not yet stopped as it is taking a while to stop description:
The user's notebook named-server has not yet stopped as it
is taking a while to stop
/users/{name}/tokens: /users/{name}/tokens:
parameters: parameters:
- name: name - name: name
@@ -491,7 +522,9 @@ paths:
properties: properties:
expires_in: expires_in:
type: number type: number
description: lifetime (in seconds) after which the requested token will expire. description:
lifetime (in seconds) after which the requested token will
expire.
note: note:
type: string type: string
description: A note attached to the token for future bookkeeping description: A note attached to the token for future bookkeeping
@@ -712,9 +745,7 @@ paths:
summary: Get a service by name summary: Get a service by name
security: security:
- oauth2: - oauth2:
- read:services - read:services - read:services:name - read:roles:services
- read:services:name
- read:roles:services
parameters: parameters:
- name: name - name: name
description: service name description: service name
@@ -729,7 +760,9 @@ paths:
/proxy: /proxy:
get: get:
summary: Get the proxy's routing table summary: Get the proxy's routing table
description: A convenience alias for getting the routing table directly from the proxy description:
A convenience alias for getting the routing table directly from
the proxy
security: security:
- oauth2: - oauth2:
- proxy - proxy
@@ -755,7 +788,9 @@ paths:
description: Routing table description: Routing table
schema: schema:
type: object type: object
description: configurable-http-proxy routing table (see configurable-http-proxy docs for details) description:
configurable-http-proxy routing table (see configurable-http-proxy
docs for details)
post: post:
summary: Force the Hub to sync with the proxy summary: Force the Hub to sync with the proxy
security: security:
@@ -774,7 +809,9 @@ paths:
- name: body - name: body
in: body in: body
required: true required: true
description: Any values that have changed for the new proxy. All keys are optional. description:
Any values that have changed for the new proxy. All keys are
optional.
schema: schema:
type: object type: object
properties: properties:
@@ -845,7 +882,9 @@ paths:
/authorizations/cookie/{cookie_name}/{cookie_value}: /authorizations/cookie/{cookie_name}/{cookie_value}:
get: get:
summary: Identify a user from a cookie summary: Identify a user from a cookie
description: Used by single-user notebook servers to hand off cookie authentication to the Hub description:
Used by single-user notebook servers to hand off cookie authentication
to the Hub
parameters: parameters:
- name: cookie_name - name: cookie_name
in: path in: path
@@ -955,10 +994,14 @@ paths:
properties: properties:
proxy: proxy:
type: boolean type: boolean
description: Whether the proxy should be shutdown as well (default from Hub config) description:
Whether the proxy should be shutdown as well (default from
Hub config)
servers: servers:
type: boolean type: boolean
description: Whether users' notebook servers should be shutdown as well (default from Hub config) description:
Whether users' notebook servers should be shutdown as well
(default from Hub config)
responses: responses:
"202": "202":
description: Shutdown successful description: Shutdown successful
@@ -1009,13 +1052,17 @@ definitions:
auth_state: auth_state:
type: string type: string
#TODO: will there be predefined states? Should it rather be object instead of string? #TODO: will there be predefined states? Should it rather be object instead of string?
description: Authentication state of the user. Only available with admin:users:auth_state scope. None otherwise. description:
Authentication state of the user. Only available with admin:users:auth_state
scope. None otherwise.
Server: Server:
type: object type: object
properties: properties:
name: name:
type: string type: string
description: The server's name. The user's default server has an empty name ('') description:
The server's name. The user's default server has an empty name
('')
ready: ready:
type: boolean type: boolean
description: | description: |
@@ -1046,10 +1093,15 @@ definitions:
description: UTC timestamp last-seen activity on this server. description: UTC timestamp last-seen activity on this server.
state: state:
type: object type: object
description: Arbitrary internal state from this server's spawner. Only available on the hub's users list or get-user-by-name method, and only with admin:users:server_state scope. None otherwise. description:
Arbitrary internal state from this server's spawner. Only available
on the hub's users list or get-user-by-name method, and only with admin:users:server_state
scope. None otherwise.
user_options: user_options:
type: object type: object
description: User specified options for the user's spawned instance of a single-user server. description:
User specified options for the user's spawned instance of a single-user
server.
Group: Group:
type: object type: object
properties: properties:
@@ -1104,7 +1156,9 @@ definitions:
properties: properties:
token: token:
type: string type: string
description: The token itself. Only present in responses to requests for a new token. description:
The token itself. Only present in responses to requests for a
new token.
id: id:
type: string type: string
description: The id of the API token. Used for modifying or deleting the token. description: The id of the API token. Used for modifying or deleting the token.
@@ -1121,7 +1175,9 @@ definitions:
type: string type: string
note: note:
type: string type: string
description: A note about the token, typically describing what it was created for. description:
A note about the token, typically describing what it was created
for.
created: created:
type: string type: string
format: date-time format: date-time

View File

@@ -1,11 +1,14 @@
import os import os
from collections import defaultdict from collections import defaultdict
from pathlib import Path
from pytablewriter import MarkdownTableWriter from pytablewriter import MarkdownTableWriter
from ruamel.yaml import YAML
from jupyterhub.scopes import scope_definitions from jupyterhub.scopes import scope_definitions
HERE = os.path.abspath(os.path.dirname(__file__)) HERE = os.path.abspath(os.path.dirname(__file__))
PARENT = Path(HERE).parent.parent.absolute()
class ScopeTableGenerator: class ScopeTableGenerator:
@@ -64,7 +67,7 @@ class ScopeTableGenerator:
doc_description = self.scopes[scopename].get('doc_description', '') doc_description = self.scopes[scopename].get('doc_description', '')
if doc_description: if doc_description:
description = doc_description description = doc_description
table_row = [f"{md_indent*depth}`{scopename}`", description] table_row = [f"{md_indent * depth}`{scopename}`", description]
table_rows.append(table_row) table_rows.append(table_row)
for subscope in scope_pairs[scopename]: for subscope in scope_pairs[scopename]:
if subscope: if subscope:
@@ -76,7 +79,7 @@ class ScopeTableGenerator:
return table_rows return table_rows
def write_table(self): def write_table(self):
"""Generates the scope table in markdown format and writes it into scope-table.md file""" """Generates the scope table in markdown format and writes it into `scope-table.md`"""
filename = f"{HERE}/scope-table.md" filename = f"{HERE}/scope-table.md"
table_name = "" table_name = ""
headers = ["Scope", "Grants permission to:"] headers = ["Scope", "Grants permission to:"]
@@ -92,10 +95,30 @@ class ScopeTableGenerator:
"Run 'make clean' before 'make html' to ensure the built scopes.html contains latest scope table changes." "Run 'make clean' before 'make html' to ensure the built scopes.html contains latest scope table changes."
) )
def write_api(self):
"""Generates the API description in markdown format and writes it into `rest-api.yml`"""
filename = f"{PARENT}/rest-api.yml"
yaml = YAML(typ='rt')
yaml.preserve_quotes = True
scope_dict = {}
with open(filename, 'r+') as f:
content = yaml.load(f.read())
f.seek(0)
for scope in self.scopes:
description = self.scopes[scope]['description']
doc_description = self.scopes[scope].get('doc_description', '')
if doc_description:
description = doc_description
scope_dict[scope] = description
content['securityDefinitions']['oauth2']['scopes'] = scope_dict
yaml.dump(content, f)
f.truncate()
def main(): def main():
table_generator = ScopeTableGenerator() table_generator = ScopeTableGenerator()
table_generator.write_table() table_generator.write_table()
table_generator.write_api()
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -22,6 +22,8 @@ from tornado.log import app_log
from . import orm from . import orm
from . import roles from . import roles
"""when modifying the scope definitions, make sure that `docs/source/rbac/generate-scope-table.py` is run
so that changes are reflected in the documentation and REST API description."""
scope_definitions = { scope_definitions = {
'(no_scope)': {'description': 'Identify the owner of the requesting entity.'}, '(no_scope)': {'description': 'Identify the owner of the requesting entity.'},
'self': { 'self': {