allianceauth-oidc-provider-eveo7 0.1.0b3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- allianceauth_oidc_provider_eveo7-0.1.0b3/LICENSE +21 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/PKG-INFO +484 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/README.md +456 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/__init__.py +3 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/admin.py +35 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/app_settings.py +211 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/apps.py +33 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/auth_hooks.py +1 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/auth_provider.py +361 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/constants.py +31 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/locale/django.pot +137 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/locale/en/LC_MESSAGES/django.mo +0 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/locale/en/LC_MESSAGES/django.po +138 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/locale/ru/LC_MESSAGES/django.mo +0 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/locale/ru/LC_MESSAGES/django.po +145 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/locale/uk/LC_MESSAGES/django.mo +0 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/locale/uk/LC_MESSAGES/django.po +148 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/management/__init__.py +1 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/management/commands/__init__.py +1 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/management/commands/_format.py +112 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/management/commands/oidc_audit_tokens.py +97 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/management/commands/oidc_create_app.py +196 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/management/commands/oidc_revoke_user_tokens.py +128 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/management/commands/oidc_rotate_secret.py +101 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0001_initial.py +130 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0002_alter_allianceauthapplication_options_and_more.py +25 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0003_remove_allianceauthapplication_logo_and_more.py +37 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0004_allianceauthapplication_allowed_origins_and_more.py +38 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0005_alter_allianceauthapplication_authorization_grant_type.py +33 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0006_alter_allianceauthapplication_debug_mode.py +23 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0007_alter_allianceauthapplication_logo_url.py +22 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0008_alter_allianceauthapplication_logo_url.py +28 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/0009_alter_allianceauthapplication_options.py +23 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/migrations/__init__.py +0 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/models.py +69 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/py.typed +0 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/security.py +287 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/signals.py +115 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/tasks.py +48 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/templates/allianceauth_oidc/authorize.html +81 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/templates/allianceauth_oidc/denied.html +77 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/templates/allianceauth_oidc/logout_confirm.html +37 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/templatetags/__init__.py +1 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/templatetags/oidc_tags.py +36 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/urls.py +55 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/utils.py +239 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/allianceauth_oidc/views.py +357 -0
- allianceauth_oidc_provider_eveo7-0.1.0b3/pyproject.toml +187 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 AaronKable
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: allianceauth-oidc-provider-eveo7
|
|
3
|
+
Version: 0.1.0b3
|
|
4
|
+
Summary: Alliance Auth OIDC Provider.
|
|
5
|
+
Author-email: AaronKable <aaronkable@gmail.com>
|
|
6
|
+
Maintainer-email: Boris Talovikov <boris.t.66@gmail.com>
|
|
7
|
+
Requires-Python: >=3.10,<3.14
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: Environment :: Web Environment
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Framework :: Django :: 4.2
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: allianceauth>=4,<5
|
|
22
|
+
Requires-Dist: django-oauth-toolkit>=3.2,<4
|
|
23
|
+
Project-URL: Homepage, https://github.com/6RUN0/allianceauth-oidc-provider
|
|
24
|
+
Project-URL: Source, https://github.com/6RUN0/allianceauth-oidc-provider
|
|
25
|
+
Project-URL: Tracker, https://github.com/6RUN0/allianceauth-oidc-provider/issues
|
|
26
|
+
Project-URL: Upstream, https://github.com/Solar-Helix-Independent-Transport/allianceauth-oidc-provider
|
|
27
|
+
|
|
28
|
+
# allianceauth_oidc
|
|
29
|
+
|
|
30
|
+
> Fork of
|
|
31
|
+
> [Solar-Helix-Independent-Transport/allianceauth-oidc-provider](https://github.com/Solar-Helix-Independent-Transport/allianceauth-oidc-provider)
|
|
32
|
+
> maintained at
|
|
33
|
+
> [6RUN0/allianceauth-oidc-provider](https://github.com/6RUN0/allianceauth-oidc-provider) — adds
|
|
34
|
+
> wire-level integration tests, an OIDC Conformance Suite harness, operator CLI commands,
|
|
35
|
+
> EVE-specific claims, runtime localisation (en / ru / uk), and a Russian-language
|
|
36
|
+
> [README.ru.md](README.ru.md).
|
|
37
|
+
|
|
38
|
+
A thin policy / auditing layer on top of
|
|
39
|
+
[`django-oauth-toolkit`](https://django-oauth-toolkit.readthedocs.io/) that turns an
|
|
40
|
+
[Alliance Auth](https://gitlab.com/allianceauth/allianceauth) installation into an OpenID Connect /
|
|
41
|
+
OAuth2 provider.
|
|
42
|
+
|
|
43
|
+
- [Overview](#overview)
|
|
44
|
+
- [Install](#install)
|
|
45
|
+
- [Configuration](#configuration)
|
|
46
|
+
- [Reference](#reference)
|
|
47
|
+
- [Operations](#operations)
|
|
48
|
+
- [Integrations](#integrations)
|
|
49
|
+
- [Development](#development)
|
|
50
|
+
|
|
51
|
+
## Overview
|
|
52
|
+
|
|
53
|
+
DOT does the OAuth / OIDC protocol work; this app adds Alliance-Auth-specific access control,
|
|
54
|
+
claim mapping, safe logging, and a custom `Application` model with `state` / `group` whitelists.
|
|
55
|
+
|
|
56
|
+
Every authorization-code exchange runs through three independent gates. Each layer is intentional;
|
|
57
|
+
removing any of them opens a hole, which is why the regression tests exercise each layer
|
|
58
|
+
separately.
|
|
59
|
+
|
|
60
|
+
```mermaid
|
|
61
|
+
sequenceDiagram
|
|
62
|
+
participant RP as Relying Party
|
|
63
|
+
participant Auth as /o/authorize/
|
|
64
|
+
participant DOT as django-oauth-toolkit
|
|
65
|
+
participant Token as /o/token/
|
|
66
|
+
participant Validator as AllianceAuthOAuth2Validator
|
|
67
|
+
|
|
68
|
+
RP->>Auth: GET/POST authorize, response_type=code
|
|
69
|
+
Note over Auth: Layer 1 — dispatch() runs global access_oidc + state/group whitelist
|
|
70
|
+
Auth->>DOT: forward (if policy passes)
|
|
71
|
+
DOT-->>RP: 302 redirect with auth code
|
|
72
|
+
RP->>Token: POST code + client_secret
|
|
73
|
+
Token->>Validator: validate_code(code, request)
|
|
74
|
+
Note over Validator: Layer 2 — re-checks state/group on exchange
|
|
75
|
+
Validator-->>Token: ok / invalid_grant
|
|
76
|
+
Token->>Validator: save_bearer_token(...)
|
|
77
|
+
Note over Validator: Layer 3 — last guard, PermissionDenied becomes invalid_grant
|
|
78
|
+
Validator-->>RP: 200 access_token + id_token
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
The plugin slots into a standard Alliance Auth project tree. AA's `auth-helper` generates this
|
|
84
|
+
layout (`myauth` is the project name you picked at `auth-helper init` time — substitute it
|
|
85
|
+
everywhere below):
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
myauth/
|
|
89
|
+
├── manage.py
|
|
90
|
+
└── myauth/
|
|
91
|
+
├── settings/
|
|
92
|
+
│ ├── base.py # AA-shipped, do not edit
|
|
93
|
+
│ └── local.py # YOUR overrides — every setting in this guide goes here
|
|
94
|
+
├── urls.py # YOUR project URL patterns
|
|
95
|
+
└── ...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If you have a different layout, the file *names* are what matters: locate the file that holds
|
|
99
|
+
`INSTALLED_APPS` (settings) and the file that holds `urlpatterns` (URL conf), and apply the
|
|
100
|
+
edits below to those.
|
|
101
|
+
|
|
102
|
+
1. Install the fork from PyPI. The fork is published under
|
|
103
|
+
`allianceauth-oidc-provider-eveo7` to avoid colliding with the upstream
|
|
104
|
+
`allianceauth-oidc-provider` release; the import path
|
|
105
|
+
(`allianceauth_oidc`) is unchanged, so settings/imports stay drop-in
|
|
106
|
+
compatible:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
pip install allianceauth-oidc-provider-eveo7
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Do **not** install both `allianceauth-oidc-provider` and
|
|
113
|
+
`allianceauth-oidc-provider-eveo7` into the same environment — both
|
|
114
|
+
ship into the `allianceauth_oidc/` directory and pip will refuse the
|
|
115
|
+
second install with a file conflict. Remove the upstream package
|
|
116
|
+
first if it is present.
|
|
117
|
+
|
|
118
|
+
Tracking `main` directly is also supported:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
pip install "git+https://github.com/6RUN0/allianceauth-oidc-provider.git@current"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
2. **In `myauth/settings/local.py`**, append to `INSTALLED_APPS`:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
INSTALLED_APPS += [
|
|
128
|
+
"allianceauth_oidc",
|
|
129
|
+
"oauth2_provider",
|
|
130
|
+
]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
3. **In the same `myauth/settings/local.py`**, append the DOT + policy-validator configuration.
|
|
134
|
+
The whole block is wrapped in an `if "allianceauth_oidc" in INSTALLED_APPS …` guard so the
|
|
135
|
+
file stays valid even if the plugin is uninstalled later. Each key is explained in
|
|
136
|
+
[Configuration → OAUTH2_PROVIDER keys](#oauth2_provider-keys); paste this verbatim and tune
|
|
137
|
+
to taste:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from pathlib import Path
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
"allianceauth_oidc" in INSTALLED_APPS
|
|
144
|
+
and "oauth2_provider" in INSTALLED_APPS
|
|
145
|
+
):
|
|
146
|
+
OAUTH2_PROVIDER_APPLICATION_MODEL = "allianceauth_oidc.AllianceAuthApplication"
|
|
147
|
+
OAUTH2_PROVIDER = {
|
|
148
|
+
"OIDC_ENABLED": True,
|
|
149
|
+
"OIDC_RSA_PRIVATE_KEY": Path("/path/to/key").read_text(),
|
|
150
|
+
"OAUTH2_VALIDATOR_CLASS": "allianceauth_oidc.auth_provider.AllianceAuthOAuth2Validator",
|
|
151
|
+
"APPLICATION_ADMIN_CLASS": "allianceauth_oidc.admin.ApplicationAdmin",
|
|
152
|
+
"SCOPES": {
|
|
153
|
+
"openid": "User Profile",
|
|
154
|
+
"email": "Registered email",
|
|
155
|
+
"profile": "Main Character affiliation and Auth groups",
|
|
156
|
+
},
|
|
157
|
+
"PKCE_REQUIRED": True,
|
|
158
|
+
"ROTATE_REFRESH_TOKEN": True,
|
|
159
|
+
"REFRESH_TOKEN_REUSE_PROTECTION": True,
|
|
160
|
+
"ACCESS_TOKEN_EXPIRE_SECONDS": 60,
|
|
161
|
+
"REFRESH_TOKEN_EXPIRE_SECONDS": 24 * 60 * 60,
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
4. **In `myauth/urls.py`**, mount the OIDC URL conf under `/o/`:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from .settings.local import INSTALLED_APPS
|
|
169
|
+
|
|
170
|
+
if "allianceauth_oidc" in INSTALLED_APPS and "oauth2_provider" in INSTALLED_APPS:
|
|
171
|
+
urlpatterns.append(
|
|
172
|
+
path(
|
|
173
|
+
"o/",
|
|
174
|
+
include("allianceauth_oidc.urls", namespace="oauth2_provider"),
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
5. From the project root (`myauth/`), run the migrations and restart Auth:
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
python manage.py migrate
|
|
183
|
+
supervisorctl restart myauth: # or your process supervisor's equivalent
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
> [!NOTE]
|
|
187
|
+
> If you customise the public login template
|
|
188
|
+
> (`authentication/templates/public/login.html`), keep the SSO link's `next` parameter
|
|
189
|
+
> URL-encoded — without it the OAuth flow drops query parameters after redirect (e.g.
|
|
190
|
+
> `client_id` is lost):
|
|
191
|
+
>
|
|
192
|
+
> ```html
|
|
193
|
+
> <a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{ request.GET.next | urlencode }}{% endif %}"></a>
|
|
194
|
+
> ```
|
|
195
|
+
|
|
196
|
+
## Configuration
|
|
197
|
+
|
|
198
|
+
The previous section already shows the paste-ready settings block. This section is the per-key
|
|
199
|
+
reference for tuning. Two surfaces:
|
|
200
|
+
|
|
201
|
+
- DOT's `OAUTH2_PROVIDER` dict — required for the protocol to work.
|
|
202
|
+
- Our optional `ALLIANCEAUTH_OIDC_*` Django settings — for logging / claim shape / portrait
|
|
203
|
+
template. All of them have sensible defaults.
|
|
204
|
+
|
|
205
|
+
Both go into `myauth/settings/local.py` next to the install snippet.
|
|
206
|
+
|
|
207
|
+
### OAUTH2_PROVIDER keys
|
|
208
|
+
|
|
209
|
+
| Setting | Recommended value | Why |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| `OAUTH2_PROVIDER_APPLICATION_MODEL` | `"allianceauth_oidc.AllianceAuthApplication"` | **Required.** Without this the state / group access policy is silently bypassed. Set as a top-level Django setting, not inside `OAUTH2_PROVIDER`. |
|
|
212
|
+
| `OIDC_ENABLED` | `True` | **Required.** Turns on DOT's OIDC layer (discovery, JWKS, id_token signing). |
|
|
213
|
+
| `OIDC_RSA_PRIVATE_KEY` | `Path("/path/to/key").read_text()` | **Required.** RSA key DOT uses to sign id_tokens. See [DOT docs](https://django-oauth-toolkit.readthedocs.io/en/stable/oidc.html#creating-rsa-private-key) for generation. |
|
|
214
|
+
| `OAUTH2_VALIDATOR_CLASS` | `"allianceauth_oidc.auth_provider.AllianceAuthOAuth2Validator"` | **Required.** Implements the three-layer policy and AA-specific claims. |
|
|
215
|
+
| `APPLICATION_ADMIN_CLASS` | `"allianceauth_oidc.admin.ApplicationAdmin"` | **Required.** AA-aware admin for the custom `Application` model. |
|
|
216
|
+
| `SCOPES` | `{"openid": "...", "email": "...", "profile": "..."}` | **Required.** Scopes shown on the consent screen. Strings are user-facing labels. |
|
|
217
|
+
| `PKCE_REQUIRED` | `True` | Recommended per RFC 9700. Disable only if you control all clients and they support PKCE. |
|
|
218
|
+
| `ROTATE_REFRESH_TOKEN` | `True` | Recommended. Mints a fresh refresh token on every use; old one is invalidated. |
|
|
219
|
+
| `REFRESH_TOKEN_REUSE_PROTECTION` | `True` | Recommended. Replay-defence per RFC 6819 §5.2.2.3 — a refresh token presented twice revokes the entire token family. |
|
|
220
|
+
| `ACCESS_TOKEN_EXPIRE_SECONDS` | `60` | Trade-off: shorter access-token TTL forces RPs to refresh more often (faster reaction to revocation, more token-endpoint round-trips); longer means slower revocation propagation but lighter traffic. |
|
|
221
|
+
| `REFRESH_TOKEN_EXPIRE_SECONDS` | `24*60*60` | Per-deployment risk tolerance. |
|
|
222
|
+
|
|
223
|
+
### Custom settings (ALLIANCEAUTH_OIDC_*)
|
|
224
|
+
|
|
225
|
+
| Setting | Default | Effect |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `ALLIANCEAUTH_OIDC_LOG_MASKED_SECRETS` | `False` | Replace `<redacted>` with masked fragments (`he…il`) in app debug logs. Enable only if log storage is restricted. |
|
|
228
|
+
| `ALLIANCEAUTH_OIDC_LOG_MASK_HEAD` | `2` | Visible characters at the start of a masked secret. |
|
|
229
|
+
| `ALLIANCEAUTH_OIDC_LOG_MASK_TAIL` | `2` | Visible characters at the end. |
|
|
230
|
+
| `ALLIANCEAUTH_OIDC_EVE_CLAIM_PREFIX` | `"eve_"` | Prefix for the EVE-specific claims. `""` removes the prefix (collision risk); any other value namespaces them. |
|
|
231
|
+
| `ALLIANCEAUTH_OIDC_EVE_CLAIM_SCOPE` | `"profile"` | OIDC scope that gates the EVE claims. **Class-level binding** — changing it requires an Auth restart. |
|
|
232
|
+
| `ALLIANCEAUTH_OIDC_PORTRAIT_URL_TEMPLATE` | `"https://images.evetech.net/characters/{character_id}/portrait?size={size}"` | URL template for the `picture` claim. Both `{character_id}` and `{size}` placeholders are required; a malformed template skips the claim with a warning. |
|
|
233
|
+
| `ALLIANCEAUTH_OIDC_PORTRAIT_SIZE` | `128` | Pixel size requested from the portrait service. EVE supports 32 / 64 / 128 / 256 / 512 / 1024. |
|
|
234
|
+
|
|
235
|
+
### Periodic cleanup of expired tokens (Celery Beat)
|
|
236
|
+
|
|
237
|
+
The `clear_expired_tokens` task is shipped but **not** scheduled by default — operators add it
|
|
238
|
+
to `CELERYBEAT_SCHEDULE` in `myauth/settings/local.py`:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from celery.schedules import crontab
|
|
242
|
+
|
|
243
|
+
CELERYBEAT_SCHEDULE["allianceauth_oidc_clear_expired_tokens"] = {
|
|
244
|
+
"task": "allianceauth_oidc.clear_expired_tokens",
|
|
245
|
+
"schedule": crontab(minute=0, hour="*/2"), # every 2 hours
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The task is idempotent (deletes only already-expired rows); broker authentication is the defence
|
|
250
|
+
against unauthorised re-runs.
|
|
251
|
+
|
|
252
|
+
## Reference
|
|
253
|
+
|
|
254
|
+
### Endpoints
|
|
255
|
+
|
|
256
|
+
| Endpoint | Path | Notes |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| Authorization | `/o/authorize/` | Policy-aware (three-layer gate). Overridden in this app. |
|
|
259
|
+
| Token | `/o/token/` | Audit signal + safe debug logging. Overridden in this app. |
|
|
260
|
+
| UserInfo | `/o/userinfo/` | DOT default. |
|
|
261
|
+
| Discovery | `/o/.well-known/openid-configuration/` | DOT default. |
|
|
262
|
+
| JWKS | `/o/.well-known/jwks.json` | DOT default. |
|
|
263
|
+
| Token revocation | `/o/revoke_token/` | RFC 7009. DOT default. |
|
|
264
|
+
| Token introspection | `/o/introspect/` | RFC 7662. DOT default. |
|
|
265
|
+
| RP-initiated logout | `/o/logout/` | DOT default. |
|
|
266
|
+
| Issuer (`iss` claim) | `https://your.host/o/` | Whatever your discovery URL resolves to. |
|
|
267
|
+
|
|
268
|
+
### Claims
|
|
269
|
+
|
|
270
|
+
Every standard OIDC claim is emitted under the scope conventionally associated with it; the
|
|
271
|
+
`groups` and `eve_*` claims are AA-specific and ride the `profile` scope by default so RPs that
|
|
272
|
+
already request `openid profile` get them without extra setup.
|
|
273
|
+
|
|
274
|
+
| Claim | Source | Scope |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `sub` | `User.pk` (DOT default) | `openid` |
|
|
277
|
+
| `email` | `user.email` | `email` |
|
|
278
|
+
| `name` | `user.profile.main_character.character_name` | `profile` |
|
|
279
|
+
| `picture` | Portrait URL for the main character (see `ALLIANCEAUTH_OIDC_PORTRAIT_URL_TEMPLATE`) | `profile` |
|
|
280
|
+
| `groups` | `user.groups[*].name`, with `user.profile.state.name` appended | `profile` |
|
|
281
|
+
| `locale` | `user.profile.language` | `profile` |
|
|
282
|
+
| `eve_character_id` | `main_character.character_id` | `profile` (set by `ALLIANCEAUTH_OIDC_EVE_CLAIM_SCOPE`) |
|
|
283
|
+
| `eve_corporation_id` / `_name` / `_ticker` | `main_character.corporation_*` | same |
|
|
284
|
+
| `eve_alliance_id` / `_name` / `_ticker` | `main_character.alliance_*` (omitted for NPC corps without an alliance) | same |
|
|
285
|
+
|
|
286
|
+
The `eve_*` prefix is configurable. Empty values are **omitted** from the payload, not emitted as
|
|
287
|
+
`null`, so RPs that key off `claim in payload` behave consistently.
|
|
288
|
+
|
|
289
|
+
The `groups` claim is capped at **256 entries** to keep id_tokens under the typical 8 KB
|
|
290
|
+
header / cookie limit. The state name is appended **after** truncation so consumers that rely on
|
|
291
|
+
the state being present don't lose it silently. Override the cap by subclassing
|
|
292
|
+
`AllianceAuthOAuth2Validator` and overriding the `MAX_GROUPS_IN_CLAIM` class attribute.
|
|
293
|
+
|
|
294
|
+
### Audit signal
|
|
295
|
+
|
|
296
|
+
Every successful token-issuance fires the `oidc_token_issued` Django signal
|
|
297
|
+
(`allianceauth_oidc.signals`). The default receiver writes a redacted audit log entry; connect
|
|
298
|
+
your own receiver to forward to a SIEM, write to a separate audit table, or push into an alerting
|
|
299
|
+
pipeline:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from django.dispatch import receiver
|
|
303
|
+
from allianceauth_oidc.signals import oidc_token_issued
|
|
304
|
+
|
|
305
|
+
@receiver(oidc_token_issued)
|
|
306
|
+
def forward_to_siem(sender, *, app, user, request, body, **kwargs):
|
|
307
|
+
# `body` is already redacted (build_oidc_debug_meta); never re-add raw secrets.
|
|
308
|
+
...
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Don't extend `TokenView` to do this — the signal is the documented integration point and survives
|
|
312
|
+
DOT version bumps that change view internals.
|
|
313
|
+
|
|
314
|
+
## Operations
|
|
315
|
+
|
|
316
|
+
### Operator commands
|
|
317
|
+
|
|
318
|
+
Four `manage.py` commands cover the day-2 operational tasks without opening the admin UI. All
|
|
319
|
+
accept `--format=table|json|csv`; destructive commands honour `--dry-run`.
|
|
320
|
+
|
|
321
|
+
| Command | Purpose | Destructive? | Key flags |
|
|
322
|
+
|---|---|---|---|
|
|
323
|
+
| `oidc_create_app` | Bootstrap a new OIDC application (CI / Ansible-friendly). Prints the raw `client_secret` once. | yes | `--name`, `--user-id`, `--redirect-uri`, `--state`, `--group`, `--client-type`, `--grant-type`, `--debug-mode` |
|
|
324
|
+
| `oidc_rotate_secret` | Rotate `client_secret` on an existing app. Existing tokens stay valid until expiry. | yes | `--client-id`, `--dry-run` |
|
|
325
|
+
| `oidc_revoke_user_tokens` | Revoke every active access + refresh token for a user (off-boarding, compromise response). Idempotent. | yes | `--username`, `--dry-run` |
|
|
326
|
+
| `oidc_audit_tokens` | Read-only listing of active tokens. | no | `--username`, `--client-id`, `--include-expired` |
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
python manage.py oidc_create_app \
|
|
330
|
+
--name="Grafana" --user-id=1 \
|
|
331
|
+
--redirect-uri="https://grafana.example/login/generic_oauth" \
|
|
332
|
+
--state=Member --group=Operators --format=json
|
|
333
|
+
|
|
334
|
+
python manage.py oidc_rotate_secret --client-id=abc123 --dry-run
|
|
335
|
+
python manage.py oidc_revoke_user_tokens --username=alice
|
|
336
|
+
python manage.py oidc_audit_tokens --client-id=abc123 --format=csv
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`create_app` writes a Django admin `LogEntry` on success so the action is visible in `/admin/`'s
|
|
340
|
+
history without code changes; the destructive commands log at `INFO` / `WARNING`.
|
|
341
|
+
|
|
342
|
+
### Debug logging
|
|
343
|
+
|
|
344
|
+
Per-application `Debug Mode` (toggled in the admin) escalates token-flow logs from `DEBUG` to
|
|
345
|
+
`INFO`. Raw token values and secrets are **never** logged; the `_LOG_MASKED_SECRETS` knob (see
|
|
346
|
+
[Custom settings](#custom-settings-allianceauth_oidc_)) controls whether they appear as
|
|
347
|
+
`<redacted>` or masked fragments.
|
|
348
|
+
|
|
349
|
+
When debugging an app, look for lines like:
|
|
350
|
+
|
|
351
|
+
```text
|
|
352
|
+
[01/Jan/2099 00:00:00] INFO [extensions.allianceauth_oidc.views:78] OIDC DEBUG token issued
|
|
353
|
+
app_id=1 client_id=abc123 user_id=42
|
|
354
|
+
meta={'grant_type': 'authorization_code', ..., 'access_token': '<redacted>', 'id_token': '<redacted>'}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Paste the (separately captured) `id_token` into <https://jwt.io/> to inspect claims. The two
|
|
358
|
+
non-obvious fields:
|
|
359
|
+
|
|
360
|
+
- `iss` — issuer; must match the value the RP has configured exactly.
|
|
361
|
+
- `sub` — the user PK; useful for "why did this user end up here?" triage.
|
|
362
|
+
|
|
363
|
+
If you need the public key to verify the signature on jwt.io and have only the private key on
|
|
364
|
+
disk:
|
|
365
|
+
|
|
366
|
+
```sh
|
|
367
|
+
ssh-keygen -y -e -m pem -f /path/to/key
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Operational hardening (operator responsibility)
|
|
371
|
+
|
|
372
|
+
The provider implements the OAuth2 / OIDC protocol semantics; runtime hardening below is
|
|
373
|
+
intentionally left to the deployment so it integrates with whatever edge / infra you already
|
|
374
|
+
operate.
|
|
375
|
+
|
|
376
|
+
- **Rate-limit `/o/token/` and `/o/authorize/`.** Neither endpoint is rate-limited by this app;
|
|
377
|
+
brute-force defence belongs at the edge (nginx `limit_req`, Cloudflare, a WAF) or via
|
|
378
|
+
`django-ratelimit` in your Auth deployment. Without it, a network-level attacker can probe
|
|
379
|
+
`client_secret` / `code` / `refresh_token` values at line speed.
|
|
380
|
+
- **Authenticate the Celery broker.** `clear_expired_tokens` is published to whichever broker
|
|
381
|
+
your AA install uses; if that broker is reachable by untrusted parties, a malicious task
|
|
382
|
+
submission can repeatedly invoke cleanup. The task is idempotent, but broker auth + network
|
|
383
|
+
ACLs are the defensive layer.
|
|
384
|
+
- **Security headers.** This app does not set CSP / HSTS / `X-Frame-Options` /
|
|
385
|
+
`X-Content-Type-Options`; rely on Alliance Auth's middleware stack and Django's `SECURE_*`
|
|
386
|
+
settings to add them globally.
|
|
387
|
+
|
|
388
|
+
## Integrations
|
|
389
|
+
|
|
390
|
+
### Register an application
|
|
391
|
+
|
|
392
|
+
In `/admin/allianceauth_oidc/`, create an `Alliance Auth application`:
|
|
393
|
+
|
|
394
|
+
| Field | Value | Notes |
|
|
395
|
+
|---|---|---|
|
|
396
|
+
| `User` | any (e.g. `1`) | Owner — passed through to DOT but not used in this app's policy. |
|
|
397
|
+
| `Client type` | `confidential` | Public clients are out of scope; we don't ship a public-client recipe. |
|
|
398
|
+
| `Authorization grant type` | `Authorization code` | The only flow this app's policy is hardened against. |
|
|
399
|
+
| `Client secret` | auto-generated | Save it before clicking save when `HASH_CLIENT_SECRET` is on (default). |
|
|
400
|
+
| `Algorithm` | `RSA with SHA-2 256` | Matches `OIDC_RSA_PRIVATE_KEY`. |
|
|
401
|
+
| `States` / `Groups` | whitelist | Empty ⇒ open; non-empty ⇒ user must be in a listed state OR group. |
|
|
402
|
+
|
|
403
|
+
Every user who logs into any registered application also needs the global
|
|
404
|
+
`allianceauth_oidc.access_oidc` permission. Without it the dispatch-layer gate (Layer 1) returns
|
|
405
|
+
`PermissionDenied` regardless of state / group whitelist.
|
|
406
|
+
|
|
407
|
+
### Grafana
|
|
408
|
+
|
|
409
|
+
Tested without group-to-team mapping (group → team mapping requires Grafana Cloud / Enterprise
|
|
410
|
+
and is out of scope here).
|
|
411
|
+
|
|
412
|
+
```ini
|
|
413
|
+
[server]
|
|
414
|
+
root_url = <URL of your grafana server>
|
|
415
|
+
|
|
416
|
+
[auth.generic_oauth]
|
|
417
|
+
enabled = true
|
|
418
|
+
name = <Your Auth Name>
|
|
419
|
+
allow_sign_up = true
|
|
420
|
+
client_id = <client_id>
|
|
421
|
+
client_secret = <unhashed client_secret>
|
|
422
|
+
scopes = openid,email,profile
|
|
423
|
+
empty_scopes = false
|
|
424
|
+
email_attribute_path = email
|
|
425
|
+
name_attribute_path = name
|
|
426
|
+
auth_url = https://<your.auth.url>/o/authorize/
|
|
427
|
+
token_url = https://<your.auth.url>/o/token/
|
|
428
|
+
api_url = https://<your.auth.url>/o/userinfo/
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### WikiJS
|
|
432
|
+
|
|
433
|
+
Pre-create the groups you want WikiJS users to land in on the AA side; WikiJS will map them at
|
|
434
|
+
login. (Create an `Administrators` group to grant the wiki admin pages.)
|
|
435
|
+
|
|
436
|
+
| WikiJS field | Value |
|
|
437
|
+
|---|---|
|
|
438
|
+
| Skip User Profile | off |
|
|
439
|
+
| Email claim | `email` |
|
|
440
|
+
| Display Name Claim | `name` |
|
|
441
|
+
| Map Groups | on |
|
|
442
|
+
| Groups Claim | `groups` |
|
|
443
|
+
| Allow Self Registration | on |
|
|
444
|
+
|
|
445
|
+
## Development
|
|
446
|
+
|
|
447
|
+
### Nox sessions
|
|
448
|
+
|
|
449
|
+
| Session | Purpose | In default `nox` run? |
|
|
450
|
+
|---|---|---|
|
|
451
|
+
| `lint` | pre-commit (ruff, mypy, basedpyright, …) | yes |
|
|
452
|
+
| `tests` | Django test suite (parallel) | yes |
|
|
453
|
+
| `coverage` | tests + term / HTML / XML coverage reports | no |
|
|
454
|
+
| `typecheck` | mypy + basedpyright (subset of `lint`, run separately for fast feedback) | no |
|
|
455
|
+
| `audit` | pip-audit | no |
|
|
456
|
+
| `markdown_lint` | rumdl + lychee + vale (each tool optional) | no |
|
|
457
|
+
| `makemessages` / `compilemessages` | i18n catalogue refresh + compile | no |
|
|
458
|
+
| `makemigrations` | generate Django migrations under test settings | no |
|
|
459
|
+
| `integration` | wire-level mock-RP via `LiveServerTestCase` | no |
|
|
460
|
+
| `conformance` | OIDC Conformance Suite via docker-compose | no |
|
|
461
|
+
|
|
462
|
+
### Integration tests (`nox -s integration`)
|
|
463
|
+
|
|
464
|
+
`tests/test_integration_mock_rp.py` boots a `LiveServerTestCase` and walks the OIDC code flow
|
|
465
|
+
with `requests` + `jwcrypto`, validating the id_token signature against a JWKS retrieved over the
|
|
466
|
+
wire. This catches regressions the standard `nox -s tests` set cannot — Django's test client
|
|
467
|
+
short-circuits the WSGI layer, so absolute-URL bugs in `iss` / `jwks_uri` and Bearer-header /
|
|
468
|
+
cookie issues only surface here.
|
|
469
|
+
|
|
470
|
+
The session forces `--parallel=1`: `LiveServerTestCase` shares its DB connection with the WSGI
|
|
471
|
+
thread, which does not survive the test runner's `fork()`.
|
|
472
|
+
|
|
473
|
+
### Conformance Suite (`nox -s conformance`)
|
|
474
|
+
|
|
475
|
+
Runs the [OpenID Foundation Conformance Suite](https://gitlab.com/openid/conformance-suite)
|
|
476
|
+
against the provider via Docker Compose: MongoDB + the suite + a provider container. The default
|
|
477
|
+
plan is driven through the suite's REST API by `tests/conformance/run_plan.py`.
|
|
478
|
+
|
|
479
|
+
This is the level above our own integration tests — it catches spec edge cases that our
|
|
480
|
+
regression tests wouldn't think to check. Run before tagging a release. See
|
|
481
|
+
[tests/conformance/README.md](tests/conformance/README.md) for prerequisites, the manual /
|
|
482
|
+
iterative workflow, configuration overrides, and the list of known conformance findings to
|
|
483
|
+
triage.
|
|
484
|
+
|