feat: Cookie-Banner ↔ Backend Integration (DSR, Retention, Consent Proof)

Phase 1: Vendor sync from service registry (82+ services → banner vendors)
Phase 2: Category-based retention (marketing=90d, statistics=790d, not hardcoded 365d)
Phase 3: DSR ↔ Banner email linking (link-email, by-email, Art.17 erasure, Art.15/20 export)
Phase 4: Consent sync (Banner → Einwilligungen bridge)
Phase 6: Consent proof (SHA256 config hash + config_version in audit log, Art. 7(1) DSGVO)

New files:
- banner_dsr_service.py — email linking + DSR integration
- vendor_banner_sync.py — service registry → vendor configs
- migration 106 — linked_email, banner_config_hash, consent_version columns

Tests: 20+ new backend tests + 2 Playwright E2E test suites (API + UI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-02 19:41:22 +02:00
parent c3f8e19e92
commit 44acd68c96
12 changed files with 1522 additions and 5 deletions
@@ -142,9 +142,33 @@ class DSRWorkflowService:
if body.result_data:
dsr.data_export = body.result_data
dsr.updated_at = now
# Phase 3: Auto-delete banner consents on Art. 17 erasure
banner_result = None
if dsr.request_type == "erasure" and dsr.requester_email:
from compliance.services.banner_dsr_service import BannerDSRService
banner_svc = BannerDSRService(self._db)
banner_result = banner_svc.delete_consents_by_email(
tenant_id, dsr.requester_email,
)
# Phase 3: Include banner consents in data export for access/portability
if dsr.request_type in ("access", "portability") and dsr.requester_email:
from compliance.services.banner_dsr_service import BannerDSRService
banner_svc = BannerDSRService(self._db)
export = banner_svc.export_for_dsr(tenant_id, dsr.requester_email)
if export.get("banner_consents"):
existing_export = dsr.data_export or {}
if isinstance(existing_export, dict):
existing_export["banner_consents"] = export
dsr.data_export = existing_export
self._db.commit()
self._db.refresh(dsr)
return _dsr_to_dict(dsr)
result = _dsr_to_dict(dsr)
if banner_result:
result["banner_consents_deleted"] = banner_result["deleted"]
return result
# -- Reject --------------------------------------------------------------