Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dedicated doc on details of allowing access #729

Merged
merged 7 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Topic guides go more in-depth on a particular topic.
:maxdepth: 2
:caption: Topic guides

topic/allowing
topic/extending
```

Expand Down
142 changes: 142 additions & 0 deletions docs/source/topic/allowing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
(allowing)=

# Allowing access to your JupyterHub

OAuthenticator is about deferring **authentication** to an external source,
assuming your users all have accounts _somewhere_.
But many of these sources (e.g. Google, GitHub) have _lots_ of users, and you don't want _all_ of them to be able to use your hub.
This is where **authorization** comes in.

In OAuthenticator, authorization is represented via configuration options that start with `allow` or `block`.

There are also lots of OAuth providers, and as a result, lots of ways to tell OAuthenticator who should be allowed to access your hub.

## Default behavior: nobody is allowed!

The default behavior of OAuthenticator (starting with version 16) is to block all users unless explicitly authorized via _some_ `allow` configuration.
If you want anyone to be able to use your hub, you must specify at least one `allow` configuration.

```{versionchanged} 16
consideRatio marked this conversation as resolved.
Show resolved Hide resolved
Prior to OAuthenticator 16, `allow_all` was _implied_ if no other `allow` configuration was specified.
Starting from 16, `allow_all` can only be enabled explicitly.
```

## Allowing access

There are several `allow_` configuration options, to grant access to users according to different rules.

When you have only one `allow` configuration, the behavior is generally unambiguous: anyone allowed by the rule can login to the Hub, while anyone not explicitly allowed cannot login.
However, once you start adding additional `allow` configuration, there is some ambiguity in how multiple rules are combined.

```{important}
Additional allow rules **can only grant access**, meaning they only _expand_ who has access to your hub.
Adding an `allow` rule cannot prevent access granted by another `allow` rule.
To block access, use `block` configuration.
```

That is, if a user is granted access by _any_ `allow` configuration, they are allowed.
An allow rule cannot _exclude_ access granted by another `allow` rule.

An example:

```python
c.GitHubOAuthenticator.allowed_users = {"mensah", "art"}
c.GitHubOAuthenticator.allowed_organizations = {"preservation"}
```

means that the users `mensah` and `art` are allowed, _and_ any member of the `preservation` organization are allowed.
Any user that doesn't meet any of the allow rules will not be allowed.

| user | allowed | reason |
| ----- | ------- | ------------------------------------------------------- |
| art | True | in `allowed_users` |
| amena | True | member of `preservation` |
| tlacy | False | not in `allowed_users` and not member of `preservation` |

### `allow_all`

The first and simplest way to allow access is to any user who can successfully authenticate:

```python
c.OAuthenticator.allow_all = True
```

This is appropriate when you use an authentication provider (e.g. an institutional single-sign-on provider), where everyone who has an account in the provider should have access to your Hub.
It may also be appropriate for unadvertised short-lived hubs, e.g. dedicated hubs for workshops that will be shutdown after a day, where you may decide it is acceptable to allow anyone who finds your hub to login.

If `allow_all` is enabled, no other `allow` configuration will have any effect.

```{seealso}
Configuration documentation for {attr}`.OAuthenticator.allow_all`
```

### `allowed_users`

This is top-level JupyterHub configuration, shared by all Authenticators.
This specifies a list of users that are allowed by name.
This is the simplest authorization mechanism when you have a small group of users whose usernames you know:

```python
c.OAuthenticator.allowed_users = {"mensah", "ratthi"}
```

If this is your only configuration, only these users will be allowed, no others.

Note that any additional usernames in the deprecated `admin_users` configuration will also be allowed to login.

```{seealso}
Configuration documentation for {attr}`.OAuthenticator.allowed_users`
```

### `allow_existing_users`

JupyterHub can allow you to add and remove users while the Hub is running via the admin page.
If you add or remove users this way, they will be added to the JupyterHub database, but they will not be able to login unless they are also granted access via an `allow` rule.

To enable managing users via the admin panel, set

```python
c.OAuthenticator.allow_existing_users = True
```

```{warning}
Enabling `allow_existing_users` means that _removing_ users from any explicit allow mechanisms will no longer revoke their access.
Once the user has been added to the database, the only way to revoke their access to the hub is to remove the user from JupyterHub entirely, via the admin page.
```

```{seealso}
Configuration documentation for {attr}`.OAuthenticator.allow_existing_users`
```

### Provider-specific rules

Each OAuthenticator provider may have its own provider-specific rules to allow groups of users access, such as:

- {attr}`.GitHubOAuthenticator.allowed_organizations`
- {attr}`.GitLabOAuthenticator.allowed_gitlab_groups`
- {attr}`.GlobusOAuthenticator.allowed_globus_groups`
- {attr}`.GoogleOAuthenticator.allowed_google_groups`

## Blocking Access

It's possible that you want to limit who has access to your Hub to less than all of the users granted access by your `allow` configuration.
`block` configuration always has higher priority than `allow` configuration, so if a user is both allowed _and_ blocked, they will not be able to login.

The only `block` configuration is the base Authenticators `block_users`,
a set of usernames that will not be allowed to login.

### Revoking previously-allowed access

Any users who have logged in previously will be present in the JupyterHub database.
Removing a user's login permissions (e.g. removing them from a GitLab project when using {attr}`.GitLabOAuthenticator.project_ids`) only prevents future logins;
it does not remove the user from the JupyterHub database.
This means that:

1. any API tokens that the user still has access to will continue to be valid, and can continue to be used
2. any still-valid browser sessions will continue to be logged in.

```{important}
To fully remove a user's access to JupyterHub,
their login permission must be revoked _and_ their user fully deleted from the Hub,
e.g. via the admin page.
```
49 changes: 29 additions & 20 deletions oauthenticator/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ class OAuthenticator(Authenticator):
help="""
Allow all authenticated users to login.

Overrides all other `allow` configuration.

.. versionadded:: 16.0
""",
)
Expand All @@ -279,37 +281,29 @@ class OAuthenticator(Authenticator):
help="""
Allow existing users to login.

An existing user is a user in JupyterHub's database of users, and it
includes all users that has previously logged in.
Enable this if you want to manage user access via the JupyterHub admin page (/hub/admin).

With this enabled, all users present in the JupyterHub database are allowed to login.
This has the effect of any user who has _previously_ been allowed to login
via any means will continue to be allowed until the user is deleted via the /hub/admin page
or REST API.

.. warning::

Before enabling this you should review the existing users in the
JupyterHub admin panel at `/hub/admin`. You may find users existing
there because they have once been declared in config such as
`allowed_users` or once been allowed to sign in.
there because they have previously been declared in config such as
`allowed_users` or allowed to sign in.

.. warning::

When this is enabled and you are to remove access for one or more
users allowed via other config options, you must make sure that they
are not part of the database of users still. This can be tricky to do
When this is enabled and you wish to remove access for one or more
users previously allowed, you must make sure that they
are removed from the jupyterhub database. This can be tricky to do
if you stop allowing a group of externally managed users for example.

With this enabled, JupyterHub admin users can visit `/hub/admin` or use
JupyterHub's REST API to add and remove users as a way to allow them
access.

The username for existing users must match the normalized username
returned by the authenticator. When creating users, only lowercase
letters should be used unless `MWOAuthenticator` is used.

.. note::

Allowing existing users is done by adding existing users on startup
and newly created users to the `allowed_users` set. Due to that, you
can't rely on this config to independently allow existing users if
you for example would reset `allowed_users` after startup.
JupyterHub's REST API to add and remove users to manage who can login.

.. versionadded:: 16.0

Expand Down Expand Up @@ -1086,3 +1080,18 @@ def __init__(self, **kwargs):
self._deprecated_oauth_trait, names=list(self._deprecated_oauth_aliases)
)
super().__init__(**kwargs)


# patch allowed_users help string to match our definition
# base Authenticator class help string gives the wrong impression
# when combined with other allow options
OAuthenticator.class_traits()[
"allowed_users"
].help = """
Copy link
Member Author

@minrk minrk Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base Authenticator.allowed_users docstring gives the wrong impression of how it relates to other allow config, since the base Authenticator doesn't have other allow config. Patching this way allows us to modify the docstring, which in turn shows up in our docs, without redeclaring the trait.

Set of usernames that should be allowed to login.

If unspecified, grants no access. You must set at least one other `allow` configuration
if any users are to have permission to access the Hub.

Any usernames in `admin_users` will also be allowed to login.
"""