1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::{ActorRef, GateDecision, Obligation};
5
6#[derive(Clone, Debug, Serialize, Deserialize)]
11pub struct AuditEvent {
12 pub timestamp: DateTime<Utc>,
14 pub actor: ActorRef,
16 pub namespace: String,
18 pub verb: String,
20 pub decision: AuditDecision,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub deny_reason: Option<String>,
25 #[serde(default)]
27 pub obligations: Vec<Obligation>,
28 pub gate_impl: String,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub session_id: Option<String>,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38pub enum AuditDecision {
39 Allow,
40 Deny,
41}
42
43impl AuditEvent {
44 pub fn from_check(req: &crate::GateRequest, decision: &GateDecision, gate_impl: &str) -> Self {
48 let (audit_decision, deny_reason, obligations) = match decision {
49 GateDecision::Allow { obligations } => {
50 (AuditDecision::Allow, None, obligations.clone())
51 }
52 GateDecision::Deny { reason } => {
53 (AuditDecision::Deny, Some(reason.clone()), Vec::new())
54 }
55 };
56 Self {
57 timestamp: req.context.timestamp.unwrap_or_else(chrono::Utc::now),
58 actor: req.actor.clone(),
59 namespace: req.namespace.as_str().to_string(),
60 verb: req.verb.clone(),
61 decision: audit_decision,
62 deny_reason,
63 obligations,
64 gate_impl: gate_impl.to_string(),
65 session_id: req.context.session_id.clone(),
66 }
67 }
68}