Files
breakpilot-compliance/backend-compliance/compliance/services/dsr_art11_service.py
T
Benjamin Admin eb4ea8bc42 feat: EmailDeliveryService + professional DSR email templates
- EmailDeliveryService: load template → find published version →
  render {{variables}} → send via SMTP → audit log. Fallback to
  inline HTML when no published template exists.
- Migration 117: Professional HTML/text content for all 5 DSR
  templates (receipt, completion, rejection, identity, extension)
  with branded styling and proper Art. references
- DSRArt11Service now uses EmailDeliveryService with dsr_rejection
  template instead of hardcoded HTML

[migration-approved]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 23:38:32 +02:00

101 lines
4.0 KiB
Python

"""
DSR Art. 11 Service — handles "data subject not identifiable" rejections.
Art. 11 Abs. 1 DSGVO: If the controller is unable to identify the data
subject, it is not obligated to obtain additional information solely to
comply with Art. 15-20 requests.
Common scenario: Website visitor requests access, but only anonymous
cookies/IP-hashes are stored — no way to link to a person.
"""
import logging
from datetime import datetime, timezone
from typing import Any, Dict
from sqlalchemy.orm import Session
from compliance.domain import ValidationError
logger = logging.getLogger(__name__)
class DSRArt11Service:
"""Handles Art. 11 DSGVO rejections for non-identifiable data subjects."""
def __init__(self, db: Session) -> None:
self._db = db
def reject_not_identifiable(
self, dsr_id: str, tenant_id: str, notes: str = "",
) -> Dict[str, Any]:
"""Reject DSR because data subject cannot be identified."""
from compliance.db.dsr_models import DSRRequestDB
from compliance.services.dsr_workflow_service import _dsr_to_dict, _record_history
dsr = (
self._db.query(DSRRequestDB)
.filter(DSRRequestDB.id == dsr_id, DSRRequestDB.tenant_id == tenant_id)
.first()
)
if not dsr:
raise ValidationError("DSR not found")
if dsr.status in ("completed", "rejected", "cancelled"):
raise ValidationError("DSR already closed")
now = datetime.now(timezone.utc)
reason = (
"Die bei uns gespeicherten Daten (anonymisierte Cookies, IP-Hashes, "
"Device-Fingerprints) erlauben keine Identifikation der betroffenen Person. "
"Gemaess Art. 11 Abs. 1 DSGVO sind wir nicht verpflichtet, zusaetzliche "
"Informationen zu erheben, um die betroffene Person zu identifizieren."
)
if notes:
reason += f" Ergaenzung: {notes}"
_record_history(self._db, dsr, "rejected",
comment="Art. 11 DSGVO — Identifikation nicht moeglich")
dsr.status = "rejected"
dsr.rejection_reason = reason
dsr.rejection_legal_basis = "Art. 11 Abs. 1 DSGVO"
dsr.identity_verified = False
dsr.verification_method = "art11_not_identifiable"
dsr.verification_notes = "Daten erlauben keine Identifikation der betroffenen Person"
dsr.completed_at = now
dsr.updated_at = now
self._db.commit()
self._db.refresh(dsr)
# Send rejection notification
self._send_art11_notification(dsr)
return _dsr_to_dict(dsr)
def _send_art11_notification(self, dsr: Any) -> None:
if not dsr.requester_email:
return
try:
from compliance.services.email_delivery_service import EmailDeliveryService
delivery = EmailDeliveryService(self._db)
variables = {
"requester_name": dsr.requester_name or "Antragsteller/in",
"reference_number": dsr.request_number or "",
"rejection_reason": "Identifikation nicht moeglich — Art. 11 Abs. 1 DSGVO",
"legal_basis": "Art. 11 Abs. 1 DSGVO",
"sender_name": "Datenschutzbeauftragter",
}
# Use published dsr_rejection template, fallback to inline
delivery.send(
tenant_id=str(dsr.tenant_id),
template_type="dsr_rejection",
recipient=dsr.requester_email,
variables=variables,
fallback_subject=f"Zu Ihrer Anfrage {dsr.request_number} — Art. 11 DSGVO",
fallback_html=f"""<p>Sehr geehrte/r {dsr.requester_name or 'Antragsteller/in'},</p>
<p>wir koennen die bei uns gespeicherten Daten keiner identifizierbaren Person zuordnen.
Gemaess Art. 11 Abs. 1 DSGVO ist eine Auskunftserteilung nicht moeglich.</p>
<p>Mit freundlichen Gruessen<br/>Datenschutzbeauftragter</p>""",
)
except Exception as e:
logger.warning("Art. 11 notification failed: %s", e)