# 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=` 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.