ricecoder_permissions/audit/
logger.rs

1//! Audit logger implementation
2
3use super::models::{AuditAction, AuditLogEntry, AuditResult};
4use std::sync::{Arc, RwLock};
5
6/// Audit logger for recording permission checks and denials
7#[derive(Clone)]
8pub struct AuditLogger {
9    entries: Arc<RwLock<Vec<AuditLogEntry>>>,
10}
11
12impl AuditLogger {
13    /// Create a new audit logger
14    pub fn new() -> Self {
15        Self {
16            entries: Arc::new(RwLock::new(Vec::new())),
17        }
18    }
19
20    /// Log a tool execution
21    pub fn log_execution(
22        &self,
23        tool: String,
24        agent: Option<String>,
25        context: Option<String>,
26    ) -> Result<(), String> {
27        let mut entry = AuditLogEntry::new(tool, AuditAction::Allowed, AuditResult::Success);
28
29        if let Some(agent_name) = agent {
30            entry.agent = Some(agent_name);
31        }
32
33        if let Some(ctx) = context {
34            entry.context = Some(ctx);
35        }
36
37        let mut entries = self
38            .entries
39            .write()
40            .map_err(|e| format!("Failed to acquire write lock: {}", e))?;
41        entries.push(entry);
42
43        Ok(())
44    }
45
46    /// Log a tool denial
47    pub fn log_denial(
48        &self,
49        tool: String,
50        agent: Option<String>,
51        context: Option<String>,
52    ) -> Result<(), String> {
53        let mut entry = AuditLogEntry::new(tool, AuditAction::Denied, AuditResult::Blocked);
54
55        if let Some(agent_name) = agent {
56            entry.agent = Some(agent_name);
57        }
58
59        if let Some(ctx) = context {
60            entry.context = Some(ctx);
61        }
62
63        let mut entries = self
64            .entries
65            .write()
66            .map_err(|e| format!("Failed to acquire write lock: {}", e))?;
67        entries.push(entry);
68
69        Ok(())
70    }
71
72    /// Log a permission prompt
73    pub fn log_prompt(
74        &self,
75        tool: String,
76        agent: Option<String>,
77        context: Option<String>,
78    ) -> Result<(), String> {
79        let mut entry = AuditLogEntry::new(tool, AuditAction::Prompted, AuditResult::Success);
80
81        if let Some(agent_name) = agent {
82            entry.agent = Some(agent_name);
83        }
84
85        if let Some(ctx) = context {
86            entry.context = Some(ctx);
87        }
88
89        let mut entries = self
90            .entries
91            .write()
92            .map_err(|e| format!("Failed to acquire write lock: {}", e))?;
93        entries.push(entry);
94
95        Ok(())
96    }
97
98    /// Get all entries
99    pub fn entries(&self) -> Result<Vec<AuditLogEntry>, String> {
100        let entries = self
101            .entries
102            .read()
103            .map_err(|e| format!("Failed to acquire read lock: {}", e))?;
104        Ok(entries.clone())
105    }
106
107    /// Get the number of entries
108    pub fn len(&self) -> Result<usize, String> {
109        let entries = self
110            .entries
111            .read()
112            .map_err(|e| format!("Failed to acquire read lock: {}", e))?;
113        Ok(entries.len())
114    }
115
116    /// Check if the logger is empty
117    pub fn is_empty(&self) -> Result<bool, String> {
118        let entries = self
119            .entries
120            .read()
121            .map_err(|e| format!("Failed to acquire read lock: {}", e))?;
122        Ok(entries.is_empty())
123    }
124
125    /// Clear all entries
126    pub fn clear(&self) -> Result<(), String> {
127        let mut entries = self
128            .entries
129            .write()
130            .map_err(|e| format!("Failed to acquire write lock: {}", e))?;
131        entries.clear();
132        Ok(())
133    }
134}
135
136impl Default for AuditLogger {
137    fn default() -> Self {
138        Self::new()
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_audit_logger_creation() {
148        let logger = AuditLogger::new();
149        assert!(logger.is_empty().unwrap());
150        assert_eq!(logger.len().unwrap(), 0);
151    }
152
153    #[test]
154    fn test_log_execution() {
155        let logger = AuditLogger::new();
156        let result = logger.log_execution("test_tool".to_string(), None, None);
157
158        assert!(result.is_ok());
159        assert_eq!(logger.len().unwrap(), 1);
160
161        let entries = logger.entries().unwrap();
162        assert_eq!(entries[0].tool, "test_tool");
163        assert_eq!(entries[0].action, AuditAction::Allowed);
164        assert_eq!(entries[0].result, AuditResult::Success);
165    }
166
167    #[test]
168    fn test_log_execution_with_agent() {
169        let logger = AuditLogger::new();
170        let result =
171            logger.log_execution("test_tool".to_string(), Some("agent1".to_string()), None);
172
173        assert!(result.is_ok());
174        let entries = logger.entries().unwrap();
175        assert_eq!(entries[0].agent, Some("agent1".to_string()));
176    }
177
178    #[test]
179    fn test_log_execution_with_context() {
180        let logger = AuditLogger::new();
181        let result = logger.log_execution(
182            "test_tool".to_string(),
183            None,
184            Some("User context".to_string()),
185        );
186
187        assert!(result.is_ok());
188        let entries = logger.entries().unwrap();
189        assert_eq!(entries[0].context, Some("User context".to_string()));
190    }
191
192    #[test]
193    fn test_log_denial() {
194        let logger = AuditLogger::new();
195        let result = logger.log_denial("test_tool".to_string(), None, None);
196
197        assert!(result.is_ok());
198        assert_eq!(logger.len().unwrap(), 1);
199
200        let entries = logger.entries().unwrap();
201        assert_eq!(entries[0].tool, "test_tool");
202        assert_eq!(entries[0].action, AuditAction::Denied);
203        assert_eq!(entries[0].result, AuditResult::Blocked);
204    }
205
206    #[test]
207    fn test_log_denial_with_agent() {
208        let logger = AuditLogger::new();
209        let result = logger.log_denial("test_tool".to_string(), Some("agent1".to_string()), None);
210
211        assert!(result.is_ok());
212        let entries = logger.entries().unwrap();
213        assert_eq!(entries[0].agent, Some("agent1".to_string()));
214    }
215
216    #[test]
217    fn test_log_prompt() {
218        let logger = AuditLogger::new();
219        let result = logger.log_prompt("test_tool".to_string(), None, None);
220
221        assert!(result.is_ok());
222        assert_eq!(logger.len().unwrap(), 1);
223
224        let entries = logger.entries().unwrap();
225        assert_eq!(entries[0].tool, "test_tool");
226        assert_eq!(entries[0].action, AuditAction::Prompted);
227        assert_eq!(entries[0].result, AuditResult::Success);
228    }
229
230    #[test]
231    fn test_log_prompt_with_agent() {
232        let logger = AuditLogger::new();
233        let result = logger.log_prompt("test_tool".to_string(), Some("agent1".to_string()), None);
234
235        assert!(result.is_ok());
236        let entries = logger.entries().unwrap();
237        assert_eq!(entries[0].agent, Some("agent1".to_string()));
238    }
239
240    #[test]
241    fn test_multiple_logs() {
242        let logger = AuditLogger::new();
243
244        logger
245            .log_execution("tool1".to_string(), None, None)
246            .unwrap();
247        logger.log_denial("tool2".to_string(), None, None).unwrap();
248        logger.log_prompt("tool3".to_string(), None, None).unwrap();
249
250        assert_eq!(logger.len().unwrap(), 3);
251
252        let entries = logger.entries().unwrap();
253        assert_eq!(entries[0].tool, "tool1");
254        assert_eq!(entries[0].action, AuditAction::Allowed);
255        assert_eq!(entries[1].tool, "tool2");
256        assert_eq!(entries[1].action, AuditAction::Denied);
257        assert_eq!(entries[2].tool, "tool3");
258        assert_eq!(entries[2].action, AuditAction::Prompted);
259    }
260
261    #[test]
262    fn test_clear_entries() {
263        let logger = AuditLogger::new();
264
265        logger
266            .log_execution("tool1".to_string(), None, None)
267            .unwrap();
268        logger
269            .log_execution("tool2".to_string(), None, None)
270            .unwrap();
271
272        assert_eq!(logger.len().unwrap(), 2);
273
274        logger.clear().unwrap();
275        assert_eq!(logger.len().unwrap(), 0);
276        assert!(logger.is_empty().unwrap());
277    }
278
279    #[test]
280    fn test_default_creation() {
281        let logger = AuditLogger::default();
282        assert!(logger.is_empty().unwrap());
283    }
284
285    #[test]
286    fn test_clone() {
287        let logger1 = AuditLogger::new();
288        logger1
289            .log_execution("tool1".to_string(), None, None)
290            .unwrap();
291
292        let logger2 = logger1.clone();
293        assert_eq!(logger2.len().unwrap(), 1);
294
295        logger2
296            .log_execution("tool2".to_string(), None, None)
297            .unwrap();
298        assert_eq!(logger1.len().unwrap(), 2);
299    }
300}