use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{ActorRef, GateDecision, Obligation};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuditEvent {
pub timestamp: DateTime<Utc>,
pub actor: ActorRef,
pub namespace: String,
pub verb: String,
pub decision: AuditDecision,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deny_reason: Option<String>,
#[serde(default)]
pub obligations: Vec<Obligation>,
pub gate_impl: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuditDecision {
Allow,
Deny,
}
impl AuditEvent {
pub fn from_check(req: &crate::GateRequest, decision: &GateDecision, gate_impl: &str) -> Self {
let (audit_decision, deny_reason, obligations) = match decision {
GateDecision::Allow { obligations } => {
(AuditDecision::Allow, None, obligations.clone())
}
GateDecision::Deny { reason } => {
(AuditDecision::Deny, Some(reason.clone()), Vec::new())
}
};
Self {
timestamp: req.context.timestamp.unwrap_or_else(chrono::Utc::now),
actor: req.actor.clone(),
namespace: req.namespace.as_str().to_string(),
verb: req.verb.clone(),
decision: audit_decision,
deny_reason,
obligations,
gate_impl: gate_impl.to_string(),
session_id: req.context.session_id.clone(),
}
}
}