1use chrono::{DateTime, Utc};
6use serde_json::Value;
7use std::sync::Arc;
8
9use super::entry::{AuditAction, AuditEntry, AuditFilter, AuditResult, ExportFormat};
10use super::storage::{AuditStorage, MemoryStorage};
11use anyhow::Result;
12
13#[derive(Debug, Clone)]
15pub struct AuditConfig {
16 pub enabled: bool,
18 pub sanitize_sensitive: bool,
20 pub sensitive_fields: Vec<String>,
22 pub default_user_id: String,
24 pub async_write: bool,
26 pub retention_days: u32,
28}
29
30impl Default for AuditConfig {
31 fn default() -> Self {
32 Self {
33 enabled: true,
34 sanitize_sensitive: true,
35 sensitive_fields: vec![
36 "password".to_string(),
37 "token".to_string(),
38 "api_key".to_string(),
39 "secret".to_string(),
40 "credential".to_string(),
41 ],
42 default_user_id: "system".to_string(),
43 async_write: true,
44 retention_days: 90,
45 }
46 }
47}
48
49pub struct AuditLogger {
51 storage: Arc<dyn AuditStorage>,
53 config: AuditConfig,
55}
56
57impl AuditLogger {
58 pub fn new(config: AuditConfig) -> Self {
60 Self {
61 storage: Arc::new(MemoryStorage::new(10000)),
62 config,
63 }
64 }
65
66 pub fn with_storage(mut self, storage: Arc<dyn AuditStorage>) -> Self {
68 self.storage = storage;
69 self
70 }
71
72 pub fn config(&self) -> &AuditConfig {
74 &self.config
75 }
76
77 pub async fn log(&self, entry: AuditEntry) -> Result<()> {
79 if !self.config.enabled {
80 return Ok(());
81 }
82
83 let entry = if self.config.sanitize_sensitive {
85 self.sanitize_entry(entry)
86 } else {
87 entry
88 };
89
90 self.storage.save(&entry).await
91 }
92
93 pub async fn log_action(
95 &self,
96 user_id: &str,
97 action: AuditAction,
98 resource_type: &str,
99 resource_id: Option<&str>,
100 result: AuditResult,
101 ) -> Result<()> {
102 let mut entry = AuditEntry::new(user_id, action, resource_type);
103
104 if let Some(id) = resource_id {
105 entry = entry.with_resource_id(id);
106 }
107
108 entry = entry.with_result(result);
109
110 self.log(entry).await
111 }
112
113 pub async fn log_success(
115 &self,
116 user_id: &str,
117 action: AuditAction,
118 resource_type: &str,
119 resource_id: Option<&str>,
120 ) -> Result<()> {
121 self.log_action(
122 user_id,
123 action,
124 resource_type,
125 resource_id,
126 AuditResult::Success,
127 )
128 .await
129 }
130
131 pub async fn log_failure(
133 &self,
134 user_id: &str,
135 action: AuditAction,
136 resource_type: &str,
137 resource_id: Option<&str>,
138 error_code: &str,
139 error_message: &str,
140 ) -> Result<()> {
141 self.log_action(
142 user_id,
143 action,
144 resource_type,
145 resource_id,
146 AuditResult::failure(error_code, error_message),
147 )
148 .await
149 }
150
151 pub async fn log_denied(
153 &self,
154 user_id: &str,
155 action: AuditAction,
156 resource_type: &str,
157 resource_id: Option<&str>,
158 reason: &str,
159 ) -> Result<()> {
160 self.log_action(
161 user_id,
162 action,
163 resource_type,
164 resource_id,
165 AuditResult::denied(reason),
166 )
167 .await
168 }
169
170 pub async fn query(&self, filter: AuditFilter) -> Result<Vec<AuditEntry>> {
172 self.storage.query(&filter).await
173 }
174
175 pub async fn export(&self, format: ExportFormat, filter: AuditFilter) -> Result<Vec<u8>> {
177 self.storage.export(format, &filter).await
178 }
179
180 pub async fn cleanup(&self, before: DateTime<Utc>) -> Result<usize> {
182 self.storage.cleanup(before).await
183 }
184
185 pub async fn count(&self) -> Result<usize> {
187 self.storage.count().await
188 }
189
190 fn sanitize_entry(&self, mut entry: AuditEntry) -> AuditEntry {
192 if let Some(details) = &entry.details {
193 entry.details = Some(self.sanitize_value(details.clone()));
194 }
195 entry
196 }
197
198 fn sanitize_value(&self, value: Value) -> Value {
200 match value {
201 Value::Object(mut map) => {
202 for field in &self.config.sensitive_fields {
203 if let Some(v) = map.get_mut(field) {
204 *v = Value::String("***REDACTED***".to_string());
205 }
206 }
207 for (_, v) in map.iter_mut() {
208 *v = self.sanitize_value(v.clone());
209 }
210 Value::Object(map)
211 }
212 Value::Array(arr) => {
213 Value::Array(arr.into_iter().map(|v| self.sanitize_value(v)).collect())
214 }
215 other => other,
216 }
217 }
218
219 pub fn builder(
221 &self,
222 user_id: &str,
223 action: AuditAction,
224 resource_type: &str,
225 ) -> AuditEntryBuilder<'_> {
226 AuditEntryBuilder {
227 entry: AuditEntry::new(user_id, action, resource_type),
228 logger: self,
229 }
230 }
231}
232
233pub struct AuditEntryBuilder<'a> {
235 entry: AuditEntry,
236 logger: &'a AuditLogger,
237}
238
239impl<'a> AuditEntryBuilder<'a> {
240 pub fn with_session(mut self, session_id: &str) -> Self {
241 self.entry = self.entry.with_session(session_id);
242 self
243 }
244
245 pub fn with_resource_id(mut self, resource_id: &str) -> Self {
246 self.entry = self.entry.with_resource_id(resource_id);
247 self
248 }
249
250 pub fn with_details(mut self, details: Value) -> Self {
251 self.entry = self.entry.with_details(details);
252 self
253 }
254
255 pub fn with_ip(mut self, ip_address: &str) -> Self {
256 self.entry = self.entry.with_ip(ip_address);
257 self
258 }
259
260 pub fn with_result(mut self, result: AuditResult) -> Self {
261 self.entry = self.entry.with_result(result);
262 self
263 }
264
265 pub fn with_duration(mut self, duration_ms: u64) -> Self {
266 self.entry = self.entry.with_duration(duration_ms);
267 self
268 }
269
270 pub async fn log(self) -> Result<()> {
271 self.logger.log(self.entry).await
272 }
273}
274
275impl Default for AuditLogger {
276 fn default() -> Self {
277 Self::new(AuditConfig::default())
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[tokio::test]
286 async fn test_audit_logger_creation() {
287 let logger = AuditLogger::default();
288 assert!(logger.config().enabled);
289 }
290
291 #[tokio::test]
292 async fn test_log_action() {
293 let logger = AuditLogger::default();
294
295 logger
296 .log_success("user1", AuditAction::Read, "document", Some("doc123"))
297 .await
298 .unwrap();
299
300 let count = logger.count().await.unwrap();
301 assert_eq!(count, 1);
302 }
303
304 #[tokio::test]
305 async fn test_query() {
306 let logger = AuditLogger::default();
307
308 logger
309 .log_success("user1", AuditAction::Read, "doc", None)
310 .await
311 .unwrap();
312 logger
313 .log_success("user2", AuditAction::Read, "doc", None)
314 .await
315 .unwrap();
316
317 let filter = AuditFilter {
318 user_id: Some("user1".to_string()),
319 ..Default::default()
320 };
321
322 let entries = logger.query(filter).await.unwrap();
323 assert_eq!(entries.len(), 1);
324 assert_eq!(entries[0].user_id, "user1");
325 }
326
327 #[tokio::test]
328 async fn test_sanitize_sensitive_data() {
329 let config = AuditConfig {
330 sanitize_sensitive: true,
331 ..Default::default()
332 };
333 let logger = AuditLogger::new(config);
334
335 let entry =
336 AuditEntry::new("user1", AuditAction::Create, "user").with_details(serde_json::json!({
337 "username": "test",
338 "password": "secret123"
339 }));
340
341 logger.log(entry).await.unwrap();
342
343 let entries = logger.query(AuditFilter::default()).await.unwrap();
344 let details = entries[0].details.as_ref().unwrap();
345
346 assert_eq!(details.get("password").unwrap(), "***REDACTED***");
347 }
348
349 #[tokio::test]
350 async fn test_builder_pattern() {
351 let logger = AuditLogger::default();
352
353 logger
354 .builder("user1", AuditAction::Login, "session")
355 .with_ip("192.168.1.1")
356 .with_duration(100)
357 .log()
358 .await
359 .unwrap();
360
361 let entries = logger.query(AuditFilter::default()).await.unwrap();
362 assert_eq!(entries.len(), 1);
363 assert_eq!(entries[0].ip_address, Some("192.168.1.1".to_string()));
364 }
365
366 #[tokio::test]
367 async fn test_disabled_logger() {
368 let config = AuditConfig {
369 enabled: false,
370 ..Default::default()
371 };
372 let logger = AuditLogger::new(config);
373
374 logger
375 .log_success("user1", AuditAction::Read, "doc", None)
376 .await
377 .unwrap();
378
379 let count = logger.count().await.unwrap();
380 assert_eq!(count, 0);
381 }
382}