""" Audit Log Complete audit trail for all platform changes """ from typing import Dict, List, Optional from dataclasses import dataclass from datetime import datetime from enum import Enum import json class AuditAction(str, Enum): """Audit actions""" CREATE = "create" READ = "read" UPDATE = "update" DELETE = "delete" LOGIN = "login" LOGOUT = "logout" EXPORT = "export" IMPORT = "import" SYNC = "sync" DECISION = "decision" INTERVENTION = "intervention" class AuditResource(str, Enum): """Audit resources""" OBSERVATION = "observation" OBJECT = "object" USER = "user" CONFIG = "config" DECISION = "decision" INTERVENTION = "intervention" SYNC = "sync" API_KEY = "api_key" @dataclass class AuditEntry: """Audit log entry""" id: str timestamp: str user_id: str action: AuditAction resource: AuditResource resource_id: str old_value: Optional[Dict] new_value: Optional[Dict] ip_address: str user_agent: str success: bool error_message: Optional[str] = None def to_dict(self) -> Dict: return { "id": self.id, "timestamp": self.timestamp, "user_id": self.user_id, "action": self.action.value, "resource": self.resource.value, "resource_id": self.resource_id, "old_value": self.old_value, "new_value": self.new_value, "ip_address": self.ip_address, "user_agent": self.user_agent, "success": self.success, "error_message": self.error_message } class AuditLogger: """ Comprehensive audit logging system Tracks: - All CRUD operations - User logins/logouts - API key usage - Decision changes - Interventions - Sync operations """ def __init__(self): self.entries: List[AuditEntry] = [] self.max_entries = 100000 def log( self, user_id: str, action: AuditAction, resource: AuditResource, resource_id: str, old_value: Optional[Dict] = None, new_value: Optional[Dict] = None, ip_address: str = "unknown", user_agent: str = "unknown", success: bool = True, error_message: Optional[str] = None ) -> AuditEntry: """Log an audit entry""" entry = AuditEntry( id=f"AUD-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{len(self.entries)}", timestamp=datetime.utcnow().isoformat(), user_id=user_id, action=action, resource=resource, resource_id=resource_id, old_value=old_value, new_value=new_value, ip_address=ip_address, user_agent=user_agent, success=success, error_message=error_message ) self.entries.append(entry) # Trim if needed if len(self.entries) > self.max_entries: self.entries = self.entries[-self.max_entries:] return entry def query( self, user_id: Optional[str] = None, action: Optional[AuditAction] = None, resource: Optional[AuditResource] = None, resource_id: Optional[str] = None, start_time: Optional[str] = None, end_time: Optional[str] = None, success: Optional[bool] = None, limit: int = 100 ) -> List[Dict]: """Query audit log""" results = self.entries if user_id: results = [e for e in results if e.user_id == user_id] if action: results = [e for e in results if e.action == action] if resource: results = [e for e in results if e.resource == resource] if resource_id: results = [e for e in results if e.resource_id == resource_id] if start_time: results = [e for e in results if e.timestamp >= start_time] if end_time: results = [e for e in results if e.timestamp <= end_time] if success is not None: results = [e for e in results if e.success == success] return [e.to_dict() for e in results[-limit:]] def get_user_activity(self, user_id: str, limit: int = 100) -> List[Dict]: """Get activity for a specific user""" return self.query(user_id=user_id, limit=limit) def get_resource_history(self, resource: AuditResource, resource_id: str) -> List[Dict]: """Get history for a specific resource""" return self.query(resource=resource, resource_id=resource_id) def get_statistics(self) -> Dict: """Get audit statistics""" total = len(self.entries) successful = sum(1 for e in self.entries if e.success) failed = total - successful action_counts = {} for e in self.entries: action_counts[e.action.value] = action_counts.get(e.action.value, 0) + 1 resource_counts = {} for e in self.entries: resource_counts[e.resource.value] = resource_counts.get(e.resource.value, 0) + 1 return { "total_entries": total, "successful": successful, "failed": failed, "success_rate": round(successful / total * 100, 2) if total > 0 else 0, "action_counts": action_counts, "resource_counts": resource_counts, "unique_users": len(set(e.user_id for e in self.entries)), "time_range": { "first": self.entries[0].timestamp if self.entries else None, "last": self.entries[-1].timestamp if self.entries else None } } def export_to_json(self, filepath: str): """Export audit log to JSON""" data = [e.to_dict() for e in self.entries] with open(filepath, 'w') as f: json.dump(data, f, indent=2) def detect_anomalies(self) -> List[Dict]: """Detect suspicious activity""" anomalies = [] # Multiple failed logins user_failures = {} for e in self.entries: if e.action == AuditAction.LOGIN and not e.success: user_failures[e.user_id] = user_failures.get(e.user_id, 0) + 1 for user_id, failures in user_failures.items(): if failures > 5: anomalies.append({ "type": "multiple_failed_logins", "user_id": user_id, "count": failures, "severity": "high" }) # Unusual access patterns user_actions = {} for e in self.entries: if e.user_id not in user_actions: user_actions[e.user_id] = [] user_actions[e.user_id].append(e.action.value) for user_id, actions in user_actions.items(): unique_actions = len(set(actions)) if unique_actions > 10: # Unusually diverse actions anomalies.append({ "type": "unusual_access_pattern", "user_id": user_id, "unique_actions": unique_actions, "severity": "medium" }) return anomalies # Example usage def example_audit_log(): """Example: Audit logging""" print("=== Audit Logger Demo ===\n") logger = AuditLogger() # Log some actions print("Logging actions...") logger.log( user_id="user-001", action=AuditAction.LOGIN, resource=AuditResource.USER, resource_id="user-001", ip_address="192.168.1.100", user_agent="Mozilla/5.0" ) logger.log( user_id="user-001", action=AuditAction.CREATE, resource=AuditResource.OBSERVATION, resource_id="OBS-001", new_value={"condition": 3, "location": "Stockholm"}, ip_address="192.168.1.100" ) logger.log( user_id="user-002", action=AuditAction.UPDATE, resource=AuditResource.OBJECT, resource_id="OBJ-001", old_value={"condition": 2}, new_value={"condition": 4}, ip_address="192.168.1.101" ) # Failed login logger.log( user_id="user-003", action=AuditAction.LOGIN, resource=AuditResource.USER, resource_id="user-003", success=False, error_message="Invalid password", ip_address="192.168.1.102" ) # Query print("\nQuerying audit log...") results = logger.query(action=AuditAction.CREATE) print(f"Create actions: {len(results)}") # Statistics print("\nAudit statistics:") stats = logger.get_statistics() print(f"Total entries: {stats['total_entries']}") print(f"Success rate: {stats['success_rate']}%") print(f"Action counts: {stats['action_counts']}") # Anomalies print("\nAnomalies detected:") anomalies = logger.detect_anomalies() for anomaly in anomalies: print(f" [{anomaly['severity']}] {anomaly['type']}: {anomaly.get('user_id', 'N/A')}") return logger if __name__ == '__main__': example_audit_log()