# Authenticators

## Users and roles

Grader Service authorizes actions through per-lecture roles. A role links a user to a lecture with a scope. The scope is the `Scope` enum (`grader_service.orm.takepart.Scope`):

| Value | Scope        |
|-------|--------------|
| 0     | `student`    |
| 1     | `tutor`      |
| 2     | `instructor` |
| 3     | `admin`      |

Grader Service does not ship a dedicated admin command for creating users or assigning roles. Users and roles are populated from the configured authenticator instead, in one or both of these ways:

- **`post_auth_hook`**: the authenticator's post-authentication hook receives the authenticated user's groups and interprets any group of the form `lecture_code:scope` (for example `intro:instructor`) as a role assignment, creating the lecture and the role if they do not yet exist and updating the role otherwise. This is how group-based identity providers (e.g. an LMS or institutional IdP) drive enrollment. Because the hook looks up the scope by name, any `Scope` value (`student`, `tutor`, `instructor`, `admin`) can be assigned this way.
- **`c.GraderService.load_roles`**: a static mapping of lecture code to role definitions that is loaded into the database at service startup. Each role definition lists its `members` (usernames or user dicts) and the `role` (`student`, `tutor` or `instructor`).

A reference implementation of both mechanisms, including a full `post_auth_hook`, is in the development configuration at `dev/local/token/grader_service_config.py`. Use it as a template for your own deployment config.

## DummyAuthenticator

```python
from grader_service.auth.dummy import DummyAuthenticator
c.GraderService.authenticator_class = DummyAuthenticator

c.DummyAuthenticator.password = 'test'
# c.Authenticator.allowed_users = {'admin1', 'admin2', 'instructor', 'tutor', 'student1', 'student2'}
c.Authenticator.allow_all = True
# c.Authenticator.admin_users = {'admin1', 'admin2'}
```

## PAMAuthenticator

Uses [PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module)-based authentication to log in local system users.

```python
from grader_service.auth.pam import PAMAuthenticator
c.GraderService.authenticator_class = PAMAuthenticator
```

## OAuthenticator

Example configuration for `grader_service_config.py` using Google OAuth.

```python
from grader_service.auth.oauth2 import OAuthenticator
c.GraderService.authenticator_class = OAuthenticator

c.OAuthenticator.client_id = "<client_id>"
c.OAuthenticator.client_secret = "<client_secret>"
c.OAuthenticator.authorize_url = "https://accounts.google.com/o/oauth2/auth"
c.OAuthenticator.token_url = "https://oauth2.googleapis.com/token"
c.OAuthenticator.scope = ['email']

c.OAuthenticator.userdata_url = "https://www.googleapis.com/oauth2/v1/userinfo"
c.OAuthenticator.username_claim = "email"
# c.OAuthenticator.admin_users = {'admin1', 'admin2'}
```

## LTI 1.3 Authenticator

Log in using the LTI authentication flow. This example configuration is based on a local Moodle setup based on [moodle-docker](https://github.com/moodlehq/moodle-docker).

```python
c.LTI13Authenticator.issuer = "http://localhost:8000"
c.LTI13Authenticator.authorize_url = "http://localhost:8000/mod/lti/auth.php"
# The platform's JWKS endpoint url providing public key sets used to verify the ID token
c.LTI13Authenticator.jwks_endpoint = "http://localhost:8000/mod/lti/certs.php"
# The external tool's client id as represented within the platform (LMS)
c.LTI13Authenticator.client_id = ["<client_id>"]  # generated by Moodle etc. after creating external tool
```

In Moodle itself the external tool configuration is as follows, given that the Grader Service runs on `localhost:4010` and JupyterHub runs on `localhost:8080`.

Tool URL:

```
http://localhost:4010/?next=http%3A%2F%2Flocalhost%3A8080
```

This becomes the `target_link_uri` of the login request. If this contains a `next` URL query parameter, it is extracted and used by the Grader Service to redirect after the authentication flow.
In this case we redirect to `http://localhost:8080`, which is the JupyterHub.

LTI version:

```
LTI 1.3
```

Public key type:

```
Keyset URL
```

The `Public keyset` field can be left empty.

Initiate login URL:

```
http://localhost:4010/services/grader/lti13/oauth_login
```

The login handler of the Grader Service.

Redirection URI(s):

```
http://localhost:4010/services/grader/lti13/oauth_callback
```

The callback handler for the OAuth flow.

## Configuration for JupyterHub

After authentication is complete the Grader Service will redirect to the JupyterHub (as specified in the `Tool URL`), from which the user is authenticated via OAuth.
JupyterHub now uses the Grader Service as an IDP using its OAuthenticator implementation.

```python
from oauthenticator.generic import GenericOAuthenticator

c.JupyterHub.authenticator_class = GenericOAuthenticator
c.GenericOAuthenticator.oauth_callback_url = "http://localhost:8080/hub/oauth_callback"

c.GenericOAuthenticator.client_id = "hub"
c.GenericOAuthenticator.client_secret = "hub"
c.GenericOAuthenticator.authorize_url = "http://localhost:4010/services/grader/api/oauth2/authorize"
c.GenericOAuthenticator.token_url = "http://localhost:4010/services/grader/api/oauth2/token"

c.GenericOAuthenticator.userdata_url = "http://localhost:4010/services/grader/api/user"
c.GenericOAuthenticator.username_claim = "name"
```

In the Grader Service config the JupyterHub has to be registered as a client.

```python
c.GraderService.oauth_clients = [{
    'client_id': 'hub',
    'client_secret': 'hub',
    'redirect_uri': 'http://localhost:8080/hub/oauth_callback'
}]
```
