v4.1.0: DMS email documents, category-specific Nachweis linking, version system
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
Code Quality / quality (push) Has been cancelled

- Save cover email body as DMS document with new 'email' context type
- Show email body separately from attachments in email detail view
- Add per-category DMS document assignment in quarterly confirmation
  (Studiennachweis, Einkommenssituation, Vermögenssituation)
- Add VERSION file and context processor for automatic version display
- Add MCP server, agent system, import/export, and new migrations
- Update compose files and production environment template

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-15 18:48:52 +00:00
parent faeb7c1073
commit e0b377014c
49 changed files with 5913 additions and 55 deletions

70
app/mcp_server/auth.py Normal file
View File

@@ -0,0 +1,70 @@
"""
MCP-Authentifizierung Token-basierte Authentifizierung mit 3 Rollen.
Tokens werden über Umgebungsvariablen konfiguriert:
MCP_TOKEN_READONLY Nur-Lese-Zugriff (alle Daten, PII maskiert)
MCP_TOKEN_EDITOR Lesen + Schreiben (PII maskiert)
MCP_TOKEN_ADMIN Voll-Zugriff (keine PII-Maskierung, alle Schreib-Ops)
Das aktive Token wird per MCP_AUTH_TOKEN übergeben (wird vom MCP-Client gesetzt).
"""
import os
ROLE_READONLY = "readonly"
ROLE_EDITOR = "editor"
ROLE_ADMIN = "admin"
# Rollenrangfolge (höher = mehr Rechte)
ROLE_RANK = {ROLE_READONLY: 1, ROLE_EDITOR: 2, ROLE_ADMIN: 3}
def _token_map() -> dict[str, str]:
"""Erstellt Mapping token → Rolle aus Umgebungsvariablen."""
mapping: dict[str, str] = {}
for role, env_var in [
(ROLE_READONLY, "MCP_TOKEN_READONLY"),
(ROLE_EDITOR, "MCP_TOKEN_EDITOR"),
(ROLE_ADMIN, "MCP_TOKEN_ADMIN"),
]:
token = os.environ.get(env_var, "").strip()
if token:
mapping[token] = role
return mapping
def get_role_for_token(token: str) -> str | None:
"""
Gibt die Rolle für einen Token zurück oder None bei ungültigem Token.
"""
if not token:
return None
return _token_map().get(token)
def get_current_role() -> str | None:
"""
Gibt die Rolle des aktuell gesetzten MCP_AUTH_TOKEN zurück.
Wird vom Server beim Start einmalig ausgewertet.
"""
token = os.environ.get("MCP_AUTH_TOKEN", "").strip()
return get_role_for_token(token)
def can_write(role: str | None) -> bool:
"""Darf die Rolle Schreiboperationen ausführen?"""
return role in (ROLE_EDITOR, ROLE_ADMIN)
def can_read_unmasked(role: str | None) -> bool:
"""Darf die Rolle ungemaskierte PII-Daten lesen?"""
return role == ROLE_ADMIN
def require_role(role: str | None) -> None:
"""Wirft ValueError wenn keine gültige Rolle vorhanden."""
if not role:
raise ValueError(
"Ungültiger oder fehlender MCP_AUTH_TOKEN. "
"Bitte MCP_AUTH_TOKEN-Umgebungsvariable setzen."
)