initial commit after split
This commit is contained in:
0
src/govoplan_core/audit/__init__.py
Normal file
0
src/govoplan_core/audit/__init__.py
Normal file
99
src/govoplan_core/audit/logging.py
Normal file
99
src/govoplan_core/audit/logging.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth.dependencies import ApiPrincipal
|
||||
from govoplan_core.db.models import AuditLog
|
||||
from govoplan_core.privacy.retention import sanitize_audit_details_for_policy
|
||||
|
||||
|
||||
SENSITIVE_DETAIL_KEYS = {
|
||||
"password",
|
||||
"smtp_password",
|
||||
"imap_password",
|
||||
"secret",
|
||||
"api_key",
|
||||
"token",
|
||||
}
|
||||
|
||||
|
||||
def _sanitize_details(value: Any) -> Any:
|
||||
if isinstance(value, dict):
|
||||
sanitized: dict[str, Any] = {}
|
||||
for key, item in value.items():
|
||||
if str(key).lower() in SENSITIVE_DETAIL_KEYS:
|
||||
sanitized[key] = "<redacted>"
|
||||
else:
|
||||
sanitized[key] = _sanitize_details(item)
|
||||
return sanitized
|
||||
if isinstance(value, list):
|
||||
return [_sanitize_details(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def audit_event(
|
||||
session: Session,
|
||||
*,
|
||||
tenant_id: str | None,
|
||||
action: str,
|
||||
scope: str = "tenant",
|
||||
user_id: str | None = None,
|
||||
api_key_id: str | None = None,
|
||||
object_type: str | None = None,
|
||||
object_id: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
commit: bool = False,
|
||||
) -> AuditLog:
|
||||
"""Persist one audit event.
|
||||
|
||||
The function deliberately accepts primitive IDs so it can be used from API
|
||||
handlers, CLI commands and worker code without coupling the lower-level
|
||||
services to FastAPI request objects.
|
||||
"""
|
||||
|
||||
if scope not in {"tenant", "system"}:
|
||||
raise ValueError(f"Unsupported audit scope: {scope}")
|
||||
|
||||
item = AuditLog(
|
||||
scope=scope,
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
api_key_id=api_key_id,
|
||||
action=action,
|
||||
object_type=object_type,
|
||||
object_id=object_id,
|
||||
details=sanitize_audit_details_for_policy(session, _sanitize_details(details or {})),
|
||||
)
|
||||
session.add(item)
|
||||
if commit:
|
||||
session.commit()
|
||||
else:
|
||||
session.flush()
|
||||
return item
|
||||
|
||||
|
||||
def audit_from_principal(
|
||||
session: Session,
|
||||
principal: ApiPrincipal,
|
||||
*,
|
||||
action: str,
|
||||
scope: str = "tenant",
|
||||
object_type: str | None = None,
|
||||
object_id: str | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
commit: bool = False,
|
||||
) -> AuditLog:
|
||||
return audit_event(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
user_id=principal.user.id,
|
||||
api_key_id=principal.api_key.id if principal.api_key else None,
|
||||
action=action,
|
||||
scope=scope,
|
||||
object_type=object_type,
|
||||
object_id=object_id,
|
||||
details=details,
|
||||
commit=commit,
|
||||
)
|
||||
Reference in New Issue
Block a user