fix(PROJ-54): Listenansicht/Pagination für Rolle "user" korrigieren

Filtert die Mails des Nutzers (From/To/Cc/Bcc) jetzt serverseitig via
req.AnyAddress vor LIMIT/OFFSET im Index, statt wie bisher per
Post-Filter nach der Paginierung. total und totalPages stimmen damit
mit den tatsächlich sichtbaren Treffern überein.
This commit is contained in:
sysops
2026-06-14 22:58:56 +02:00
parent 4c20a0025a
commit 776dee8a56
2 changed files with 16 additions and 21 deletions
+2 -1
View File
@@ -70,7 +70,8 @@
| PROJ-51 | Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien) | Deployed | [PROJ-51](PROJ-51-retention-kategorien.md) | 2026-06-13 | | PROJ-51 | Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien) | Deployed | [PROJ-51](PROJ-51-retention-kategorien.md) | 2026-06-13 |
| PROJ-52 | Vollständigkeits-Reconciliation (Zähl-Report) | Planned | [PROJ-52](PROJ-52-vollstaendigkeits-reconciliation.md) | 2026-06-13 | | PROJ-52 | Vollständigkeits-Reconciliation (Zähl-Report) | Planned | [PROJ-52](PROJ-52-vollstaendigkeits-reconciliation.md) | 2026-06-13 |
| PROJ-53 | Konfigurierbare Listenanzahl pro Seite | In Review | [PROJ-53](PROJ-53-konfigurierbare-listenanzahl.md) | 2026-06-14 | | PROJ-53 | Konfigurierbare Listenanzahl pro Seite | In Review | [PROJ-53](PROJ-53-konfigurierbare-listenanzahl.md) | 2026-06-14 |
| PROJ-54 | Fix Listenansicht/Pagination für Rolle "user" (Nachbesserung PROJ-6/PROJ-21) | In Review | [PROJ-54](PROJ-54-fix-listenansicht-total.md) | 2026-06-14 |
<!-- Add features above this line --> <!-- Add features above this line -->
## Next Available ID: PROJ-54 ## Next Available ID: PROJ-55
+14 -20
View File
@@ -87,6 +87,20 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
return return
} }
// PROJ-54: For user role, restrict results to mails the user is involved
// in (From/To/CC/BCC) at the INDEX level — before LIMIT/OFFSET — so that
// Total and pagination reflect the user's own mails, not the full
// tenant/global result set. Email comes from the JWT session — no DB
// lookup needed. If email is missing, block all results (fail-safe).
if sess.Role == userstore.RoleUser {
userEmailFilter := strings.ToLower(sess.Email)
if userEmailFilter == "" {
writeJSON(w, http.StatusOK, map[string]interface{}{"total": 0, "hits": []interface{}{}})
return
}
req.AnyAddress = userEmailFilter
}
// PROJ-21 Phase 4: Use per-tenant index when available; fall back to // PROJ-21 Phase 4: Use per-tenant index when available; fall back to
// global index + post-filter when the tenant index manager is not wired. // global index + post-filter when the tenant index manager is not wired.
// auditor always uses the global index — they see no-tenant mails only, // auditor always uses the global index — they see no-tenant mails only,
@@ -164,18 +178,6 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
} }
} }
// SEC: For user role, restrict results to mails the user is involved in
// (From, To, or CC). Email comes from the JWT session — no DB lookup needed.
// If email is missing, block all results (fail-safe).
var userEmailFilter string
if sess.Role == userstore.RoleUser {
userEmailFilter = strings.ToLower(sess.Email)
if userEmailFilter == "" {
writeJSON(w, http.StatusOK, map[string]interface{}{"total": 0, "hits": []interface{}{}})
return
}
}
// Batch-load thread info and received_at fallback for all hits // Batch-load thread info and received_at fallback for all hits
hitIDs := make([]string, len(result.Hits)) hitIDs := make([]string, len(result.Hits))
for i, h := range result.Hits { for i, h := range result.Hits {
@@ -201,14 +203,6 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
eh.Date = t.UTC().Format(time.RFC3339) eh.Date = t.UTC().Format(time.RFC3339)
} }
eh.HasAttachments = len(pm.Attachments) > 0 eh.HasAttachments = len(pm.Attachments) > 0
// User isolation: skip mails the user is not involved in.
if userEmailFilter != "" && !mailBelongsToUser(pm, userEmailFilter) {
continue
}
} else if userEmailFilter != "" {
// If mail can't be parsed, deny access to user role.
continue
} }
// Auditor isolation: skip mails that belong to a tenant. // Auditor isolation: skip mails that belong to a tenant.
if auditorAllowedIDs != nil { if auditorAllowedIDs != nil {