204 lines
7.2 KiB
Python
204 lines
7.2 KiB
Python
# 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,
|
|
"fk_target": {"db": "poweron_app", "table": "User"},
|
|
},
|
|
)
|
|
|
|
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,
|
|
"fk_target": {"db": "poweron_app", "table": "Mandate"},
|
|
},
|
|
)
|
|
|
|
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,
|
|
"fk_target": {"db": "poweron_app", "table": "FeatureInstance"},
|
|
},
|
|
)
|
|
|
|
# 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}
|
|
)
|
|
|