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:
+10
-4
@@ -356,8 +356,10 @@ func (s *Server) handleExportPDF(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// auditor: only mails with no tenant assignment.
|
||||
if sess.Role == userstore.RoleAuditor {
|
||||
// Global auditor (no tenant_id): only mails with no tenant assignment.
|
||||
// A tenant-scoped auditor is already constrained by the tenant-isolation
|
||||
// block above (sess.TenantID != nil) — PROJ-55.
|
||||
if auditorIsGlobal(sess) {
|
||||
ok, err := s.store.IsWithoutTenant(r.Context(), id)
|
||||
if err != nil || !ok {
|
||||
writeError(w, http.StatusForbidden, "access denied")
|
||||
@@ -391,6 +393,7 @@ func (s *Server) handleExportPDF(w http.ResponseWriter, r *http.Request) {
|
||||
s.audlog.Log(audit.Entry{
|
||||
EventType: audit.EventExport,
|
||||
Username: sess.Username,
|
||||
TenantID: sess.TenantID,
|
||||
IPAddress: s.remoteIP(r),
|
||||
MailID: id,
|
||||
Detail: "pdf",
|
||||
@@ -446,9 +449,11 @@ func (s *Server) handleExportZIP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Auditor: pre-load the set of no-tenant mail IDs for efficient per-mail checks.
|
||||
// Global auditor (no tenant_id): pre-load the set of no-tenant mail IDs for
|
||||
// efficient per-mail checks. A tenant-scoped auditor is already constrained
|
||||
// by the tenant-isolation block above (PROJ-55).
|
||||
var auditorAllowed map[string]struct{}
|
||||
if sess.Role == userstore.RoleAuditor {
|
||||
if auditorIsGlobal(sess) {
|
||||
ids, err := s.store.GetAllIDsWithoutTenant(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "tenant check failed")
|
||||
@@ -560,6 +565,7 @@ func (s *Server) handleExportZIP(w http.ResponseWriter, r *http.Request) {
|
||||
s.audlog.Log(audit.Entry{
|
||||
EventType: audit.EventExport,
|
||||
Username: sess.Username,
|
||||
TenantID: sess.TenantID,
|
||||
IPAddress: s.remoteIP(r),
|
||||
Detail: fmt.Sprintf("zip: %d mails", exported),
|
||||
Success: true,
|
||||
|
||||
Reference in New Issue
Block a user