1use serde::{Deserialize, Serialize};
11use std::collections::VecDeque;
12use std::sync::{Arc, RwLock};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum AuditEventType {
18 ProcessingStarted,
20 ProcessingCompleted,
22 ProcessingFailed,
24 ApiKeyCreated,
26 ApiKeyRevoked,
28 ApiKeyDeleted,
30 AccessDenied,
32 RateLimitExceeded,
34 QuotaExceeded,
36 ConfigurationChanged,
38 DataExported,
40 AuditLogAccessed,
42}
43
44impl std::fmt::Display for AuditEventType {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 Self::ProcessingStarted => write!(f, "PROCESSING_STARTED"),
48 Self::ProcessingCompleted => write!(f, "PROCESSING_COMPLETED"),
49 Self::ProcessingFailed => write!(f, "PROCESSING_FAILED"),
50 Self::ApiKeyCreated => write!(f, "API_KEY_CREATED"),
51 Self::ApiKeyRevoked => write!(f, "API_KEY_REVOKED"),
52 Self::ApiKeyDeleted => write!(f, "API_KEY_DELETED"),
53 Self::AccessDenied => write!(f, "ACCESS_DENIED"),
54 Self::RateLimitExceeded => write!(f, "RATE_LIMIT_EXCEEDED"),
55 Self::QuotaExceeded => write!(f, "QUOTA_EXCEEDED"),
56 Self::ConfigurationChanged => write!(f, "CONFIGURATION_CHANGED"),
57 Self::DataExported => write!(f, "DATA_EXPORTED"),
58 Self::AuditLogAccessed => write!(f, "AUDIT_LOG_ACCESSED"),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
65pub enum AuditSeverity {
66 Info,
68 Warning,
70 Error,
72 Critical,
74}
75
76impl std::fmt::Display for AuditSeverity {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 match self {
79 Self::Info => write!(f, "INFO"),
80 Self::Warning => write!(f, "WARNING"),
81 Self::Error => write!(f, "ERROR"),
82 Self::Critical => write!(f, "CRITICAL"),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct AuditEvent {
90 pub id: String,
92
93 pub event_type: AuditEventType,
95
96 pub severity: AuditSeverity,
98
99 pub timestamp: u64,
101
102 pub user_id: Option<String>,
104
105 pub api_key: Option<String>,
107
108 pub ip_address: Option<String>,
110
111 pub request_id: Option<String>,
113
114 pub resource: Option<String>,
116
117 pub action: String,
119
120 pub result: AuditResult,
122
123 pub details: std::collections::HashMap<String, String>,
125
126 pub error: Option<String>,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
132pub enum AuditResult {
133 Success,
135 Failure,
137 Denied,
139}
140
141impl std::fmt::Display for AuditResult {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 Self::Success => write!(f, "SUCCESS"),
145 Self::Failure => write!(f, "FAILURE"),
146 Self::Denied => write!(f, "DENIED"),
147 }
148 }
149}
150
151impl AuditEvent {
152 pub fn new(event_type: AuditEventType, action: impl Into<String>) -> Self {
154 let severity = match event_type {
155 AuditEventType::ProcessingStarted
156 | AuditEventType::ProcessingCompleted
157 | AuditEventType::DataExported
158 | AuditEventType::AuditLogAccessed => AuditSeverity::Info,
159
160 AuditEventType::ConfigurationChanged
161 | AuditEventType::ApiKeyCreated
162 | AuditEventType::ApiKeyRevoked => AuditSeverity::Warning,
163
164 AuditEventType::ProcessingFailed | AuditEventType::ApiKeyDeleted => {
165 AuditSeverity::Error
166 }
167
168 AuditEventType::AccessDenied
169 | AuditEventType::RateLimitExceeded
170 | AuditEventType::QuotaExceeded => AuditSeverity::Critical,
171 };
172
173 Self {
174 id: generate_event_id(),
175 event_type,
176 severity,
177 timestamp: current_timestamp_ms(),
178 user_id: None,
179 api_key: None,
180 ip_address: None,
181 request_id: None,
182 resource: None,
183 action: action.into(),
184 result: AuditResult::Success,
185 details: std::collections::HashMap::new(),
186 error: None,
187 }
188 }
189
190 pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
192 self.user_id = Some(user_id.into());
193 self
194 }
195
196 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
198 self.api_key = Some(api_key.into());
199 self
200 }
201
202 pub fn with_ip_address(mut self, ip: impl Into<String>) -> Self {
204 self.ip_address = Some(ip.into());
205 self
206 }
207
208 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
210 self.request_id = Some(request_id.into());
211 self
212 }
213
214 pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
216 self.resource = Some(resource.into());
217 self
218 }
219
220 pub fn with_result(mut self, result: AuditResult) -> Self {
222 self.result = result;
223 self
224 }
225
226 pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
228 self.details.insert(key.into(), value.into());
229 self
230 }
231
232 pub fn with_error(mut self, error: impl Into<String>) -> Self {
234 self.error = Some(error.into());
235 self
236 }
237
238 pub fn to_json(&self) -> String {
240 serde_json::to_string(self)
241 .unwrap_or_else(|_| "{\"error\":\"failed to serialize audit event\"}".to_string())
242 }
243
244 pub fn to_csv_row(&self) -> String {
246 format!(
247 "{},{},{},{},{},{},{},{},{},{},{}",
248 self.id,
249 self.timestamp,
250 self.event_type,
251 self.severity,
252 self.user_id.as_deref().unwrap_or(""),
253 self.api_key.as_deref().unwrap_or(""),
254 self.ip_address.as_deref().unwrap_or(""),
255 self.resource.as_deref().unwrap_or(""),
256 self.action,
257 self.result,
258 self.error.as_deref().unwrap_or("")
259 )
260 }
261}
262
263fn generate_event_id() -> String {
265 use std::collections::hash_map::RandomState;
266 use std::hash::{BuildHasher, Hasher};
267
268 let mut hasher = RandomState::new().build_hasher();
269 let timestamp = current_timestamp_ms();
270 hasher.write_u64(timestamp);
271
272 let hash = hasher.finish();
273 format!("audit_{:016x}", hash)
274}
275
276fn current_timestamp_ms() -> u64 {
278 SystemTime::now()
279 .duration_since(UNIX_EPOCH)
280 .unwrap()
281 .as_millis() as u64
282}
283
284#[derive(Debug, Clone, Copy)]
286pub struct RetentionPolicy {
287 pub max_events: usize,
289
290 pub max_age_seconds: Option<u64>,
292
293 pub auto_export: bool,
295}
296
297impl Default for RetentionPolicy {
298 fn default() -> Self {
299 Self {
300 max_events: 100_000,
301 max_age_seconds: Some(90 * 24 * 3600), auto_export: false,
303 }
304 }
305}
306
307impl RetentionPolicy {
308 pub fn compliance() -> Self {
310 Self {
311 max_events: 10_000_000,
312 max_age_seconds: Some(7 * 365 * 24 * 3600), auto_export: true,
314 }
315 }
316
317 pub fn short_term() -> Self {
319 Self {
320 max_events: 10_000,
321 max_age_seconds: Some(30 * 24 * 3600), auto_export: false,
323 }
324 }
325
326 pub fn unlimited() -> Self {
328 Self {
329 max_events: usize::MAX,
330 max_age_seconds: None,
331 auto_export: false,
332 }
333 }
334}
335
336pub struct AuditLogger {
338 events: Arc<RwLock<VecDeque<AuditEvent>>>,
339 policy: RetentionPolicy,
340}
341
342impl AuditLogger {
343 pub fn new(policy: RetentionPolicy) -> Self {
345 Self {
346 events: Arc::new(RwLock::new(VecDeque::new())),
347 policy,
348 }
349 }
350
351 pub fn log(&self, event: AuditEvent) {
353 let mut events = self.events.write().unwrap();
354
355 self.apply_retention_policy(&mut events);
357
358 events.push_back(event);
360 }
361
362 fn apply_retention_policy(&self, events: &mut VecDeque<AuditEvent>) {
364 let now = current_timestamp_ms();
365
366 while events.len() >= self.policy.max_events {
368 events.pop_front();
369 }
370
371 if let Some(max_age_ms) = self.policy.max_age_seconds.map(|s| s * 1000) {
373 while let Some(event) = events.front() {
374 if now - event.timestamp > max_age_ms {
375 events.pop_front();
376 } else {
377 break;
378 }
379 }
380 }
381 }
382
383 pub fn get_events(&self) -> Vec<AuditEvent> {
385 self.events.read().unwrap().iter().cloned().collect()
386 }
387
388 pub fn get_events_by_type(&self, event_type: AuditEventType) -> Vec<AuditEvent> {
390 self.events
391 .read()
392 .unwrap()
393 .iter()
394 .filter(|e| e.event_type == event_type)
395 .cloned()
396 .collect()
397 }
398
399 pub fn get_events_by_user(&self, user_id: &str) -> Vec<AuditEvent> {
401 self.events
402 .read()
403 .unwrap()
404 .iter()
405 .filter(|e| e.user_id.as_deref() == Some(user_id))
406 .cloned()
407 .collect()
408 }
409
410 pub fn get_events_by_severity(&self, min_severity: AuditSeverity) -> Vec<AuditEvent> {
412 self.events
413 .read()
414 .unwrap()
415 .iter()
416 .filter(|e| e.severity >= min_severity)
417 .cloned()
418 .collect()
419 }
420
421 pub fn get_events_in_range(&self, start_ms: u64, end_ms: u64) -> Vec<AuditEvent> {
423 self.events
424 .read()
425 .unwrap()
426 .iter()
427 .filter(|e| e.timestamp >= start_ms && e.timestamp <= end_ms)
428 .cloned()
429 .collect()
430 }
431
432 pub fn export_json(&self) -> String {
434 let events = self.get_events();
435 serde_json::to_string_pretty(&events).unwrap_or_else(|_| "[]".to_string())
436 }
437
438 pub fn export_csv(&self) -> String {
440 let mut csv = String::from("id,timestamp,event_type,severity,user_id,api_key,ip_address,resource,action,result,error\n");
441
442 for event in self.get_events() {
443 csv.push_str(&event.to_csv_row());
444 csv.push('\n');
445 }
446
447 csv
448 }
449
450 pub fn count(&self) -> usize {
452 self.events.read().unwrap().len()
453 }
454
455 pub fn clear(&self) {
457 self.events.write().unwrap().clear();
458 }
459
460 pub fn stats(&self) -> AuditStats {
462 let events = self.events.read().unwrap();
463
464 let mut stats = AuditStats {
465 total_events: events.len(),
466 ..Default::default()
467 };
468
469 for event in events.iter() {
470 match event.event_type {
471 AuditEventType::ProcessingStarted => stats.processing_events += 1,
472 AuditEventType::ApiKeyCreated | AuditEventType::ApiKeyRevoked => {
473 stats.key_management_events += 1
474 }
475 AuditEventType::AccessDenied
476 | AuditEventType::RateLimitExceeded
477 | AuditEventType::QuotaExceeded => stats.security_events += 1,
478 _ => {}
479 }
480
481 match event.severity {
482 AuditSeverity::Info => stats.info_events += 1,
483 AuditSeverity::Warning => stats.warning_events += 1,
484 AuditSeverity::Error => stats.error_events += 1,
485 AuditSeverity::Critical => stats.critical_events += 1,
486 }
487 }
488
489 stats
490 }
491}
492
493impl Default for AuditLogger {
494 fn default() -> Self {
495 Self::new(RetentionPolicy::default())
496 }
497}
498
499#[derive(Debug, Clone, Default, Serialize, Deserialize)]
501pub struct AuditStats {
502 pub total_events: usize,
503 pub processing_events: usize,
504 pub key_management_events: usize,
505 pub security_events: usize,
506 pub info_events: usize,
507 pub warning_events: usize,
508 pub error_events: usize,
509 pub critical_events: usize,
510}
511
512impl std::fmt::Display for AuditStats {
513 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
514 writeln!(f, "Audit Statistics:")?;
515 writeln!(f, " Total Events: {}", self.total_events)?;
516 writeln!(f, " Processing Events: {}", self.processing_events)?;
517 writeln!(f, " Key Management: {}", self.key_management_events)?;
518 writeln!(f, " Security Events: {}", self.security_events)?;
519 writeln!(f, " Info: {}", self.info_events)?;
520 writeln!(f, " Warning: {}", self.warning_events)?;
521 writeln!(f, " Error: {}", self.error_events)?;
522 writeln!(f, " Critical: {}", self.critical_events)?;
523 Ok(())
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_audit_event_type_display() {
533 assert_eq!(
534 format!("{}", AuditEventType::ProcessingStarted),
535 "PROCESSING_STARTED"
536 );
537 assert_eq!(format!("{}", AuditEventType::AccessDenied), "ACCESS_DENIED");
538 }
539
540 #[test]
541 fn test_audit_severity_ordering() {
542 assert!(AuditSeverity::Critical > AuditSeverity::Error);
543 assert!(AuditSeverity::Error > AuditSeverity::Warning);
544 assert!(AuditSeverity::Warning > AuditSeverity::Info);
545 }
546
547 #[test]
548 fn test_audit_event_creation() {
549 let event = AuditEvent::new(AuditEventType::ProcessingStarted, "OCR processing");
550 assert_eq!(event.event_type, AuditEventType::ProcessingStarted);
551 assert_eq!(event.severity, AuditSeverity::Info);
552 assert_eq!(event.action, "OCR processing");
553 }
554
555 #[test]
556 fn test_audit_event_with_fields() {
557 let event = AuditEvent::new(AuditEventType::ProcessingCompleted, "Complete")
558 .with_user_id("user123")
559 .with_api_key("key456")
560 .with_resource("image.png")
561 .with_detail("duration_ms", "1500");
562
563 assert_eq!(event.user_id, Some("user123".to_string()));
564 assert_eq!(event.api_key, Some("key456".to_string()));
565 assert_eq!(event.resource, Some("image.png".to_string()));
566 assert_eq!(event.details.get("duration_ms"), Some(&"1500".to_string()));
567 }
568
569 #[test]
570 fn test_audit_event_to_json() {
571 let event = AuditEvent::new(AuditEventType::ApiKeyCreated, "Create key");
572 let json = event.to_json();
573 assert!(json.contains("\"event_type\":\"ApiKeyCreated\""));
574 }
575
576 #[test]
577 fn test_audit_result_display() {
578 assert_eq!(format!("{}", AuditResult::Success), "SUCCESS");
579 assert_eq!(format!("{}", AuditResult::Failure), "FAILURE");
580 assert_eq!(format!("{}", AuditResult::Denied), "DENIED");
581 }
582
583 #[test]
584 fn test_retention_policy_default() {
585 let policy = RetentionPolicy::default();
586 assert_eq!(policy.max_events, 100_000);
587 assert!(policy.max_age_seconds.is_some());
588 }
589
590 #[test]
591 fn test_retention_policy_presets() {
592 let compliance = RetentionPolicy::compliance();
593 assert!(compliance.auto_export);
594 assert!(compliance.max_age_seconds.unwrap() > 365 * 24 * 3600);
595
596 let short = RetentionPolicy::short_term();
597 assert!(!short.auto_export);
598
599 let unlimited = RetentionPolicy::unlimited();
600 assert_eq!(unlimited.max_events, usize::MAX);
601 assert!(unlimited.max_age_seconds.is_none());
602 }
603
604 #[test]
605 fn test_audit_logger_creation() {
606 let logger = AuditLogger::new(RetentionPolicy::default());
607 assert_eq!(logger.count(), 0);
608 }
609
610 #[test]
611 fn test_audit_logger_log_event() {
612 let logger = AuditLogger::default();
613 let event = AuditEvent::new(AuditEventType::ProcessingStarted, "Start");
614
615 logger.log(event);
616 assert_eq!(logger.count(), 1);
617 }
618
619 #[test]
620 fn test_audit_logger_get_events() {
621 let logger = AuditLogger::default();
622 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "1"));
623 logger.log(AuditEvent::new(AuditEventType::ProcessingCompleted, "2"));
624
625 let events = logger.get_events();
626 assert_eq!(events.len(), 2);
627 }
628
629 #[test]
630 fn test_audit_logger_get_events_by_type() {
631 let logger = AuditLogger::default();
632 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "1"));
633 logger.log(AuditEvent::new(AuditEventType::ProcessingCompleted, "2"));
634 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "3"));
635
636 let events = logger.get_events_by_type(AuditEventType::ProcessingStarted);
637 assert_eq!(events.len(), 2);
638 }
639
640 #[test]
641 fn test_audit_logger_get_events_by_user() {
642 let logger = AuditLogger::default();
643 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "1").with_user_id("user1"));
644 logger.log(AuditEvent::new(AuditEventType::ProcessingCompleted, "2").with_user_id("user2"));
645 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "3").with_user_id("user1"));
646
647 let events = logger.get_events_by_user("user1");
648 assert_eq!(events.len(), 2);
649 }
650
651 #[test]
652 fn test_audit_logger_get_events_by_severity() {
653 let logger = AuditLogger::default();
654 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "1")); logger.log(AuditEvent::new(AuditEventType::AccessDenied, "2")); let events = logger.get_events_by_severity(AuditSeverity::Critical);
658 assert_eq!(events.len(), 1);
659 }
660
661 #[test]
662 fn test_audit_logger_export_json() {
663 let logger = AuditLogger::default();
664 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "Test"));
665
666 let json = logger.export_json();
667 assert!(json.contains("ProcessingStarted"));
668 }
669
670 #[test]
671 fn test_audit_logger_export_csv() {
672 let logger = AuditLogger::default();
673 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "Test"));
674
675 let csv = logger.export_csv();
676 assert!(csv.contains("id,timestamp"));
677 assert!(csv.contains("PROCESSING_STARTED"));
678 }
679
680 #[test]
681 fn test_audit_logger_clear() {
682 let logger = AuditLogger::default();
683 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "Test"));
684 assert_eq!(logger.count(), 1);
685
686 logger.clear();
687 assert_eq!(logger.count(), 0);
688 }
689
690 #[test]
691 fn test_audit_logger_stats() {
692 let logger = AuditLogger::default();
693 logger.log(AuditEvent::new(AuditEventType::ProcessingStarted, "1"));
694 logger.log(AuditEvent::new(AuditEventType::ApiKeyCreated, "2"));
695 logger.log(AuditEvent::new(AuditEventType::AccessDenied, "3"));
696
697 let stats = logger.stats();
698 assert_eq!(stats.total_events, 3);
699 assert_eq!(stats.processing_events, 1);
700 assert_eq!(stats.key_management_events, 1);
701 assert_eq!(stats.security_events, 1);
702 }
703
704 #[test]
705 fn test_generate_event_id_format() {
706 let id = generate_event_id();
707 assert!(id.starts_with("audit_"));
708 }
709}