Back-Office: globaler Platform-Admin-Zugang ueber alle Mandanten
Neue, bewusst von tenant_memberships/saas_user_has_role() komplett getrennte Platform-Admin-Ebene (neue Tabelle platform_admins), damit die bestehende, automatisiert getestete Mandanten-Isolation (check-m8-tenant-isolation.php, check-m8-role-matrix.php) unangetastet bleibt - Platform-Admin-Rechte wirken ausschliesslich auf den neuen backoffice-*.php-Seiten. - backoffice.php: Uebersicht aller Mandanten (Status, Teilnehmerzahl, Saldensumme). - backoffice-mandant.php: reine Leseansicht eines Mandanten (Einstellungen, Mitglieder/Rollen, letzte Buchungen, letzte Admin-Aktionen). Bewusst kein Schreibzugriff von hier aus. - backoffice-export.php: nutzt dieselbe app_export_tenant_data() wie der Selbstbedienungs-Export, ausgeloest durch den Platform-Admin fuer beliebige Mandanten. - scripts/grant-platform-admin.php: CLI-only Bootstrap fuer den ersten Platform-Admin, bewusst keine Web-UI dafuer. - Jede Back-Office-Ansicht/-Export wird im Audit-Log DES BETROFFENEN MANDANTEN protokolliert (Transparenzpflicht), nicht nur beim Betreiber. Der bestehende Selbstbedienungs-Export (datenexport.php aus M8) bleibt zusaetzlich bestehen statt ersetzt zu werden: der Mandant ist im AV- Verhaeltnis Verantwortlicher, Art. 15/20/28 DSGVO verpflichten den Auftragsverarbeiter zur Unterstuetzung bei Ausk''unfts-/Portabilitaets- rechten - ein jederzeit verfuegbarer Mandanten-Export erfuellt das direkt. Details und Begruendung in docs/backoffice.md. Live getestet: Back-Office zeigt alle Mandanten korrekt (inkl. echter Bestandsmandanten), Detail/Export fuer Test-Mandant funktioniert, Audit-Log korrekt geschrieben. Kritischer Test bestanden: derselbe Platform-Admin sieht auf normalen Mandanten-Seiten weiterhin nur seinen eigenen Mandanten; ein eingeloggter Nicht-Platform-Admin bekommt 403. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# Back-Office (Platform-Admin)
|
||||
|
||||
Stand: 2026-07-16
|
||||
|
||||
Ergänzung außerhalb der ursprünglichen M0–M9-Meilensteine: ein
|
||||
Betreiber-Zugang, der mandantenübergreifend alle Kunden einsehen kann –
|
||||
getrennt von der normalen, streng mandantengebundenen Rollenlogik.
|
||||
|
||||
## Design-Entscheidung: getrennt von `tenant_memberships`
|
||||
|
||||
Platform-Admin-Rechte sind bewusst **kein** Teil von
|
||||
`saas_user_has_role()`/`tenant_memberships`, sondern eine eigene, komplett
|
||||
separate Tabelle `platform_admins` (`user_id` → `users.id`). Das ist
|
||||
kein Zufall: Alle bestehenden Mandanten-Seiten und die automatisierten
|
||||
Isolationstests (`scripts/check-m8-tenant-isolation.php`,
|
||||
`scripts/check-m8-role-matrix.php`) verlassen sich darauf, dass ein
|
||||
Login niemals automatisch mandantenübergreifenden Zugriff bekommt. Ein
|
||||
Platform-Admin-Flag direkt in `tenant_memberships` oder `users` einzubauen
|
||||
hätte dieses Fundament unterlaufen können. Stattdessen prüft
|
||||
ausschließlich der neue Code-Pfad in `app/platform-admin.php`
|
||||
(`app_require_platform_admin()`) auf Back-Office-Seiten die
|
||||
Platform-Admin-Eigenschaft; alle bestehenden Seiten sind unverändert und
|
||||
bleiben strikt mandantengebunden.
|
||||
|
||||
## Umgesetzte Dateien
|
||||
|
||||
```text
|
||||
database/migrations/0013_saas_platform_admins.sql
|
||||
app/platform-admin.php
|
||||
scripts/grant-platform-admin.php
|
||||
backoffice.php
|
||||
backoffice-mandant.php
|
||||
backoffice-export.php
|
||||
```
|
||||
|
||||
## Erster Platform-Admin
|
||||
|
||||
Es gibt bewusst **keine Weboberfläche**, um den ersten Platform-Admin zu
|
||||
setzen – das wäre ein öffentlich erreichbarer "werde Admin"-Endpunkt und
|
||||
ein erhebliches Risiko. Stattdessen: Person registriert sich normal als
|
||||
Mandant (oder nutzt einen bestehenden Login), danach per Shell-Zugriff auf
|
||||
dem Server:
|
||||
|
||||
```bash
|
||||
php scripts/grant-platform-admin.php person@example.com
|
||||
```
|
||||
|
||||
Das Skript prüft, dass der Account existiert, und legt nur dann den
|
||||
`platform_admins`-Eintrag an. Zugang entziehen aktuell nur per direktem
|
||||
`DELETE FROM platform_admins WHERE user_id = ?` (keine UI dafür – aus
|
||||
demselben Grund wie beim Setzen).
|
||||
|
||||
## Funktionsumfang
|
||||
|
||||
- `backoffice.php`: Übersicht aller Mandanten mit Kürzel, Status,
|
||||
Erstelldatum, Teilnehmerzahl (aktiv/gesamt) und Saldensumme.
|
||||
- `backoffice-mandant.php?tenant_id=X`: reine Leseansicht eines einzelnen
|
||||
Mandanten – Einstellungen, Mitglieder mit Rolle, letzte 20 Buchungen,
|
||||
letzte 20 Admin-Aktionen. Absichtlich **kein** Schreibzugriff auf
|
||||
Mandantendaten von hier aus, um das Risiko einer versehentlichen
|
||||
Fremdänderung auszuschließen.
|
||||
- `backoffice-export.php`: nutzt dieselbe `app_export_tenant_data()`-
|
||||
Funktion wie der Selbstbedienungs-Export, aber ausgelöst durch den
|
||||
Platform-Admin für einen beliebigen Mandanten.
|
||||
|
||||
## Verhältnis zum Selbstbedienungs-Export (`datenexport.php`)
|
||||
|
||||
Bewusste Entscheidung: Der bestehende Selbstbedienungs-Export für
|
||||
Mandanten-Owner/Admin (`datenexport.php`, aus M8) bleibt zusätzlich
|
||||
bestehen, statt ihn durch das Back-Office zu ersetzen. Begründung: Der
|
||||
Mandant ist im Auftragsverarbeitungs-Verhältnis der Verantwortliche
|
||||
(Controller), der Betreiber dieser App der Auftragsverarbeiter
|
||||
(Processor). Art. 15/20 DSGVO geben den betroffenen Personen ein
|
||||
Auskunfts-/Portabilitätsrecht, und Art. 28 DSGVO verpflichtet den
|
||||
Auftragsverarbeiter, den Verantwortlichen bei der Erfüllung dieser
|
||||
Rechte zu unterstützen sowie Daten am Vertragsende zurückzugeben – ein
|
||||
jederzeit verfügbarer Selbstbedienungs-Export erfüllt genau das. Das
|
||||
Back-Office ergänzt das um einen Betreiber-seitigen Zugriff für Support,
|
||||
Migrationen oder eigene Nachweispflichten, ersetzt den
|
||||
Mandanten-Selbstexport aber nicht.
|
||||
|
||||
## Audit-Trail
|
||||
|
||||
Jede Back-Office-Ansicht und jeder Back-Office-Export wird im Audit-Log
|
||||
**des betroffenen Mandanten** protokolliert
|
||||
(`backoffice.tenant_viewed`, `backoffice.tenant_exported`, mit dem
|
||||
Platform-Admin als `actor_user_id`). Ein Mandant sieht damit über sein
|
||||
eigenes Protokoll (`mandant-einstellungen.php`), wann der Betreiber auf
|
||||
seine Daten zugegriffen hat – Transparenzpflicht statt stiller
|
||||
Einsicht.
|
||||
|
||||
## Prüfstatus
|
||||
|
||||
- `scripts/check-m8-tenant-isolation.php`: weiterhin grün mit 10
|
||||
Assertions – das Back-Office berührt die geprüften Pfade nicht.
|
||||
- `scripts/check-m8-role-matrix.php`: weiterhin grün mit 55 Assertions.
|
||||
- HTTP-Smoke: grün mit 30 geprüften Seiten (zwei neue Login-Schutz-Checks
|
||||
für `backoffice.php`/`backoffice-mandant.php`).
|
||||
- Golden Master: grün mit 104 Assertions.
|
||||
|
||||
Live getestet: eigens angelegter Test-Mandant, per CLI-Skript zum
|
||||
Platform-Admin gemacht, Back-Office-Übersicht zeigt korrekt alle
|
||||
Mandanten (inklusive der echten Bestandsmandanten), Detailansicht und
|
||||
Export für den eigenen Test-Mandanten funktionieren, Audit-Log-Einträge
|
||||
korrekt geschrieben. Kritischer Isolationstest bestätigt: derselbe
|
||||
Platform-Admin sieht auf regulären Mandanten-Seiten (`kaffeeliste.php`)
|
||||
weiterhin ausschließlich seinen eigenen Mandanten. Ein zweiter,
|
||||
eingeloggter, aber nicht privilegierter Testnutzer bekommt auf
|
||||
`backoffice.php` korrekt `403 Forbidden`. Alle Testdaten anschließend
|
||||
vollständig entfernt.
|
||||
Reference in New Issue
Block a user