use crate::config::audit::AuditConfig;
use crate::dashboard::error::DashboardError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AuditRecord {
pub timestamp: String,
pub method: String,
pub initiator_hash: String,
pub correlation_id: Option<String>,
pub allowed: bool,
pub denial_code: Option<String>,
pub denial_control_point: Option<String>,
}
pub enum AuditBackend {
Memory {
buffer: Vec<AuditRecord>,
position: usize,
},
#[allow(dead_code)]
File {
path: String,
},
}
impl AuditBackend {
pub fn new_memory(capacity: usize) -> Self {
Self::Memory {
buffer: Vec::with_capacity(capacity),
position: 0,
}
}
#[allow(dead_code)]
pub fn new_file(path: String) -> Self {
Self::File { path }
}
pub fn from_config(config: &AuditConfig) -> Self {
let backend: AuditBackend = match config.backend.as_str() {
"file" => match &config.file_path {
Some(p) => AuditBackend::new_file(p.as_str().to_owned()),
None => AuditBackend::new_memory(4096),
},
_ => AuditBackend::new_memory(4096),
};
backend
}
pub fn write(&mut self, record: &AuditRecord) -> Result<(), DashboardError> {
match self {
Self::Memory { buffer, position } => {
if buffer.len() < buffer.capacity() {
buffer.push(record.clone());
} else {
buffer[*position] = record.clone();
*position = (*position + 1) % buffer.capacity();
}
Ok(())
}
Self::File { path: _path } => {
let _line = serde_json::to_string(record).map_err(|error| {
DashboardError::audit_write_failed(format!(
"audit serialization failed: {error}"
))
})?;
Ok(())
}
}
}
pub fn recent(&self, count: usize) -> Vec<AuditRecord> {
match self {
Self::Memory {
buffer,
position: _,
} => {
let start = if buffer.len() > count {
buffer.len() - count
} else {
0
};
buffer[start..].iter().rev().take(count).cloned().collect()
}
Self::File { .. } => {
vec![]
}
}
}
}
pub mod alerts {
use std::sync::atomic::{AtomicU64, Ordering};
static AUDIT_WRITE_FAILURES: AtomicU64 = AtomicU64::new(0);
pub fn increment_failure_count() -> u64 {
AUDIT_WRITE_FAILURES.fetch_add(1, Ordering::Relaxed)
}
pub fn failure_count() -> u64 {
AUDIT_WRITE_FAILURES.load(Ordering::Relaxed)
}
}