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 GateUnavailable,
42}
43
44impl AuditEvent {
45 pub fn from_check(req: &crate::GateRequest, decision: &GateDecision, gate_impl: &str) -> Self {
49 let (audit_decision, deny_reason, obligations) = match decision {
50 GateDecision::Allow { obligations } => {
51 (AuditDecision::Allow, None, obligations.clone())
52 }
53 GateDecision::Deny { reason } => {
54 (AuditDecision::Deny, Some(reason.clone()), Vec::new())
55 }
56 };
57 Self {
58 timestamp: req.context.timestamp.unwrap_or_else(chrono::Utc::now),
59 actor: req.actor.clone(),
60 namespace: req.namespace.as_str().to_string(),
61 verb: req.verb.clone(),
62 decision: audit_decision,
63 deny_reason,
64 obligations,
65 gate_impl: gate_impl.to_string(),
66 session_id: req.context.session_id.clone(),
67 }
68 }
69
70 pub fn gate_unavailable(req: &crate::GateRequest, gate_impl: &str) -> Self {
72 Self {
73 timestamp: req.context.timestamp.unwrap_or_else(chrono::Utc::now),
74 actor: req.actor.clone(),
75 namespace: req.namespace.as_str().to_string(),
76 verb: req.verb.clone(),
77 decision: AuditDecision::GateUnavailable,
78 deny_reason: None,
79 obligations: Vec::new(),
80 gate_impl: gate_impl.to_string(),
81 session_id: req.context.session_id.clone(),
82 }
83 }
84}