ricecoder_permissions/audit/
models.rs

1//! Audit log data models
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7/// Action recorded in audit log
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum AuditAction {
11    /// Tool execution was allowed
12    Allowed,
13    /// Tool execution was denied
14    Denied,
15    /// User was prompted for permission
16    Prompted,
17    /// User approved tool execution
18    Approved,
19    /// User rejected tool execution
20    Rejected,
21}
22
23impl std::fmt::Display for AuditAction {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            AuditAction::Allowed => write!(f, "allowed"),
27            AuditAction::Denied => write!(f, "denied"),
28            AuditAction::Prompted => write!(f, "prompted"),
29            AuditAction::Approved => write!(f, "approved"),
30            AuditAction::Rejected => write!(f, "rejected"),
31        }
32    }
33}
34
35/// Result of a permission check
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "lowercase")]
38pub enum AuditResult {
39    /// Tool execution succeeded
40    Success,
41    /// Tool execution was blocked
42    Blocked,
43    /// User cancelled the operation
44    Cancelled,
45}
46
47impl std::fmt::Display for AuditResult {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            AuditResult::Success => write!(f, "success"),
51            AuditResult::Blocked => write!(f, "blocked"),
52            AuditResult::Cancelled => write!(f, "cancelled"),
53        }
54    }
55}
56
57/// Entry in the audit log
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct AuditLogEntry {
60    /// Unique identifier for this log entry
61    pub id: String,
62    /// Timestamp of the event
63    pub timestamp: DateTime<Utc>,
64    /// Name of the tool being accessed
65    pub tool: String,
66    /// Action that was taken
67    pub action: AuditAction,
68    /// Result of the action
69    pub result: AuditResult,
70    /// Optional agent identifier
71    pub agent: Option<String>,
72    /// Optional additional context
73    pub context: Option<String>,
74}
75
76impl AuditLogEntry {
77    /// Create a new audit log entry
78    pub fn new(tool: String, action: AuditAction, result: AuditResult) -> Self {
79        Self {
80            id: Uuid::new_v4().to_string(),
81            timestamp: Utc::now(),
82            tool,
83            action,
84            result,
85            agent: None,
86            context: None,
87        }
88    }
89
90    /// Create a new audit log entry with agent
91    pub fn with_agent(
92        tool: String,
93        action: AuditAction,
94        result: AuditResult,
95        agent: String,
96    ) -> Self {
97        Self {
98            id: Uuid::new_v4().to_string(),
99            timestamp: Utc::now(),
100            tool,
101            action,
102            result,
103            agent: Some(agent),
104            context: None,
105        }
106    }
107
108    /// Add context to the log entry
109    pub fn with_context(mut self, context: String) -> Self {
110        self.context = Some(context);
111        self
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_audit_action_display() {
121        assert_eq!(AuditAction::Allowed.to_string(), "allowed");
122        assert_eq!(AuditAction::Denied.to_string(), "denied");
123        assert_eq!(AuditAction::Prompted.to_string(), "prompted");
124        assert_eq!(AuditAction::Approved.to_string(), "approved");
125        assert_eq!(AuditAction::Rejected.to_string(), "rejected");
126    }
127
128    #[test]
129    fn test_audit_result_display() {
130        assert_eq!(AuditResult::Success.to_string(), "success");
131        assert_eq!(AuditResult::Blocked.to_string(), "blocked");
132        assert_eq!(AuditResult::Cancelled.to_string(), "cancelled");
133    }
134
135    #[test]
136    fn test_audit_log_entry_creation() {
137        let entry = AuditLogEntry::new(
138            "test_tool".to_string(),
139            AuditAction::Allowed,
140            AuditResult::Success,
141        );
142
143        assert_eq!(entry.tool, "test_tool");
144        assert_eq!(entry.action, AuditAction::Allowed);
145        assert_eq!(entry.result, AuditResult::Success);
146        assert_eq!(entry.agent, None);
147        assert_eq!(entry.context, None);
148        assert!(!entry.id.is_empty());
149    }
150
151    #[test]
152    fn test_audit_log_entry_with_agent() {
153        let entry = AuditLogEntry::with_agent(
154            "test_tool".to_string(),
155            AuditAction::Denied,
156            AuditResult::Blocked,
157            "agent1".to_string(),
158        );
159
160        assert_eq!(entry.tool, "test_tool");
161        assert_eq!(entry.action, AuditAction::Denied);
162        assert_eq!(entry.result, AuditResult::Blocked);
163        assert_eq!(entry.agent, Some("agent1".to_string()));
164    }
165
166    #[test]
167    fn test_audit_log_entry_with_context() {
168        let entry = AuditLogEntry::new(
169            "test_tool".to_string(),
170            AuditAction::Prompted,
171            AuditResult::Success,
172        )
173        .with_context("User approved after 5 seconds".to_string());
174
175        assert_eq!(
176            entry.context,
177            Some("User approved after 5 seconds".to_string())
178        );
179    }
180
181    #[test]
182    fn test_audit_log_entry_serialization() {
183        let entry = AuditLogEntry::new(
184            "test_tool".to_string(),
185            AuditAction::Allowed,
186            AuditResult::Success,
187        );
188
189        let json = serde_json::to_string(&entry).unwrap();
190        let deserialized: AuditLogEntry = serde_json::from_str(&json).unwrap();
191
192        assert_eq!(deserialized.tool, entry.tool);
193        assert_eq!(deserialized.action, entry.action);
194        assert_eq!(deserialized.result, entry.result);
195    }
196
197    #[test]
198    fn test_audit_log_entry_timestamp() {
199        let before = Utc::now();
200        let entry = AuditLogEntry::new(
201            "test_tool".to_string(),
202            AuditAction::Allowed,
203            AuditResult::Success,
204        );
205        let after = Utc::now();
206
207        assert!(entry.timestamp >= before);
208        assert!(entry.timestamp <= after);
209    }
210}