Files
boc/iom/audit/audit_log.py
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- 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
2026-06-29 16:24:48 +00:00

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()