Merge branch 'feature/oidc-validation'

This commit is contained in:
2025-12-28 13:58:59 +01:00
17 changed files with 191 additions and 24 deletions

View File

@ -10,6 +10,11 @@ By: Codex (GPT-5)
| 28.12.2025 | 🏗️ Planning | ID: EPIC_000007 und US_000020-US_000023 dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | 🏗️ Planning | ID: EPIC_000007 und US_000020-US_000023 dokumentiert. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: US_000024 dokumentiert; OIDC- und Login-Stories praezisiert. By: Codex (GPT-5) | | 28.12.2025 | 🏗️ Planning | ID: US_000024 dokumentiert; OIDC- und Login-Stories praezisiert. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: US_000024 Theme-Assets unter assets/design vorbereitet. By: Codex (GPT-5) | | 28.12.2025 | 🏗️ Planning | ID: US_000024 Theme-Assets unter assets/design vorbereitet. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: US_000025 und TASK_000025-TASK_000026 fuer OIDC-Validierung gestartet. By: Codex (GPT-5) |
| 28.12.2025 | 📝 Req | ID: US_000025 Runbook/Validierungsschritte dokumentiert. By: Codex (GPT-5) |
| 28.12.2025 | 📝 Req | ID: US_000025 Validation blocked (Service/IdP nicht bereit). By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: PR-Vorbereitung fuer feature/oidc-validation geplant. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: PAM immer aktiv; OIDC optional mit deaktivierter UI-Option dokumentiert. By: Codex (GPT-5) |
--- ---
## Legende ## Legende

View File

@ -16,11 +16,11 @@ cd /opt/sk
./scripts/install.sh ./scripts/install.sh
sudo systemctl status skd.service sudo systemctl status skd.service
``` ```
Then open `http://localhost:8000/` and set the API token in the UI. Then open `http://localhost:8000/` and log in via PAM (default) to start quickly.
## Configuration ## Configuration
Set in `/etc/skd/env` (see `env.example`): Set in `/etc/skd/env` (see `env.example`):
- `SKD_AUTH_MODE`: `pam` (default) or `oidc`. - PAM-Login ist immer aktiv. OIDC wird zusaetzlich angeboten, wenn konfiguriert.
- `SKD_AUTH_SECRET`: HMAC secret for bearer tokens/cookies (set a strong value). - `SKD_AUTH_SECRET`: HMAC secret for bearer tokens/cookies (set a strong value).
- `SKD_TOKEN_TTL_SECONDS`: token lifetime (default 900s). - `SKD_TOKEN_TTL_SECONDS`: token lifetime (default 900s).
- `SKD_AUTH_ALLOWED_USERS`: optional comma list of accounts allowed to log in (used for PAM and as an allowlist for OIDC claims). - `SKD_AUTH_ALLOWED_USERS`: optional comma list of accounts allowed to log in (used for PAM and as an allowlist for OIDC claims).
@ -34,6 +34,31 @@ Set in `/etc/skd/env` (see `env.example`):
Notes: Notes:
- `./scripts/install.sh` will create `/etc/skd/env` from `env.example` if missing (edit afterwards) and ensure the `skd` service user/group exist. - `./scripts/install.sh` will create `/etc/skd/env` from `env.example` if missing (edit afterwards) and ensure the `skd` service user/group exist.
## OIDC Setup
OIDC ist optional. Wenn der Provider noch nicht bereit ist, bleibe bei `SKD_AUTH_MODE=pam`.
1. Issuer muss der externen URL des Providers entsprechen (TLS trust erforderlich).
2. OIDC Client registrieren (DCR), z.B.:
```bash
export SKD_OIDC_ISSUER="https://auth.example.org"
export SKD_OIDC_REDIRECT_URI="https://<device-host>[:port]/login/oidc/callback"
export OIDC_INITIAL_ACCESS_TOKEN="<initial-access-token>"
./scripts/register_oidc_client.sh
```
3. Danach in `/etc/skd/env` setzen:
```
SKD_AUTH_MODE=oidc
SKD_OIDC_ISSUER=...
SKD_OIDC_CLIENT_ID=...
SKD_OIDC_CLIENT_SECRET=...
SKD_OIDC_REDIRECT_URI=...
SKD_OIDC_SCOPES=openid profile email
SKD_SESSION_COOKIE_SECURE=true
```
Hinweise:
- Redirect-URI muss exakt sein (keine Wildcards).
- Bei Host/Port-Aenderung neu registrieren und neue Credentials setzen.
- Allowlist fuer OIDC: `SKD_AUTH_ALLOWED_USERS` prueft `preferred_username`, `email` oder `sub`.
## Running ## Running
- Service: managed by systemd; `./scripts/install.sh` writes the unit dynamically to `/etc/systemd/system/skd.service` with the current repo path and restarts it. - Service: managed by systemd; `./scripts/install.sh` writes the unit dynamically to `/etc/systemd/system/skd.service` with the current repo path and restarts it.
- Manual run: `./scripts/run.sh` (uses `.venv`, defaults to `0.0.0.0:8000`). - Manual run: `./scripts/run.sh` (uses `.venv`, defaults to `0.0.0.0:8000`).
@ -41,6 +66,12 @@ Notes:
- Login (OIDC): open `http://localhost:8000/login/oidc/start` → provider → redirected back with session cookie set. - Login (OIDC): open `http://localhost:8000/login/oidc/start` → provider → redirected back with session cookie set.
- Health: `curl -H "Authorization: Bearer <token>" http://localhost:8000/health` - Health: `curl -H "Authorization: Bearer <token>" http://localhost:8000/health`
## OIDC Validation & Fallbacks
- Validierungsschritte: `docs/oidc-validation.md` (State, Token-Exchange, Claims, Cookie).
- Falls Discovery/JWKS nicht verfuegbar: OIDC deaktivieren und PAM nutzen.
- Falls DCR nicht verfuegbar: Client manuell im IdP anlegen und `SKD_OIDC_*` setzen.
- Bei Self-Signed TLS: CA im System trusten oder in Dev PAM nutzen.
## API (Bearer token via `/login`) ## API (Bearer token via `/login`)
- `GET /users` → `[{user, logged_in}]` (manageable system users; excludes root) - `GET /users` → `[{user, logged_in}]` (manageable system users; excludes root)
- `POST /users/{name}/disable` with JSON `{countdown?, sound?, message?}` - `POST /users/{name}/disable` with JSON `{countdown?, sound?, message?}`

View File

@ -29,10 +29,10 @@ templates = Jinja2Templates(directory="backend/templates")
def get_oidc_client(settings: Settings = Depends(get_settings)) -> OIDCClient: def get_oidc_client(settings: Settings = Depends(get_settings)) -> OIDCClient:
if settings.auth_mode != "oidc": if not settings.oidc_enabled:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="OIDC auth not enabled", detail="OIDC not configured",
) )
try: try:
return OIDCClient(settings) return OIDCClient(settings)
@ -59,7 +59,8 @@ def whoami(
current_user: str = Depends(get_current_admin), current_user: str = Depends(get_current_admin),
settings: Settings = Depends(get_settings), settings: Settings = Depends(get_settings),
) -> dict: ) -> dict:
return {"user": current_user, "auth_mode": settings.auth_mode} auth_mode = "pam+oidc" if settings.oidc_enabled else "pam"
return {"user": current_user, "auth_mode": auth_mode}
@app.post("/login", response_model=LoginResponse) @app.post("/login", response_model=LoginResponse)
@ -115,7 +116,7 @@ def oidc_callback(
username = oidc.extract_username(claims) username = oidc.extract_username(claims)
if not username: if not username:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing username claim") raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing username claim")
if not is_authorized_admin(username, settings): if not is_authorized_admin(username, settings, mode="oidc"):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not authorized to log in") raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not authorized to log in")
token = issue_token(username, settings) token = issue_token(username, settings)
@ -132,6 +133,11 @@ def oidc_callback(
return redirect return redirect
@app.get("/login/oidc/status")
def oidc_status(settings: Settings = Depends(get_settings)) -> dict:
return {"enabled": settings.oidc_enabled}
@app.get("/users", response_model=List[UserStatus], dependencies=[Depends(get_current_admin)]) @app.get("/users", response_model=List[UserStatus], dependencies=[Depends(get_current_admin)])
def users(settings: Settings = Depends(get_settings)) -> List[UserStatus]: def users(settings: Settings = Depends(get_settings)) -> List[UserStatus]:
logged_in = set(actions.list_logged_in_users()) logged_in = set(actions.list_logged_in_users())

View File

@ -1,7 +1,7 @@
import datetime as dt import datetime as dt
import grp import grp
import pwd import pwd
from typing import List, Set from typing import List, Set, Optional
import jwt import jwt
import pam import pam
@ -23,8 +23,8 @@ def _is_member_of(username: str, groups: Set[str]) -> bool:
return bool(user_groups & groups) return bool(user_groups & groups)
def is_authorized_admin(username: str, settings: Settings) -> bool: def is_authorized_admin(username: str, settings: Settings, mode: Optional[str] = None) -> bool:
if settings.auth_mode == "oidc": if mode == "oidc":
allowed_users = set(settings.auth_allowed_users) allowed_users = set(settings.auth_allowed_users)
if allowed_users and username not in allowed_users: if allowed_users and username not in allowed_users:
return False return False
@ -46,12 +46,7 @@ def is_authorized_admin(username: str, settings: Settings) -> bool:
def authenticate_admin_user(username: str, password: str, settings: Settings) -> None: def authenticate_admin_user(username: str, password: str, settings: Settings) -> None:
if settings.auth_mode != "pam": if not is_authorized_admin(username, settings, mode="pam"):
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Password login disabled; OIDC is configured",
)
if not is_authorized_admin(username, settings):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="User not authorized to log in", detail="User not authorized to log in",
@ -82,7 +77,12 @@ def decode_token(token: str, settings: Settings) -> str:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") from exc raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") from exc
username = payload.get("sub") username = payload.get("sub")
if not username or not is_authorized_admin(username, settings): if not username:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
if not (
is_authorized_admin(username, settings, mode="pam")
or is_authorized_admin(username, settings, mode="oidc")
):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Unauthorized user") raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Unauthorized user")
return username return username

View File

@ -32,6 +32,9 @@ class Settings:
self.oidc_state_cookie_name: str = os.getenv( self.oidc_state_cookie_name: str = os.getenv(
"SKD_OIDC_STATE_COOKIE_NAME", "skd_oidc_state" "SKD_OIDC_STATE_COOKIE_NAME", "skd_oidc_state"
) )
self.oidc_enabled: bool = bool(
self.oidc_issuer and self.oidc_client_id and self.oidc_client_secret
)
self.default_countdown: int = int(os.getenv("SKD_DEFAULT_COUNTDOWN", "60")) self.default_countdown: int = int(os.getenv("SKD_DEFAULT_COUNTDOWN", "60"))
self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true" self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true"
self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5")) self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5"))

View File

@ -18,11 +18,11 @@
</header> </header>
<section> <section>
<h3>Login (nur Root-User)</h3> <h3>Login</h3>
<p>Bevorzugt OIDC nutzen, falls konfiguriert. Die Anmeldung öffnet den Identity Provider und setzt eine Session-Cookie.</p> <p>Bevorzugt OIDC nutzen, falls konfiguriert. Die Anmeldung öffnet den Identity Provider und setzt eine Session-Cookie.</p>
<button id="oidcLogin" type="button">Login via OIDC</button> <button id="oidcLogin" type="button">Login via OIDC</button>
<hr /> <hr />
<p>Lokale Anmeldung (PAM) nur falls OIDC nicht verfügbar:</p> <p>Lokale Anmeldung (PAM) ist immer moeglich:</p>
<form id="loginForm"> <form id="loginForm">
<div class="grid"> <div class="grid">
<div> <div>
@ -136,6 +136,20 @@
} }
} }
async function checkOidcStatus() {
const button = document.getElementById('oidcLogin');
try {
const data = await api('/login/oidc/status');
if (!data.enabled) {
button.disabled = true;
button.title = 'OIDC nicht konfiguriert';
}
} catch (err) {
button.disabled = true;
button.title = 'OIDC-Status nicht erreichbar';
}
}
async function refreshUsers() { async function refreshUsers() {
statusDiv.textContent = 'Lade...'; statusDiv.textContent = 'Lade...';
try { try {
@ -206,6 +220,7 @@
}); });
checkSession(); checkSession();
checkOidcStatus();
</script> </script>
</body> </body>
</html> </html>

38
docs/oidc-validation.md Normal file
View File

@ -0,0 +1,38 @@
ID: DOC_000003 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# OIDC End-to-End Validation (Kiddo)
## Purpose
Validiere den OIDC-Login-Flow gegen einen realen oder Stub-Provider und dokumentiere Ergebnisse.
## Preconditions
- Kiddo laeuft und ist erreichbar (z.B. `http://localhost:8000`).
- OIDC Provider oder Stub erreichbar.
- `SKD_AUTH_MODE=oidc` und `SKD_OIDC_*` gesetzt.
- Redirect-URI: `https://<device-host>[:port]/login/oidc/callback` ist registriert.
## Validation Steps
1. OIDC Start
- Aufruf: `GET /login/oidc/start`
- Erwartet: Redirect zum Provider, State-Cookie gesetzt.
2. Callback
- Provider ruft `GET /login/oidc/callback?code=...&state=...` auf.
- Erwartet: State valid, Token-Exchange erfolgreich, Session-Cookie gesetzt, Redirect `/`.
3. Session Check
- Aufruf: `GET /me` mit Cookie oder Bearer.
- Erwartet: `user` und `auth_mode=oidc`.
4. Allowlist
- Falls `SKD_AUTH_ALLOWED_USERS` gesetzt: nicht erlaubte User werden mit 403 abgewiesen.
## Results
- Datum: 28.12.2025
- Provider: nicht konfiguriert (IdP noch nicht bereit)
- Host/Redirect: n/a
- Ergebnis: Blocked (Service nicht erreichbar unter http://localhost:8000/health)
- Fehlerbilder: curl (7) Couldn't connect to server
## Fallbacks bei unvollstaendigem IdP
- Discovery/JWKS fehlt: OIDC deaktivieren und PAM nutzen.
- DCR fehlt: Client manuell im IdP anlegen und `SKD_OIDC_*` setzen.
- TLS-Probleme: CA trusten oder PAM fuer Dev nutzen.

View File

@ -3,7 +3,8 @@
SKD_ALLOWED_USERS=child1,child2 SKD_ALLOWED_USERS=child1,child2
SKD_AUTH_SECRET=change-me-secret SKD_AUTH_SECRET=change-me-secret
SKD_TOKEN_TTL_SECONDS=900 SKD_TOKEN_TTL_SECONDS=900
# Auth mode: pam (default) or oidc # PAM ist immer aktiv; OIDC wird zusaetzlich angeboten, wenn konfiguriert.
# SKD_AUTH_MODE bleibt optional und wird derzeit nicht erzwungen.
SKD_AUTH_MODE=pam SKD_AUTH_MODE=pam
SKD_AUTH_ALLOWED_USERS= SKD_AUTH_ALLOWED_USERS=
SKD_AUTH_ALLOWED_GROUPS=sudo SKD_AUTH_ALLOWED_GROUPS=sudo

View File

@ -45,6 +45,9 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
- [x] TASK_000008: OIDC auth callback - [x] TASK_000008: OIDC auth callback
- [x] US_000009: Autorisierung und /me-Identitaet - [x] US_000009: Autorisierung und /me-Identitaet
- [x] TASK_000009: Authorization /me gate - [x] TASK_000009: Authorization /me gate
- [ ] US_000025: OIDC End-to-End Validierung und Runbook
- [ ] TASK_000025: OIDC E2E validation
- [ ] TASK_000026: OIDC runbook update
### EPIC_000004: Web UI ### EPIC_000004: Web UI
- [x] US_000010: Index-Seite ausliefern - [x] US_000010: Index-Seite ausliefern
@ -84,7 +87,9 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
## Offene Risiken / Abhaengigkeiten ## Offene Risiken / Abhaengigkeiten
- Betrieb erfordert Root/sudo und lokale System-Tools (notify-send, sound player, uvicorn). - Betrieb erfordert Root/sudo und lokale System-Tools (notify-send, sound player, uvicorn).
- OIDC-Validierung blockiert bis IdP bereit und Service laeuft.
## Naechste Schritte ## Naechste Schritte
- Anforderungen beim naechsten Feature-Start erweitern. - Anforderungen beim naechsten Feature-Start erweitern.
- Tests fuer kritische Pfade evaluieren. - OIDC-Validierung abschliessen und US_000025 auf Done setzen.
- PR vorbereiten: feature/oidc-validation (Summary, Risiko, Testschritte).

View File

@ -34,7 +34,8 @@ Stelle einen sicheren Admin-Login bereit, der Token oder Session-Cookies ausstel
- Abhaengigkeit von PAM und OIDC-Provider-Verfuegbarkeit. - Abhaengigkeit von PAM und OIDC-Provider-Verfuegbarkeit.
- Cookie-Sicherheit muss korrekt konfiguriert sein. - Cookie-Sicherheit muss korrekt konfiguriert sein.
## Zugeordnete User Stories (Done) ## Zugeordnete User Stories
- US_000007: PAM-Login mit Token - US_000007: PAM-Login mit Token
- US_000008: OIDC-Login Flow - US_000008: OIDC-Login Flow
- US_000009: Autorisierung und /me-Identitaet - US_000009: Autorisierung und /me-Identitaet
- US_000025: OIDC End-to-End Validierung und Runbook

View File

@ -13,6 +13,7 @@ Als Admin moechte ich mich per PAM-Login anmelden, damit ich ein Session-Token e
- Then die Antwort enthaelt `token` und `expires_in` - Then die Antwort enthaelt `token` und `expires_in`
- And ein Session-Cookie mit dem Token wird gesetzt - And ein Session-Cookie mit dem Token wird gesetzt
- And der Login ist ohne OIDC-Konfiguration als Schnellstart moeglich - And der Login ist ohne OIDC-Konfiguration als Schnellstart moeglich
- And PAM-Login bleibt auch bei aktivem OIDC verfuegbar
## Task-Platzhalter ## Task-Platzhalter
- TASK_000007: PAM login token (Details bei Story-Start) - TASK_000007: PAM login token (Details bei Story-Start)

View File

@ -8,12 +8,13 @@ Status: Done
Als Admin moechte ich mich per OIDC anmelden, damit ich ohne Passwort-Login zugreifen kann. Als Admin moechte ich mich per OIDC anmelden, damit ich ohne Passwort-Login zugreifen kann.
## Akzeptanzkriterien ## Akzeptanzkriterien
- Given `SKD_AUTH_MODE=oidc` und ein erreichbarer OIDC-Provider - Given OIDC ist konfiguriert und ein erreichbarer OIDC-Provider
- When ein GET auf `/login/oidc/start` erfolgt - When ein GET auf `/login/oidc/start` erfolgt
- Then der Nutzer wird zum Provider umgeleitet und ein State-Cookie gesetzt - Then der Nutzer wird zum Provider umgeleitet und ein State-Cookie gesetzt
- When der Provider auf `/login/oidc/callback` mit Code und State zurueckleitet - When der Provider auf `/login/oidc/callback` mit Code und State zurueckleitet
- Then der State wird validiert und ein Session-Cookie gesetzt - Then der State wird validiert und ein Session-Cookie gesetzt
- And bei ungueltigem State erfolgt eine 400-Antwort - And bei ungueltigem State erfolgt eine 400-Antwort
- And die OIDC-Option wird deaktiviert, wenn keine OIDC-Konfiguration vorliegt
## Task-Platzhalter ## Task-Platzhalter
- TASK_000008: OIDC auth callback (Details bei Story-Start) - TASK_000008: OIDC auth callback (Details bei Story-Start)

View File

@ -10,7 +10,7 @@ Als Operator moechte ich Konfigurationen per ENV setzen, damit Verhalten und Def
## Akzeptanzkriterien ## Akzeptanzkriterien
- Given Umgebungsvariablen aus `env.example` - Given Umgebungsvariablen aus `env.example`
- When der Service startet - When der Service startet
- Then Auth- und Session-Settings werden aus ENV geladen (`SKD_AUTH_MODE`, `SKD_AUTH_SECRET`, `SKD_TOKEN_TTL_SECONDS`, `SKD_AUTH_ALLOWED_USERS`, `SKD_AUTH_ALLOWED_GROUPS`, `SKD_AUTH_PAM_SERVICE`, `SKD_SESSION_COOKIE_NAME`, `SKD_SESSION_COOKIE_SECURE`, `SKD_OIDC_STATE_COOKIE_NAME`) - Then Auth- und Session-Settings werden aus ENV geladen (`SKD_AUTH_SECRET`, `SKD_TOKEN_TTL_SECONDS`, `SKD_AUTH_ALLOWED_USERS`, `SKD_AUTH_ALLOWED_GROUPS`, `SKD_AUTH_PAM_SERVICE`, `SKD_SESSION_COOKIE_NAME`, `SKD_SESSION_COOKIE_SECURE`, `SKD_OIDC_STATE_COOKIE_NAME`)
- And OIDC-Settings werden aus ENV geladen (`SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, `SKD_OIDC_CLIENT_SECRET`, `SKD_OIDC_REDIRECT_URI`, `SKD_OIDC_SCOPES`) - And OIDC-Settings werden aus ENV geladen (`SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, `SKD_OIDC_CLIENT_SECRET`, `SKD_OIDC_REDIRECT_URI`, `SKD_OIDC_SCOPES`)
- And Allowlist/Defaults werden aus ENV geladen (`SKD_ALLOWED_USERS`, `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`, `SKD_DRY_RUN`) - And Allowlist/Defaults werden aus ENV geladen (`SKD_ALLOWED_USERS`, `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`, `SKD_DRY_RUN`)
- And Sound/Notify-Pfade sind ueber ENV ueberschreibbar (`SKD_SOUND_PLAYER`, `SKD_SOUND_FILE`, `SKD_NOTIFY_SEND_PATH`) - And Sound/Notify-Pfade sind ueber ENV ueberschreibbar (`SKD_SOUND_PLAYER`, `SKD_SOUND_FILE`, `SKD_NOTIFY_SEND_PATH`)

View File

@ -14,6 +14,7 @@ Als Admin moechte ich mich im Web-UI anmelden, Nutzer laden und Aktionen ausfueh
- When ich Nutzer lade und eine Aktion sende - When ich Nutzer lade und eine Aktion sende
- Then die Aktionsergebnisse (Steps/Status) werden als Text angezeigt - Then die Aktionsergebnisse (Steps/Status) werden als Text angezeigt
- And Fehlerantworten werden als Text angezeigt - And Fehlerantworten werden als Text angezeigt
- And die OIDC-Option ist deaktiviert, wenn keine Konfiguration vorliegt
## Task-Platzhalter ## Task-Platzhalter
- TASK_000022: UI login and actions (Details bei Story-Start) - TASK_000022: UI login and actions (Details bei Story-Start)

View File

@ -0,0 +1,19 @@
ID: US_000025 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# US_000025: OIDC End-to-End Validierung und Runbook
Status: Blocked
Als Betreiber moechte ich den OIDC-Login Ende-zu-Ende validieren und dokumentieren, damit der Betrieb auch mit einem noch nicht vollstaendigen IdP planbar ist.
## Akzeptanzkriterien
- Given ein erreichbarer OIDC-Provider oder ein Stub/Test-Provider
- When der OIDC-Flow ueber `/login/oidc/start` und `/login/oidc/callback` durchlaufen wird
- Then Login, Token-Validierung und Session-Cookie werden erfolgreich nachvollzogen
- And die erforderlichen ENV-Variablen und Redirect-URIs sind im Runbook dokumentiert
- And Fallback-Optionen fuer nicht verfuegbare OIDC-Funktionen sind beschrieben
## Task-Platzhalter
- TASK_000025: OIDC E2E validation (Details bei Story-Start)
- TASK_000026: OIDC runbook update (Details bei Story-Start)

View File

@ -0,0 +1,20 @@
ID: TASK_000025 | Version: 0.1.0 | Status: Blocked
By: Codex (GPT-5)
# TASK_000025: OIDC E2E validation
## Outcome
OIDC-Login Flow ist Ende-zu-Ende gegen einen realen oder Stub-Provider verifiziert.
## Story-Bezug
US_000025
## Beschreibung
- Teste `/login/oidc/start` und `/login/oidc/callback` mit einem Provider (oder Stub).
- Validierung: State, Token-Exchange, Claims, Allowlist, Session-Cookie.
- Dokumentiere Abweichungen bei nicht verfuegbaren IdP-Funktionen.
- Testplan liegt in `docs/oidc-validation.md`.
## Definition of Done (DoD)
- Testschritte sind dokumentiert.
- Ergebnisse inklusive Fehlerbilder sind festgehalten.

View File

@ -0,0 +1,20 @@
ID: TASK_000026 | Version: 0.1.0 | Status: Blocked
By: Codex (GPT-5)
# TASK_000026: OIDC runbook update
## Outcome
Runbook beschreibt OIDC-Setup, Redirect-URIs und Fallbacks fuer einen nicht vollstaendigen IdP.
## Story-Bezug
US_000025
## Beschreibung
- Ergaenze README mit klaren OIDC-ENV-Beispielen.
- Beschreibe exakte Redirect-URI und Host/Port-Wechsel.
- Fuege Fallbacks hinzu (PAM-Login, Hinweise zu fehlenden IdP-Features).
- Verweise auf `docs/oidc-validation.md` fuer die Testschritte.
## Definition of Done (DoD)
- README enthaelt konkrete OIDC-Setup-Schritte.
- Fallbacks sind fuer Operatoren nachvollziehbar.