c867478791
CI / loc-budget (push) Failing after 16s
Build + Deploy / build-admin-compliance (push) Successful in 14s
Build + Deploy / build-backend-compliance (push) Successful in 16s
Build + Deploy / build-ai-sdk (push) Successful in 20s
Build + Deploy / build-developer-portal (push) Successful in 12s
Build + Deploy / build-tts (push) Successful in 15s
Build + Deploy / build-document-crawler (push) Successful in 13s
Build + Deploy / build-dsms-gateway (push) Successful in 13s
Build + Deploy / build-dsms-node (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / test-python-document-crawler (push) Successful in 26s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m49s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 45s
CI / test-python-backend (push) Successful in 38s
CI / test-python-dsms-gateway (push) Successful in 23s
CI / validate-canonical-controls (push) Successful in 15s
Build + Deploy / trigger-orca (push) Successful in 2m23s
Phase 1-2 of the closed quality loop: - GVL cache (consent-tester/services/gvl_cache.py): downloads and caches IAB Global Vendor List with 24h TTL, resolves vendor IDs to names, purposes, policy URLs, retention, country - Vendor extraction (consent_interceptor.py): extract_tcf_vendors() reads __tcfapi after accept phase, resolves via GVL - Scan response: tcf_vendors field added to /scan endpoint - VVT mapper (vendor_vvt_mapper.py): maps TCF vendors to VVT format with purpose labels, Rechtsgrundlage, Drittland detection - Vendor cross-check (banner_cookie_cross_check.py): checks all TCF vendors against DSI text — missing vendors, undocumented transfers - Compliance check integrates Step 3d: TCF vendors vs DSI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
105 lines
3.5 KiB
Python
105 lines
3.5 KiB
Python
"""
|
|
Vendor VVT Mapper — map TCF vendors to VVT entries.
|
|
|
|
Converts resolved TCF vendor data (from GVL) into the format
|
|
needed for the Verarbeitungsverzeichnis (VVT) and for DSI
|
|
cross-checking.
|
|
"""
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# IAB TCF v2.2 Purpose definitions (German)
|
|
TCF_PURPOSE_LABELS = {
|
|
1: "Speicherung/Zugriff auf Endgeraet",
|
|
2: "Auswahl einfacher Anzeigen",
|
|
3: "Personalisiertes Anzeigenprofil erstellen",
|
|
4: "Personalisierte Anzeigen auswaehlen",
|
|
5: "Personalisiertes Inhaltsprofil erstellen",
|
|
6: "Personalisierte Inhalte auswaehlen",
|
|
7: "Anzeigenleistung messen",
|
|
8: "Inhaltsleistung messen",
|
|
9: "Marktforschung",
|
|
10: "Produkte entwickeln und verbessern",
|
|
11: "Geraeteeigenschaften zur Identifizierung nutzen",
|
|
}
|
|
|
|
# Purpose → Banner-Kategorie Mapping
|
|
PURPOSE_CATEGORY = {
|
|
1: "necessary",
|
|
2: "marketing", 3: "marketing", 4: "marketing",
|
|
5: "marketing", 6: "marketing",
|
|
7: "statistics", 8: "statistics",
|
|
9: "statistics", 10: "functional", 11: "functional",
|
|
}
|
|
|
|
# EWR countries
|
|
_EU_EWR = {
|
|
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
|
|
"DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL",
|
|
"PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO",
|
|
"CH", "GB",
|
|
}
|
|
|
|
|
|
def tcf_vendor_to_vvt(vendor: dict) -> dict:
|
|
"""Map a resolved TCF vendor to a VVT entry.
|
|
|
|
Args:
|
|
vendor: Resolved GVL vendor dict with name, purposes, country, etc.
|
|
|
|
Returns:
|
|
VVT-compatible dict with name, zweck, rechtsgrundlage, drittland, etc.
|
|
"""
|
|
purposes = vendor.get("purposes", [])
|
|
country = vendor.get("country")
|
|
is_eu = vendor.get("is_eu", country in _EU_EWR if country else None)
|
|
|
|
# Determine primary category from purposes
|
|
categories = set()
|
|
for p in purposes:
|
|
cat = PURPOSE_CATEGORY.get(p, "functional")
|
|
categories.add(cat)
|
|
|
|
# Rechtsgrundlage depends on category
|
|
if "marketing" in categories or "statistics" in categories:
|
|
rechtsgrundlage = "Einwilligung (Art. 6(1)(a) DSGVO, §25 Abs. 1 TDDDG)"
|
|
else:
|
|
rechtsgrundlage = "Berechtigtes Interesse (Art. 6(1)(f) DSGVO, §25 Abs. 2 TDDDG)"
|
|
|
|
return {
|
|
"vendor_id": vendor.get("vendor_id"),
|
|
"name": vendor.get("name", ""),
|
|
"zweck": [TCF_PURPOSE_LABELS.get(p, f"Zweck {p}") for p in purposes],
|
|
"zweck_kurz": _summarize_purposes(purposes),
|
|
"kategorie": sorted(categories)[0] if categories else "functional",
|
|
"rechtsgrundlage": rechtsgrundlage,
|
|
"drittland": not is_eu if is_eu is not None else None,
|
|
"land": country,
|
|
"transfermechanismus": "SCC/DPF" if (not is_eu and is_eu is not None) else None,
|
|
"speicherdauer_tage": vendor.get("retention_days"),
|
|
"policy_url": vendor.get("policy_url", ""),
|
|
"uses_cookies": vendor.get("uses_cookies", False),
|
|
}
|
|
|
|
|
|
def map_vendors_to_vvt(vendors: list[dict]) -> list[dict]:
|
|
"""Map a list of TCF vendors to VVT entries."""
|
|
return [tcf_vendor_to_vvt(v) for v in vendors]
|
|
|
|
|
|
def _summarize_purposes(purposes: list[int]) -> str:
|
|
"""Short German summary of purposes."""
|
|
if not purposes:
|
|
return "Keine Zwecke angegeben"
|
|
cats = set(PURPOSE_CATEGORY.get(p, "sonstig") for p in purposes)
|
|
labels = {
|
|
"marketing": "Marketing/Werbung",
|
|
"statistics": "Analyse/Messung",
|
|
"functional": "Funktional",
|
|
"necessary": "Technisch notwendig",
|
|
"sonstig": "Sonstige",
|
|
}
|
|
return ", ".join(labels.get(c, c) for c in sorted(cats))
|