oxify_connect_vision/
audit.rs

1//! Audit logging module for compliance and security tracking.
2//!
3//! This module provides:
4//! - Comprehensive audit trail of all OCR operations
5//! - User activity tracking
6//! - Compliance reporting
7//! - Data retention policies
8//! - Export capabilities for audit logs
9
10use serde::{Deserialize, Serialize};
11use std::collections::VecDeque;
12use std::sync::{Arc, RwLock};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15/// Audit event type
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum AuditEventType {
18    /// OCR processing started
19    ProcessingStarted,
20    /// OCR processing completed
21    ProcessingCompleted,
22    /// OCR processing failed
23    ProcessingFailed,
24    /// API key created
25    ApiKeyCreated,
26    /// API key revoked
27    ApiKeyRevoked,
28    /// API key deleted
29    ApiKeyDeleted,
30    /// Access denied
31    AccessDenied,
32    /// Rate limit exceeded
33    RateLimitExceeded,
34    /// Quota exceeded
35    QuotaExceeded,
36    /// Configuration changed
37    ConfigurationChanged,
38    /// Data exported
39    DataExported,
40    /// Audit log accessed
41    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/// Audit event severity
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
65pub enum AuditSeverity {
66    /// Informational events
67    Info,
68    /// Warning events
69    Warning,
70    /// Error events
71    Error,
72    /// Critical security events
73    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/// Audit event entry
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct AuditEvent {
90    /// Unique event ID
91    pub id: String,
92
93    /// Event type
94    pub event_type: AuditEventType,
95
96    /// Event severity
97    pub severity: AuditSeverity,
98
99    /// Timestamp (Unix timestamp in milliseconds)
100    pub timestamp: u64,
101
102    /// User ID (optional)
103    pub user_id: Option<String>,
104
105    /// API key used (optional)
106    pub api_key: Option<String>,
107
108    /// IP address (optional)
109    pub ip_address: Option<String>,
110
111    /// Request ID for correlation (optional)
112    pub request_id: Option<String>,
113
114    /// Resource affected (e.g., image path, key name)
115    pub resource: Option<String>,
116
117    /// Action performed
118    pub action: String,
119
120    /// Result of the action (success/failure)
121    pub result: AuditResult,
122
123    /// Additional details
124    pub details: std::collections::HashMap<String, String>,
125
126    /// Error message if applicable
127    pub error: Option<String>,
128}
129
130/// Result of an audited action
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
132pub enum AuditResult {
133    /// Action succeeded
134    Success,
135    /// Action failed
136    Failure,
137    /// Action was denied
138    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    /// Create a new audit event
153    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    /// Set user ID
191    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    /// Set API key
197    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    /// Set IP address
203    pub fn with_ip_address(mut self, ip: impl Into<String>) -> Self {
204        self.ip_address = Some(ip.into());
205        self
206    }
207
208    /// Set request ID
209    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    /// Set resource
215    pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
216        self.resource = Some(resource.into());
217        self
218    }
219
220    /// Set result
221    pub fn with_result(mut self, result: AuditResult) -> Self {
222        self.result = result;
223        self
224    }
225
226    /// Add detail
227    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    /// Set error
233    pub fn with_error(mut self, error: impl Into<String>) -> Self {
234        self.error = Some(error.into());
235        self
236    }
237
238    /// Convert to JSON string
239    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    /// Convert to CSV row
245    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
263/// Generate a unique event ID
264fn 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
276/// Get current timestamp in milliseconds
277fn current_timestamp_ms() -> u64 {
278    SystemTime::now()
279        .duration_since(UNIX_EPOCH)
280        .unwrap()
281        .as_millis() as u64
282}
283
284/// Data retention policy
285#[derive(Debug, Clone, Copy)]
286pub struct RetentionPolicy {
287    /// Maximum number of events to retain
288    pub max_events: usize,
289
290    /// Maximum age of events in seconds (None = infinite)
291    pub max_age_seconds: Option<u64>,
292
293    /// Whether to auto-export before deletion
294    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), // 90 days
302            auto_export: false,
303        }
304    }
305}
306
307impl RetentionPolicy {
308    /// Create a policy for compliance (7 years)
309    pub fn compliance() -> Self {
310        Self {
311            max_events: 10_000_000,
312            max_age_seconds: Some(7 * 365 * 24 * 3600), // 7 years
313            auto_export: true,
314        }
315    }
316
317    /// Create a policy for short-term retention (30 days)
318    pub fn short_term() -> Self {
319        Self {
320            max_events: 10_000,
321            max_age_seconds: Some(30 * 24 * 3600), // 30 days
322            auto_export: false,
323        }
324    }
325
326    /// Create an unlimited policy
327    pub fn unlimited() -> Self {
328        Self {
329            max_events: usize::MAX,
330            max_age_seconds: None,
331            auto_export: false,
332        }
333    }
334}
335
336/// Audit logger
337pub struct AuditLogger {
338    events: Arc<RwLock<VecDeque<AuditEvent>>>,
339    policy: RetentionPolicy,
340}
341
342impl AuditLogger {
343    /// Create a new audit logger
344    pub fn new(policy: RetentionPolicy) -> Self {
345        Self {
346            events: Arc::new(RwLock::new(VecDeque::new())),
347            policy,
348        }
349    }
350
351    /// Log an audit event
352    pub fn log(&self, event: AuditEvent) {
353        let mut events = self.events.write().unwrap();
354
355        // Apply retention policy
356        self.apply_retention_policy(&mut events);
357
358        // Add the new event
359        events.push_back(event);
360    }
361
362    /// Apply retention policy
363    fn apply_retention_policy(&self, events: &mut VecDeque<AuditEvent>) {
364        let now = current_timestamp_ms();
365
366        // Remove events exceeding max count
367        while events.len() >= self.policy.max_events {
368            events.pop_front();
369        }
370
371        // Remove events exceeding max age
372        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    /// Get all events
384    pub fn get_events(&self) -> Vec<AuditEvent> {
385        self.events.read().unwrap().iter().cloned().collect()
386    }
387
388    /// Get events by type
389    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    /// Get events by user
400    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    /// Get events by severity
411    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    /// Get events in time range
422    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    /// Export events to JSON
433    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    /// Export events to CSV
439    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    /// Get event count
451    pub fn count(&self) -> usize {
452        self.events.read().unwrap().len()
453    }
454
455    /// Clear all events
456    pub fn clear(&self) {
457        self.events.write().unwrap().clear();
458    }
459
460    /// Get statistics
461    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/// Audit statistics
500#[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")); // Info
655        logger.log(AuditEvent::new(AuditEventType::AccessDenied, "2")); // Critical
656
657        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}