bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
317 lines
9.2 KiB
Python
317 lines
9.2 KiB
Python
"""
|
|
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()
|