codex_memory/security/
audit.rs

1use crate::security::{AuditConfig, Result, SecurityError};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use sqlx::{PgPool, Row};
6use std::collections::HashMap;
7use std::sync::Arc;
8use tracing::{debug, error, info, warn};
9use uuid::Uuid;
10
11/// Audit event types
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub enum AuditEventType {
14    Authentication,
15    Authorization,
16    DataAccess,
17    DataModification,
18    DataDeletion,
19    SystemAccess,
20    ConfigurationChange,
21    SecurityEvent,
22    RateLimitViolation,
23    Error,
24}
25
26/// Audit event severity levels
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum AuditSeverity {
29    Low,
30    Medium,
31    High,
32    Critical,
33}
34
35/// Individual audit event
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct AuditEvent {
38    pub id: String,
39    pub timestamp: DateTime<Utc>,
40    pub event_type: AuditEventType,
41    pub severity: AuditSeverity,
42    pub user_id: Option<String>,
43    pub session_id: Option<String>,
44    pub ip_address: Option<String>,
45    pub user_agent: Option<String>,
46    pub resource: Option<String>,
47    pub action: String,
48    pub outcome: AuditOutcome,
49    pub details: HashMap<String, Value>,
50    pub error_message: Option<String>,
51    pub request_id: Option<String>,
52}
53
54/// Audit outcome
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum AuditOutcome {
57    Success,
58    Failure,
59    Partial,
60}
61
62/// Audit statistics
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct AuditStatistics {
65    pub total_events: u64,
66    pub events_by_type: HashMap<String, u64>,
67    pub events_by_user: HashMap<String, u64>,
68    pub failed_events: u64,
69    pub critical_events: u64,
70    pub retention_days: u32,
71    pub oldest_event: Option<DateTime<Utc>>,
72    pub newest_event: Option<DateTime<Utc>>,
73}
74
75/// Audit logging manager
76#[derive(Debug)]
77pub struct AuditManager {
78    config: AuditConfig,
79    db_pool: Arc<PgPool>,
80}
81
82impl AuditManager {
83    pub fn new(config: AuditConfig, db_pool: Arc<PgPool>) -> Self {
84        Self { config, db_pool }
85    }
86
87    /// Initialize audit logging system
88    pub async fn initialize(&self) -> Result<()> {
89        if !self.config.enabled {
90            debug!("Audit logging is disabled");
91            return Ok(());
92        }
93
94        info!("Initializing audit logging system");
95
96        // Create audit events table if it doesn't exist
97        let create_table_sql = r#"
98            CREATE TABLE IF NOT EXISTS audit_events (
99                id UUID PRIMARY KEY,
100                timestamp TIMESTAMPTZ NOT NULL,
101                event_type TEXT NOT NULL,
102                severity TEXT NOT NULL,
103                user_id TEXT,
104                session_id TEXT,
105                ip_address INET,
106                user_agent TEXT,
107                resource TEXT,
108                action TEXT NOT NULL,
109                outcome TEXT NOT NULL,
110                details JSONB,
111                error_message TEXT,
112                request_id TEXT,
113                created_at TIMESTAMPTZ DEFAULT NOW()
114            );
115        "#;
116
117        sqlx::query(create_table_sql)
118            .execute(self.db_pool.as_ref())
119            .await
120            .map_err(|e| SecurityError::AuditError {
121                message: format!("Failed to create audit events table: {e}"),
122            })?;
123
124        // Create indexes for better performance
125        let create_indexes_sql = vec![
126            "CREATE INDEX IF NOT EXISTS idx_audit_events_timestamp ON audit_events (timestamp);",
127            "CREATE INDEX IF NOT EXISTS idx_audit_events_user_id ON audit_events (user_id);",
128            "CREATE INDEX IF NOT EXISTS idx_audit_events_event_type ON audit_events (event_type);",
129            "CREATE INDEX IF NOT EXISTS idx_audit_events_severity ON audit_events (severity);",
130            "CREATE INDEX IF NOT EXISTS idx_audit_events_outcome ON audit_events (outcome);",
131        ];
132
133        for sql in create_indexes_sql {
134            sqlx::query(sql)
135                .execute(self.db_pool.as_ref())
136                .await
137                .map_err(|e| SecurityError::AuditError {
138                    message: format!("Failed to create audit index: {e}"),
139                })?;
140        }
141
142        info!("Audit logging system initialized successfully");
143        Ok(())
144    }
145
146    /// Log an audit event
147    pub async fn log_event(&self, event: AuditEvent) -> Result<()> {
148        if !self.config.enabled {
149            return Ok(());
150        }
151
152        // Check if this type of event should be logged
153        if !self.should_log_event(&event.event_type) {
154            return Ok(());
155        }
156
157        debug!(
158            "Logging audit event: {:?} - {}",
159            event.event_type, event.action
160        );
161
162        let insert_sql = r#"
163            INSERT INTO audit_events (
164                id, timestamp, event_type, severity, user_id, session_id,
165                ip_address, user_agent, resource, action, outcome, details,
166                error_message, request_id
167            ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
168        "#;
169
170        sqlx::query(insert_sql)
171            .bind(&event.id)
172            .bind(event.timestamp)
173            .bind(format!("{:?}", event.event_type))
174            .bind(format!("{:?}", event.severity))
175            .bind(&event.user_id)
176            .bind(&event.session_id)
177            .bind(event.ip_address.as_ref())
178            .bind(&event.user_agent)
179            .bind(&event.resource)
180            .bind(&event.action)
181            .bind(format!("{:?}", event.outcome))
182            .bind(serde_json::to_value(&event.details).unwrap_or(Value::Null))
183            .bind(&event.error_message)
184            .bind(&event.request_id)
185            .execute(self.db_pool.as_ref())
186            .await
187            .map_err(|e| SecurityError::AuditError {
188                message: format!("Failed to log audit event: {e}"),
189            })?;
190
191        // Log critical events to application log as well
192        if matches!(event.severity, AuditSeverity::Critical) {
193            error!(
194                "CRITICAL AUDIT EVENT - Type: {:?}, Action: {}, User: {:?}, Details: {:?}",
195                event.event_type, event.action, event.user_id, event.details
196            );
197        }
198
199        Ok(())
200    }
201
202    /// Log authentication event
203    pub async fn log_authentication(
204        &self,
205        user_id: &str,
206        action: &str,
207        outcome: AuditOutcome,
208        ip_address: Option<String>,
209        details: HashMap<String, Value>,
210    ) -> Result<()> {
211        let event = AuditEvent {
212            id: Uuid::new_v4().to_string(),
213            timestamp: Utc::now(),
214            event_type: AuditEventType::Authentication,
215            severity: if matches!(outcome, AuditOutcome::Failure) {
216                AuditSeverity::High
217            } else {
218                AuditSeverity::Medium
219            },
220            user_id: Some(user_id.to_string()),
221            session_id: None,
222            ip_address,
223            user_agent: None,
224            resource: Some("authentication".to_string()),
225            action: action.to_string(),
226            outcome,
227            details,
228            error_message: None,
229            request_id: None,
230        };
231
232        self.log_event(event).await
233    }
234
235    /// Log data access event
236    pub async fn log_data_access(
237        &self,
238        user_id: Option<&str>,
239        resource: &str,
240        action: &str,
241        outcome: AuditOutcome,
242        details: HashMap<String, Value>,
243    ) -> Result<()> {
244        let event = AuditEvent {
245            id: Uuid::new_v4().to_string(),
246            timestamp: Utc::now(),
247            event_type: AuditEventType::DataAccess,
248            severity: AuditSeverity::Low,
249            user_id: user_id.map(|s| s.to_string()),
250            session_id: None,
251            ip_address: None,
252            user_agent: None,
253            resource: Some(resource.to_string()),
254            action: action.to_string(),
255            outcome,
256            details,
257            error_message: None,
258            request_id: None,
259        };
260
261        self.log_event(event).await
262    }
263
264    /// Log data modification event
265    pub async fn log_data_modification(
266        &self,
267        user_id: Option<&str>,
268        resource: &str,
269        action: &str,
270        outcome: AuditOutcome,
271        details: HashMap<String, Value>,
272    ) -> Result<()> {
273        let event = AuditEvent {
274            id: Uuid::new_v4().to_string(),
275            timestamp: Utc::now(),
276            event_type: AuditEventType::DataModification,
277            severity: AuditSeverity::Medium,
278            user_id: user_id.map(|s| s.to_string()),
279            session_id: None,
280            ip_address: None,
281            user_agent: None,
282            resource: Some(resource.to_string()),
283            action: action.to_string(),
284            outcome,
285            details,
286            error_message: None,
287            request_id: None,
288        };
289
290        self.log_event(event).await
291    }
292
293    /// Log security event
294    pub async fn log_security_event(
295        &self,
296        action: &str,
297        severity: AuditSeverity,
298        user_id: Option<&str>,
299        ip_address: Option<String>,
300        details: HashMap<String, Value>,
301    ) -> Result<()> {
302        let event = AuditEvent {
303            id: Uuid::new_v4().to_string(),
304            timestamp: Utc::now(),
305            event_type: AuditEventType::SecurityEvent,
306            severity,
307            user_id: user_id.map(|s| s.to_string()),
308            session_id: None,
309            ip_address,
310            user_agent: None,
311            resource: Some("security".to_string()),
312            action: action.to_string(),
313            outcome: AuditOutcome::Success,
314            details,
315            error_message: None,
316            request_id: None,
317        };
318
319        self.log_event(event).await
320    }
321
322    /// Log authentication event (specific to MCP auth)
323    pub async fn log_auth_event(
324        &self,
325        client_id: &str,
326        user_id: &str,
327        method_name: &str,
328        success: bool,
329        error_message: Option<&str>,
330    ) -> Result<()> {
331        let mut details = HashMap::new();
332        details.insert(
333            "client_id".to_string(),
334            serde_json::Value::String(client_id.to_string()),
335        );
336        details.insert(
337            "method".to_string(),
338            serde_json::Value::String(method_name.to_string()),
339        );
340
341        let event = AuditEvent {
342            id: Uuid::new_v4().to_string(),
343            timestamp: Utc::now(),
344            event_type: AuditEventType::Authentication,
345            severity: if success {
346                AuditSeverity::Low
347            } else {
348                AuditSeverity::High
349            },
350            user_id: Some(user_id.to_string()),
351            session_id: None,
352            ip_address: None,
353            user_agent: None,
354            resource: Some("mcp_auth".to_string()),
355            action: if success {
356                "auth_success"
357            } else {
358                "auth_failure"
359            }
360            .to_string(),
361            outcome: if success {
362                AuditOutcome::Success
363            } else {
364                AuditOutcome::Failure
365            },
366            details,
367            error_message: error_message.map(|s| s.to_string()),
368            request_id: None,
369        };
370
371        self.log_event(event).await
372    }
373
374    /// Log rate limit violation
375    pub async fn log_rate_limit_violation(
376        &self,
377        client_id: &str,
378        tool_name: &str,
379        limit_type: &str,
380    ) -> Result<()> {
381        let mut details = HashMap::new();
382        details.insert(
383            "client_id".to_string(),
384            serde_json::Value::String(client_id.to_string()),
385        );
386        details.insert(
387            "tool_name".to_string(),
388            serde_json::Value::String(tool_name.to_string()),
389        );
390        details.insert(
391            "limit_type".to_string(),
392            serde_json::Value::String(limit_type.to_string()),
393        );
394
395        let event = AuditEvent {
396            id: Uuid::new_v4().to_string(),
397            timestamp: Utc::now(),
398            event_type: AuditEventType::RateLimitViolation,
399            severity: AuditSeverity::Medium,
400            user_id: Some(client_id.to_string()),
401            session_id: None,
402            ip_address: None,
403            user_agent: None,
404            resource: Some("mcp_rate_limiter".to_string()),
405            action: "rate_limit_exceeded".to_string(),
406            outcome: AuditOutcome::Failure,
407            details,
408            error_message: Some(format!(
409                "Rate limit exceeded for {limit_type} on tool {tool_name}"
410            )),
411            request_id: None,
412        };
413
414        self.log_event(event).await
415    }
416
417    /// Get audit events with filtering
418    pub async fn get_events(&self, filter: AuditFilter) -> Result<Vec<AuditEvent>> {
419        if !self.config.enabled {
420            return Ok(Vec::new());
421        }
422
423        let mut where_clauses = Vec::new();
424        let mut bind_count = 0;
425
426        if let Some(_user_id) = &filter.user_id {
427            bind_count += 1;
428            where_clauses.push(format!("user_id = ${bind_count}"));
429        }
430
431        if let Some(_event_type) = &filter.event_type {
432            bind_count += 1;
433            where_clauses.push(format!("event_type = ${bind_count}"));
434        }
435
436        if let Some(_start_time) = &filter.start_time {
437            bind_count += 1;
438            where_clauses.push(format!("timestamp >= ${bind_count}"));
439        }
440
441        if let Some(_end_time) = &filter.end_time {
442            bind_count += 1;
443            where_clauses.push(format!("timestamp <= ${bind_count}"));
444        }
445
446        let where_clause = if where_clauses.is_empty() {
447            String::new()
448        } else {
449            format!("WHERE {}", where_clauses.join(" AND "))
450        };
451
452        let limit = filter.limit.unwrap_or(100).min(1000); // Cap at 1000 events
453        let offset = filter.offset.unwrap_or(0);
454
455        let query = format!(
456            "SELECT * FROM audit_events {where_clause} ORDER BY timestamp DESC LIMIT {limit} OFFSET {offset}"
457        );
458
459        let mut sql_query = sqlx::query(&query);
460
461        // Bind parameters in the same order as where clauses
462        if let Some(user_id) = &filter.user_id {
463            sql_query = sql_query.bind(user_id);
464        }
465        if let Some(event_type) = &filter.event_type {
466            sql_query = sql_query.bind(format!("{event_type:?}"));
467        }
468        if let Some(start_time) = &filter.start_time {
469            sql_query = sql_query.bind(start_time);
470        }
471        if let Some(end_time) = &filter.end_time {
472            sql_query = sql_query.bind(end_time);
473        }
474
475        let rows = sql_query
476            .fetch_all(self.db_pool.as_ref())
477            .await
478            .map_err(|e| SecurityError::AuditError {
479                message: format!("Failed to fetch audit events: {e}"),
480            })?;
481
482        let mut events = Vec::new();
483        for row in rows {
484            events.push(self.row_to_audit_event(row)?);
485        }
486
487        Ok(events)
488    }
489
490    /// Get audit statistics
491    pub async fn get_statistics(&self) -> Result<AuditStatistics> {
492        if !self.config.enabled {
493            return Ok(AuditStatistics {
494                total_events: 0,
495                events_by_type: HashMap::new(),
496                events_by_user: HashMap::new(),
497                failed_events: 0,
498                critical_events: 0,
499                retention_days: self.config.retention_days,
500                oldest_event: None,
501                newest_event: None,
502            });
503        }
504
505        // Get basic statistics
506        let total_events: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM audit_events")
507            .fetch_one(self.db_pool.as_ref())
508            .await
509            .map_err(|e| SecurityError::AuditError {
510                message: format!("Failed to get total events count: {e}"),
511            })?;
512
513        let failed_events: i64 =
514            sqlx::query_scalar("SELECT COUNT(*) FROM audit_events WHERE outcome = 'Failure'")
515                .fetch_one(self.db_pool.as_ref())
516                .await
517                .map_err(|e| SecurityError::AuditError {
518                    message: format!("Failed to get failed events count: {e}"),
519                })?;
520
521        let critical_events: i64 =
522            sqlx::query_scalar("SELECT COUNT(*) FROM audit_events WHERE severity = 'Critical'")
523                .fetch_one(self.db_pool.as_ref())
524                .await
525                .map_err(|e| SecurityError::AuditError {
526                    message: format!("Failed to get critical events count: {e}"),
527                })?;
528
529        // Get oldest and newest events
530        let oldest_event: Option<DateTime<Utc>> =
531            sqlx::query_scalar("SELECT MIN(timestamp) FROM audit_events")
532                .fetch_one(self.db_pool.as_ref())
533                .await
534                .map_err(|e| SecurityError::AuditError {
535                    message: format!("Failed to get oldest event: {e}"),
536                })?;
537
538        let newest_event: Option<DateTime<Utc>> =
539            sqlx::query_scalar("SELECT MAX(timestamp) FROM audit_events")
540                .fetch_one(self.db_pool.as_ref())
541                .await
542                .map_err(|e| SecurityError::AuditError {
543                    message: format!("Failed to get newest event: {e}"),
544                })?;
545
546        // Get events by type
547        let type_rows = sqlx::query(
548            "SELECT event_type, COUNT(*) as count FROM audit_events GROUP BY event_type",
549        )
550        .fetch_all(self.db_pool.as_ref())
551        .await
552        .map_err(|e| SecurityError::AuditError {
553            message: format!("Failed to get events by type: {e}"),
554        })?;
555
556        let mut events_by_type = HashMap::new();
557        for row in type_rows {
558            let event_type: String = row.get("event_type");
559            let count: i64 = row.get("count");
560            events_by_type.insert(event_type, count as u64);
561        }
562
563        // Get events by user (top 20)
564        let user_rows = sqlx::query("SELECT user_id, COUNT(*) as count FROM audit_events WHERE user_id IS NOT NULL GROUP BY user_id ORDER BY count DESC LIMIT 20")
565            .fetch_all(self.db_pool.as_ref())
566            .await
567            .map_err(|e| SecurityError::AuditError {
568                message: format!("Failed to get events by user: {e}"),
569            })?;
570
571        let mut events_by_user = HashMap::new();
572        for row in user_rows {
573            let user_id: String = row.get("user_id");
574            let count: i64 = row.get("count");
575            events_by_user.insert(user_id, count as u64);
576        }
577
578        Ok(AuditStatistics {
579            total_events: total_events as u64,
580            events_by_type,
581            events_by_user,
582            failed_events: failed_events as u64,
583            critical_events: critical_events as u64,
584            retention_days: self.config.retention_days,
585            oldest_event,
586            newest_event,
587        })
588    }
589
590    /// Clean up old audit events based on retention policy
591    pub async fn cleanup_old_events(&self) -> Result<u64> {
592        if !self.config.enabled {
593            return Ok(0);
594        }
595
596        let cutoff_date = Utc::now() - chrono::Duration::days(self.config.retention_days as i64);
597
598        let deleted_count: i64 =
599            sqlx::query_scalar("DELETE FROM audit_events WHERE timestamp < $1 RETURNING COUNT(*)")
600                .bind(cutoff_date)
601                .fetch_optional(self.db_pool.as_ref())
602                .await
603                .map_err(|e| SecurityError::AuditError {
604                    message: format!("Failed to cleanup old audit events: {e}"),
605                })?
606                .unwrap_or(0);
607
608        if deleted_count > 0 {
609            info!("Cleaned up {} old audit events", deleted_count);
610        }
611
612        Ok(deleted_count as u64)
613    }
614
615    fn should_log_event(&self, event_type: &AuditEventType) -> bool {
616        match event_type {
617            AuditEventType::Authentication => self.config.log_auth_events,
618            AuditEventType::DataAccess => self.config.log_data_access,
619            AuditEventType::DataModification => self.config.log_modifications,
620            AuditEventType::DataDeletion => self.config.log_modifications,
621            _ => true, // Log all other events by default
622        }
623    }
624
625    fn row_to_audit_event(&self, row: sqlx::postgres::PgRow) -> Result<AuditEvent> {
626        let event_type_str: String = row.get("event_type");
627        let event_type = match event_type_str.as_str() {
628            "Authentication" => AuditEventType::Authentication,
629            "Authorization" => AuditEventType::Authorization,
630            "DataAccess" => AuditEventType::DataAccess,
631            "DataModification" => AuditEventType::DataModification,
632            "DataDeletion" => AuditEventType::DataDeletion,
633            "SystemAccess" => AuditEventType::SystemAccess,
634            "ConfigurationChange" => AuditEventType::ConfigurationChange,
635            "SecurityEvent" => AuditEventType::SecurityEvent,
636            "RateLimitViolation" => AuditEventType::RateLimitViolation,
637            "Error" => AuditEventType::Error,
638            _ => AuditEventType::SystemAccess,
639        };
640
641        let severity_str: String = row.get("severity");
642        let severity = match severity_str.as_str() {
643            "Low" => AuditSeverity::Low,
644            "Medium" => AuditSeverity::Medium,
645            "High" => AuditSeverity::High,
646            "Critical" => AuditSeverity::Critical,
647            _ => AuditSeverity::Low,
648        };
649
650        let outcome_str: String = row.get("outcome");
651        let outcome = match outcome_str.as_str() {
652            "Success" => AuditOutcome::Success,
653            "Failure" => AuditOutcome::Failure,
654            "Partial" => AuditOutcome::Partial,
655            _ => AuditOutcome::Success,
656        };
657
658        let details_value: Value = row.get("details");
659        let details: HashMap<String, Value> =
660            serde_json::from_value(details_value).unwrap_or_else(|_| HashMap::new());
661
662        Ok(AuditEvent {
663            id: row.get("id"),
664            timestamp: row.get("timestamp"),
665            event_type,
666            severity,
667            user_id: row.get("user_id"),
668            session_id: row.get("session_id"),
669            ip_address: row.get::<Option<String>, _>("ip_address"),
670            user_agent: row.get("user_agent"),
671            resource: row.get("resource"),
672            action: row.get("action"),
673            outcome,
674            details,
675            error_message: row.get("error_message"),
676            request_id: row.get("request_id"),
677        })
678    }
679
680    pub fn is_enabled(&self) -> bool {
681        self.config.enabled
682    }
683}
684
685/// Filter for querying audit events
686#[derive(Debug, Clone, Default)]
687pub struct AuditFilter {
688    pub user_id: Option<String>,
689    pub event_type: Option<AuditEventType>,
690    pub start_time: Option<DateTime<Utc>>,
691    pub end_time: Option<DateTime<Utc>>,
692    pub limit: Option<i64>,
693    pub offset: Option<i64>,
694}
695
696/// Simple audit logger for use by authentication and rate limiting modules
697/// This provides a lightweight interface without requiring full database setup
698#[derive(Debug)]
699pub struct AuditLogger {
700    config: AuditConfig,
701    manager: Option<Arc<AuditManager>>,
702}
703
704impl AuditLogger {
705    /// Create a new audit logger with file-based logging
706    pub fn new(config: AuditConfig) -> Result<Self> {
707        Ok(Self {
708            config,
709            manager: None,
710        })
711    }
712
713    /// Create audit logger with database manager
714    pub fn with_manager(config: AuditConfig, manager: Arc<AuditManager>) -> Self {
715        Self {
716            config,
717            manager: Some(manager),
718        }
719    }
720
721    /// Log authentication event
722    pub async fn log_auth_event(
723        &self,
724        client_id: &str,
725        user_id: &str,
726        method_name: &str,
727        success: bool,
728        error_message: Option<&str>,
729    ) {
730        if let Some(ref manager) = self.manager {
731            let _ = manager
732                .log_auth_event(client_id, user_id, method_name, success, error_message)
733                .await;
734        } else {
735            // Fallback to tracing logs
736            if success {
737                debug!(
738                    "AUTH_SUCCESS: client_id={}, user_id={}, method={}",
739                    client_id, user_id, method_name
740                );
741            } else {
742                error!(
743                    "AUTH_FAILURE: client_id={}, user_id={}, method={}, error={:?}",
744                    client_id, user_id, method_name, error_message
745                );
746            }
747        }
748    }
749
750    /// Log rate limit violation
751    pub async fn log_rate_limit_violation(
752        &self,
753        client_id: &str,
754        tool_name: &str,
755        limit_type: &str,
756    ) {
757        if let Some(ref manager) = self.manager {
758            let _ = manager
759                .log_rate_limit_violation(client_id, tool_name, limit_type)
760                .await;
761        } else {
762            // Fallback to tracing logs
763            warn!(
764                "RATE_LIMIT_VIOLATION: client_id={}, tool={}, limit_type={}",
765                client_id, tool_name, limit_type
766            );
767        }
768    }
769
770    /// Log general security event
771    pub async fn log_security_event(
772        &self,
773        action: &str,
774        severity: AuditSeverity,
775        user_id: Option<&str>,
776        details: HashMap<String, Value>,
777    ) {
778        if let Some(ref manager) = self.manager {
779            let _ = manager
780                .log_security_event(action, severity, user_id, None, details)
781                .await;
782        } else {
783            // Fallback to tracing logs
784            match severity {
785                AuditSeverity::Critical => error!(
786                    "SECURITY_CRITICAL: action={}, user_id={:?}, details={:?}",
787                    action, user_id, details
788                ),
789                AuditSeverity::High => error!(
790                    "SECURITY_HIGH: action={}, user_id={:?}, details={:?}",
791                    action, user_id, details
792                ),
793                AuditSeverity::Medium => warn!(
794                    "SECURITY_MEDIUM: action={}, user_id={:?}, details={:?}",
795                    action, user_id, details
796                ),
797                AuditSeverity::Low => debug!(
798                    "SECURITY_LOW: action={}, user_id={:?}, details={:?}",
799                    action, user_id, details
800                ),
801            }
802        }
803    }
804}
805
806#[cfg(test)]
807mod tests {
808    use super::*;
809    use serde_json::json;
810
811    #[test]
812    fn test_audit_event_creation() {
813        let mut details = HashMap::new();
814        details.insert("test_key".to_string(), json!("test_value"));
815
816        let event = AuditEvent {
817            id: Uuid::new_v4().to_string(),
818            timestamp: Utc::now(),
819            event_type: AuditEventType::Authentication,
820            severity: AuditSeverity::Medium,
821            user_id: Some("test-user".to_string()),
822            session_id: None,
823            ip_address: Some("192.168.1.1".to_string()),
824            user_agent: None,
825            resource: Some("login".to_string()),
826            action: "user_login".to_string(),
827            outcome: AuditOutcome::Success,
828            details,
829            error_message: None,
830            request_id: None,
831        };
832
833        assert!(!event.id.is_empty());
834        assert_eq!(event.action, "user_login");
835        assert!(matches!(event.event_type, AuditEventType::Authentication));
836        assert!(matches!(event.severity, AuditSeverity::Medium));
837        assert!(matches!(event.outcome, AuditOutcome::Success));
838        assert_eq!(event.user_id.unwrap(), "test-user");
839    }
840
841    #[test]
842    fn test_audit_filter_default() {
843        let filter = AuditFilter::default();
844        assert!(filter.user_id.is_none());
845        assert!(filter.event_type.is_none());
846        assert!(filter.start_time.is_none());
847        assert!(filter.end_time.is_none());
848        assert!(filter.limit.is_none());
849        assert!(filter.offset.is_none());
850    }
851
852    #[test]
853    fn test_audit_statistics_default() {
854        let stats = AuditStatistics {
855            total_events: 0,
856            events_by_type: HashMap::new(),
857            events_by_user: HashMap::new(),
858            failed_events: 0,
859            critical_events: 0,
860            retention_days: 90,
861            oldest_event: None,
862            newest_event: None,
863        };
864
865        assert_eq!(stats.total_events, 0);
866        assert_eq!(stats.failed_events, 0);
867        assert_eq!(stats.critical_events, 0);
868        assert_eq!(stats.retention_days, 90);
869        assert!(stats.events_by_type.is_empty());
870        assert!(stats.events_by_user.is_empty());
871    }
872
873    #[test]
874    fn test_event_type_serialization() {
875        let event_type = AuditEventType::Authentication;
876        let serialized = serde_json::to_string(&event_type).unwrap();
877        assert_eq!(serialized, "\"Authentication\"");
878
879        let deserialized: AuditEventType = serde_json::from_str(&serialized).unwrap();
880        assert!(matches!(deserialized, AuditEventType::Authentication));
881    }
882
883    #[test]
884    fn test_severity_ordering() {
885        // Test that we can compare severity levels
886        let low = AuditSeverity::Low;
887        let critical = AuditSeverity::Critical;
888
889        // This is just to ensure the enum variants exist and can be pattern matched
890        match low {
891            AuditSeverity::Low => assert!(true),
892            _ => assert!(false),
893        }
894
895        match critical {
896            AuditSeverity::Critical => assert!(true),
897            _ => assert!(false),
898        }
899    }
900
901    #[test]
902    fn test_outcome_variants() {
903        let outcomes = vec![
904            AuditOutcome::Success,
905            AuditOutcome::Failure,
906            AuditOutcome::Partial,
907        ];
908
909        assert_eq!(outcomes.len(), 3);
910
911        for outcome in outcomes {
912            match outcome {
913                AuditOutcome::Success | AuditOutcome::Failure | AuditOutcome::Partial => {
914                    // All variants are valid
915                    assert!(true);
916                }
917            }
918        }
919    }
920}