Files
kaffeekasse-saas/docs/m8-haertung.md
T
clemensandClaude Sonnet 5 f3045dbab9 M8: Datenexport pro Mandant
Neue Seite datenexport.php (Owner/Admin) laedt einen vollstaendigen
JSON-Export des eigenen Mandanten herunter: Stammdaten, Einstellungen,
Teilnehmer, Mitglieder mit Rolle, alle Ledger-Buchungen, Hinweise,
CSV-Importe, Mail-Versandlog und Admin-Protokoll. Passwort- und
Token-Hashes werden bewusst nicht exportiert; der Export selbst wird im
Audit-Log protokolliert. Link von konto.php aus.

Live getestet: eigens angelegter Test-Mandant, Export heruntergeladen,
Header und JSON-Struktur geprueft, keine Passwoerter im Export gefunden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 20:10:30 +02:00

6.6 KiB
Raw Blame History

M8 Härtung, Datenschutz und Betrieb

Stand: 2026-07-15

M8 macht die SaaS-App für den mehrmandantenfähigen Betrieb sicherer und nachvollziehbarer: Security-Headers, Rate-Limits, ein zentrales Audit-Log, sowie (folgend) Mandanten-Isolation, Rollenmatrix, Datenexport und Löschung/Anonymisierung.

Security-Headers

Umgesetzte Dateien:

app/bootstrap.php
landing.php
  • app_send_security_headers() setzt X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy (Geolocation/Mikrofon/Kamera aus) sowie Strict- Transport-Security bei HTTPS. Der Aufruf steht am Ende von app/bootstrap.php, das transitiv von jeder dynamischen Seite geladen wird, und läuft damit automatisch einmal pro Request.
  • Bewusst kein Content-Security-Policy-Header: Die bestehenden Templates nutzen durchgängig style=""-Inline-Attribute (Banner, Tabellenzellen, Status-Einfärbung). Ein CSP-Lockdown würde diese brechen und bräuchte einen eigenen Template-Durchgang als offener Punkt vermerkt, nicht in diesem Schritt umgesetzt.
  • landing.php war die einzige Seite ganz ohne PHP (reines HTML). Ein minimaler require_once app/bootstrap.php-Aufruf am Dateianfang sorgt dafür, dass auch sie die Header bekommt, ohne das Markup zu verändern.

Rate-Limits

Umgesetzte Dateien:

database/migrations/0010_saas_rate_limits.sql
app/rate-limit.php
login.php
register.php
passwort-vergessen.php
  • Neue Tabelle rate_limit_attempts (Bucket + Zeitstempel) mit app_rate_limit_check(): schreibt einen Versuch, räumt alte Einträge für denselben Bucket auf und meldet, ob das Limit noch eingehalten ist.
  • Login: 10 Versuche / 15 Minuten je E-Mail-Adresse und 20 Versuche / 15 Minuten je IP (beide müssen greifen, damit ein einzelnes Konto nicht gezielt von verteilten IPs aus brute-forced werden kann und eine IP nicht viele Konten gleichzeitig durchprobieren kann).
  • Registrierung: 5 Versuche / Stunde je IP (gegen Spam-Registrierungen).
  • Passwort-Reset-Anfrage: 5 / Stunde je E-Mail, 10 / Stunde je IP. Bei überschrittenem Limit erscheint bewusst dieselbe generische Erfolgsmeldung wie im echten Erfolgsfall, damit ein ausgereiztes Limit nicht zusätzlich verrät, ob ein Konto existiert.
  • Live getestet: 11 aufeinanderfolgende Fehlversuche gegen login.php die ersten 10 zeigen „ungültig“, der 11. wird korrekt mit „Zu viele Anmeldeversuche“ abgewiesen.

Audit-Log

Umgesetzte Dateien:

database/migrations/0011_saas_audit_log.sql
app/audit.php
mitarbeiterverwalten.php
letzteneintraege.php
mandant-einstellungen.php
hinweise.php
csvupload.php
jahresauswertung.php
mailversenden.php
  • Neue Tabelle audit_log (Mandant, ausführender Nutzer, Aktion, betroffener Datensatz, Metadaten als JSON, IP, Zeitpunkt) gemäß Kernschema aus dem Umstrukturierungsplan.
  • app_audit_log() schreibt einen Eintrag, app_fetch_audit_log() liest die letzten N Einträge eines Mandanten.
  • Protokollierte Aktionen: Mitglied anlegen/bearbeiten/(de)aktivieren, Zugang gewähren/entziehen, Storno von Einzahlung/Strich, Mandant- Einstellungen ändern, Hinweis anlegen/löschen, CSV-Import bestätigen, Jahresbonus-Verteilung, Live-Mailversand.
  • Sichtbar für Owner/Admin auf mandant-einstellungen.php unter „Protokoll" (letzte 50 Einträge, mit Namen des ausführenden Nutzers).
  • Live getestet: Hinweis anlegen erzeugt einen Log-Eintrag mit korrekten Metadaten; über einen eigens angelegten Test-Mandanten geprüft, dass eine Einstellungsänderung im UI-Protokoll mit korrektem Nutzernamen erscheint. Testdaten anschließend entfernt.

Mandanten-Isolation (automatisiert)

Umgesetzte Datei: scripts/check-m8-tenant-isolation.php

Legt zwei frische, isolierte Test-Mandanten mit je einem Teilnehmer, einer Ledger-Buchung, einem Hinweis und einem Audit-Log-Eintrag an und prüft 10 Fälle: Teilnehmerlisten, Einzelabruf, letzte Buchungen, aktive Hinweise, Schreibversuche (Buchung, Zugangsvergabe, Storno) und Audit-Log sind strikt auf den jeweils richtigen Mandanten beschränkt; ein Zugriff über den falschen Mandanten liefert nichts beziehungsweise schlägt sauber fehl statt fremde Daten zurückzugeben. Räumt die Testdaten am Ende selbst auf.

Ergebnis: grün mit 10 Assertions.

Rollenmatrix (automatisiert)

Umgesetzte Datei: scripts/check-m8-role-matrix.php

Legt einen Test-Mandanten mit je einem Nutzer pro Rolle (owner, admin, treasurer, member, viewer) an, loggt sich für jede Rolle per echtem HTTP-Request ein (manueller Cookie-Jar über file_get_contents, da diese PHP-Installation keine curl-Extension hat) und ruft alle rollen-geschützten Seiten auf: kaffeeliste.php, mitarbeiterverwalten.php, hinweise.php, stricheintragen.php, einzahlung.php, letzteneintraege.php, csvupload.php, exportKaffeeliste.php, mailversenden.php, jahresauswertung.php, mandant-einstellungen.php. Für jede Rolle-Seite-Kombination wird geprüft, ob der tatsächliche Zugriff (anhand der „Kein Zugriff"/„keine Berechtigung"-Marker in der Antwort) mit der erwarteten Rollenliste übereinstimmt.

Ergebnis: grün mit 55 Assertions (5 Rollen × 11 Seiten). Räumt die Testdaten am Ende selbst auf.

Datenexport pro Mandant

Umgesetzte Dateien:

app/data-export.php
datenexport.php
konto.php
scripts/http-smoke.php
  • Neue Seite datenexport.php (Owner/Admin, echte SaaS-Session nötig) lädt einen vollständigen JSON-Export des eigenen Mandanten herunter: Tenant-Stammdaten, Einstellungen, Teilnehmer, Mitglieder mit Rolle, alle Ledger-Buchungen, Hinweise, CSV-Importe (Batches und Zeilen), Mail-Versandlog und Admin-Protokoll.
  • Passwort-Hashes und Token-Hashes werden bewusst nicht exportiert.
  • Der Export selbst wird im Audit-Log protokolliert (tenant_data.exported).
  • Link von konto.php aus neben „Mandant-Einstellungen".
  • Live getestet: eigens angelegter Test-Mandant, Export heruntergeladen, Header (Content-Type, Content-Disposition) und JSON-Struktur geprüft, verifiziert dass keine Passwörter enthalten sind. Testdaten anschließend entfernt.

Prüfstatus

  • Golden Master: grün mit 104 Assertions.
  • M4 Ledger-Migration: grün mit 73 Assertions.
  • M3 Settings-Flow: grün mit 13 Assertions.
  • HTTP-Smoke: grün mit 27 geprüften Seiten.
  • M8 Mandanten-Isolation: grün mit 10 Assertions.
  • M8 Rollenmatrix: grün mit 55 Assertions.

Noch offen in M8

  • Content-Security-Policy (braucht Template-Bereinigung der Inline-Styles).
  • Lösch-/Anonymisierungsprozess für Teilnehmer und Kunden.
  • Backup-/Restore-Prozess und Monitoring/Fehlerlogging dokumentieren.