Update audit logging and dashboard
This commit is contained in:
@ -4,46 +4,96 @@
|
||||
|
||||
import logging
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, cast
|
||||
from fastapi import APIRouter, Depends, Request, Query
|
||||
from pydantic import TypeAdapter
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel, Field, TypeAdapter
|
||||
from sqlalchemy import select, func, and_
|
||||
from sqlalchemy.orm import InstrumentedAttribute, Session
|
||||
from sqlalchemy.sql.expression import ColumnExpressionArgument
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from sshecret_backend.models import AuditLog
|
||||
from sshecret_backend.models import AuditLog, Operation, SubSystem
|
||||
from sshecret_backend.types import DBSessionDep
|
||||
from sshecret_backend.view_models import AuditInfo, AuditView
|
||||
from sshecret_backend.view_models import AuditInfo, AuditView, AuditListResult
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuditFilter(BaseModel):
|
||||
"""Audit filter."""
|
||||
|
||||
offset: int = Field(0, ge=0)
|
||||
limit: int = Field(100, le=100)
|
||||
subsystem: SubSystem | None = None
|
||||
operation: Operation | None = None
|
||||
client_id: str | None = None
|
||||
client_name: str | None = None
|
||||
secret_id: str | None = None
|
||||
secret_name: str | None = None
|
||||
origin: str | None = None
|
||||
|
||||
@property
|
||||
def filter_mapping(self) -> list[ColumnExpressionArgument[bool]]:
|
||||
"""Construct filter mapping."""
|
||||
fields = self.model_dump(
|
||||
exclude_none=True, exclude_unset=True, exclude_defaults=True
|
||||
)
|
||||
|
||||
fieldmap: dict[str, InstrumentedAttribute[Any]] = {
|
||||
"subsystem": AuditLog.subsystem,
|
||||
"operation": AuditLog.operation,
|
||||
"client_id": AuditLog.client_id,
|
||||
"client_name": AuditLog.client_name,
|
||||
"secret_id": AuditLog.secret_id,
|
||||
"secret_name": AuditLog.secret_name,
|
||||
"origin": AuditLog.origin,
|
||||
}
|
||||
|
||||
return [
|
||||
column == value
|
||||
for key, value in fields.items()
|
||||
if (column := fieldmap.get(key)) is not None
|
||||
]
|
||||
|
||||
|
||||
def get_audit_api(get_db_session: DBSessionDep) -> APIRouter:
|
||||
"""Construct audit sub-api."""
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/audit/", response_model=list[AuditView])
|
||||
@router.get("/audit/", response_model=AuditListResult)
|
||||
async def get_audit_logs(
|
||||
request: Request,
|
||||
session: Annotated[Session, Depends(get_db_session)],
|
||||
offset: Annotated[int, Query()] = 0,
|
||||
limit: Annotated[int, Query(le=100)] = 100,
|
||||
filter_client: Annotated[str | None, Query()] = None,
|
||||
filter_subsystem: Annotated[str | None, Query()] = None,
|
||||
) -> Sequence[AuditView]:
|
||||
filters: Annotated[AuditFilter, Depends()],
|
||||
) -> AuditListResult:
|
||||
"""Get audit logs."""
|
||||
#audit.audit_access_audit_log(session, request)
|
||||
statement = select(AuditLog).offset(offset).limit(limit).order_by(AuditLog.timestamp.desc())
|
||||
if filter_client:
|
||||
statement = statement.where(AuditLog.client_name == filter_client)
|
||||
# audit.audit_access_audit_log(session, request)
|
||||
|
||||
if filter_subsystem:
|
||||
statement = statement.where(AuditLog.subsystem == filter_subsystem)
|
||||
total = session.scalars(
|
||||
select(func.count("*"))
|
||||
.select_from(AuditLog)
|
||||
.where(and_(True, *filters.filter_mapping))
|
||||
).one()
|
||||
|
||||
remaining = total - filters.offset
|
||||
statement = (
|
||||
select(AuditLog)
|
||||
.offset(filters.offset)
|
||||
.limit(filters.limit)
|
||||
.order_by(AuditLog.timestamp.desc())
|
||||
.where(and_(True, *filters.filter_mapping))
|
||||
)
|
||||
|
||||
LogAdapt = TypeAdapter(list[AuditView])
|
||||
results = session.scalars(statement).all()
|
||||
return LogAdapt.validate_python(results, from_attributes=True)
|
||||
|
||||
entries = LogAdapt.validate_python(results, from_attributes=True)
|
||||
return AuditListResult(
|
||||
results=entries,
|
||||
total=total,
|
||||
remaining=remaining,
|
||||
)
|
||||
|
||||
@router.post("/audit/")
|
||||
async def add_audit_log(
|
||||
@ -58,10 +108,13 @@ def get_audit_api(get_db_session: DBSessionDep) -> APIRouter:
|
||||
return AuditView.model_validate(audit_log, from_attributes=True)
|
||||
|
||||
@router.get("/audit/info")
|
||||
async def get_audit_info(request: Request, session: Annotated[Session, Depends(get_db_session)]) -> AuditInfo:
|
||||
async def get_audit_info(
|
||||
request: Request, session: Annotated[Session, Depends(get_db_session)]
|
||||
) -> AuditInfo:
|
||||
"""Get audit info."""
|
||||
audit_count = session.scalars(select(func.count('*')).select_from(AuditLog)).one()
|
||||
audit_count = session.scalars(
|
||||
select(func.count("*")).select_from(AuditLog)
|
||||
).one()
|
||||
return AuditInfo(entries=audit_count)
|
||||
|
||||
|
||||
return router
|
||||
|
||||
@ -172,7 +172,7 @@ def get_secrets_api(get_db_session: DBSessionDep) -> APIRouter:
|
||||
client_secret_map[client_secret.name] = []
|
||||
continue
|
||||
client_secret_map[client_secret.name].append(client_secret.client.name)
|
||||
audit.audit_client_secret_list(session, request)
|
||||
#audit.audit_client_secret_list(session, request)
|
||||
return [
|
||||
ClientSecretList(name=secret_name, clients=clients)
|
||||
for secret_name, clients in client_secret_map.items()
|
||||
@ -191,7 +191,7 @@ def get_secrets_api(get_db_session: DBSessionDep) -> APIRouter:
|
||||
if not client_secret.client:
|
||||
continue
|
||||
client_secrets[client_secret.name].clients.append(ClientReference(id=str(client_secret.client.id), name=client_secret.client.name))
|
||||
audit.audit_client_secret_list(session, request)
|
||||
#`audit.audit_client_secret_list(session, request)
|
||||
return list(client_secrets.values())
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user