Files
multitenetsaas/backend/security/pdpa_compliance.py
AHMET YILMAZ b3fff546e9
Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled
project initialization
2025-10-05 02:37:33 +08:00

1288 lines
43 KiB
Python

"""
Malaysian Personal Data Protection Act (PDPA) compliance features.
"""
import json
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Tuple
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.db import models, transaction
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
import hashlib
import hmac
import secrets
from enum import Enum
import io
import csv
import xlsxwriter
import PyPDF2
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
logger = logging.getLogger(__name__)
User = get_user_model()
class PDPAConsentType(Enum):
"""Types of PDPA consent."""
MARKETING = "marketing"
ANALYTICS = "analytics"
PERSONALIZATION = "personalization"
THIRD_PARTY = "third_party"
DATA_SHARING = "data_sharing"
AUTOMATED_DECISION = "automated_decision"
class PDPARetentionPeriod(Enum):
"""Data retention periods."""
MINIMAL = "minimal"
STANDARD = "standard"
EXTENDED = "extended"
LEGAL_HOLD = "legal_hold"
class PDPADataCategory(Enum):
"""Categories of personal data under PDPA."""
IDENTIFICATION = "identification"
CONTACT = "contact"
FINANCIAL = "financial"
HEALTH = "health"
EMPLOYMENT = "employment"
EDUCATION = "education"
LOCATION = "location"
BEHAVIORAL = "behavioral"
BIOMETRIC = "biometric"
GENEALOGICAL = "genealogical"
OPINION = "opinion"
class PDPAConsentManager:
"""
Manager for PDPA consent tracking and management.
"""
def __init__(self):
self.logger = logging.getLogger('security.pdpa.consent')
def record_consent(self, user_id: int, consent_type: PDPAConsentType,
consent_given: bool, metadata: Dict = None) -> bool:
"""
Record user consent for data processing.
"""
try:
from .models import PDPAConsentRecord
# Create consent record
consent_record = PDPAConsentRecord.objects.create(
user_id=user_id,
consent_type=consent_type.value,
consent_given=consent_given,
metadata=metadata or {},
ip_address="127.0.0.1", # Would get from request
user_agent="Mozilla/5.0", # Would get from request
)
# Update cache
cache_key = f"pdpa_consent_{user_id}"
self._update_consent_cache(user_id, consent_type, consent_given)
# Log consent
self.logger.info(f"Consent recorded: user={user_id}, type={consent_type.value}, given={consent_given}")
return True
except Exception as e:
self.logger.error(f"Consent recording error: {e}")
return False
def check_consent(self, user_id: int, consent_type: PDPAConsentType) -> bool:
"""
Check if user has given consent for specific processing.
"""
try:
# Check cache first
cache_key = f"pdpa_consent_{user_id}"
cached_consents = cache.get(cache_key, {})
if consent_type.value in cached_consents:
return cached_consents[consent_type.value]
# Check database
from .models import PDPAConsentRecord
latest_consent = PDPAConsentRecord.objects.filter(
user_id=user_id,
consent_type=consent_type.value,
).order_by('-created_at').first()
consent_given = latest_consent.consent_given if latest_consent else False
# Update cache
self._update_consent_cache(user_id, consent_type, consent_given)
return consent_given
except Exception as e:
self.logger.error(f"Consent check error: {e}")
return False
def get_user_consents(self, user_id: int) -> Dict[str, Dict]:
"""
Get all consent records for a user.
"""
try:
from .models import PDPAConsentRecord
# Get all consent records
records = PDPAConsentRecord.objects.filter(user_id=user_id).order_by('-created_at')
# Group by consent type
consents = {}
for record in records:
if record.consent_type not in consents:
consents[record.consent_type] = {
'latest': None,
'history': []
}
consents[record.consent_type]['latest'] = {
'consent_given': record.consent_given,
'timestamp': record.created_at.isoformat(),
'metadata': record.metadata,
}
consents[record.consent_type]['history'].append({
'consent_given': record.consent_given,
'timestamp': record.created_at.isoformat(),
'metadata': record.metadata,
})
return consents
except Exception as e:
self.logger.error(f"Consent retrieval error: {e}")
return {}
def _update_consent_cache(self, user_id: int, consent_type: PDPAConsentType, consent_given: bool):
"""
Update consent cache.
"""
cache_key = f"pdpa_consent_{user_id}"
cached_consents = cache.get(cache_key, {})
cached_consents[consent_type.value] = consent_given
cache.set(cache_key, cached_consents, timeout=3600) # 1 hour
class PDPADataProcessor:
"""
PDPA-compliant data processing and anonymization.
"""
def __init__(self):
self.logger = logging.getLogger('security.pdpa.data')
self.encryption_key = self._get_encryption_key()
def anonymize_data(self, data: Dict, data_category: PDPADataCategory) -> Dict:
"""
Anonymize personal data according to PDPA requirements.
"""
try:
anonymized_data = data.copy()
# Apply anonymization based on data category
if data_category == PDPADataCategory.IDENTIFICATION:
anonymized_data = self._anonymize_identification_data(anonymized_data)
elif data_category == PDPADataCategory.CONTACT:
anonymized_data = self._anonymize_contact_data(anonymized_data)
elif data_category == PDPADataCategory.FINANCIAL:
anonymized_data = self._anonymize_financial_data(anonymized_data)
elif data_category == PDPADataCategory.HEALTH:
anonymized_data = self._anonymize_health_data(anonymized_data)
elif data_category == PDPADataCategory.LOCATION:
anonymized_data = self._anonymize_location_data(anonymized_data)
return anonymized_data
except Exception as e:
self.logger.error(f"Data anonymization error: {e}")
return data
def encrypt_sensitive_data(self, data: str, user_id: int) -> str:
"""
Encrypt sensitive data with user-specific key.
"""
try:
# Generate user-specific key
user_key = self._generate_user_encryption_key(user_id)
# Create cipher
cipher = Fernet(user_key)
# Encrypt data
encrypted_data = cipher.encrypt(data.encode())
return base64.b64encode(encrypted_data).decode()
except Exception as e:
self.logger.error(f"Data encryption error: {e}")
return data
def decrypt_sensitive_data(self, encrypted_data: str, user_id: int) -> str:
"""
Decrypt sensitive data with user-specific key.
"""
try:
# Generate user-specific key
user_key = self._generate_user_encryption_key(user_id)
# Create cipher
cipher = Fernet(user_key)
# Decrypt data
decoded_data = base64.b64decode(encrypted_data.encode())
decrypted_data = cipher.decrypt(decoded_data)
return decrypted_data.decode()
except Exception as e:
self.logger.error(f"Data decryption error: {e}")
return encrypted_data
def _anonymize_identification_data(self, data: Dict) -> Dict:
"""
Anonymize identification data.
"""
anonymized = data.copy()
# Anonymize IC number (keep last 4 digits)
if 'ic_number' in anonymized:
ic = anonymized['ic_number']
if len(ic) > 4:
anonymized['ic_number'] = '*' * (len(ic) - 4) + ic[-4:]
# Anonymize passport number
if 'passport_number' in anonymized:
passport = anonymized['passport_number']
if len(passport) > 3:
anonymized['passport_number'] = '*' * (len(passport) - 3) + passport[-3:]
# Anonymize full name (keep initials)
if 'full_name' in anonymized:
name = anonymized['full_name']
names = name.split()
anonymized_name = []
for n in names:
if len(n) > 1:
anonymized_name.append(n[0] + '*')
else:
anonymized_name.append(n)
anonymized['full_name'] = ' '.join(anonymized_name)
return anonymized
def _anonymize_contact_data(self, data: Dict) -> Dict:
"""
Anonymize contact data.
"""
anonymized = data.copy()
# Anonymize email (keep domain)
if 'email' in anonymized:
email = anonymized['email']
if '@' in email:
local_part, domain = email.split('@', 1)
anonymized_email = local_part[0] + '*' * (len(local_part) - 1) + '@' + domain
anonymized['email'] = anonymized_email
# Anonymize phone number
if 'phone_number' in anonymized:
phone = anonymized['phone_number']
digits = ''.join(c for c in phone if c.isdigit())
if len(digits) > 4:
anonymized_phone = '*' * (len(digits) - 4) + digits[-4:]
anonymized['phone_number'] = anonymized_phone
return anonymized
def _anonymize_financial_data(self, data: Dict) -> Dict:
"""
Anonymize financial data.
"""
anonymized = data.copy()
# Anonymize bank account number
if 'bank_account_number' in anonymized:
account = anonymized['bank_account_number']
if len(account) > 4:
anonymized['bank_account_number'] = '*' * (len(account) - 4) + account[-4:]
# Anonymize credit card number
if 'credit_card_number' in anonymized:
card = anonymized['credit_card_number']
digits = ''.join(c for c in card if c.isdigit())
if len(digits) > 4:
anonymized['credit_card_number'] = '*' * (len(digits) - 4) + digits[-4:]
return anonymized
def _anonymize_health_data(self, data: Dict) -> Dict:
"""
Anonymize health data (redact most information).
"""
anonymized = data.copy()
# Redact sensitive health information
sensitive_fields = ['medical_history', 'diagnosis', 'treatment', 'medication']
for field in sensitive_fields:
if field in anonymized:
anonymized[field] = '[REDACTED]'
return anonymized
def _anonymize_location_data(self, data: Dict) -> Dict:
"""
Anonymize location data.
"""
anonymized = data.copy()
# Anonymize specific address (keep city/state)
if 'address' in anonymized:
address = anonymized['address']
anonymized['address'] = '[ADDRESS REDACTED]'
# Keep only city and state
for field in ['city', 'state']:
if field in anonymized:
anonymized[field] = anonymized[field]
return anonymized
def _get_encryption_key(self) -> bytes:
"""
Get or create encryption key.
"""
try:
# Try to get key from cache
cache_key = 'pdpa_encryption_key'
key = cache.get(cache_key)
if key:
return base64.b64decode(key)
# Generate new key
key = Fernet.generate_key()
cache.set(cache_key, base64.b64encode(key).decode(), timeout=None)
return key
except Exception as e:
self.logger.error(f"Encryption key generation error: {e}")
# Fallback to hardcoded key (not recommended for production)
return b'your-secret-key-here'
def _generate_user_encryption_key(self, user_id: int) -> bytes:
"""
Generate user-specific encryption key.
"""
try:
# Use PBKDF2 to derive key from user ID and master key
password = str(user_id).encode()
salt = b'malaysian_pdpa_salt' # Should be properly secured
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
key = base64.urlsafe_b64encode(kdf.derive(password))
return key
except Exception as e:
self.logger.error(f"User key generation error: {e}")
return self._get_encryption_key()
class PDPADataRetentionManager:
"""
Manager for PDPA data retention and deletion policies.
"""
def __init__(self):
self.logger = logging.getLogger('security.pdpa.retention')
def apply_retention_policies(self):
"""
Apply data retention policies across all user data.
"""
try:
# Get users with data retention policies
from .models import PDPADataRetention
retention_policies = PDPADataRetention.objects.all()
for policy in retention_policies:
self._apply_user_retention_policy(policy)
self.logger.info(f"Applied retention policies to {retention_policies.count()} users")
except Exception as e:
self.logger.error(f"Retention policy application error: {e}")
def _apply_user_retention_policy(self, policy):
"""
Apply retention policy for a specific user.
"""
try:
user_id = policy.user_id
retention_period = policy.retention_period
# Calculate cutoff date
cutoff_date = self._calculate_cutoff_date(retention_period)
# Apply retention to different data types
self._apply_activity_retention(user_id, cutoff_date)
self._apply_log_retention(user_id, cutoff_date)
self._apply_session_retention(user_id, cutoff_date)
self._apply_consent_retention(user_id, cutoff_date)
except Exception as e:
self.logger.error(f"User retention policy error for user {user_id}: {e}")
def _calculate_cutoff_date(self, retention_period: str) -> datetime:
"""
Calculate cutoff date based on retention period.
"""
now = timezone.now()
if retention_period == PDPARetentionPeriod.MINIMAL.value:
return now - timedelta(days=30)
elif retention_period == PDPARetentionPeriod.STANDARD.value:
return now - timedelta(days=365)
elif retention_period == PDPARetentionPeriod.EXTENDED.value:
return now - timedelta(days=1825) # 5 years
elif retention_period == PDPARetentionPeriod.LEGAL_HOLD.value:
return now - timedelta(days=3650) # 10 years
else:
return now - timedelta(days=365) # Default to standard
def _apply_activity_retention(self, user_id: int, cutoff_date: datetime):
"""
Apply retention to user activity data.
"""
try:
from django.contrib.auth.models import LogEntry
# Delete old log entries
LogEntry.objects.filter(
user_id=user_id,
action_time__lt=cutoff_date
).delete()
except Exception as e:
self.logger.error(f"Activity retention error for user {user_id}: {e}")
def _apply_log_retention(self, user_id: int, cutoff_date: datetime):
"""
Apply retention to user log data.
"""
try:
# Delete old security logs
from monitoring.models import SecurityLog
SecurityLog.objects.filter(
user_id=user_id,
timestamp__lt=cutoff_date
).delete()
except Exception as e:
self.logger.error(f"Log retention error for user {user_id}: {e}")
def _apply_session_retention(self, user_id: int, cutoff_date: datetime):
"""
Apply retention to user session data.
"""
try:
from django.contrib.sessions.models import Session
# Delete old sessions
Session.objects.filter(
expire_date__lt=cutoff_date
).delete()
except Exception as e:
self.logger.error(f"Session retention error for user {user_id}: {e}")
def _apply_consent_retention(self, user_id: int, cutoff_date: datetime):
"""
Apply retention to consent data (keep only latest).
"""
try:
from .models import PDPAConsentRecord
# Get all consent records
records = PDPAConsentRecord.objects.filter(
user_id=user_id,
created_at__lt=cutoff_date
)
# Group by consent type and keep only latest
for consent_type in PDPAConsentType:
type_records = records.filter(consent_type=consent_type.value)
if type_records.count() > 1:
# Keep only the latest record
latest = type_records.order_by('-created_at').first()
type_records.exclude(id=latest.id).delete()
except Exception as e:
self.logger.error(f"Consent retention error for user {user_id}: {e}")
class PDPADataExporter:
"""
PDPA-compliant data export for data subject requests.
"""
def __init__(self):
self.logger = logging.getLogger('security.pdpa.export')
self.data_processor = PDPADataProcessor()
def export_user_data(self, user_id: int, format: str = 'json') -> HttpResponse:
"""
Export user data in PDPA-compliant format.
"""
try:
# Collect user data
user_data = self._collect_user_data(user_id)
# Apply anonymization if needed
user_data = self._apply_export_anonymization(user_data)
# Generate export
if format == 'json':
return self._export_json(user_data)
elif format == 'csv':
return self._export_csv(user_data)
elif format == 'xlsx':
return self._export_xlsx(user_data)
elif format == 'pdf':
return self._export_pdf(user_data)
else:
raise ValidationError(f"Unsupported export format: {format}")
except Exception as e:
self.logger.error(f"Data export error for user {user_id}: {e}")
return JsonResponse(
{'error': 'Export failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
def _collect_user_data(self, user_id: int) -> Dict:
"""
Collect all user data for export.
"""
try:
from .models import PDPAConsentRecord, PDPADataRetention
data = {
'user_profile': self._get_user_profile(user_id),
'consent_records': self._get_consent_records(user_id),
'retention_policy': self._get_retention_policy(user_id),
'business_data': self._get_business_data(user_id),
'activity_logs': self._get_activity_logs(user_id),
'security_logs': self._get_security_logs(user_id),
'export_metadata': {
'export_date': timezone.now().isoformat(),
'export_type': 'pdpa_compliant',
'data_categories': ['identification', 'contact', 'activity'],
}
}
return data
except Exception as e:
self.logger.error(f"Data collection error for user {user_id}: {e}")
return {}
def _get_user_profile(self, user_id: int) -> Dict:
"""
Get user profile data.
"""
try:
user = User.objects.get(id=user_id)
return {
'id': user.id,
'username': user.username,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
'date_joined': user.date_joined.isoformat(),
'last_login': user.last_login.isoformat() if user.last_login else None,
'is_active': user.is_active,
'profile': {
field.name: getattr(user, field.name)
for field in user._meta.fields
if field.name not in ['id', 'password', 'username', 'email', 'first_name', 'last_name', 'date_joined', 'last_login', 'is_active']
}
}
except User.DoesNotExist:
return {}
except Exception as e:
self.logger.error(f"User profile retrieval error for user {user_id}: {e}")
return {}
def _get_consent_records(self, user_id: int) -> List[Dict]:
"""
Get consent records for user.
"""
try:
from .models import PDPAConsentRecord
records = PDPAConsentRecord.objects.filter(user_id=user_id)
return [
{
'id': record.id,
'consent_type': record.consent_type,
'consent_given': record.consent_given,
'created_at': record.created_at.isoformat(),
'metadata': record.metadata,
}
for record in records
]
except Exception as e:
self.logger.error(f"Consent records retrieval error for user {user_id}: {e}")
return []
def _get_retention_policy(self, user_id: int) -> Dict:
"""
Get retention policy for user.
"""
try:
from .models import PDPADataRetention
policy = PDPADataRetention.objects.filter(user_id=user_id).first()
if policy:
return {
'retention_period': policy.retention_period,
'created_at': policy.created_at.isoformat(),
'updated_at': policy.updated_at.isoformat(),
}
return {'retention_period': 'standard'}
except Exception as e:
self.logger.error(f"Retention policy retrieval error for user {user_id}: {e}")
return {'retention_period': 'standard'}
def _get_business_data(self, user_id: int) -> Dict:
"""
Get business-related data for user.
"""
try:
user = User.objects.get(id=user_id)
business_data = {}
# Get business registration if exists
if hasattr(user, 'business_registration'):
business_data['registration'] = {
'registration_number': user.business_registration.registration_number,
'business_name': user.business_registration.business_name,
'business_type': user.business_registration.business_type,
'state': user.business_registration.state,
'registration_date': user.business_registration.registration_date.isoformat(),
}
# Get Malaysian services data
business_data['malaysian_services'] = {
'ic_validations': list(user.malaysian_ic_validation.all().values(
'id', 'ic_number', 'validation_result', 'created_at'
)),
'sst_calculations': list(user.sst_calculation.all().values(
'id', 'amount', 'sst_amount', 'state', 'created_at'
)),
'postcode_lookups': list(user.postcode_lookup.all().values(
'id', 'postcode', 'result', 'created_at'
)),
}
return business_data
except Exception as e:
self.logger.error(f"Business data retrieval error for user {user_id}: {e}")
return {}
def _get_activity_logs(self, user_id: int) -> List[Dict]:
"""
Get activity logs for user.
"""
try:
from django.contrib.auth.models import LogEntry
logs = LogEntry.objects.filter(user_id=user_id).order_by('-action_time')[:100]
return [
{
'id': log.id,
'action_time': log.action_time.isoformat(),
'action_flag': log.action_flag,
'change_message': log.change_message,
}
for log in logs
]
except Exception as e:
self.logger.error(f"Activity logs retrieval error for user {user_id}: {e}")
return []
def _get_security_logs(self, user_id: int) -> List[Dict]:
"""
Get security logs for user.
"""
try:
from monitoring.models import SecurityLog
logs = SecurityLog.objects.filter(user_id=user_id).order_by('-timestamp')[:100]
return [
{
'id': log.id,
'timestamp': log.timestamp.isoformat(),
'event_type': log.event_type,
'severity': log.severity,
'description': log.description,
}
for log in logs
]
except Exception as e:
self.logger.error(f"Security logs retrieval error for user {user_id}: {e}")
return []
def _apply_export_anonymization(self, user_data: Dict) -> Dict:
"""
Apply anonymization for export.
"""
# Apply anonymization to sensitive data
if 'user_profile' in user_data:
user_data['user_profile'] = self.data_processor.anonymize_data(
user_data['user_profile'], PDPADataCategory.IDENTIFICATION
)
if 'business_data' in user_data:
user_data['business_data'] = self.data_processor.anonymize_data(
user_data['business_data'], PDPADataCategory.FINANCIAL
)
return user_data
def _export_json(self, data: Dict) -> HttpResponse:
"""
Export data as JSON.
"""
response = HttpResponse(
json.dumps(data, indent=2, default=str),
content_type='application/json'
)
response['Content-Disposition'] = 'attachment; filename="user_data_export.json"'
return response
def _export_csv(self, data: Dict) -> HttpResponse:
"""
Export data as CSV.
"""
output = io.StringIO()
writer = csv.writer(output)
# Write user profile
writer.writerow(['User Profile'])
if 'user_profile' in data:
for key, value in data['user_profile'].items():
writer.writerow([key, value])
writer.writerow([]) # Empty row
# Write consent records
writer.writerow(['Consent Records'])
if 'consent_records' in data:
writer.writerow(['Consent Type', 'Given', 'Date'])
for record in data['consent_records']:
writer.writerow([
record['consent_type'],
record['consent_given'],
record['created_at']
])
response = HttpResponse(output.getvalue(), content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="user_data_export.csv"'
return response
def _export_xlsx(self, data: Dict) -> HttpResponse:
"""
Export data as Excel file.
"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output)
# User profile sheet
if 'user_profile' in data:
profile_sheet = workbook.add_worksheet('User Profile')
row = 0
for key, value in data['user_profile'].items():
profile_sheet.write(row, 0, key)
profile_sheet.write(row, 1, str(value))
row += 1
# Consent records sheet
if 'consent_records' in data:
consent_sheet = workbook.add_worksheet('Consent Records')
consent_sheet.write_row(0, 0, ['Consent Type', 'Given', 'Date'])
row = 1
for record in data['consent_records']:
consent_sheet.write_row(row, 0, [
record['consent_type'],
record['consent_given'],
record['created_at']
])
row += 1
workbook.close()
response = HttpResponse(output.getvalue(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename="user_data_export.xlsx"'
return response
def _export_pdf(self, data: Dict) -> HttpResponse:
"""
Export data as PDF.
"""
output = io.BytesIO()
# Create simple PDF report
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
p = canvas.Canvas(output, pagesize=letter)
p.drawString(100, 750, "User Data Export Report")
p.drawString(100, 730, f"Generated: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}")
y_position = 700
if 'user_profile' in data:
p.drawString(100, y_position, "User Profile:")
y_position -= 20
for key, value in data['user_profile'].items():
p.drawString(120, y_position, f"{key}: {str(value)}")
y_position -= 15
p.save()
response = HttpResponse(output.getvalue(), content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="user_data_export.pdf"'
return response
class PDPARequestHandler:
"""
Handler for PDPA data subject requests.
"""
def __init__(self):
self.logger = logging.getLogger('security.pdpa.requests')
self.consent_manager = PDPAConsentManager()
self.data_exporter = PDPADataExporter()
self.retention_manager = PDPADataRetentionManager()
def handle_data_access_request(self, user_id: int, format: str = 'json') -> HttpResponse:
"""
Handle data subject access request.
"""
try:
# Log the request
self._log_access_request(user_id)
# Export user data
return self.data_exporter.export_user_data(user_id, format)
except Exception as e:
self.logger.error(f"Data access request error for user {user_id}: {e}")
return JsonResponse(
{'error': 'Access request failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
def handle_data_deletion_request(self, user_id: int, confirmation: bool = False) -> JsonResponse:
"""
Handle data subject deletion request.
"""
try:
if not confirmation:
return JsonResponse({
'message': 'Confirmation required for data deletion',
'requires_confirmation': True
})
# Log the request
self._log_deletion_request(user_id)
# Apply retention policies
self.retention_manager.apply_retention_policies()
# Anonymize user data
self._anonymize_user_data(user_id)
return JsonResponse({
'message': 'Data deletion request processed successfully',
'user_id': user_id,
'timestamp': timezone.now().isoformat()
})
except Exception as e:
self.logger.error(f"Data deletion request error for user {user_id}: {e}")
return JsonResponse(
{'error': 'Deletion request failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
def handle_consent_withdrawal(self, user_id: int, consent_type: str) -> JsonResponse:
"""
Handle consent withdrawal request.
"""
try:
# Validate consent type
try:
consent_enum = PDPAConsentType(consent_type)
except ValueError:
return JsonResponse(
{'error': 'Invalid consent type'},
status=status.HTTP_400_BAD_REQUEST
)
# Withdraw consent
success = self.consent_manager.record_consent(
user_id=user_id,
consent_type=consent_enum,
consent_given=False,
metadata={'action': 'withdrawal'}
)
if success:
return JsonResponse({
'message': f'Consent withdrawn for {consent_type}',
'user_id': user_id,
'consent_type': consent_type,
'timestamp': timezone.now().isoformat()
})
else:
return JsonResponse(
{'error': 'Consent withdrawal failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
except Exception as e:
self.logger.error(f"Consent withdrawal error for user {user_id}: {e}")
return JsonResponse(
{'error': 'Consent withdrawal failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
def _log_access_request(self, user_id: int):
"""
Log data access request.
"""
try:
self.logger.info(f"Data access request: user={user_id}")
# Store in audit log
from monitoring.models import SecurityLog
SecurityLog.objects.create(
user_id=user_id,
event_type='data_access_request',
severity='info',
description=f'User requested data access',
metadata={'request_type': 'access', 'timestamp': timezone.now().isoformat()}
)
except Exception as e:
self.logger.error(f"Access request logging error: {e}")
def _log_deletion_request(self, user_id: int):
"""
Log data deletion request.
"""
try:
self.logger.info(f"Data deletion request: user={user_id}")
# Store in audit log
from monitoring.models import SecurityLog
SecurityLog.objects.create(
user_id=user_id,
event_type='data_deletion_request',
severity='warning',
description=f'User requested data deletion',
metadata={'request_type': 'deletion', 'timestamp': timezone.now().isoformat()}
)
except Exception as e:
self.logger.error(f"Deletion request logging error: {e}")
def _anonymize_user_data(self, user_id: int):
"""
Anonymize user data.
"""
try:
user = User.objects.get(id=user_id)
# Anonymize profile data
user.first_name = "ANONYMIZED"
user.last_name = "USER"
user.email = f"anon_{user_id}@example.com"
user.username = f"anon_user_{user_id}"
user.save()
self.logger.info(f"User data anonymized: user={user_id}")
except Exception as e:
self.logger.error(f"User anonymization error: {e}")
# PDPA Views
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def pdpa_consent_view(request):
"""
Record PDPA consent.
"""
try:
consent_type = request.data.get('consent_type')
consent_given = request.data.get('consent_given', False)
if not consent_type:
return JsonResponse(
{'error': 'Consent type required'},
status=status.HTTP_400_BAD_REQUEST
)
# Validate consent type
try:
consent_enum = PDPAConsentType(consent_type)
except ValueError:
return JsonResponse(
{'error': 'Invalid consent type'},
status=status.HTTP_400_BAD_REQUEST
)
# Record consent
consent_manager = PDPAConsentManager()
success = consent_manager.record_consent(
user_id=request.user.id,
consent_type=consent_enum,
consent_given=consent_given,
metadata=request.data.get('metadata', {})
)
if success:
return JsonResponse({
'message': 'Consent recorded successfully',
'consent_type': consent_type,
'consent_given': consent_given
})
else:
return JsonResponse(
{'error': 'Consent recording failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
except Exception as e:
logger.error(f"PDPA consent error: {e}")
return JsonResponse(
{'error': 'Consent recording failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def pdpa_consents_view(request):
"""
Get user's PDPA consents.
"""
try:
consent_manager = PDPAConsentManager()
consents = consent_manager.get_user_consents(request.user.id)
return JsonResponse(consents)
except Exception as e:
logger.error(f"PDPA consents retrieval error: {e}")
return JsonResponse(
{'error': 'Consents retrieval failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def pdpa_data_access_view(request):
"""
Request data access.
"""
try:
format_type = request.data.get('format', 'json')
request_handler = PDPARequestHandler()
return request_handler.handle_data_access_request(request.user.id, format_type)
except Exception as e:
logger.error(f"PDPA data access error: {e}")
return JsonResponse(
{'error': 'Data access request failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def pdpa_data_deletion_view(request):
"""
Request data deletion.
"""
try:
confirmation = request.data.get('confirmation', False)
request_handler = PDPARequestHandler()
return request_handler.handle_data_deletion_request(request.user.id, confirmation)
except Exception as e:
logger.error(f"PDPA data deletion error: {e}")
return JsonResponse(
{'error': 'Data deletion request failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def pdpa_consent_withdrawal_view(request):
"""
Withdraw consent.
"""
try:
consent_type = request.data.get('consent_type')
if not consent_type:
return JsonResponse(
{'error': 'Consent type required'},
status=status.HTTP_400_BAD_REQUEST
)
request_handler = PDPARequestHandler()
return request_handler.handle_consent_withdrawal(request.user.id, consent_type)
except Exception as e:
logger.error(f"PDPA consent withdrawal error: {e}")
return JsonResponse(
{'error': 'Consent withdrawal failed'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# PDPA Management Commands
class PDPAManagementCommand:
"""
Management commands for PDPA compliance.
"""
def apply_retention_policies(self):
"""
Apply data retention policies.
"""
try:
retention_manager = PDPADataRetentionManager()
retention_manager.apply_retention_policies()
print("Retention policies applied successfully")
except Exception as e:
print(f"Error applying retention policies: {e}")
def audit_consent_records(self):
"""
Audit consent records for compliance.
"""
try:
from .models import PDPAConsentRecord
# Get consent records without recent updates
outdated_records = PDPAConsentRecord.objects.filter(
created_at__lt=timezone.now() - timedelta(days=365)
)
print(f"Found {outdated_records.count()} outdated consent records")
# Get users without consent records
users_without_consent = User.objects.exclude(
id__in=PDPAConsentRecord.objects.values('user_id')
).count()
print(f"Found {users_without_consent} users without consent records")
except Exception as e:
print(f"Error auditing consent records: {e}")
def generate_compliance_report(self):
"""
Generate PDPA compliance report.
"""
try:
from .models import PDPAConsentRecord, PDPADataRetention
report = {
'generated_at': timezone.now().isoformat(),
'total_users': User.objects.count(),
'users_with_consent': PDPAConsentRecord.objects.values('user_id').distinct().count(),
'users_with_retention_policy': PDPADataRetention.objects.count(),
'consent_records': {
'marketing': PDPAConsentRecord.objects.filter(consent_type='marketing').count(),
'analytics': PDPAConsentRecord.objects.filter(consent_type='analytics').count(),
'personalization': PDPAConsentRecord.objects.filter(consent_type='personalization').count(),
'third_party': PDPAConsentRecord.objects.filter(consent_type='third_party').count(),
},
'retention_policies': {
'minimal': PDPADataRetention.objects.filter(retention_period='minimal').count(),
'standard': PDPADataRetention.objects.filter(retention_period='standard').count(),
'extended': PDPADataRetention.objects.filter(retention_period='extended').count(),
'legal_hold': PDPADataRetention.objects.filter(retention_period='legal_hold').count(),
}
}
print("PDPA Compliance Report:")
print(json.dumps(report, indent=2))
return report
except Exception as e:
print(f"Error generating compliance report: {e}")
return {}