# Sicherheitsmodell

Letzte Aktualisierung: 2026-02-21

## 1) Grundprinzip

Die Anwendung kombiniert mehrere Sicherheitslayer:

1. Request-Grenze (Nginx + Minty Firewall)
2. Session/Auth-Guard
3. Permission-Checks
4. Tenant-Scope-Checks
5. Endpoint-spezifische Schutzlogik (z. B. Avatar-Dateien, Exporte, Actions)

Kein einzelner Layer ist ausreichend; die Kombination ist der Schutz.

## 2) Authentifizierung

- Session-basierter Login.
- Optionales Remember-Me über `user_remember_tokens`.
- Auto-Login aus Cookie erfolgt früh in `/web/index.php`.
- Zentraler Login-Entry ist `login` (optional `login?tenant=<slug>` für Tenant-Vorauswahl).
- Unknown-Email-Verhalten im Login bleibt generisch (keine Tenant-/Account-Details vor erfolgreicher Auflösung).

Kritisch:

- Tokens sind gehasht gespeichert.
- Tokens können zentral ablaufen gelassen werden (Settings-Gefahrenzone).

## 3) Public vs. Protected Routes

- Public Routes werden in `/config/routes.php` mit `public => true` definiert.
- Daraus wird zur Laufzeit `APP_PUBLIC_PATHS` aufgebaut.
- Alles andere ist loginpflichtig.

## 4) Permission-System

- Berechtigungen sind feingranular (`permissions` + `role_permissions`).
- Rollen vergeben Berechtigungen an User (`user_roles`).
- Actions prüfen Berechtigungen explizit.

Empfehlung:

- Rechte für View/Edit/Delete getrennt halten.
- Admin-only Endpunkte immer serverseitig absichern, nicht nur im UI verstecken.
- Onboarding-PDFs für Benutzer laufen über eigenes Recht `users.access_pdf` (Single + Bulk).
- Tenant-Zusatzfelder sind getrennt abgesichert:
  - `custom_fields.manage` für Definitionen/Optionen im Tenant
  - `custom_fields.edit_values` für Wertepflege am User.
  - `field_key` wird serverseitig erzeugt/stabil gehalten (kein editierbares UI-Feld), damit tenant-weite Eindeutigkeit erhalten bleibt.
- Theme-Wechsel für User wird tenant-spezifisch abgesichert:
  - effektive Regel kommt aus `current_tenant.allow_user_theme` (Fallback global `app_theme_user`)
  - Endpoint `admin/users/theme` blockt bei deaktivierter Regel mit `403` (`theme_disabled`).
- Tenant-Microsoft-SSO ist separat abgesichert:
  - `tenants.sso_manage` für Pflege der Tenant-SSO-Konfiguration.
  - Shared-App-Credentials bleiben unter `settings.update`.
  - Secrets werden nur verschlüsselt gespeichert (`APP_CRYPTO_KEY` + AES-256-GCM), nie im Klartext gerendert.
  - Profil-Sync ist tenant-spezifisch konfigurierbar (`sync_profile_on_login`, `sync_profile_fields`).
  - Erlaubte Sync-Felder: `first_name`, `last_name`, `phone`, `mobile`, `avatar`.
  - Graph-basierte Felder (`phone/mobile/avatar`) laufen fail-open: Login bleibt erfolgreich, auch wenn Graph nicht verfügbar ist.

## 5) Tenant Scope

- Datenzugriff für tenant-gebundene Inhalte erfolgt nur bei Tenant-Match.
- Departments sind genau einem Tenant zugeordnet (`departments.tenant_id`).
- Beim Speichern von User-Zuordnungen werden Departments serverseitig gegen die aktuell zugewiesenen User-Tenants gefiltert; ungültige Department-Zuordnungen werden entfernt.
- User-Zusatzfeldwerte werden auf Tenant-Wechsel bereinigt (Werte außerhalb der aktuell zugewiesenen Tenants werden gelöscht).
- Filterbare Zusatzfelder sind auf sichere Typen begrenzt (`select`, `multiselect`, `boolean`, `date`); freie Texttypen werden nicht als dynamische Filter zugelassen.
- Theme-Auflösung ist tenant-spezifisch: `default_theme` und `allow_user_theme` werden je `current_tenant` aufgelöst; ohne Tenant-Snapshot greifen globale Defaults.
- SSO-Mapping bleibt tenant-scoped:
  - Primär über externe Identität (`provider + tid + oid`).
  - Fallback einmalig über E-Mail (create-or-link), danach feste Identity-Linkung.
  - Domain-Allowlist (falls gesetzt) wird im Callback serverseitig erzwungen.
  - Profil-Sync überschreibt nur konfigurierte Felder und ignoriert leere Quelle-Werte;
    `email` bleibt nach JIT-Create unverändert (insert-only).
- Lokaler/SSO-Login ist nur mit mindestens einem aktiven User-Tenant erlaubt (kein Sonderfall ohne Tenant).
- Strict/Non-Strict Verhalten über `TENANT_SCOPE_STRICT`.
- Inaktiv-Status von Tenants beeinflusst Sichtbarkeit (je nach Kontext).

## 6) Verbotene Zugriffe

- Bei fehlender Permission/Tenant-Bindung: forbidden flow (403 Seite) statt stilles Durchrutschen.
- Keine sensitiven Details in Fehlermeldungen leaken.

## 7) Datei-/Media-Endpunkte

- Avatar/Favicon/Logo Endpunkte prüfen Session + Zugriffskontext.
- Storage liegt außerhalb direkter Business-Logik; Zugriff nur über kontrollierte Endpunkte/Symlinks.

## 8) CSRF und Form-Schutz

- Schreibaktionen nutzen CSRF-Token.
- Delete/Status-Aktionen nur per POST und serverseitiger Validierung.
- Bulk-Download für Onboarding-PDF (`admin/users/access-pdf-bulk`) ist POST+CSRF, Scope bleibt aktiv.
- OIDC-Loginfluss nutzt `state`, `nonce` und PKCE; Callback validiert zusätzlich `aud`, `iss`, `tid`, Signatur (JWKS), `exp/nbf`.

## 9) Operative Checks

Regelmäßig prüfen:

- Inaktive Rollen/Permissions werden nicht mehr aktiv zugewiesen/ausgewertet.
- Tenant-Entzüge invalidieren Sichtkontext erwartungsgemäß.
- Remember-Tokens können zentral invalidiert werden.
- Mail-Log enthält Fehlerdaten für Auditing.
- User-Lifecycle-Policy (falls aktiv) läuft täglich und setzt inaktive Accounts automatisch auf `inactive`
  bzw. löscht sie später (2-stufig), privilegierte Admins sind ausgenommen.
  - Delete schreibt vorher zwingend ein Audit-Snapshot (fail-closed).
  - Snapshot ist verschlüsselt gespeichert (`snapshot_enc`) und enthält keine Secrets/Tokens.
  - Restore ist separat berechtigt (`users.lifecycle_restore`) und stellt bewusst keine Assignments wieder her.
- Geplante Aufgaben (Scheduler) sind Whitelist-basiert:
  - keine frei ausführbaren Shell-Kommandos aus der Datenbank.
  - zentrale Rechte: `jobs.view`, `jobs.manage`, `jobs.run_now`.
  - Runner-Lock verhindert parallele Mehrfach-Ausführung.

## 10) API-Audit

- Alle `/api/v1/*` Requests werden zentral mit Metadaten in `api_audit_log` protokolliert.
- V1 speichert keine Request-/Response-Bodies, nur Metadaten (Pfad, Status, Dauer, Scope-Kontext).
- Query-Parameter werden redacted gespeichert (z. B. Token/Secret/Password-Felder).
- Zugriff auf die Audit-UI ist über Permission `api_audit.view` abgesichert.
- Retention ist auf 90 Tage ausgelegt (Bereinigung per Admin-Action oder Cron).

## 11) User-Lifecycle-Audit

- Lifecycle-Audit-UI ist über `user_lifecycle_audit.view` abgesichert.
- Restore aus Delete-Snapshot ist getrennt über `users.lifecycle_restore` abgesichert.
- Ohne gültigen Snapshot wird kein Auto-Delete ausgeführt (fail-closed).
- Retention für Lifecycle-Audit-Einträge: 365 Tage.
