fix(PROJ-43): Dry-Run für from_addr/to_addr matcht bare Adressen statt <addr>-Form
dryRunCondition() erwartete faelschlich die Winkelklammer-Form "Name <addr>", waehrend mail_from/mail_to die Adresse bare speichern - Dry-Run zeigte dadurch immer 0 Treffer fuer Adress-Regeln, obwohl der Live-Matcher (routeBareAddr) korrekt matcht. QA-Ergebnisse (Bug-1) in die Feature-Spec uebernommen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
04e5b0f74a
commit
4c92587b60
@@ -110,3 +110,75 @@ RoutingRule-Shape: `{id, tenant_id, match_type, pattern, priority, created_at}`.
|
||||
### Offen / Handoff
|
||||
- QA gegen Acceptance Criteria (CRUD, Dry-Run, Rollen-Gate) auf Testserver.
|
||||
- Bereits archivierte Mails werden NICHT rückwirkend umgeroutet (nur neue Ingests).
|
||||
|
||||
## QA Test Results
|
||||
|
||||
**Getestet:** 2026-07-04 auf Testserver 192.168.1.132 (Binary v0.9.1, deployt 2026-07-03).
|
||||
Test-Accounts: `qa-superadmin` (superadmin), `qa-da-t1` (domain_admin Tenant 1),
|
||||
`qa-da-t3` (domain_admin Tenant 3). **Gesamtergebnis: BESTANDEN MIT 1 BUG** (Dry-Run für
|
||||
`from_addr`/`to_addr` liefert falsche Nullwerte — siehe Bug-1). CRUD, Tenant-Scoping/IDOR und
|
||||
Wildcard-Matching sind grün.
|
||||
|
||||
### Ergebnis je Acceptance Criterion
|
||||
- [x] **Tabelle `tenant_routing_rules`** — BESTANDEN. Schema mit Spalten (id, tenant_id NOT NULL
|
||||
FK, match_type, pattern, priority, created_at) vorhanden; CRUD liefert erwartete Shape.
|
||||
- [x] **SMTP-Daemon + IMAP-Import prüfen Regeln** — Code-verifiziert (nicht per Live-Mail).
|
||||
`resolveTenantByRules()` in `internal/smtpd/smtpd.go:107` VOR `tenant_domains`-Logik;
|
||||
`ResolveTenantByRoutingRules()` in `internal/imap/importer.go:255`. Verdrahtung vorhanden.
|
||||
Hinweis: End-to-End-Zuordnung per echtem Mailversand nicht durchgeführt (kein Live-SMTP-Test).
|
||||
- [x] **API CRUD (Admin only)** — BESTANDEN. GET/POST/PUT/DELETE unter `/api/admin/routing-rules`
|
||||
funktionieren; ohne Cookie → `401`. POST als domain_admin ohne `tenant_id` erzwingt eigenen
|
||||
Tenant (`201 {"id":5}`).
|
||||
- [x] **Frontend Regel-Verwaltung** — Komponente `RoutingRulesTab.tsx` vorhanden, Datenkontrakt
|
||||
passt zur API. UI visuell nicht separat durchgeklickt.
|
||||
- [x] **Priorität: höhere gewinnt** — Code-verifiziert. `ListTenantRoutingRules` sortiert
|
||||
`ORDER BY priority DESC, id ASC`; `ResolveTenantByRoutingRules` nimmt ersten Match →
|
||||
höchste Priorität, bei Gleichstand niedrigste id. Logik korrekt.
|
||||
- [~] **Dry-Run** — TEILWEISE. `from_domain`/`to_domain` korrekt (perlbach24.de → 955 bzw.
|
||||
2962 Treffer, inkl. Wildcard). `from_addr`/`to_addr` liefern falsche 0-Treffer → **Bug-1**.
|
||||
|
||||
### Sicherheit / Tenant-Isolation / IDOR
|
||||
- **Auth-Bypass:** Ohne Cookie → `401` auf allen Endpunkten. BESTANDEN.
|
||||
- **IDOR Create:** domain_admin T1 versucht Regel mit `tenant_id:3` → `403
|
||||
{"error":"tenant_id required and must match your scope"}`. BESTANDEN.
|
||||
- **IDOR Update:** T1 versucht PUT auf Regel id 2 (gehört Tenant 3) → `403
|
||||
{"error":"forbidden"}`, Regel unverändert. BESTANDEN.
|
||||
- **IDOR Delete:** T1 versucht DELETE Regel id 2 (Tenant 3) → `403 {"error":"forbidden"}`,
|
||||
Regel bleibt bestehen. BESTANDEN.
|
||||
- **Tenant-Move via Body:** T1 versucht eigene Regel id 5 per PUT `tenant_id:3` zu verschieben
|
||||
→ `403 {"error":"tenant_id must match your scope"}`, Regel bleibt Tenant 1. BESTANDEN.
|
||||
- **Listing-Scope:** domain_admin T1 sieht nur Tenant-1-Regeln, T3 nur Tenant-3; superadmin
|
||||
alle. Kein Cross-Tenant-Leck. BESTANDEN. (Deckt die PROJ-61-Bugklasse ab: Tenant-Scope wird
|
||||
zusätzlich zum Rollen-Check via `tenantAccessAllowed` geprüft.)
|
||||
- **SQL-Injection Dry-Run:** Pattern `' OR 1=1 --` → `match_count:0`, keine Fehler, keine
|
||||
Anomalie im Log. Parametrisierte Query (`$1`), sauber. BESTANDEN.
|
||||
|
||||
### Wildcard-Matching
|
||||
- `domainMatches()` (`tenant_routing_rules.go:217`): `*.base` matcht Sub-Domains UND die
|
||||
Apex-Domain (dokumentiert im Code-Kommentar Zeile 216). Dry-Run `*.perlbach24.de` = 955 =
|
||||
`perlbach24.de` bestätigt dieses beabsichtigte Verhalten. BESTANDEN (Spec "matcht
|
||||
Sub-Domains" wird als "Sub-Domains + Apex" umgesetzt — konsistent Engine↔Dry-Run).
|
||||
|
||||
### Offene Bugs
|
||||
**Bug-1 — Dry-Run für `from_addr`/`to_addr` liefert falsche Nullwerte. Severity: MEDIUM.
|
||||
Priorität: Hoch (Kernfunktion des AC "Dry-Run" für 2 von 4 Match-Typen unbrauchbar).**
|
||||
- Repro: `POST /api/admin/routing-rules/dry-run {"match_type":"from_addr","pattern":
|
||||
"support@perlbach24.de"}` → `match_count:0`, obwohl 955 Mails exakt von dieser Adresse
|
||||
existieren (bestätigt via `SELECT ... WHERE mail_from ILIKE '%support@perlbach24.de%'`).
|
||||
- Ursache: `dryRunCondition()` in `internal/storage/tenant_routing_rules.go:317-319` baut für
|
||||
`from_addr`/`to_addr` die Bedingung `LOWER(mail_from) LIKE '%<' + pattern + '>%'`, erwartet
|
||||
also die Winkelklammer-Form `Name <addr>`. Die Spalte `mail_from` speichert die Adresse aber
|
||||
bare (`support@perlbach24.de`, ohne `<>`), daher matcht das Pattern nie. Der Live-Matcher
|
||||
(`routeBareAddr`, Zeile 205) normalisiert korrekt und würde matchen → Dry-Run und echte
|
||||
Regel-Anwendung sind inkonsistent. Admin sieht "0 betroffen" und konfiguriert Adress-Regeln
|
||||
im Blindflug.
|
||||
- Fix-Richtung (für Backend Developer, nicht von QA umgesetzt): Bedingung so bauen, dass beide
|
||||
Speicherformen abgedeckt sind, z.B. Match auf bare Adresse (`LOWER(mail_from) LIKE '%'||p||'%'`
|
||||
bzw. genauer gegen `@`-Grenze) statt harte `<...>`-Umklammerung.
|
||||
|
||||
### Testdaten-Hygiene
|
||||
- Die für den Test angelegte Regel id 5 (Tenant 1) wurde nach dem Test wieder gelöscht
|
||||
(`DELETE /api/admin/routing-rules/5` → `200 {"ok":true}`). Vorbestehende Regeln id 1-4
|
||||
(aus früherem QA-Lauf) unangetastet gelassen.
|
||||
- Passwörter der `qa-*`-Accounts für den Test gesetzt (siehe PROJ-52 QA-Hinweis). Kein
|
||||
Produktivserver (131) berührt.
|
||||
|
||||
Reference in New Issue
Block a user