feat(banner): Consent-Historie/Widerruf live erkennen (Borlabs-Stil, #62)
consent_history.detect_consent_history: erkennt CMP-Anbieter (Borlabs/ Usercentrics/OneTrust/Cookiebot/…) aus Storage+Cookies, versionierten Consent (historie-fähig) + dauerhaftes Widerruf-/Einstellungs-Widget. consent_scanner ruft es in Phase A; scan_matrix_summary surft summary.consent_history; browser_cross_finding: positiver Befund wenn vorhanden, sonst Best-Practice-LOW („Nutzer sehen, wann sie welcher Version zugestimmt haben"); BrowserBehaviorView zeigt es im Engine-Detail. Tests: 7 (classify/versioned) + 2 Cross-Finding. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""Consent-Historie-/Widerruf-Erkennung (Borlabs-Stil) während des Scans.
|
||||
|
||||
Erkennt, ob die Site ihre Einwilligung versioniert speichert (Borlabs hält die
|
||||
zugestimmte Version + Zeitstempel → Nutzer kann nachvollziehen, welcher Version
|
||||
er wann zugestimmt hat) und ob ein dauerhaftes Widerruf-/„Cookie-Einstellungen"-
|
||||
Widget angeboten wird. Reine Klassifikation (`classify_provider`) ist ohne
|
||||
Browser unit-testbar; `detect_consent_history` kapselt das Playwright-IO.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
# Signatur-Fragmente in Storage-Keys/Cookie-Namen → CMP-Anbieter.
|
||||
_PROVIDERS = [
|
||||
("Borlabs", ["borlabs-cookie", "borlabscookie", "borlabs"]),
|
||||
("Usercentrics", ["uc_settings", "uc_user_interaction", "usercentrics"]),
|
||||
("OneTrust", ["optanonconsent", "optanonalertbox", "onetrust"]),
|
||||
("Cookiebot", ["cookieconsent", "cookiebot"]),
|
||||
("Complianz", ["cmplz_", "complianz"]),
|
||||
("Cookie-Script", ["cookiescriptconsent"]),
|
||||
]
|
||||
|
||||
# Wer trägt von Haus aus eine versionierte Consent-Historie (Capability).
|
||||
_HISTORY_CAPABLE = {"Borlabs", "Usercentrics", "OneTrust", "Cookiebot"}
|
||||
|
||||
# Selektoren für ein dauerhaftes Widerruf-/Einstellungs-Widget.
|
||||
_WITHDRAW_SELECTOR = (
|
||||
'a:has-text("Cookie-Einstellungen"), button:has-text("Cookie-Einstellungen"), '
|
||||
'a:has-text("Einwilligung"), button:has-text("Einwilligung"), '
|
||||
'a:has-text("Cookie Settings"), button:has-text("Cookie Settings"), '
|
||||
'a:has-text("Consent"), button:has-text("Consent"), '
|
||||
'[id*="borlabs-cookie"], [class*="borlabs-cookie"], #BorlabsCookieBox, '
|
||||
'[class*="cookie-preference"], [class*="cmplz-manage"]'
|
||||
)
|
||||
|
||||
|
||||
def classify_provider(names: list[str]) -> str:
|
||||
"""Storage-Keys + Cookie-Namen → CMP-Anbieter ('' wenn unbekannt). Pur."""
|
||||
blob = " ".join(n.lower() for n in names if n)
|
||||
for provider, sigs in _PROVIDERS:
|
||||
if any(s in blob for s in sigs):
|
||||
return provider
|
||||
return ""
|
||||
|
||||
|
||||
def _is_versioned(provider: str, stored_value: Optional[str]) -> bool:
|
||||
"""True, wenn der gespeicherte Consent eine Version/Consent-Liste trägt
|
||||
(Indiz für nachvollziehbare Historie)."""
|
||||
if not stored_value:
|
||||
return provider in _HISTORY_CAPABLE # Capability auch ohne Wert
|
||||
low = stored_value.lower()
|
||||
return any(t in low for t in ("version", "consents", "timestamp", "consentid"))
|
||||
|
||||
|
||||
async def detect_consent_history(page: Any) -> dict:
|
||||
"""Liest Storage/Cookies + DOM und liefert:
|
||||
{provider, stored, versioned_consent, history_capable, withdraw_ui}."""
|
||||
keys: list[str] = []
|
||||
try:
|
||||
keys = await page.evaluate("() => Object.keys(window.localStorage || {})")
|
||||
except Exception:
|
||||
keys = []
|
||||
cookie_names: list[str] = []
|
||||
try:
|
||||
cookie_names = [c.get("name", "") for c in await page.context.cookies()]
|
||||
except Exception:
|
||||
cookie_names = []
|
||||
|
||||
provider = classify_provider(list(keys) + cookie_names)
|
||||
|
||||
stored_value = None
|
||||
if provider == "Borlabs":
|
||||
try:
|
||||
stored_value = await page.evaluate(
|
||||
"() => localStorage.getItem('borlabs-cookie') || "
|
||||
"localStorage.getItem('BorlabsCookie')")
|
||||
except Exception:
|
||||
stored_value = None
|
||||
|
||||
versioned = _is_versioned(provider, stored_value)
|
||||
|
||||
withdraw = False
|
||||
try:
|
||||
withdraw = await page.locator(_WITHDRAW_SELECTOR).count() > 0
|
||||
except Exception:
|
||||
withdraw = False
|
||||
|
||||
return {
|
||||
"provider": provider,
|
||||
"stored": bool(provider),
|
||||
"versioned_consent": versioned,
|
||||
"history_capable": versioned or provider in _HISTORY_CAPABLE,
|
||||
"withdraw_ui": withdraw,
|
||||
}
|
||||
Reference in New Issue
Block a user