fix(SEC-29): Rollen-Trennung Admins/Auditoren, domain_auditor Rolle

- superadmin + domain_admin haben keinen Mail-Zugriff mehr (requireMailAccess)
- Neue Rolle domain_auditor: alle Tenant-Mails, kein Admin-Zugriff
- auditor + user: nur eigene Mails
- ZIP-Export: kein separates Attachment-Entpacken mehr, nur EML
- roleLevel() um domain_auditor (Level 3) erweitert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-31 01:09:19 +02:00
parent e0f6a818eb
commit 64433aa847
5 changed files with 47 additions and 48 deletions
+7 -27
View File
@@ -356,8 +356,8 @@ func (s *Server) handleExportPDF(w http.ResponseWriter, r *http.Request) {
}
}
// User role: only own mailbox
if sess.Role == userstore.RoleUser {
// user and auditor: only own mails; domain_auditor: all tenant mails (no filter)
if sess.Role == userstore.RoleUser || sess.Role == userstore.RoleAuditor {
u, err := s.users.GetByUsername(sess.Username)
if err != nil || !mailBelongsToUser(pm, u.Email) {
writeError(w, http.StatusForbidden, "access denied")
@@ -393,8 +393,7 @@ func (s *Server) handleExportPDF(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleExportZIP(w http.ResponseWriter, r *http.Request) {
var req struct {
IDs []string `json:"ids"`
Attachments bool `json:"attachments"`
IDs []string `json:"ids"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
@@ -438,9 +437,9 @@ func (s *Server) handleExportZIP(w http.ResponseWriter, r *http.Request) {
}
}
// For RoleUser, look up user email once for access checks
// User and Auditor: look up email once for per-mail access checks
var userEmail string
if sess.Role == userstore.RoleUser {
if sess.Role == userstore.RoleUser || sess.Role == userstore.RoleAuditor {
u, err := s.users.GetByUsername(sess.Username)
if err != nil {
writeError(w, http.StatusInternalServerError, "user lookup failed")
@@ -477,8 +476,8 @@ func (s *Server) handleExportZIP(w http.ResponseWriter, r *http.Request) {
continue
}
// Access check for user role
if sess.Role == userstore.RoleUser && !mailBelongsToUser(pm, userEmail) {
// user and auditor: only own mails; domain_auditor: all tenant mails (no filter)
if (sess.Role == userstore.RoleUser || sess.Role == userstore.RoleAuditor) && !mailBelongsToUser(pm, userEmail) {
continue
}
@@ -509,25 +508,6 @@ func (s *Server) handleExportZIP(w http.ResponseWriter, r *http.Request) {
date: dateStr,
})
exported++
// Attachments
if req.Attachments {
shortID := id
if len(shortID) > 8 {
shortID = shortID[:8]
}
for _, a := range pm.Attachments {
if a.Filename == "" || len(a.Data) == 0 {
continue
}
attPath := fmt.Sprintf("attachments/%s/%s", shortID, a.Filename)
af, err := zw.Create(attPath)
if err != nil {
continue
}
af.Write(a.Data) //nolint:errcheck
}
}
}
// Write manifest.csv