Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Implement GDPR-compliant data handling: consent management, data subject rights, and PII controls.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/details.md
1# gdpr-data-handling — detailed worked examples23## Implementation Patterns45### Pattern 1: Consent Management67```javascript8// Consent data model9const consentSchema = {10userId: String,11consents: [12{13purpose: String, // 'marketing', 'analytics', etc.14granted: Boolean,15timestamp: Date,16source: String, // 'web_form', 'api', etc.17version: String, // Privacy policy version18ipAddress: String, // For proof19userAgent: String, // For proof20},21],22auditLog: [23{24action: String, // 'granted', 'withdrawn', 'updated'25purpose: String,26timestamp: Date,27source: String,28},29],30};3132// Consent service33class ConsentManager {34async recordConsent(userId, purpose, granted, metadata) {35const consent = {36purpose,37granted,38timestamp: new Date(),39source: metadata.source,40version: await this.getCurrentPolicyVersion(),41ipAddress: metadata.ipAddress,42userAgent: metadata.userAgent,43};4445// Store consent46await this.db.consents.updateOne(47{ userId },48{49$push: {50consents: consent,51auditLog: {52action: granted ? "granted" : "withdrawn",53purpose,54timestamp: consent.timestamp,55source: metadata.source,56},57},58},59{ upsert: true },60);6162// Emit event for downstream systems63await this.eventBus.emit("consent.changed", {64userId,65purpose,66granted,67timestamp: consent.timestamp,68});69}7071async hasConsent(userId, purpose) {72const record = await this.db.consents.findOne({ userId });73if (!record) return false;7475const latestConsent = record.consents76.filter((c) => c.purpose === purpose)77.sort((a, b) => b.timestamp - a.timestamp)[0];7879return latestConsent?.granted === true;80}8182async getConsentHistory(userId) {83const record = await this.db.consents.findOne({ userId });84return record?.auditLog || [];85}86}87```8889```html90<!-- GDPR-compliant consent UI -->91<div class="consent-banner" role="dialog" aria-labelledby="consent-title">92<h2 id="consent-title">Cookie Preferences</h2>9394<p>95We use cookies to improve your experience. Select your preferences below.96</p>9798<form id="consent-form">99<!-- Necessary - always on, no consent needed -->100<div class="consent-category">101<input type="checkbox" id="necessary" checked disabled />102<label for="necessary">103<strong>Necessary</strong>104<span>Required for the website to function. Cannot be disabled.</span>105</label>106</div>107108<!-- Analytics - requires consent -->109<div class="consent-category">110<input type="checkbox" id="analytics" name="analytics" />111<label for="analytics">112<strong>Analytics</strong>113<span>Help us understand how you use our site.</span>114</label>115</div>116117<!-- Marketing - requires consent -->118<div class="consent-category">119<input type="checkbox" id="marketing" name="marketing" />120<label for="marketing">121<strong>Marketing</strong>122<span>Personalized ads based on your interests.</span>123</label>124</div>125126<div class="consent-actions">127<button type="button" id="accept-all">Accept All</button>128<button type="button" id="reject-all">Reject All</button>129<button type="submit">Save Preferences</button>130</div>131132<p class="consent-links">133<a href="/privacy-policy">Privacy Policy</a> |134<a href="/cookie-policy">Cookie Policy</a>135</p>136</form>137</div>138```139140### Pattern 2: Data Subject Access Request (DSAR)141142```python143from datetime import datetime, timedelta144from typing import Dict, List, Optional145import json146147class DSARHandler:148"""Handle Data Subject Access Requests."""149150RESPONSE_DEADLINE_DAYS = 30151EXTENSION_ALLOWED_DAYS = 60 # For complex requests152153def __init__(self, data_sources: List['DataSource']):154self.data_sources = data_sources155156async def submit_request(157self,158request_type: str, # 'access', 'erasure', 'rectification', 'portability'159user_id: str,160verified: bool,161details: Optional[Dict] = None162) -> str:163"""Submit a new DSAR."""164request = {165'id': self.generate_request_id(),166'type': request_type,167'user_id': user_id,168'status': 'pending_verification' if not verified else 'processing',169'submitted_at': datetime.utcnow(),170'deadline': datetime.utcnow() + timedelta(days=self.RESPONSE_DEADLINE_DAYS),171'details': details or {},172'audit_log': [{173'action': 'submitted',174'timestamp': datetime.utcnow(),175'details': 'Request received'176}]177}178179await self.db.dsar_requests.insert_one(request)180await self.notify_dpo(request)181182return request['id']183184async def process_access_request(self, request_id: str) -> Dict:185"""Process a data access request."""186request = await self.get_request(request_id)187188if request['type'] != 'access':189raise ValueError("Not an access request")190191# Collect data from all sources192user_data = {}193for source in self.data_sources:194try:195data = await source.get_user_data(request['user_id'])196user_data[source.name] = data197except Exception as e:198user_data[source.name] = {'error': str(e)}199200# Format response201response = {202'request_id': request_id,203'generated_at': datetime.utcnow().isoformat(),204'data_categories': list(user_data.keys()),205'data': user_data,206'retention_info': await self.get_retention_info(),207'processing_purposes': await self.get_processing_purposes(),208'third_party_recipients': await self.get_recipients()209}210211# Update request status212await self.update_request(request_id, 'completed', response)213214return response215216async def process_erasure_request(self, request_id: str) -> Dict:217"""Process a right to erasure request."""218request = await self.get_request(request_id)219220if request['type'] != 'erasure':221raise ValueError("Not an erasure request")222223results = {}224exceptions = []225226for source in self.data_sources:227try:228# Check for legal exceptions229can_delete, reason = await source.can_delete(request['user_id'])230231if can_delete:232await source.delete_user_data(request['user_id'])233results[source.name] = 'deleted'234else:235exceptions.append({236'source': source.name,237'reason': reason # e.g., 'legal retention requirement'238})239results[source.name] = f'retained: {reason}'240except Exception as e:241results[source.name] = f'error: {str(e)}'242243response = {244'request_id': request_id,245'completed_at': datetime.utcnow().isoformat(),246'results': results,247'exceptions': exceptions248}249250await self.update_request(request_id, 'completed', response)251252return response253254async def process_portability_request(self, request_id: str) -> bytes:255"""Generate portable data export."""256request = await self.get_request(request_id)257user_data = await self.process_access_request(request_id)258259# Convert to machine-readable format (JSON)260portable_data = {261'export_date': datetime.utcnow().isoformat(),262'format_version': '1.0',263'data': user_data['data']264}265266return json.dumps(portable_data, indent=2, default=str).encode()267```268269### Pattern 3: Data Retention270271```python272from datetime import datetime, timedelta273from enum import Enum274275class RetentionBasis(Enum):276CONSENT = "consent"277CONTRACT = "contract"278LEGAL_OBLIGATION = "legal_obligation"279LEGITIMATE_INTEREST = "legitimate_interest"280281class DataRetentionPolicy:282"""Define and enforce data retention policies."""283284POLICIES = {285'user_account': {286'retention_period_days': 365 * 3, # 3 years after last activity287'basis': RetentionBasis.CONTRACT,288'trigger': 'last_activity_date',289'archive_before_delete': True290},291'transaction_records': {292'retention_period_days': 365 * 7, # 7 years for tax293'basis': RetentionBasis.LEGAL_OBLIGATION,294'trigger': 'transaction_date',295'archive_before_delete': True,296'legal_reference': 'Tax regulations require 7 year retention'297},298'marketing_consent': {299'retention_period_days': 365 * 2, # 2 years300'basis': RetentionBasis.CONSENT,301'trigger': 'consent_date',302'archive_before_delete': False303},304'support_tickets': {305'retention_period_days': 365 * 2,306'basis': RetentionBasis.LEGITIMATE_INTEREST,307'trigger': 'ticket_closed_date',308'archive_before_delete': True309},310'analytics_data': {311'retention_period_days': 365, # 1 year312'basis': RetentionBasis.CONSENT,313'trigger': 'collection_date',314'archive_before_delete': False,315'anonymize_instead': True316}317}318319async def apply_retention_policies(self):320"""Run retention policy enforcement."""321for data_type, policy in self.POLICIES.items():322cutoff_date = datetime.utcnow() - timedelta(323days=policy['retention_period_days']324)325326if policy.get('anonymize_instead'):327await self.anonymize_old_data(data_type, cutoff_date)328else:329if policy.get('archive_before_delete'):330await self.archive_data(data_type, cutoff_date)331await self.delete_old_data(data_type, cutoff_date)332333await self.log_retention_action(data_type, cutoff_date)334335async def anonymize_old_data(self, data_type: str, before_date: datetime):336"""Anonymize data instead of deleting."""337# Example: Replace identifying fields with hashes338if data_type == 'analytics_data':339await self.db.analytics.update_many(340{'collection_date': {'$lt': before_date}},341{'$set': {342'user_id': None,343'ip_address': None,344'device_id': None,345'anonymized': True,346'anonymized_date': datetime.utcnow()347}}348)349```350351### Pattern 4: Privacy by Design352353```python354class PrivacyFirstDataModel:355"""Example of privacy-by-design data model."""356357# Separate PII from behavioral data358user_profile_schema = {359'user_id': str, # UUID, not sequential360'email_hash': str, # Hashed for lookups361'created_at': datetime,362# Minimal data collection363'preferences': {364'language': str,365'timezone': str366}367}368369# Encrypted at rest370user_pii_schema = {371'user_id': str,372'email': str, # Encrypted373'name': str, # Encrypted374'phone': str, # Encrypted (optional)375'address': dict, # Encrypted (optional)376'encryption_key_id': str377}378379# Pseudonymized behavioral data380analytics_schema = {381'session_id': str, # Not linked to user_id382'pseudonym_id': str, # Rotating pseudonym383'events': list,384'device_category': str, # Generalized, not specific385'country': str, # Not city-level386}387388class DataMinimization:389"""Implement data minimization principles."""390391@staticmethod392def collect_only_needed(form_data: dict, purpose: str) -> dict:393"""Filter form data to only fields needed for purpose."""394REQUIRED_FIELDS = {395'account_creation': ['email', 'password'],396'newsletter': ['email'],397'purchase': ['email', 'name', 'address', 'payment'],398'support': ['email', 'message']399}400401allowed = REQUIRED_FIELDS.get(purpose, [])402return {k: v for k, v in form_data.items() if k in allowed}403404@staticmethod405def generalize_location(ip_address: str) -> str:406"""Generalize IP to country level only."""407import geoip2.database408reader = geoip2.database.Reader('GeoLite2-Country.mmdb')409try:410response = reader.country(ip_address)411return response.country.iso_code412except:413return 'UNKNOWN'414```415416### Pattern 5: Breach Notification417418```python419from datetime import datetime420from enum import Enum421422class BreachSeverity(Enum):423LOW = "low"424MEDIUM = "medium"425HIGH = "high"426CRITICAL = "critical"427428class BreachNotificationHandler:429"""Handle GDPR breach notification requirements."""430431AUTHORITY_NOTIFICATION_HOURS = 72432AFFECTED_NOTIFICATION_REQUIRED_SEVERITY = BreachSeverity.HIGH433434async def report_breach(435self,436description: str,437data_types: List[str],438affected_count: int,439severity: BreachSeverity440) -> dict:441"""Report and handle a data breach."""442breach = {443'id': self.generate_breach_id(),444'reported_at': datetime.utcnow(),445'description': description,446'data_types_affected': data_types,447'affected_individuals_count': affected_count,448'severity': severity.value,449'status': 'investigating',450'timeline': [{451'event': 'breach_reported',452'timestamp': datetime.utcnow(),453'details': description454}]455}456457await self.db.breaches.insert_one(breach)458459# Immediate notifications460await self.notify_dpo(breach)461await self.notify_security_team(breach)462463# Authority notification required within 72 hours464if self.requires_authority_notification(severity, data_types):465breach['authority_notification_deadline'] = (466datetime.utcnow() + timedelta(hours=self.AUTHORITY_NOTIFICATION_HOURS)467)468await self.schedule_authority_notification(breach)469470# Affected individuals notification471if severity.value in [BreachSeverity.HIGH.value, BreachSeverity.CRITICAL.value]:472await self.schedule_individual_notifications(breach)473474return breach475476def requires_authority_notification(477self,478severity: BreachSeverity,479data_types: List[str]480) -> bool:481"""Determine if supervisory authority must be notified."""482# Always notify for sensitive data483sensitive_types = ['health', 'financial', 'credentials', 'biometric']484if any(t in sensitive_types for t in data_types):485return True486487# Notify for medium+ severity488return severity in [BreachSeverity.MEDIUM, BreachSeverity.HIGH, BreachSeverity.CRITICAL]489490async def generate_authority_report(self, breach_id: str) -> dict:491"""Generate report for supervisory authority."""492breach = await self.get_breach(breach_id)493494return {495'organization': {496'name': self.config.org_name,497'contact': self.config.dpo_contact,498'registration': self.config.registration_number499},500'breach': {501'nature': breach['description'],502'categories_affected': breach['data_types_affected'],503'approximate_number_affected': breach['affected_individuals_count'],504'likely_consequences': self.assess_consequences(breach),505'measures_taken': await self.get_remediation_measures(breach_id),506'measures_proposed': await self.get_proposed_measures(breach_id)507},508'timeline': breach['timeline'],509'submitted_at': datetime.utcnow().isoformat()510}511```512513## Compliance Checklist514515```markdown516## GDPR Implementation Checklist517518### Legal Basis519520- [ ] Documented legal basis for each processing activity521- [ ] Consent mechanisms meet GDPR requirements522- [ ] Legitimate interest assessments completed523524### Transparency525526- [ ] Privacy policy is clear and accessible527- [ ] Processing purposes clearly stated528- [ ] Data retention periods documented529530### Data Subject Rights531532- [ ] Access request process implemented533- [ ] Erasure request process implemented534- [ ] Portability export available535- [ ] Rectification process available536- [ ] Response within 30-day deadline537538### Security539540- [ ] Encryption at rest implemented541- [ ] Encryption in transit (TLS)542- [ ] Access controls in place543- [ ] Audit logging enabled544545### Breach Response546547- [ ] Breach detection mechanisms548- [ ] 72-hour notification process549- [ ] Breach documentation system550551### Documentation552553- [ ] Records of processing activities (Art. 30)554- [ ] Data protection impact assessments555- [ ] Data processing agreements with vendors556```557