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:
@@ -20,12 +20,16 @@ from compliance.api._http_errors import translate_domain_errors
|
||||
from compliance.schemas.banner import (
|
||||
CategoryConfigCreate,
|
||||
ConsentCreate,
|
||||
ConsentSyncRequest,
|
||||
LinkEmailRequest,
|
||||
SiteConfigCreate,
|
||||
SiteConfigUpdate,
|
||||
VendorConfigCreate,
|
||||
)
|
||||
from compliance.services.banner_admin_service import BannerAdminService
|
||||
from compliance.services.banner_consent_service import BannerConsentService
|
||||
from compliance.services.banner_dsr_service import BannerDSRService
|
||||
from compliance.services.vendor_banner_sync import get_banner_vendors_from_registry
|
||||
|
||||
router = APIRouter(prefix="/banner", tags=["compliance-banner"])
|
||||
|
||||
@@ -48,6 +52,10 @@ def get_admin_service(db: Session = Depends(get_db)) -> BannerAdminService:
|
||||
return BannerAdminService(db)
|
||||
|
||||
|
||||
def get_dsr_service(db: Session = Depends(get_db)) -> BannerDSRService:
|
||||
return BannerDSRService(db)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Public SDK Endpoints (fuer Einbettung in Kunden-Websites)
|
||||
# =============================================================================
|
||||
@@ -118,6 +126,69 @@ async def export_consent(
|
||||
return service.export_consent(tenant_id, site_id, device_fingerprint)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DSR Integration — Email Linking + Consent Sync (Phase 3 + 4)
|
||||
# =============================================================================
|
||||
|
||||
@router.post("/consent/link-email")
|
||||
async def link_email(
|
||||
body: LinkEmailRequest,
|
||||
tenant_id: str = Depends(_get_tenant),
|
||||
service: BannerDSRService = Depends(get_dsr_service),
|
||||
) -> dict[str, Any]:
|
||||
"""Link an email to a device fingerprint (e.g. after signup/login)."""
|
||||
with translate_domain_errors():
|
||||
return service.link_email(
|
||||
tenant_id, body.site_id, body.device_fingerprint, body.email,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/consent/by-email/{email}")
|
||||
async def get_consents_by_email(
|
||||
email: str,
|
||||
tenant_id: str = Depends(_get_tenant),
|
||||
service: BannerDSRService = Depends(get_dsr_service),
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Find all banner consents linked to an email (Art. 15 DSGVO)."""
|
||||
with translate_domain_errors():
|
||||
return service.get_consents_by_email(tenant_id, email)
|
||||
|
||||
|
||||
@router.delete("/consent/by-email/{email}")
|
||||
async def delete_consents_by_email(
|
||||
email: str,
|
||||
tenant_id: str = Depends(_get_tenant),
|
||||
service: BannerDSRService = Depends(get_dsr_service),
|
||||
) -> dict[str, Any]:
|
||||
"""Delete all banner consents for an email (Art. 17 DSGVO erasure)."""
|
||||
with translate_domain_errors():
|
||||
return service.delete_consents_by_email(tenant_id, email)
|
||||
|
||||
|
||||
@router.get("/consent/dsr-export/{email}")
|
||||
async def export_for_dsr(
|
||||
email: str,
|
||||
tenant_id: str = Depends(_get_tenant),
|
||||
service: BannerDSRService = Depends(get_dsr_service),
|
||||
) -> dict[str, Any]:
|
||||
"""Export all banner consent data for DSR (Art. 15/20 DSGVO)."""
|
||||
with translate_domain_errors():
|
||||
return service.export_for_dsr(tenant_id, email)
|
||||
|
||||
|
||||
@router.post("/consent/sync")
|
||||
async def sync_consent(
|
||||
body: ConsentSyncRequest,
|
||||
tenant_id: str = Depends(_get_tenant),
|
||||
service: BannerDSRService = Depends(get_dsr_service),
|
||||
) -> dict[str, Any]:
|
||||
"""Sync banner consent to Einwilligungen (Phase 4 — user-based bridge)."""
|
||||
with translate_domain_errors():
|
||||
return service.sync_consent_to_einwilligungen(
|
||||
tenant_id, body.device_fingerprint, body.email, body.site_id,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Admin — Stats
|
||||
# =============================================================================
|
||||
@@ -253,3 +324,43 @@ async def delete_vendor(
|
||||
"""Delete a vendor."""
|
||||
with translate_domain_errors():
|
||||
service.delete_vendor(vendor_id)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Admin — Vendor Sync from Service Registry (Phase 1)
|
||||
# =============================================================================
|
||||
|
||||
@router.post("/admin/sites/{site_id}/sync-vendors")
|
||||
async def sync_vendors_from_registry(
|
||||
site_id: str,
|
||||
tenant_id: str = Depends(_get_tenant),
|
||||
service: BannerAdminService = Depends(get_admin_service),
|
||||
) -> dict[str, Any]:
|
||||
"""Sync 82+ services from service registry to banner vendor configs."""
|
||||
with translate_domain_errors():
|
||||
vendors = get_banner_vendors_from_registry()
|
||||
created = 0
|
||||
updated = 0
|
||||
for v in vendors:
|
||||
try:
|
||||
existing = service.list_vendors(tenant_id, site_id)
|
||||
match = next(
|
||||
(e for e in existing if e["vendor_name"] == v["vendor_name"]),
|
||||
None,
|
||||
)
|
||||
if match:
|
||||
updated += 1
|
||||
else:
|
||||
from compliance.schemas.banner import VendorConfigCreate
|
||||
service.create_vendor(tenant_id, site_id, VendorConfigCreate(
|
||||
vendor_name=v["vendor_name"],
|
||||
category_key=v["category_key"],
|
||||
description_de=v["description_de"],
|
||||
description_en=v["description_en"],
|
||||
cookie_names=v["cookie_names"],
|
||||
retention_days=v["retention_days"],
|
||||
))
|
||||
created += 1
|
||||
except Exception:
|
||||
continue
|
||||
return {"created": created, "updated": updated, "total": len(vendors)}
|
||||
|
||||
Reference in New Issue
Block a user