You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

75 lines
2.4 KiB

from __future__ import annotations
from collections.abc import Mapping
from flask import current_app, has_request_context, request
from sqlalchemy.exc import SQLAlchemyError
from app.extensions import db
from app.models import Account, AuditLog
ANONYMOUS_ACTOR_USERNAME = "anonymous"
def account_snapshot(account: Account | None) -> dict[str, object] | None:
if account is None:
return None
return {
"id": account.id,
"username": account.username,
"display_name": account.display_name,
"role": account.role,
"status": account.status,
}
def write_audit_log(
*,
action_type: str,
target_type: str,
actor: Account | None = None,
actor_username: str | None = None,
actor_display_name: str | None = None,
target_id: int | None = None,
target_display_name: str | None = None,
before_data: Mapping[str, object] | list[object] | None = None,
after_data: Mapping[str, object] | list[object] | None = None,
commit: bool = True,
) -> AuditLog | None:
log = AuditLog(
actor_user_id=actor.id if actor is not None else None,
actor_username=actor.username if actor is not None else (actor_username or ANONYMOUS_ACTOR_USERNAME),
actor_display_name=actor.display_name if actor is not None else actor_display_name,
action_type=action_type,
target_type=target_type,
target_id=target_id,
target_display_name=target_display_name,
before_data_json=dict(before_data) if isinstance(before_data, Mapping) else before_data,
after_data_json=dict(after_data) if isinstance(after_data, Mapping) else after_data,
)
_attach_request_metadata(log)
db.session.add(log)
if not commit:
return log
try:
db.session.commit()
except SQLAlchemyError:
db.session.rollback()
current_app.logger.exception("Failed to persist audit log for action '%s'.", action_type)
return None
return log
def _attach_request_metadata(log: AuditLog) -> None:
if not has_request_context():
return
log.request_path = request.path
log.request_method = request.method
log.ip_address = request.headers.get("X-Forwarded-For", request.remote_addr)
user_agent = request.user_agent.string if request.user_agent is not None else None
log.user_agent = user_agent[:512] if user_agent else None