fix(i-d): RBAC-Comprehensive 4 Failures behoben — http_exception_handler um dict-detail-Durchreichung erweitert (strukturierte Error-Codes AGENTS.md-konform, body[detail] = raw_detail dict statt stringify); 3 Contact-Payload-Feldnamen korrigiert (firstname/surname statt first_name/last_name in legacy-editor Tests); test_rbac_comprehensive 102/102 gruen
This commit is contained in:
@@ -92,6 +92,8 @@
|
||||
| E1-b | E1 Beweis: Dedizierter Test test_audit_middleware.py — POST auf /api/v1/saved-views (Route OHNE explizites log_audit) erzeugt Audit-Zeile mit source=middleware | ✅ Test grün; Regressionssmoke test_permissions+test_audit_middleware 23/23 grün; ruff clean; dabei log_audit-details-Schwäche entdeckt (details-Parameter wird nicht persistiert — nur changes) und Middleware entsprechend auf changes umgestellt | — |
|
||||
| E3-a | E3 Restore-Drill: Neues Skript scripts/restore_drill.sh — vollständiger lokaler Drill ohne Production-Zugriff: Migrations-DB+Seed → pg_dump → frische DB → Restore → Integritäts-Checks | ✅ DRILL_EXIT=0, alle 12 Checks bestanden: Tabellen-Parität 69=69, Alembic-Version-Parität 0142, RLS-Policies-Parität 57, tenant-scoped contacts-Parität, audit_log-Parität, RLS fail-closed mit restricted role (NOSUPERUSER NOBYPASSRLS sieht 0 Zeilen ohne Tenant), Policy-Rollen-Bindung an crm_api bewiesen; dabei 2 Test-Harness-Fallen behoben (Superuser bypassed RLS by design; uuidgen fehlt im Container) | — |
|
||||
| E3-b | E3 CI-Integration: restore_drill.sh als automatisierbarer Drill (Exit-Codes 0/1, Cleanup via trap) für wöchentlichen Lauf | ✅ Skript ist idempotent (einzigartige DB-Namen pro Lauf via $$), räumt Temp-DBs selbst auf; Einbindung in CI/wöchentlichen Cron als Follow-up für Server-Admin dokumentiert | 81aea8c |
|
||||
| E/I-D | Geister-Komponenten eliminiert + RBAC-Failures behoben: AIAssistant-Seite gebaut; 5 Ghost-Tabs entfernt; http_exception_handler um dict-detail-Durchreichung erweitert (strukturierte Error-Codes AGENTS.md-konform); 3 Contact-Payload-Feldnamen korrigiert | ✅ test_rbac_comprehensive **102/102 grün** (vorher 4 failed); tsc exit=0; Production-Build mit AIAssistant-Chunks; ruff clean ×6 Dateien | — |
|
||||
|
||||
| E6-a | E6 Secrets-Hygiene: docs/deploy-guide.md enthielt 7 echte Credentials im Klartext (Forgejo-Token, Coolify-Token, DB-Passwort, Redis-Passwort, SECRET_KEY, Admin-Passwort) — durch Git-Historie kompromittiert | ✅ Alle Werte entfernt und durch Secretstore-Referenzen ersetzt; Credential-Rotation-Anleitung mit konkreten Schritten für alle 7 Credentials ergänzt (Reihenfolge: SECRET_KEY zuletzt da Session-Invalidierung); Verifikation: 0 echte Credentials in der Datei; ⚠️ ROTATION MUSS VOM USER AUF SERVER-SEITE DURCHGEFÜHRT WERDEN | — |
|
||||
| E2/E4/E5 | E2 E2E gegen Production-Build, E4 Monitoring-Reality-Check, E5 Performance-Baseline: Benötigen Server-/Deployment-Kontext (Coolify-Deploy, externes Alerting, Lasttest-Umgebung) | ⏳ Als Server-Admin-Follow-ups dokumentiert; lokale Vorbereitung (Playwright-Config mit BASE_URL, seed_perf_data.py, spike_e_benchmark.py) existiert bereits | — |
|
||||
|
||||
|
||||
+16
-1
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
from app.config import get_settings # noqa: E402
|
||||
from app.core.db import close_engine, get_engine # noqa: E402
|
||||
from app.core.error_codes import ApiError, build_error_response # noqa: E402
|
||||
from app.core.error_codes import ERROR_CODES, ApiError, build_error_response # noqa: E402
|
||||
from app.core.middleware import ( # noqa: E402
|
||||
AuditMiddleware,
|
||||
CSRFMiddleware,
|
||||
@@ -519,6 +519,21 @@ def create_app() -> FastAPI:
|
||||
504: "service_timeout",
|
||||
}
|
||||
code = status_to_code.get(exc.status_code, "internal_error" if exc.status_code >= 500 else "validation_error")
|
||||
# Structured detail passthrough (AGENTS.md): when a route raises
|
||||
# HTTPException with a dict detail containing a machine-readable ``code``,
|
||||
# preserve the structured shape instead of stringifying it.
|
||||
raw_detail = exc.detail
|
||||
if isinstance(raw_detail, dict):
|
||||
inner_code = raw_detail.get("code", code)
|
||||
body = build_error_response(
|
||||
code=inner_code if inner_code in ERROR_CODES else code,
|
||||
detail=raw_detail.get("detail") or str(raw_detail),
|
||||
trace_id=trace_id,
|
||||
)
|
||||
# Preserve the full structured detail as a nested object so clients
|
||||
# can read ``resp.json()["detail"]["code"]``.
|
||||
body["detail"] = raw_detail
|
||||
else:
|
||||
body = build_error_response(
|
||||
code=code,
|
||||
detail=str(exc.detail) if exc.detail else None,
|
||||
|
||||
@@ -926,7 +926,7 @@ class TestRBACRouteGuard:
|
||||
csrf = await login_with_csrf(client, "viewer@tenanta.com")
|
||||
resp = await client.post(
|
||||
"/api/v1/contacts",
|
||||
json={"first_name": "Test", "last_name": "User", "type": "person"},
|
||||
json={"firstname": "Test", "surname": "User", "type": "person"},
|
||||
headers=csrf_headers(csrf),
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
@@ -1071,7 +1071,7 @@ class TestRBACRouteGuard:
|
||||
csrf = await login_with_csrf(client, "editor@tenanta.com")
|
||||
resp = await client.post(
|
||||
"/api/v1/contacts",
|
||||
json={"first_name": "Editor", "last_name": "Contact", "type": "person"},
|
||||
json={"firstname": "Editor", "surname": "Contact", "type": "person"},
|
||||
headers=csrf_headers(csrf),
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
@@ -1085,7 +1085,7 @@ class TestRBACRouteGuard:
|
||||
csrf = await login_with_csrf(client, "viewer@tenanta.com")
|
||||
resp = await client.post(
|
||||
"/api/v1/contacts",
|
||||
json={"first_name": "Viewer", "last_name": "Contact", "type": "person"},
|
||||
json={"firstname": "Viewer", "surname": "Contact", "type": "person"},
|
||||
headers=csrf_headers(csrf),
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
||||
Reference in New Issue
Block a user