gwoe-antragspruefer/app/validators.py
Dotty Dotter c13292133c fix: Validator akzeptiert Bundesrats-3-Komponenten-Drucksachen
Damit /api/analyze-drucksache die Bundesrats-spezifische 400-Meldung
liefern kann (vorher haengen blieb am Path-Traversal-Validator mit
generischem 'Ungueltige Drucksache-ID').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:30:29 +02:00

53 lines
1.9 KiB
Python

"""Input-Validators für die FastAPI-Endpoints.
Eigenes Modul, damit die Unit-Tests die Helper direkt importieren können
ohne den vollen FastAPI-/slowapi-Stack aus app.main mitzuziehen. Issue
#57 Befunde #3 (Path-Traversal via drucksache) und #7 (Search-Query-DoS).
"""
from __future__ import annotations
import re
from fastapi import HTTPException
# Drucksache-Format: erlaubt sind alle bisher beobachteten Schreibweisen:
# "8/6390", "18/12345", "8/6390(neu)", "23/3700-A", "21/754S" (HB: S=Stadtbürgerschaft),
# Bundesrat: "186/3/26" (3 Komponenten, mittlere = Sub-Nummer, letzte = Jahr).
# Restriktiv genug für Path-Traversal-Schutz (#57 Befund #3).
# Bundesrats-Drucksachen kommen durch den Validator und werden in
# /api/analyze-drucksache explizit per is_bundesrat-Flag abgelehnt.
_DRUCKSACHE_RE = re.compile(
r"^\d{1,3}/\d{1,7}(?:/\d{1,4})?[A-Z]?([-(].{1,20})?$"
)
def validate_drucksache(drucksache: str) -> str:
"""Lehnt jede Drucksache-ID ab, die nicht dem erwarteten Format
entspricht. Gemeinsamer Validation-Funnel für alle Endpoints, die
``drucksache`` als Parameter nehmen. Issue #57 Befund #3.
"""
if not drucksache or not _DRUCKSACHE_RE.match(drucksache):
raise HTTPException(
status_code=400,
detail=f"Ungültige Drucksache-ID: {drucksache!r}",
)
return drucksache
# Längen-Limit für freie Search-Queries — verhindert, dass jemand mit einem
# 10-MB-Query die SQL-LIKE-Pattern oder den HTTP-Adapter aushungert.
# Issue #57 Befund #7.
MAX_SEARCH_QUERY_LEN = 200
def validate_search_query(q: str) -> str:
if q is None:
raise HTTPException(status_code=400, detail="Suchbegriff fehlt")
if len(q) > MAX_SEARCH_QUERY_LEN:
raise HTTPException(
status_code=400,
detail=f"Suchbegriff zu lang (max {MAX_SEARCH_QUERY_LEN} Zeichen, war {len(q)})",
)
return q