Docs: Feld-Mapping-Tabelle pro Adapter + ADR 0005 + Auth-Tests
Adapter-Capabilities-Matrix (#93) erweitert um detailliertes Feld- Mapping: Pro Adapter welches API-/HTML-/JSON-Feld zu welchem Drucksache-Feld wird (title, datum, fraktionen, drucksache, link, typ) mit konkreten Beispielwerten. 12 Adapter-Sektionen. ADR 0005: Keycloak SSO mit Dev-Bypass — dokumentiert die Entscheidung für Read/Write-Trennung (GET offen, POST mit JWT) und den Dev-Modus (Auth deaktiviert wenn KEYCLOAK_URL nicht gesetzt). Auth-Tests: 7 neue Tests für Token-Extraction, Auth-Enabled-Detection, _pick_best_title (letztere skipped wenn slowapi nicht installiert). 201 passed, 5 skipped.
This commit is contained in:
parent
8bd311dbc8
commit
f0f1c39911
80
docs/adr/0005-keycloak-sso-with-dev-bypass.md
Normal file
80
docs/adr/0005-keycloak-sso-with-dev-bypass.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# 0005 — Keycloak SSO mit Dev-Bypass für Read/Write-Trennung
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Status** | accepted |
|
||||||
|
| **Datum** | 2026-04-10 |
|
||||||
|
| **Refs** | Issue #43, Commit 7159240 + 303b30f, app/auth.py |
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
|
||||||
|
Die Webapp soll öffentlich durchsuchbar sein (Assessments lesen, PDFs
|
||||||
|
ansehen, Auswertungen), aber Analyse-Aktionen (Antrag bewerten, Programme
|
||||||
|
indexieren) nur authentifizierten Nutzern erlauben. Der User betreibt
|
||||||
|
bereits einen Keycloak-Server unter `sso.toppyr.de` mit Realm `collaboration`.
|
||||||
|
|
||||||
|
## Optionen
|
||||||
|
|
||||||
|
### Option A — Keycloak-only mit hartem 401
|
||||||
|
|
||||||
|
Alle POST-Endpoints erfordern JWT. Ohne Keycloak-Server läuft nichts.
|
||||||
|
|
||||||
|
**Vorteile:** Klar, sicher.
|
||||||
|
**Nachteile:** Lokale Entwicklung blockiert ohne Keycloak-Instanz.
|
||||||
|
|
||||||
|
### Option B — Dev-Bypass: Auth nur wenn ENV gesetzt
|
||||||
|
|
||||||
|
Wenn `KEYCLOAK_URL` leer ist → ALLE Endpoints offen (Dev-Modus).
|
||||||
|
Wenn gesetzt → POST-Endpoints erfordern JWT, GET bleibt offen.
|
||||||
|
|
||||||
|
**Vorteile:** Lokale Dev ohne Keycloak. Prod sofort sicher per ENV.
|
||||||
|
**Nachteile:** Versehentlich Prod ohne ENV = alles offen.
|
||||||
|
|
||||||
|
### Option C — API-Key statt Keycloak
|
||||||
|
|
||||||
|
Einfacher API-Key-Header für POST-Endpoints.
|
||||||
|
|
||||||
|
**Vorteile:** Zero-Dependency.
|
||||||
|
**Nachteile:** Kein SSO, kein User-Identity, keine Gruppen/Rollen.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
|
||||||
|
**Option B.** Dev-Bypass ermöglicht reibungslose lokale Entwicklung und
|
||||||
|
Batch-Skripte (die keinen JWT haben). Prod-Absicherung über die drei
|
||||||
|
ENV-Vars in docker-compose.yml. Die Keycloak-Client-Registrierung in
|
||||||
|
`sso.toppyr.de` ist ein einmaliger manueller Schritt.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
- `app/auth.py`: JWKS-Cache (1h TTL), `get_current_user` (optional),
|
||||||
|
`require_auth` (pflicht), `keycloak_login_url` (für Browser-Redirect)
|
||||||
|
- POST-Endpoints (`/analyze`, `/api/analyze-drucksache`,
|
||||||
|
`/api/programme/index`): `user: dict = Depends(require_auth)`
|
||||||
|
- GET-Endpoints: unverändert offen
|
||||||
|
- Frontend: `initAuth()` prüft `/api/auth/me`, steuert
|
||||||
|
"Jetzt prüfen"-Button (disabled + Tooltip wenn nicht eingeloggt)
|
||||||
|
und "Anmelden"-Button im Header
|
||||||
|
|
||||||
|
## Konsequenzen
|
||||||
|
|
||||||
|
### Positiv
|
||||||
|
|
||||||
|
- Read-Only für alle, Write nur mit Login — klare Trennung
|
||||||
|
- Batch-Skripte und auto-Re-Analyse im Container laufen im Dev-Modus
|
||||||
|
(KEYCLOAK_URL nicht gesetzt), keine Auth-Hürde für Maintenance
|
||||||
|
- Keycloak-Rollen über `realm_access.roles` im JWT verfügbar für
|
||||||
|
künftige Gruppen-Features (#94 Bookmarks/Kommentare)
|
||||||
|
|
||||||
|
### Negativ
|
||||||
|
|
||||||
|
- Dev-Modus ist unsicher — wenn jemand KEYCLOAK_URL in Prod vergisst
|
||||||
|
zu setzen, ist alles offen. Mitigation: Health-Endpoint oder
|
||||||
|
Startup-Warning wenn ENV fehlt.
|
||||||
|
- JWKS-Cache (1h) bedeutet: nach Key-Rotation dauert es bis zu 1h bis
|
||||||
|
alte Tokens abgelehnt werden. Für dieses Projekt akzeptabel.
|
||||||
|
|
||||||
|
### Folgen für andere ADRs
|
||||||
|
|
||||||
|
- #94 Bookmarks/Kommentare baut auf der User-Identity aus dem JWT auf
|
||||||
|
(`sub`, `email`, `realm_access.roles`).
|
||||||
|
- #95 Queuing könnte Auth-User priorisieren.
|
||||||
@ -55,6 +55,146 @@ Satz von Treffern nach WP/Datum/Typ und filtert dann im Python-Code per
|
|||||||
| **HE** | Perl-Data::Dumper in HTML-Comments, Hex-Escape-Decoding (`\x{e9}` → `é`) |
|
| **HE** | Perl-Data::Dumper in HTML-Comments, Hex-Escape-Decoding (`\x{e9}` → `é`) |
|
||||||
| **NI** | ❌ **Nicht implementiert** — NILAS-StarWeb braucht Session-Cookie, HAR-Capture nötig |
|
| **NI** | ❌ **Nicht implementiert** — NILAS-StarWeb braucht Session-Cookie, HAR-Capture nötig |
|
||||||
|
|
||||||
|
## Feld-Mapping: Woher kommen die Daten?
|
||||||
|
|
||||||
|
Pro Adapter: welches API-Feld wird zu welchem Drucksache-Feld, mit Beispiel.
|
||||||
|
|
||||||
|
### NRW (NRWAdapter — OPAL HTML)
|
||||||
|
|
||||||
|
| Feld | Quelle | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | HTML `.e-document-result-item__title` `<a>` Text | "Kostenloses Parken für E-Fahrzeuge aufheben" |
|
||||||
|
| datum | `<time>` Element, DD.MM.YYYY → ISO | "07.04.2026" → "2026-04-07" |
|
||||||
|
| fraktionen | `<p>` mit "Urheber:" → `extract_fraktionen()` | "Urheber: SPD, GRÜNE" → `["SPD", "GRÜNE"]` |
|
||||||
|
| drucksache | Regex aus href `MMD18-12345.pdf` → "18/12345" | "18/18085" |
|
||||||
|
| link | `https://www.landtag.nrw.de{href}` | `https://www.landtag.nrw.de/.../MMD18-18085.pdf` |
|
||||||
|
| typ | HTML `.e-document-result-item__category` | "Antrag", "Kleine Anfrage" |
|
||||||
|
|
||||||
|
### LSA / BB / RP (PortalaAdapter — eUI Perl-Dump)
|
||||||
|
|
||||||
|
| Feld | Quelle (Perl-Dump-Feld) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `WEV06[0].main` | "Energiewende und Klimaschutz" |
|
||||||
|
| datum | `WEV32[0].main` → Regex, DD.MM.YYYY → ISO | "01.10.2024" → "2024-10-01" |
|
||||||
|
| fraktionen | `WEV32[0].main` Urheber-Text → `extract_fraktionen()` | "CDU, SPD" → `["CDU", "SPD"]` |
|
||||||
|
| drucksache | `WEV32[0].main` → Regex | "8/6645" |
|
||||||
|
| link | `{base_url}{pdf_url_prefix}{WEV32[0].5}` | `https://padoka.landtag.sachsen-anhalt.de/files/drs/8/6645.pdf` |
|
||||||
|
| typ | Fest: "Antrag" (server-side gefiltert via `ETYPF`) | "Antrag" |
|
||||||
|
|
||||||
|
### BE (PortalaAdapter — eUI HTML-Cards)
|
||||||
|
|
||||||
|
| Feld | Quelle (HTML-Element) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `<h3 class="h5">` Text | "Kinder- und Familienförderung" |
|
||||||
|
| datum | `<h6>` Metadata-Zeile → Regex DD.MM.YYYY → ISO | "31.03.2026" → "2026-03-31" |
|
||||||
|
| fraktionen | `<h6>` Doctype-Zeile → `extract_fraktionen()` | "Antrag CDU, SPD" → `["CDU", "SPD"]` |
|
||||||
|
| drucksache | `<h6>` → Regex | "19/3104" |
|
||||||
|
| link | `<a href="…pdf">` (http→https, relativ→absolut) | `https://pardok.parlament-berlin.de/...` |
|
||||||
|
| typ | `<h6>` vor Fraktionsnamen | "Antrag", "Vorlage" |
|
||||||
|
|
||||||
|
### MV / HH / TH (ParLDokAdapter — JSON-API)
|
||||||
|
|
||||||
|
| Feld | Quelle (JSON-Feld) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `title` | "Weiterbildungsförderung" |
|
||||||
|
| datum | `date`, DD.MM.YYYY → ISO | "15.03.2026" → "2026-03-15" |
|
||||||
|
| fraktionen | `authorhtml` → `extract_fraktionen()` | "Klaus Meyer (CDU)" → `["CDU"]` |
|
||||||
|
| drucksache | `f"{lp}/{number}"` | lp=8, number=1594 → "8/1594" |
|
||||||
|
| link | `{base_url}{prefix}{link}` (Fragment `#navpanes=0` gestrippt) | `https://dokumentation.landtag-mv.de/parldok/dokument/8` |
|
||||||
|
| typ | `type` (TH: Substring-Match auf "Antrag") | "Antrag", "Antrag gemäß § 79 GO" |
|
||||||
|
|
||||||
|
### SH (StarFinderCGIAdapter — Legacy CGI)
|
||||||
|
|
||||||
|
| Feld | Quelle (HTML-Regex) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `<b>…</b>` in Tabellenzeile | "Energiewende vorantreiben" |
|
||||||
|
| datum | Nach `</b>`, DD.MM.YYYY → ISO | "07.04.2026" → "2026-04-07" |
|
||||||
|
| fraktionen | Urheber-Text → `extract_fraktionen()` | "Christian Dirschauer (SSW)" → `["SSW"]` |
|
||||||
|
| drucksache | `<a>` Text | "20/5136" |
|
||||||
|
| link | `<a href="…">` direkt | `http://lissh.lvn.parlanet.de/.../20-5136.pdf` |
|
||||||
|
| typ | Fest: "Antrag" (server-side `dtyp=antrag`) | "Antrag" |
|
||||||
|
|
||||||
|
### HE (StarWebHEAdapter — eUI Perl-Dump)
|
||||||
|
|
||||||
|
| Feld | Quelle (Perl-Dump-Feld) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `WEV01[0].main` (Hex-Escape-Decoding `\x{e9}` → `é`) | "Schulinfrastruktur modernisieren" |
|
||||||
|
| datum | `WEV02[0].main`, DD.MM.YYYY → ISO | "29.02.2026" → "2026-02-29" |
|
||||||
|
| fraktionen | `WEV12[0].main` → `extract_fraktionen()` | "Klaus Dieter (GRÜNE)" → `["GRÜNE"]` |
|
||||||
|
| drucksache | `WEV08[0].main` | "21/8532" |
|
||||||
|
| link | `WEV07[0].main` (http→https) | `https://starweb.hessen.de/.../21_8532.pdf` |
|
||||||
|
| typ | `WEV03[0].main` | "Antrag", "Antrag mit Änderung" |
|
||||||
|
|
||||||
|
### HB (PARiSHBAdapter — Java-Servlet HTML)
|
||||||
|
|
||||||
|
| Feld | Quelle (HTML-Regex) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `<h2><a>…</a></h2>` | "Bremenpass für Kultur" |
|
||||||
|
| datum | Nach Drucksache, DD.MM.YYYY → ISO | "23.02.2026" → "2026-02-23" |
|
||||||
|
| fraktionen | Nach Datum → `extract_fraktionen()` | "SPD, BÜNDNIS 90/DIE GRÜNEN, Die Linke" → `["SPD", "GRÜNE", "LINKE"]` |
|
||||||
|
| drucksache | `Drs <b>21/730</b>` + optionaler Suffix (S/L) | "21/730", "21/730S" |
|
||||||
|
| link | `<a href="…pdf" target="new">` | `https://www.bremische-buergerschaft.de/.../21/730.pdf` |
|
||||||
|
| typ | Regex vor Datum | "Antrag", "Änderungsantrag" |
|
||||||
|
|
||||||
|
### BW (PARLISAdapter — eUI async Polling, JSON-in-HTML)
|
||||||
|
|
||||||
|
| Feld | Quelle (Perl/JSON-Feld) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `WMV33` (Schlagworte, `<i>` gestrippt) / Fallback `EWBV23` | "Energiewirtschaft; Stromversorgung" |
|
||||||
|
| datum | `EWBV23` → Regex DD.MM.YYYY → ISO | "16.03.2026" → "2026-03-16" |
|
||||||
|
| fraktionen | `WMV30` (Kurz-Urheber) → `extract_fraktionen()` | "Felix Herkens (GRÜNE) u. a." → `["GRÜNE"]` |
|
||||||
|
| drucksache | `EWBV22` oder `EWBD01` → Regex | "17/10323" |
|
||||||
|
| link | `EWBD05[0].main` (direkte PDF-URL) | `https://parlis.landtag-bw.de/.../17_10323.pdf` |
|
||||||
|
| typ | Fest: `document_typ="Antrag"` (in `lines.l4`) | "Antrag" |
|
||||||
|
|
||||||
|
### BY (BayernAdapter — TYPO3-Solr HTML)
|
||||||
|
|
||||||
|
| Feld | Quelle (HTML-Regex) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `<h5><strong>…</strong></h5>` | "Kostenloses Parken für E-Fahrzeuge aufheben" |
|
||||||
|
| datum | `<h4>` "Drucksache Nr. 19/11407 vom 08.04.2026" DD.MM.YYYY → ISO | "08.04.2026" → "2026-04-08" |
|
||||||
|
| fraktionen | `<p>` "Antrag AfD" → `extract_fraktionen()` | "Antrag CSU, FREIE WÄHLER" → `["CSU", "FW-BAYERN"]` |
|
||||||
|
| drucksache | `<h4>` Regex | "19/11407" |
|
||||||
|
| link | `<a href="…pdf">` (absolute URL) | `https://www.bayern.landtag.de/.../0000009107.pdf` |
|
||||||
|
| typ | Erstes Wort aus `<p>` | "Antrag" |
|
||||||
|
|
||||||
|
### SL (SaarlandAdapter — Umbraco JSON)
|
||||||
|
|
||||||
|
| Feld | Quelle (JSON-Feld) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `Title` | "Schule als Lern- und Bildungsort weiter stärken" |
|
||||||
|
| datum | `PublicDate` ISO-Format, erste 10 Zeichen | "2022-05-12T00:00:00" → "2022-05-12" |
|
||||||
|
| fraktionen | `Publisher` + `DocumentAuthor` → `extract_fraktionen()` | Publisher "CDU" → `["CDU"]` |
|
||||||
|
| drucksache | `DocumentNumber` | "17/11" |
|
||||||
|
| link | `FilePath` mit `/file.ashx` → `/Downloadfile.ashx` Rewrite | `https://www.landtag-saar.de/Downloadfile.ashx?FileId=14230&FileName=Ag17_0011.pdf` |
|
||||||
|
| typ | `DocumentType` | "Antrag", "Anfrage", "Gesetzentwurf" |
|
||||||
|
|
||||||
|
### SN (SNEdasXmlAdapter — Manueller XML-Export)
|
||||||
|
|
||||||
|
| Feld | Quelle (XML-Element) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `<Titel>` (CDATA) | "Geschäftsordnung des Sächsischen Landtags" |
|
||||||
|
| datum | `<Fundstelle>` → Regex "datum"-Gruppe, DD.MM.YYYY → ISO | "01.10.2024" → "2024-10-01" |
|
||||||
|
| fraktionen | `<Fundstelle>` → Regex "urheber"-Gruppe → `extract_fraktionen()` | "CDU, BSW, SPD" → `["CDU", "BSW", "SPD"]` |
|
||||||
|
| drucksache | `f"{Wahlperiode}/{Dokumentennummer}"` | "8/2" |
|
||||||
|
| link | Konstruiert: `.../viewer.aspx?dok_nr={nr}&dok_art=Drs&leg_per={wp}` (PDF wird on-demand aus Iframe gelöst) | `https://edas.landtag.sachsen.de/.../viewer.aspx?dok_nr=2&dok_art=Drs&leg_per=8` |
|
||||||
|
| typ | Fest: "Antrag" | "Antrag" |
|
||||||
|
|
||||||
|
### BUND (BundestagAdapter — DIP REST-API)
|
||||||
|
|
||||||
|
| Feld | Quelle (JSON-Feld) | Beispiel |
|
||||||
|
|---|---|---|
|
||||||
|
| title | `titel` | "Förderung von Genossenschaften im sozialen Bereich" |
|
||||||
|
| datum | `datum` (bereits ISO YYYY-MM-DD) | "2026-04-08" |
|
||||||
|
| fraktionen | `urheber[*].titel` → `extract_fraktionen()` | "Fraktion der AfD" → `["AfD"]` |
|
||||||
|
| drucksache | `dokumentnummer` | "21/5136" |
|
||||||
|
| link | `fundstelle.pdf_url` | `https://dip.bundestag.de/documents/btd/21/051/2105136.pdf` |
|
||||||
|
| typ | `drucksachetyp` (server-side gefiltert) | "Antrag" |
|
||||||
|
|
||||||
|
## Gemeinsames Pattern
|
||||||
|
|
||||||
|
Alle Adapter normalisieren Fraktionen über den zentralen `parteien.extract_fraktionen(text, bundesland=...)` Mapper (#55). Alle konvertieren DE-Datumsformat (DD.MM.YYYY) nach ISO (YYYY-MM-DD), außer BUND (schon ISO) und SL (schon ISO mit T-Suffix).
|
||||||
|
|
||||||
## Historien-Tiefe (ältere WPs)
|
## Historien-Tiefe (ältere WPs)
|
||||||
|
|
||||||
Alle Adapter sind aktuell auf **eine feste Wahlperiode** konfiguriert.
|
Alle Adapter sind aktuell auf **eine feste Wahlperiode** konfiguriert.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user