1use std::collections::HashMap;
8
9use chrono::{DateTime, Utc};
10
11use crate::secrets_manager::SecretsError;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum OperationType {
16 Insert,
18 Select,
20 Update,
22 Delete,
24}
25
26impl std::fmt::Display for OperationType {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 Self::Insert => write!(f, "insert"),
30 Self::Select => write!(f, "select"),
31 Self::Update => write!(f, "update"),
32 Self::Delete => write!(f, "delete"),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum EventStatus {
40 Success,
42 Failure,
44}
45
46impl std::fmt::Display for EventStatus {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 Self::Success => write!(f, "success"),
50 Self::Failure => write!(f, "failure"),
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct AuditLogEntry {
58 timestamp: DateTime<Utc>,
60 user_id: String,
62 field_name: String,
64 operation: OperationType,
66 status: EventStatus,
68 error_message: Option<String>,
70 request_id: String,
72 session_id: String,
74 context: HashMap<String, String>,
76}
77
78impl AuditLogEntry {
79 pub fn new(
81 user_id: impl Into<String>,
82 field_name: impl Into<String>,
83 operation: OperationType,
84 request_id: impl Into<String>,
85 session_id: impl Into<String>,
86 ) -> Self {
87 Self {
88 timestamp: Utc::now(),
89 user_id: user_id.into(),
90 field_name: field_name.into(),
91 operation,
92 status: EventStatus::Success,
93 error_message: None,
94 request_id: request_id.into(),
95 session_id: session_id.into(),
96 context: HashMap::new(),
97 }
98 }
99
100 pub fn with_failure(mut self, error: impl Into<String>) -> Self {
102 self.status = EventStatus::Failure;
103 self.error_message = Some(error.into());
104 self
105 }
106
107 pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
109 self.context.insert(key.into(), value.into());
110 self
111 }
112
113 pub fn with_security_context(self, ip_address: Option<&str>, user_role: Option<&str>) -> Self {
115 let mut entry = self;
116 if let Some(ip) = ip_address {
117 entry = entry.with_context("ip_address", ip);
118 }
119 if let Some(role) = user_role {
120 entry = entry.with_context("user_role", role);
121 }
122 entry
123 }
124
125 pub fn timestamp(&self) -> DateTime<Utc> {
127 self.timestamp
128 }
129
130 pub fn user_id(&self) -> &str {
132 &self.user_id
133 }
134
135 pub fn field_name(&self) -> &str {
137 &self.field_name
138 }
139
140 pub fn operation(&self) -> OperationType {
142 self.operation
143 }
144
145 pub fn status(&self) -> EventStatus {
147 self.status
148 }
149
150 pub fn error_message(&self) -> Option<&str> {
152 self.error_message.as_deref()
153 }
154
155 pub fn request_id(&self) -> &str {
157 &self.request_id
158 }
159
160 pub fn session_id(&self) -> &str {
162 &self.session_id
163 }
164
165 pub fn context(&self) -> &HashMap<String, String> {
167 &self.context
168 }
169
170 pub fn to_csv(&self) -> String {
172 let error = self.error_message.as_deref().unwrap_or("");
173 format!(
174 "{},{},{},{},{},{},{},{}",
175 self.timestamp.to_rfc3339(),
176 self.user_id,
177 self.field_name,
178 self.operation,
179 self.status,
180 error,
181 self.request_id,
182 self.session_id
183 )
184 }
185
186 pub fn to_json_like(&self) -> String {
188 format!(
189 "{{ \"timestamp\": \"{}\", \"user_id\": \"{}\", \"field_name\": \"{}\", \
190 \"operation\": \"{}\", \"status\": \"{}\", \"error\": \"{}\", \
191 \"request_id\": \"{}\", \"session_id\": \"{}\" }}",
192 self.timestamp.to_rfc3339(),
193 self.user_id,
194 self.field_name,
195 self.operation,
196 self.status,
197 self.error_message.as_deref().unwrap_or(""),
198 self.request_id,
199 self.session_id
200 )
201 }
202}
203
204pub struct AuditLogger {
208 entries: Vec<AuditLogEntry>,
210 max_entries: usize,
212}
213
214impl AuditLogger {
215 pub fn new(max_entries: usize) -> Self {
217 Self {
218 entries: Vec::new(),
219 max_entries,
220 }
221 }
222
223 pub fn log_entry(&mut self, entry: AuditLogEntry) -> Result<(), SecretsError> {
225 if self.entries.len() >= self.max_entries {
227 self.entries.remove(0);
228 }
229
230 self.entries.push(entry);
231 Ok(())
232 }
233
234 fn filter_entries<F>(&self, predicate: F) -> Vec<AuditLogEntry>
236 where
237 F: Fn(&&AuditLogEntry) -> bool,
238 {
239 self.entries.iter().filter(predicate).cloned().collect()
240 }
241
242 pub fn recent_entries(&self, count: usize) -> Vec<AuditLogEntry> {
244 let start = if self.entries.len() > count {
245 self.entries.len() - count
246 } else {
247 0
248 };
249 self.entries[start..].to_vec()
250 }
251
252 pub fn entries_for_user(&self, user_id: &str) -> Vec<AuditLogEntry> {
254 self.filter_entries(|e| e.user_id == user_id)
255 }
256
257 pub fn entries_for_field(&self, field_name: &str) -> Vec<AuditLogEntry> {
259 self.filter_entries(|e| e.field_name == field_name)
260 }
261
262 pub fn entries_for_operation(&self, operation: OperationType) -> Vec<AuditLogEntry> {
264 self.filter_entries(|e| e.operation == operation)
265 }
266
267 pub fn failed_entries(&self) -> Vec<AuditLogEntry> {
269 self.filter_entries(|e| e.status == EventStatus::Failure)
270 }
271
272 pub fn successful_entries(&self) -> Vec<AuditLogEntry> {
274 self.filter_entries(|e| e.status == EventStatus::Success)
275 }
276
277 pub fn entries_for_user_operation(
279 &self,
280 user_id: &str,
281 operation: OperationType,
282 ) -> Vec<AuditLogEntry> {
283 self.filter_entries(|e| e.user_id == user_id && e.operation == operation)
284 }
285
286 pub fn entry_count(&self) -> usize {
288 self.entries.len()
289 }
290
291 pub fn clear(&mut self) {
293 self.entries.clear();
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_audit_log_entry_creation() {
303 let entry =
304 AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
305 assert_eq!(entry.user_id(), "user123");
306 assert_eq!(entry.field_name(), "email");
307 assert_eq!(entry.operation(), OperationType::Insert);
308 assert_eq!(entry.status(), EventStatus::Success);
309 }
310
311 #[test]
312 fn test_audit_log_entry_with_failure() {
313 let entry =
314 AuditLogEntry::new("user123", "email", OperationType::Select, "req456", "sess789")
315 .with_failure("Decryption failed: wrong key");
316 assert_eq!(entry.status(), EventStatus::Failure);
317 assert_eq!(entry.error_message(), Some("Decryption failed: wrong key"));
318 }
319
320 #[test]
321 fn test_audit_log_entry_with_context() {
322 let entry =
323 AuditLogEntry::new("user123", "email", OperationType::Update, "req456", "sess789")
324 .with_context("ip_address", "192.168.1.1")
325 .with_context("user_role", "admin");
326 assert_eq!(entry.context().get("ip_address"), Some(&"192.168.1.1".to_string()));
327 assert_eq!(entry.context().get("user_role"), Some(&"admin".to_string()));
328 }
329
330 #[test]
331 fn test_audit_log_entry_to_csv() {
332 let entry =
333 AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
334 let csv = entry.to_csv();
335 assert!(csv.contains("user123"));
336 assert!(csv.contains("email"));
337 assert!(csv.contains("insert"));
338 assert!(csv.contains("success"));
339 }
340
341 #[test]
342 fn test_audit_log_entry_to_json_like() {
343 let entry =
344 AuditLogEntry::new("user123", "email", OperationType::Select, "req456", "sess789");
345 let json = entry.to_json_like();
346 assert!(json.contains("user123"));
347 assert!(json.contains("email"));
348 assert!(json.contains("select"));
349 }
350
351 #[test]
352 fn test_audit_logger_logging() {
353 let mut logger = AuditLogger::new(10);
354 let entry =
355 AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
356 let result = logger.log_entry(entry);
357 assert!(result.is_ok());
358 assert_eq!(logger.entry_count(), 1);
359 }
360
361 #[test]
362 fn test_audit_logger_recent_entries() {
363 let mut logger = AuditLogger::new(10);
364 for i in 0..5 {
365 let entry = AuditLogEntry::new(
366 format!("user{}", i),
367 "email",
368 OperationType::Insert,
369 "req456",
370 "sess789",
371 );
372 let _ = logger.log_entry(entry);
373 }
374 let recent = logger.recent_entries(2);
375 assert_eq!(recent.len(), 2);
376 }
377
378 #[test]
379 fn test_audit_logger_entries_for_user() {
380 let mut logger = AuditLogger::new(10);
381 for i in 0..3 {
382 let entry = AuditLogEntry::new(
383 "user123",
384 format!("field{}", i),
385 OperationType::Insert,
386 "req456",
387 "sess789",
388 );
389 let _ = logger.log_entry(entry);
390 }
391 for i in 0..2 {
392 let entry = AuditLogEntry::new(
393 "user456",
394 format!("field{}", i),
395 OperationType::Select,
396 "req456",
397 "sess789",
398 );
399 let _ = logger.log_entry(entry);
400 }
401 let user_entries = logger.entries_for_user("user123");
402 assert_eq!(user_entries.len(), 3);
403 }
404
405 #[test]
406 fn test_audit_logger_entries_for_field() {
407 let mut logger = AuditLogger::new(10);
408 for i in 0..3 {
409 let entry = AuditLogEntry::new(
410 format!("user{}", i),
411 "email",
412 OperationType::Insert,
413 "req456",
414 "sess789",
415 );
416 let _ = logger.log_entry(entry);
417 }
418 let email_entries = logger.entries_for_field("email");
419 assert_eq!(email_entries.len(), 3);
420 }
421
422 #[test]
423 fn test_audit_logger_failed_entries() {
424 let mut logger = AuditLogger::new(10);
425 let success =
426 AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
427 let failure =
428 AuditLogEntry::new("user456", "phone", OperationType::Select, "req789", "sess123")
429 .with_failure("Key not found");
430 let _ = logger.log_entry(success);
431 let _ = logger.log_entry(failure);
432 let failed = logger.failed_entries();
433 assert_eq!(failed.len(), 1);
434 }
435
436 #[test]
437 fn test_audit_logger_bounded_history() {
438 let mut logger = AuditLogger::new(3);
439 for i in 0..5 {
440 let entry = AuditLogEntry::new(
441 format!("user{}", i),
442 "email",
443 OperationType::Insert,
444 "req456",
445 "sess789",
446 );
447 let _ = logger.log_entry(entry);
448 }
449 assert_eq!(logger.entry_count(), 3);
450 }
451
452 #[test]
453 fn test_audit_logger_clear() {
454 let mut logger = AuditLogger::new(10);
455 let entry =
456 AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789");
457 let _ = logger.log_entry(entry);
458 assert_eq!(logger.entry_count(), 1);
459 logger.clear();
460 assert_eq!(logger.entry_count(), 0);
461 }
462
463 #[test]
464 fn test_operation_type_display() {
465 assert_eq!(OperationType::Insert.to_string(), "insert");
466 assert_eq!(OperationType::Select.to_string(), "select");
467 assert_eq!(OperationType::Update.to_string(), "update");
468 assert_eq!(OperationType::Delete.to_string(), "delete");
469 }
470
471 #[test]
472 fn test_event_status_display() {
473 assert_eq!(EventStatus::Success.to_string(), "success");
474 assert_eq!(EventStatus::Failure.to_string(), "failure");
475 }
476
477 #[test]
478 fn test_audit_log_entry_with_security_context() {
479 let entry =
480 AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789")
481 .with_security_context(Some("192.168.1.1"), Some("admin"));
482 assert_eq!(entry.context().get("ip_address"), Some(&"192.168.1.1".to_string()));
483 assert_eq!(entry.context().get("user_role"), Some(&"admin".to_string()));
484 }
485
486 #[test]
487 fn test_audit_log_entry_with_partial_security_context() {
488 let entry =
489 AuditLogEntry::new("user123", "email", OperationType::Insert, "req456", "sess789")
490 .with_security_context(Some("192.168.1.1"), None);
491 assert_eq!(entry.context().get("ip_address"), Some(&"192.168.1.1".to_string()));
492 assert!(!entry.context().contains_key("user_role"));
493 }
494
495 #[test]
496 fn test_audit_logger_entries_for_operation() {
497 let mut logger = AuditLogger::new(10);
498 let entry1 = AuditLogEntry::new("user1", "email", OperationType::Insert, "req1", "sess1");
499 let entry2 = AuditLogEntry::new("user2", "phone", OperationType::Select, "req2", "sess2");
500 let entry3 = AuditLogEntry::new("user3", "ssn", OperationType::Insert, "req3", "sess3");
501 let _ = logger.log_entry(entry1);
502 let _ = logger.log_entry(entry2);
503 let _ = logger.log_entry(entry3);
504 let inserts = logger.entries_for_operation(OperationType::Insert);
505 assert_eq!(inserts.len(), 2);
506 let selects = logger.entries_for_operation(OperationType::Select);
507 assert_eq!(selects.len(), 1);
508 }
509
510 #[test]
511 fn test_audit_logger_successful_entries() {
512 let mut logger = AuditLogger::new(10);
513 let success = AuditLogEntry::new("user1", "email", OperationType::Insert, "req1", "sess1");
514 let failure = AuditLogEntry::new("user2", "phone", OperationType::Select, "req2", "sess2")
515 .with_failure("Key not found");
516 let _ = logger.log_entry(success);
517 let _ = logger.log_entry(failure);
518 let successful = logger.successful_entries();
519 assert_eq!(successful.len(), 1);
520 assert_eq!(successful[0].user_id(), "user1");
521 }
522
523 #[test]
524 fn test_audit_logger_entries_for_user_operation() {
525 let mut logger = AuditLogger::new(10);
526 let entry1 = AuditLogEntry::new("user1", "email", OperationType::Insert, "req1", "sess1");
527 let entry2 = AuditLogEntry::new("user1", "phone", OperationType::Select, "req2", "sess2");
528 let entry3 = AuditLogEntry::new("user2", "email", OperationType::Insert, "req3", "sess3");
529 let _ = logger.log_entry(entry1);
530 let _ = logger.log_entry(entry2);
531 let _ = logger.log_entry(entry3);
532 let user1_inserts = logger.entries_for_user_operation("user1", OperationType::Insert);
533 assert_eq!(user1_inserts.len(), 1);
534 let user1_selects = logger.entries_for_user_operation("user1", OperationType::Select);
535 assert_eq!(user1_selects.len(), 1);
536 }
537}