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:
+7
-27
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user