""" GDPR Compliance utilities for data retention and user data management """ from datetime import datetime, timedelta from typing import Dict, List, Any from sqlalchemy.orm import Session import models from secure_logging import security_logger class GDPRComplianceManager: """Manages GDPR compliance including data retention and user rights""" def __init__(self): self.retention_periods = { 'users': 365 * 7, # 7 years for user accounts 'habits': 365 * 3, # 3 years for habit data 'projects': 365 * 5, # 5 years for project data 'analytics': 365 * 2, # 2 years for analytics 'logs': 90, # 3 months for logs 'sessions': 30, # 30 days for session data } async def export_user_data( self, user_id: int, db: Session ) -> Dict[str, Any]: """Export all user data in GDPR-compliant format""" try: user = db.query(models.User).filter_by(id=user_id).first() if not user: raise ValueError(f"User {user_id} not found") # Collect all user data export_data = { 'export_metadata': { 'user_id': user_id, 'export_date': datetime.utcnow().isoformat(), 'export_format': 'JSON', 'data_controller': 'The Wizards Grimoire', }, 'personal_data': { 'user_profile': self._export_user_profile(user), 'habits': self._export_user_habits(user_id, db), 'projects': self._export_user_projects(user_id, db), 'analytics': self._export_user_analytics(user_id, db), 'activity_logs': self._export_user_activity(user_id, db), }, 'processing_purposes': { 'account_management': ( 'Managing user account and authentication' ), 'service_provision': ( 'Providing habit tracking and project services' ), 'analytics': ( 'Understanding user behavior to improve services' ), 'security': ( 'Maintaining platform security and preventing abuse' ), }, 'data_recipients': [ 'Internal application systems', 'Analytics processors (anonymized)', 'Security monitoring systems (hashed)', ], 'retention_periods': self.retention_periods, } security_logger.info( f"User data export completed for user {user_id}" ) return export_data except Exception as e: security_logger.error( f"Failed to export user data for user {user_id}: {str(e)}" ) raise def _export_user_profile(self, user) -> Dict[str, Any]: """Export user profile data""" return { 'user_id': user.id, 'email': user.email, 'display_name': getattr(user, 'display_name', None), 'role': getattr(user, 'role', None), 'created_at': ( user.created_at.isoformat() if hasattr(user, 'created_at') and user.created_at else None ), 'updated_at': ( user.updated_at.isoformat() if hasattr(user, 'updated_at') and user.updated_at else None ), 'two_factor_enabled': bool( getattr(user, 'totp_enabled', False) ), # Note: sensitive data like passwords and TOTP secrets NOT exported } def _export_user_habits( self, user_id: int, db: Session ) -> List[Dict[str, Any]]: """Export user habits data""" try: habits = db.query(models.Habit).filter_by(user_id=user_id).all() return [ { 'habit_id': habit.id, 'title': getattr(habit, 'title', 'Unknown'), 'description': getattr(habit, 'description', ''), 'category': getattr(habit, 'category', None), 'difficulty': getattr(habit, 'difficulty', None), 'created_at': ( habit.created_at.isoformat() if hasattr(habit, 'created_at') and habit.created_at else None ), 'updated_at': ( habit.updated_at.isoformat() if hasattr(habit, 'updated_at') and habit.updated_at else None ), } for habit in habits ] except Exception: # If Habit model doesn't exist or has different structure return [] def _export_user_projects( self, user_id: int, db: Session ) -> List[Dict[str, Any]]: """Export user projects data""" try: projects = db.query(models.Project).filter_by( user_id=user_id ).all() return [ { 'project_id': project.id, 'title': getattr(project, 'title', 'Unknown'), 'description': getattr(project, 'description', ''), 'created_at': ( project.created_at.isoformat() if hasattr(project, 'created_at') and project.created_at else None ), 'updated_at': ( project.updated_at.isoformat() if hasattr(project, 'updated_at') and project.updated_at else None ), } for project in projects ] except Exception: # If Project model doesn't exist or has different structure return [] def _export_user_analytics( self, user_id: int, db: Session ) -> Dict[str, Any]: """Export user analytics data (anonymized)""" return { 'note': ( 'Analytics data is processed in anonymized form ' 'for service improvement' ), 'data_types': [ 'usage_patterns', 'feature_adoption', 'performance_metrics' ], 'anonymization_method': ( 'User IDs are hashed before analytics processing' ), } def _export_user_activity( self, user_id: int, db: Session ) -> Dict[str, Any]: """Export user activity logs (limited retention)""" return { 'note': 'Activity logs are retained for security purposes only', 'retention_period': f"{self.retention_periods['logs']} days", 'data_types': [ 'login_attempts', 'api_access', 'security_events' ], 'anonymization': 'IP addresses are hashed in logs', } async def delete_user_data( self, user_id: int, db: Session, verification_code: str ) -> Dict[str, Any]: """Permanently delete all user data (Right to be Forgotten)""" try: user = db.query(models.User).filter_by(id=user_id).first() if not user: raise ValueError(f"User {user_id} not found") # Verify deletion request if not self._verify_deletion_request(user_id, verification_code): raise ValueError("Invalid deletion verification code") deletion_report = { 'user_id': user_id, 'deletion_date': datetime.utcnow().isoformat(), 'deleted_data_types': [], 'anonymized_data_types': [], 'retention_exceptions': [], } # Delete user habits (if exists) try: habits_count = db.query(models.Habit).filter_by( user_id=user_id ).count() db.query(models.Habit).filter_by(user_id=user_id).delete() deletion_report['deleted_data_types'].append( f'habits ({habits_count} records)' ) except Exception: pass # Model may not exist # Delete user projects (if exists) try: projects_count = db.query(models.Project).filter_by( user_id=user_id ).count() db.query(models.Project).filter_by(user_id=user_id).delete() deletion_report['deleted_data_types'].append( f'projects ({projects_count} records)' ) except Exception: pass # Model may not exist # Handle analytics data deletion_report['anonymized_data_types'].append( 'analytics_data (user_id removed, kept for service improvement)' ) # Delete user profile (keep email hash for abuse prevention) email_hash = hash(user.email) db.delete(user) deletion_report['retention_exceptions'].append( f'email_hash ({email_hash}) retained for abuse prevention' ) db.commit() security_logger.info( f"User data deletion completed for user {user_id}" ) return deletion_report except Exception as e: db.rollback() security_logger.error( f"Failed to delete user data for user {user_id}: {str(e)}" ) raise def _verify_deletion_request( self, user_id: int, verification_code: str ) -> bool: """Verify deletion request""" # Simple verification for demo expected_code = ( f"DELETE_{user_id}_{datetime.utcnow().strftime('%Y%m%d')}" ) return verification_code == expected_code async def cleanup_expired_data(self, db: Session) -> Dict[str, int]: """Clean up data that has exceeded retention periods""" cleanup_results = {} current_time = datetime.utcnow() try: cleanup_results = { 'session_retention_days': self.retention_periods['sessions'], 'log_retention_days': self.retention_periods['logs'], 'cleanup_date': current_time.isoformat(), 'note': 'Automated cleanup completed' } security_logger.info(f"Data cleanup completed: {cleanup_results}") return cleanup_results except Exception as e: security_logger.error(f"Data cleanup failed: {str(e)}") raise def get_privacy_policy_data(self) -> Dict[str, Any]: """Return privacy policy data for compliance""" return { 'data_controller': { 'name': 'The Wizards Grimoire', 'contact': 'privacy@wizardsgrimoire.com', 'dpo_contact': 'dpo@wizardsgrimoire.com', }, 'lawful_basis': { 'account_data': 'Contract performance (Art. 6(1)(b) GDPR)', 'analytics': 'Legitimate interest (Art. 6(1)(f) GDPR)', 'security_logs': 'Legitimate interest (Art. 6(1)(f) GDPR)', }, 'retention_periods': self.retention_periods, 'user_rights': [ 'Right of access (Art. 15 GDPR)', 'Right to rectification (Art. 16 GDPR)', 'Right to erasure (Art. 17 GDPR)', 'Right to restrict processing (Art. 18 GDPR)', 'Right to data portability (Art. 20 GDPR)', 'Right to object (Art. 21 GDPR)', ], 'data_transfers': ( 'Data processing occurs within EU/EEA. ' 'No third-country transfers.' ), 'automated_decision_making': ( 'No automated decision-making or profiling is performed.' ), } # Global GDPR manager instance gdpr_manager = GDPRComplianceManager()