Scopes in JupyterHub

A scope has a syntax-based design that reveals which resources it provides access to. Resources are objects with a type, associated data, relationships to other resources, and a set of methods that operate on them (see RESTful API documentation for more information).

<resource> in the RBAC scope design refers to the resource name in the JupyterHub’s API endpoints in most cases. For instance, <resource> equal to users corresponds to JupyterHub’s API endpoints beginning with /users.

Scope conventions

  • <resource>
    The top-level <resource> scopes, such as users or groups, grant read, write, and list 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>
    Limits permissions to read-only operations on single resources.

  • list:<resource>
    Read-only access to listing endpoints. Use read:<resource>:<subresource> to control what fields are returned.

  • admin:<resource>
    Grants additional permissions such as create/delete on the corresponding resource in addition to read and write permissions.

  • access:<resource>
    Grants access permissions to the <resource> via API or browser.

  • <resource>:<subresource>
    The vertically filtered 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>
    Horizontal filtering is implemented by the !<object>=<objectname>scope structure. A resource (or sub-resource) can be filtered based on user, server, group or service name. For instance, <resource>!user=charlie limits access to only return resources of user charlie.
    Only one filter per scope is allowed, but filters for the same scope have an additive effect; a larger filter can be used by supplying the scope multiple times with different filters.

By adding a scope to an existing role, all role bearers will gain the associated permissions.

Metascopes

Metascopes do not follow the general scope syntax. Instead, a metascope resolves to a set of scopes, which can refer to different resources, based on their owning entity. In JupyterHub, there are currently two metascopes:

  1. default user scope self, and

  2. default token scope all.

Default user scope

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.

  • 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.

  • access:servers!user=gerard which allows the user to access their own servers via API or browser.

The self scope is only valid for user entities. In other cases (e.g., for services) it resolves to an empty set of scopes.

Default token scope

The token metascope all covers the same scopes as the token owner’s scopes during requests. For example, if a token owner has roles containing the scopes read:groups and read:users, the all scope resolves to the set of scopes {read:groups, read:users}.

If the token owner has default user role, the all scope resolves to self, which will subsequently be expanded to include all the user-specific scopes (or empty set in the case of services).

If the token owner is a member of any group with roles, the group scopes will also be included in resolving the all scope.

Horizontal filtering

Horizontal filtering, also called resource filtering, is the concept of reducing the payload of an API call to cover only the subset of the resources that the scopes of the client provides them access to. Requested resources are filtered based on the filter of the corresponding scope. For instance, if a service requests a user list (guarded with scope read:users) with a role that only contains scopes read:users!user=hannah and read:users!user=ivan, the returned list of user models will be an intersection of all users and the collection {hannah, ivan}. In case this intersection is empty, the API call returns an HTTP 404 error, regardless if any users exist outside of the clients scope filter collection.

In case a user resource is being accessed, any scopes with group filters will be expanded to filters for each user in those groups.

!user filter

The !user filter is a special horizontal filter that strictly refers to the “owner only” scopes, where owner is a user entity. The filter resolves internally into !user=<ownerusername> ensuring that only the owner’s resources may be accessed through the associated scopes.

For example, the server role assigned by default to server tokens contains access:servers!user and users:activity!user scopes. This allows the token to access and post activity of only the servers owned by the token owner.

The filter can be applied to any scope.

Vertical filtering

Vertical filtering, also called attribute filtering, is the concept of reducing the payload of an API call to cover only the attributes of the resources that the scopes of the client provides them access to. This occurs when the client scopes are subscopes of the API endpoint that is called. For instance, if a client requests a user list with the only scope being read:users:groups, the returned list of user models will contain only a list of groups per user. In case the client has multiple subscopes, the call returns the union of the data the client has access to.

The payload of an API call can be filtered both horizontally and vertically simultaneously. For instance, performing an API call to the endpoint /users/ with the scope users:name!user=juliette returns a payload of [{name: 'juliette'}] (provided that this name is present in the database).

Available scopes

Table below lists all available scopes and illustrates their hierarchy. Indented scopes indicate subscopes of the scope(s) above them.

There are four exceptions to the general scope conventions:

  • read:users:name is a subscope of both read:users and read:servers.
    The read:servers scope requires access to the user name (server owner) due to named servers distinguished internally in the form !server=username/servername.

  • read:users:activity is a subscope of both read:users and users:activity.
    Posting activity via the users:activity, which is not included in users scope, needs to check the last valid activity of the user.

  • read:roles:users is a subscope of both read:roles and admin:users.
    Admin privileges to the users resource include the information about user roles.

  • read:roles:groups is a subscope of both read:roles and admin:groups.
    Similar to the read:roles:users above.

Table 1. Available scopes and their hierarchy

Scope

Grants permission to:

(no_scope)

Identify the owner of the requesting entity.

self

The user’s own resources (metascope for users, resolves to (no_scope) for services)

all

Everything that the token-owning entity can access (metascope for tokens)

admin:users

Read, write, create and delete users and their authentication state, not including their servers or tokens.

   admin:auth_state

Read a user’s authentication state.

   users

Read and write permissions to user models (excluding servers, tokens and authentication state).

      read:users

Read user models (excluding including servers, tokens and authentication state).

         read:users:name

Read names of users.

         read:users:groups

Read users’ group membership.

         read:users:activity

Read time of last user activity.

      list:users

List users, including at least their names.

         read:users:name

Read names of users.

      users:activity

Update time of last user activity.

         read:users:activity

Read time of last user activity.

   read:roles:users

Read user role assignments.

   delete:users

Delete users.

read:roles

Read role assignments.

   read:roles:users

Read user role assignments.

   read:roles:services

Read service role assignments.

   read:roles:groups

Read group role assignments.

admin:servers

Read, start, stop, create and delete user servers and their state.

   admin:server_state

Read and write users’ server state.

   servers

Start and stop user servers.

      read:servers

Read users’ names and their server models (excluding the server state).

         read:users:name

Read names of users.

      delete:servers

Stop and delete users’ servers.

tokens

Read, write, create and delete user tokens.

   read:tokens

Read user tokens.

admin:groups

Read and write group information, create and delete groups.

   groups

Read and write group information, including adding/removing users to/from groups.

      read:groups

Read group models.

         read:groups:name

Read group names.

      list:groups

List groups, including at least their names.

         read:groups:name

Read group names.

   read:roles:groups

Read group role assignments.

   delete:groups

Delete groups.

list:services

List services, including at least their names.

   read:services:name

Read service 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 proxy’s routing table, sync the Hub with the proxy and notify the Hub about a new proxy.

shutdown

Shutdown the hub.

Caution

Note that only the horizontal filtering can be added to scopes to customize them.
Metascopes self and all, <resource>, <resource>:<subresource>, read:<resource>, admin:<resource>, and access:<resource> scopes are predefined and cannot be changed otherwise.

Scopes and APIs

The scopes are also listed in the JupyterHub REST API documentation. Each API endpoint has a list of scopes which can be used to access the API; if no scopes are listed, the API is not authenticated and can be accessed without any permissions (i.e., no scopes).

Listed scopes by each API endpoint reflect the “lowest” permissions required to gain any access to the corresponding API. For example, posting user’s activity (POST /users/:name/activity) needs users:activity scope. If scope users is passed during the request, the access will be granted as the required scope is a subscope of the users scope. If, on the other hand, read:users:activity scope is passed, the access will be denied.