Keycloak shenanigans
All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 17s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 3s

This commit is contained in:
2026-02-25 22:24:44 +01:00
parent 88ff6ddae5
commit 4ad03403aa
6 changed files with 29 additions and 20 deletions

View File

@@ -47,7 +47,7 @@ Go to **Clients → labhelper → Client scopes** tab → click the dedicated sc
|---|---|---|
| Name | `groups` | Label for this mapper |
| Token Claim Name | `groups` | The claim name the app reads from the token |
| Full group path | Off | Sends `Lab Administrators` instead of `/Lab Administrators`. The app strips leading slashes anyway, but this is cleaner |
| Full group path | Off | Sends `LabHelper Administrators` instead of `/LabHelper Administrators`. The app strips leading slashes anyway, but this is cleaner |
| Add to ID token | On | |
| Add to access token | On | |
| Add to userinfo | On | The app fetches userinfo after the token exchange |
@@ -56,9 +56,9 @@ Go to **Clients → labhelper → Client scopes** tab → click the dedicated sc
Go to **Groups** (left sidebar) and create these three groups with exactly these names — they map to the existing Django groups:
- `Lab Administrators` — gets `is_staff=True` in Django (admin access)
- `Lab Staff`
- `Lab Viewers`
- `LabHelper Administrators` — gets `is_staff=True` in Django (admin access)
- `LabHelper Staff`
- `LabHelper Viewers`
### 6. Ensure users have an email address
@@ -120,7 +120,7 @@ Overrides `OIDCAuthenticationBackend` to:
- Use `preferred_username` from Keycloak as the Django username
- Set `first_name` and `last_name` from `given_name` / `family_name` claims
- Sync group memberships on every login — if a user is added to or removed from a Keycloak group, it takes effect at their next login
- Set `is_staff=True` for members of `Lab Administrators` (grants Django admin access)
- Set `is_staff=True` for members of `LabHelper Administrators` (grants Django admin access)
`django.contrib.auth.backends.ModelBackend` is kept as a fallback so the Django admin login form still works with a local username/password (useful for emergency superuser access without Keycloak).

View File

@@ -18,6 +18,7 @@ data:
OIDC_OP_BASE_URL: "https://sso.baumann.gr/realms/homelab"
OIDC_RP_CLIENT_ID: "labhelper"
LOGIN_REDIRECT_URL: "index"
LOGOUT_REDIRECT_URL: "login"
LOGOUT_REDIRECT_URL: "/login/"
OIDC_AUTHENTICATION_FAILURE_REDIRECT_URL: "/login/"
GUNICORN_OPTS: "--access-logfile -"
IMAGE_TAG: "0.077"
IMAGE_TAG: "0.078"

View File

@@ -27,7 +27,7 @@ spec:
mountPath: /data
containers:
- name: web
image: git.baumann.gr/adebaumann/labhelper:0.077
image: git.baumann.gr/adebaumann/labhelper:0.078
imagePullPolicy: Always
ports:
- containerPort: 8000
@@ -117,6 +117,11 @@ spec:
configMapKeyRef:
name: django-config
key: LOGOUT_REDIRECT_URL
- name: OIDC_AUTHENTICATION_FAILURE_REDIRECT_URL
valueFrom:
configMapKeyRef:
name: django-config
key: OIDC_AUTHENTICATION_FAILURE_REDIRECT_URL
- name: GUNICORN_OPTS
valueFrom:
configMapKeyRef:

View File

@@ -2,16 +2,16 @@ from mozilla_django_oidc.auth import OIDCAuthenticationBackend
from django.contrib.auth.models import Group
# Keycloak group name → Django group name mapping.
# Keycloak may send group paths with a leading slash (e.g. "/Lab Administrators");
# Keycloak may send group paths with a leading slash (e.g. "/LabHelper Administrators");
# these are stripped before comparison.
KEYCLOAK_GROUP_MAP = {
'Lab Administrators': 'Lab Administrators',
'Lab Staff': 'Lab Staff',
'Lab Viewers': 'Lab Viewers',
'LabHelper Administrators': 'LabHelper Administrators',
'LabHelper Staff': 'LabHelper Staff',
'LabHelper Viewers': 'LabHelper Viewers',
}
# Members of these groups receive is_staff=True (Django admin access)
STAFF_GROUPS = {'Lab Administrators'}
STAFF_GROUPS = {'LabHelper Administrators'}
class KeycloakOIDCBackend(OIDCAuthenticationBackend):
@@ -35,7 +35,7 @@ class KeycloakOIDCBackend(OIDCAuthenticationBackend):
user.first_name = claims.get('given_name', user.first_name)
user.last_name = claims.get('family_name', user.last_name)
# Keycloak sends group paths like "/Lab Administrators"; normalise them.
# Keycloak sends group paths like "/LabHelper Administrators"; normalise them.
raw_groups = claims.get('groups', [])
keycloak_groups = {g.lstrip('/') for g in raw_groups}

View File

@@ -9,9 +9,9 @@ class Command(BaseCommand):
self.stdout.write('Creating default users and groups...')
groups = {
'Lab Administrators': 'Full access to all lab functions',
'Lab Staff': 'Can view and search items, add things to boxes',
'Lab Viewers': 'Read-only access to view and search',
'LabHelper Administrators': 'Full access to all lab functions',
'LabHelper Staff': 'Can view and search items, add things to boxes',
'LabHelper Viewers': 'Read-only access to view and search',
}
for group_name, description in groups.items():
@@ -22,9 +22,9 @@ class Command(BaseCommand):
self.stdout.write(f'Group already exists: {group_name}')
users = {
'admin': ('Lab Administrators', True),
'staff': ('Lab Staff', False),
'viewer': ('Lab Viewers', False),
'admin': ('LabHelper Administrators', True),
'staff': ('LabHelper Staff', False),
'viewer': ('LabHelper Viewers', False),
}
for username, (group_name, is_superuser) in users.items():

View File

@@ -178,5 +178,8 @@ OIDC_OP_LOGOUT_ENDPOINT = os.environ.get('OIDC_OP_LOGOUT_ENDPOINT', f'{_oidc_con
# Store the ID token in the session so Keycloak logout can use id_token_hint
OIDC_STORE_ID_TOKEN = True
# Redirect to the static login page on auth failure instead of looping back into OIDC
OIDC_AUTHENTICATION_FAILURE_REDIRECT_URL = os.environ.get('OIDC_AUTHENTICATION_FAILURE_REDIRECT_URL', '/login/')
# Exempt AJAX endpoints from the session-refresh middleware redirect
OIDC_EXEMPT_URLS = ['search_api']