fix(PROJ-55): Tenant-Isolation für Rolle "auditor" + Audit-Log korrigieren

Kritischer Sicherheitsbug: Auditoren mit zugewiesenem Tenant sahen Mails
und Audit-Log-Einträge anderer Tenants (DSGVO-relevant). auditor wird
jetzt analog zu domain_auditor pro Tenant gescoped, sofern tenant_id
gesetzt ist (Abwärtskompatibilität: ohne tenant_id bleibt der bisherige
globale Zugriff erhalten). Betrifft Mail-Suche, Mail-Detailzugriff,
Export, eDiscovery, Threads, OCR sowie das Audit-Log (DB + tamper-evidentes
Flat-File), inkl. Befüllung von tenant_id an allen Audit-Log-Schreibstellen.
This commit is contained in:
sysops
2026-06-21 22:26:06 +02:00
parent 4a8b8964e5
commit 92e57431c3
28 changed files with 526 additions and 27 deletions
+23
View File
@@ -36,6 +36,22 @@ func (s *Server) handleGetThread(w http.ResponseWriter, r *http.Request) {
return
}
// PROJ-55: a global auditor (no tenant_id) may only see mails without a
// tenant assignment. A tenant-scoped auditor (tenant_id set) is already
// constrained by GetMailsByThread's tenant filter above.
var auditorAllowed map[string]struct{}
if auditorIsGlobal(sess) {
noTenant, idErr := s.store.GetAllIDsWithoutTenant(r.Context())
if idErr != nil {
writeError(w, http.StatusInternalServerError, "access check failed")
return
}
auditorAllowed = make(map[string]struct{}, len(noTenant))
for _, nid := range noTenant {
auditorAllowed[nid] = struct{}{}
}
}
type mailSummary struct {
ID string `json:"id"`
From string `json:"from,omitempty"`
@@ -63,6 +79,13 @@ func (s *Server) handleGetThread(w http.ResponseWriter, r *http.Request) {
}
}
// global auditor isolation: skip mails that belong to a tenant.
if auditorAllowed != nil {
if _, ok := auditorAllowed[id]; !ok {
continue
}
}
var dateStr string
if !pm.Date.IsZero() {
dateStr = pm.Date.UTC().Format(time.RFC3339)