# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ Audit Log Data Model for database-based audit logging. This model stores security-relevant audit events for GDPR compliance and security monitoring. GDPR-Relevant Events: - User access: login, logout, failed login attempts - Data access: create, read, update, delete operations on personal data - Security events: password changes, token refresh, session management - Key access: encryption/decryption of sensitive data - GDPR actions: data export, data portability, account deletion - Mandate/permission changes: user added/removed from mandates, role changes """ from typing import Optional from pydantic import BaseModel, Field from enum import Enum import uuid from modules.shared.timeUtils import getUtcTimestamp from modules.shared.i18nRegistry import i18nModel class AuditCategory(str, Enum): """Categories for audit log entries""" ACCESS = "access" # Login/logout events KEY = "key" # Encryption key access DATA = "data" # Data CRUD operations SECURITY = "security" # Security-related events GDPR = "gdpr" # GDPR-specific actions PERMISSION = "permission" # Permission/role changes SYSTEM = "system" # System-level events class AuditAction(str, Enum): """Actions for audit log entries""" # Access actions LOGIN = "login" LOGIN_FAILED = "login_failed" LOGOUT = "logout" TOKEN_REFRESH = "token_refresh" TOKEN_REVOKE = "token_revoke" SESSION_EXPIRED = "session_expired" # Key actions KEY_ENCODE = "encode" KEY_DECODE = "decode" KEY_ACCESS = "key_access" # Data actions DATA_CREATE = "create" DATA_READ = "read" DATA_UPDATE = "update" DATA_DELETE = "delete" DATA_EXPORT = "export" # Security actions PASSWORD_CHANGE = "password_change" PASSWORD_RESET = "password_reset" MFA_ENABLED = "mfa_enabled" MFA_DISABLED = "mfa_disabled" # GDPR actions GDPR_DATA_EXPORT = "gdpr_data_export" GDPR_DATA_PORTABILITY = "gdpr_data_portability" GDPR_ACCOUNT_DELETION = "gdpr_account_deletion" GDPR_CONSENT_UPDATE = "gdpr_consent_update" # Permission actions USER_ADDED_TO_MANDATE = "user_added_to_mandate" USER_REMOVED_FROM_MANDATE = "user_removed_from_mandate" ROLE_ASSIGNED = "role_assigned" ROLE_REVOKED = "role_revoked" FEATURE_ACCESS_GRANTED = "feature_access_granted" FEATURE_ACCESS_REVOKED = "feature_access_revoked" # System actions SYSTEM_STARTUP = "system_startup" SYSTEM_SHUTDOWN = "system_shutdown" CONFIG_CHANGE = "config_change" @i18nModel("Audit-Log-Eintrag") class AuditLogEntry(BaseModel): """ Audit log entry for database storage. Stores all security-relevant events for compliance and monitoring. Entries are immutable once created (append-only audit trail). """ id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Unique identifier for the audit entry", json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) # Timestamp timestamp: float = Field( default_factory=getUtcTimestamp, description="UTC timestamp when the event occurred", json_schema_extra={"label": "Zeitstempel", "frontend_type": "datetime", "frontend_readonly": True, "frontend_required": True} ) # Actor identification userId: str = Field( description="ID of the user who performed the action (or 'system' for system events)", json_schema_extra={"label": "Benutzer-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True} ) username: Optional[str] = Field( default=None, description="Username at the time of the event (for historical reference)", json_schema_extra={"label": "Benutzername", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) # Context mandateId: Optional[str] = Field( default=None, description="Mandate context (if applicable)", json_schema_extra={"label": "Mandanten-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) featureInstanceId: Optional[str] = Field( default=None, description="Feature instance context (if applicable)", json_schema_extra={"label": "Feature-Instanz-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) # Event classification category: str = Field( description="Event category (access, key, data, security, gdpr, permission, system)", json_schema_extra={"label": "Kategorie", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True} ) action: str = Field( description="Specific action performed", json_schema_extra={"label": "Aktion", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True} ) # Event details resourceType: Optional[str] = Field( default=None, description="Type of resource affected (e.g., 'User', 'ChatWorkflow', 'TrusteeContract')", json_schema_extra={"label": "Ressourcentyp", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) resourceId: Optional[str] = Field( default=None, description="ID of the affected resource", json_schema_extra={"label": "Ressourcen-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) details: Optional[str] = Field( default=None, description="Additional details about the event", json_schema_extra={"label": "Details", "frontend_type": "textarea", "frontend_readonly": True, "frontend_required": False} ) # Request metadata ipAddress: Optional[str] = Field( default=None, description="IP address of the client", json_schema_extra={"label": "IP-Adresse", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) userAgent: Optional[str] = Field( default=None, description="User agent string from the request", json_schema_extra={"label": "User-Agent", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) # Outcome success: bool = Field( default=True, description="Whether the action was successful", json_schema_extra={"label": "Erfolgreich", "frontend_type": "checkbox", "frontend_readonly": True, "frontend_required": True} ) errorMessage: Optional[str] = Field( default=None, description="Error message if the action failed", json_schema_extra={"label": "Fehlermeldung", "frontend_type": "textarea", "frontend_readonly": True, "frontend_required": False} )