Skip to main content

actix_security_core/http/security/
audit.rs

1//! Security Audit Logging system.
2//!
3//! Provides comprehensive logging of security-related events for compliance,
4//! debugging, and threat detection.
5//!
6//! # Spring Security Equivalent
7//! Similar to Spring Security's `AuthenticationEventPublisher` and
8//! `ApplicationEventPublisher` for security events.
9//!
10//! # Example
11//!
12//! ```ignore
13//! use actix_security::http::security::audit::{AuditLogger, SecurityEvent};
14//!
15//! // Create an audit logger
16//! let audit_logger = AuditLogger::new()
17//!     .with_handler(|event| {
18//!         println!("[AUDIT] {:?}", event);
19//!     });
20//!
21//! // Log events
22//! audit_logger.log(SecurityEvent::login_success("admin", "192.168.1.1"));
23//! ```
24
25use std::collections::HashMap;
26use std::fmt;
27use std::sync::Arc;
28use std::time::{SystemTime, UNIX_EPOCH};
29
30use tokio::sync::RwLock;
31
32/// Security event types for audit logging.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum SecurityEventType {
35    // Authentication events
36    /// Successful login
37    AuthenticationSuccess,
38    /// Failed login attempt
39    AuthenticationFailure,
40    /// User logout
41    Logout,
42    /// Session created
43    SessionCreated,
44    /// Session destroyed
45    SessionDestroyed,
46    /// Session expired
47    SessionExpired,
48
49    // Authorization events
50    /// Access granted to resource
51    AccessGranted,
52    /// Access denied to resource
53    AccessDenied,
54    /// Insufficient permissions
55    InsufficientPermissions,
56
57    // Account events
58    /// Account locked due to failed attempts
59    AccountLocked,
60    /// Account unlocked
61    AccountUnlocked,
62    /// Password changed
63    PasswordChanged,
64    /// Password reset requested
65    PasswordResetRequested,
66
67    // Token events
68    /// Token generated
69    TokenGenerated,
70    /// Token refreshed
71    TokenRefreshed,
72    /// Token revoked
73    TokenRevoked,
74    /// Token expired
75    TokenExpired,
76    /// Invalid token used
77    InvalidToken,
78
79    // Rate limiting events
80    /// Rate limit exceeded
81    RateLimitExceeded,
82    /// Rate limit warning (approaching limit)
83    RateLimitWarning,
84
85    // CSRF events
86    /// CSRF validation failed
87    CsrfValidationFailed,
88    /// Missing CSRF token
89    CsrfTokenMissing,
90
91    // Suspicious activity
92    /// Potential brute force attack detected
93    BruteForceDetected,
94    /// Suspicious IP address
95    SuspiciousIp,
96    /// Multiple failed attempts from same source
97    MultipleFailures,
98
99    // Custom events
100    /// Custom security event
101    Custom(String),
102}
103
104impl fmt::Display for SecurityEventType {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            SecurityEventType::AuthenticationSuccess => write!(f, "AUTHENTICATION_SUCCESS"),
108            SecurityEventType::AuthenticationFailure => write!(f, "AUTHENTICATION_FAILURE"),
109            SecurityEventType::Logout => write!(f, "LOGOUT"),
110            SecurityEventType::SessionCreated => write!(f, "SESSION_CREATED"),
111            SecurityEventType::SessionDestroyed => write!(f, "SESSION_DESTROYED"),
112            SecurityEventType::SessionExpired => write!(f, "SESSION_EXPIRED"),
113            SecurityEventType::AccessGranted => write!(f, "ACCESS_GRANTED"),
114            SecurityEventType::AccessDenied => write!(f, "ACCESS_DENIED"),
115            SecurityEventType::InsufficientPermissions => write!(f, "INSUFFICIENT_PERMISSIONS"),
116            SecurityEventType::AccountLocked => write!(f, "ACCOUNT_LOCKED"),
117            SecurityEventType::AccountUnlocked => write!(f, "ACCOUNT_UNLOCKED"),
118            SecurityEventType::PasswordChanged => write!(f, "PASSWORD_CHANGED"),
119            SecurityEventType::PasswordResetRequested => write!(f, "PASSWORD_RESET_REQUESTED"),
120            SecurityEventType::TokenGenerated => write!(f, "TOKEN_GENERATED"),
121            SecurityEventType::TokenRefreshed => write!(f, "TOKEN_REFRESHED"),
122            SecurityEventType::TokenRevoked => write!(f, "TOKEN_REVOKED"),
123            SecurityEventType::TokenExpired => write!(f, "TOKEN_EXPIRED"),
124            SecurityEventType::InvalidToken => write!(f, "INVALID_TOKEN"),
125            SecurityEventType::RateLimitExceeded => write!(f, "RATE_LIMIT_EXCEEDED"),
126            SecurityEventType::RateLimitWarning => write!(f, "RATE_LIMIT_WARNING"),
127            SecurityEventType::CsrfValidationFailed => write!(f, "CSRF_VALIDATION_FAILED"),
128            SecurityEventType::CsrfTokenMissing => write!(f, "CSRF_TOKEN_MISSING"),
129            SecurityEventType::BruteForceDetected => write!(f, "BRUTE_FORCE_DETECTED"),
130            SecurityEventType::SuspiciousIp => write!(f, "SUSPICIOUS_IP"),
131            SecurityEventType::MultipleFailures => write!(f, "MULTIPLE_FAILURES"),
132            SecurityEventType::Custom(name) => write!(f, "CUSTOM_{}", name.to_uppercase()),
133        }
134    }
135}
136
137/// Severity level of security events.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
139pub enum SecurityEventSeverity {
140    /// Informational (successful operations)
141    #[default]
142    Info,
143    /// Warning (potential issues)
144    Warning,
145    /// Error (failed operations)
146    Error,
147    /// Critical (security threats)
148    Critical,
149}
150
151impl fmt::Display for SecurityEventSeverity {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        match self {
154            SecurityEventSeverity::Info => write!(f, "INFO"),
155            SecurityEventSeverity::Warning => write!(f, "WARNING"),
156            SecurityEventSeverity::Error => write!(f, "ERROR"),
157            SecurityEventSeverity::Critical => write!(f, "CRITICAL"),
158        }
159    }
160}
161
162impl SecurityEventType {
163    /// Get the default severity for this event type.
164    pub fn default_severity(&self) -> SecurityEventSeverity {
165        match self {
166            SecurityEventType::AuthenticationSuccess
167            | SecurityEventType::Logout
168            | SecurityEventType::SessionCreated
169            | SecurityEventType::AccessGranted
170            | SecurityEventType::TokenGenerated
171            | SecurityEventType::TokenRefreshed
172            | SecurityEventType::PasswordChanged => SecurityEventSeverity::Info,
173
174            SecurityEventType::SessionExpired
175            | SecurityEventType::TokenExpired
176            | SecurityEventType::RateLimitWarning => SecurityEventSeverity::Warning,
177
178            SecurityEventType::AuthenticationFailure
179            | SecurityEventType::SessionDestroyed
180            | SecurityEventType::AccessDenied
181            | SecurityEventType::InsufficientPermissions
182            | SecurityEventType::TokenRevoked
183            | SecurityEventType::InvalidToken
184            | SecurityEventType::RateLimitExceeded
185            | SecurityEventType::CsrfValidationFailed
186            | SecurityEventType::CsrfTokenMissing
187            | SecurityEventType::AccountLocked
188            | SecurityEventType::PasswordResetRequested => SecurityEventSeverity::Error,
189
190            SecurityEventType::BruteForceDetected
191            | SecurityEventType::SuspiciousIp
192            | SecurityEventType::MultipleFailures => SecurityEventSeverity::Critical,
193
194            SecurityEventType::AccountUnlocked | SecurityEventType::Custom(_) => {
195                SecurityEventSeverity::Info
196            }
197        }
198    }
199}
200
201/// A security audit event.
202#[derive(Debug, Clone)]
203pub struct SecurityEvent {
204    /// Unique event ID
205    pub id: String,
206    /// Event timestamp (Unix epoch milliseconds)
207    pub timestamp: u64,
208    /// Event type
209    pub event_type: SecurityEventType,
210    /// Event severity
211    pub severity: SecurityEventSeverity,
212    /// Username (if applicable)
213    pub username: Option<String>,
214    /// Source IP address
215    pub ip_address: Option<String>,
216    /// User agent
217    pub user_agent: Option<String>,
218    /// Request path
219    pub path: Option<String>,
220    /// HTTP method
221    pub method: Option<String>,
222    /// Session ID (if applicable)
223    pub session_id: Option<String>,
224    /// Additional details
225    pub details: HashMap<String, String>,
226    /// Error message (for failure events)
227    pub error: Option<String>,
228}
229
230impl SecurityEvent {
231    /// Create a new security event.
232    pub fn new(event_type: SecurityEventType) -> Self {
233        let now = SystemTime::now()
234            .duration_since(UNIX_EPOCH)
235            .unwrap_or_default()
236            .as_millis() as u64;
237
238        Self {
239            id: generate_event_id(),
240            timestamp: now,
241            severity: event_type.default_severity(),
242            event_type,
243            username: None,
244            ip_address: None,
245            user_agent: None,
246            path: None,
247            method: None,
248            session_id: None,
249            details: HashMap::new(),
250            error: None,
251        }
252    }
253
254    /// Set the username.
255    pub fn username(mut self, username: impl Into<String>) -> Self {
256        self.username = Some(username.into());
257        self
258    }
259
260    /// Set the IP address.
261    pub fn ip_address(mut self, ip: impl Into<String>) -> Self {
262        self.ip_address = Some(ip.into());
263        self
264    }
265
266    /// Set the user agent.
267    pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
268        self.user_agent = Some(ua.into());
269        self
270    }
271
272    /// Set the request path.
273    pub fn path(mut self, path: impl Into<String>) -> Self {
274        self.path = Some(path.into());
275        self
276    }
277
278    /// Set the HTTP method.
279    pub fn method(mut self, method: impl Into<String>) -> Self {
280        self.method = Some(method.into());
281        self
282    }
283
284    /// Set the session ID.
285    pub fn session_id(mut self, id: impl Into<String>) -> Self {
286        self.session_id = Some(id.into());
287        self
288    }
289
290    /// Set the severity (overrides default).
291    pub fn severity(mut self, severity: SecurityEventSeverity) -> Self {
292        self.severity = severity;
293        self
294    }
295
296    /// Add a detail.
297    pub fn detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
298        self.details.insert(key.into(), value.into());
299        self
300    }
301
302    /// Set the error message.
303    pub fn error(mut self, error: impl Into<String>) -> Self {
304        self.error = Some(error.into());
305        self
306    }
307
308    // Convenience constructors
309
310    /// Create a login success event.
311    pub fn login_success(username: &str, ip: &str) -> Self {
312        Self::new(SecurityEventType::AuthenticationSuccess)
313            .username(username)
314            .ip_address(ip)
315    }
316
317    /// Create a login failure event.
318    pub fn login_failure(username: &str, ip: &str, reason: &str) -> Self {
319        Self::new(SecurityEventType::AuthenticationFailure)
320            .username(username)
321            .ip_address(ip)
322            .error(reason)
323    }
324
325    /// Create an access denied event.
326    pub fn access_denied(username: &str, path: &str, ip: &str) -> Self {
327        Self::new(SecurityEventType::AccessDenied)
328            .username(username)
329            .path(path)
330            .ip_address(ip)
331    }
332
333    /// Create a rate limit exceeded event.
334    pub fn rate_limit_exceeded(ip: &str, path: &str) -> Self {
335        Self::new(SecurityEventType::RateLimitExceeded)
336            .ip_address(ip)
337            .path(path)
338    }
339
340    /// Create an account locked event.
341    pub fn account_locked(username: &str, ip: &str, reason: &str) -> Self {
342        Self::new(SecurityEventType::AccountLocked)
343            .username(username)
344            .ip_address(ip)
345            .detail("reason", reason)
346    }
347
348    /// Create a brute force detected event.
349    pub fn brute_force_detected(ip: &str, attempts: u32) -> Self {
350        Self::new(SecurityEventType::BruteForceDetected)
351            .ip_address(ip)
352            .detail("attempts", attempts.to_string())
353    }
354
355    /// Format the event as a log line.
356    pub fn to_log_line(&self) -> String {
357        let mut parts = vec![
358            format!("[{}]", self.severity),
359            format!("[{}]", self.event_type),
360        ];
361
362        if let Some(ref username) = self.username {
363            parts.push(format!("user={}", username));
364        }
365        if let Some(ref ip) = self.ip_address {
366            parts.push(format!("ip={}", ip));
367        }
368        if let Some(ref path) = self.path {
369            parts.push(format!("path={}", path));
370        }
371        if let Some(ref error) = self.error {
372            parts.push(format!("error=\"{}\"", error));
373        }
374        for (k, v) in &self.details {
375            parts.push(format!("{}={}", k, v));
376        }
377
378        parts.join(" ")
379    }
380
381    /// Format the event as JSON.
382    ///
383    /// Requires features that include serde (e.g., `jwt` or `session` features).
384    #[cfg(any(feature = "jwt", feature = "session", feature = "oauth2"))]
385    pub fn to_json(&self) -> String {
386        serde_json::to_string(self).unwrap_or_else(|_| self.to_log_line())
387    }
388
389    /// Format the event as a simple JSON-like string (without serde).
390    #[cfg(not(any(feature = "jwt", feature = "session", feature = "oauth2")))]
391    pub fn to_json(&self) -> String {
392        self.to_log_line()
393    }
394}
395
396#[cfg(any(feature = "jwt", feature = "session", feature = "oauth2"))]
397impl serde::Serialize for SecurityEvent {
398    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
399    where
400        S: serde::Serializer,
401    {
402        use serde::ser::SerializeStruct;
403        let mut state = serializer.serialize_struct("SecurityEvent", 12)?;
404        state.serialize_field("id", &self.id)?;
405        state.serialize_field("timestamp", &self.timestamp)?;
406        state.serialize_field("event_type", &self.event_type.to_string())?;
407        state.serialize_field("severity", &self.severity.to_string())?;
408        state.serialize_field("username", &self.username)?;
409        state.serialize_field("ip_address", &self.ip_address)?;
410        state.serialize_field("user_agent", &self.user_agent)?;
411        state.serialize_field("path", &self.path)?;
412        state.serialize_field("method", &self.method)?;
413        state.serialize_field("session_id", &self.session_id)?;
414        state.serialize_field("details", &self.details)?;
415        state.serialize_field("error", &self.error)?;
416        state.end()
417    }
418}
419
420/// Generate a unique event ID.
421fn generate_event_id() -> String {
422    use rand::Rng;
423    let timestamp = SystemTime::now()
424        .duration_since(UNIX_EPOCH)
425        .unwrap_or_default()
426        .as_micros();
427    let random: u32 = rand::thread_rng().gen();
428    format!("{:x}-{:08x}", timestamp, random)
429}
430
431/// Trait for handling security events.
432pub trait SecurityEventHandler: Send + Sync {
433    /// Handle a security event.
434    fn handle(&self, event: &SecurityEvent);
435}
436
437/// Simple logging handler that prints to stdout.
438#[derive(Default)]
439pub struct StdoutHandler {
440    min_severity: SecurityEventSeverity,
441}
442
443impl StdoutHandler {
444    /// Create a new stdout handler.
445    pub fn new() -> Self {
446        Self::default()
447    }
448
449    /// Set minimum severity to log.
450    pub fn min_severity(mut self, severity: SecurityEventSeverity) -> Self {
451        self.min_severity = severity;
452        self
453    }
454}
455
456impl SecurityEventHandler for StdoutHandler {
457    fn handle(&self, event: &SecurityEvent) {
458        if event.severity >= self.min_severity {
459            println!("[SECURITY] {}", event.to_log_line());
460        }
461    }
462}
463
464/// Handler that emits security events using the `tracing` crate.
465///
466/// This integrates security audit logging with the broader Rust ecosystem's
467/// tracing infrastructure, allowing security events to be processed by any
468/// tracing subscriber (console, file, OpenTelemetry, etc.).
469///
470/// # Example
471///
472/// ```ignore
473/// use actix_security::http::security::audit::{AuditLogger, TracingHandler};
474/// use tracing_subscriber;
475///
476/// // Initialize tracing subscriber
477/// tracing_subscriber::fmt::init();
478///
479/// // Create audit logger with tracing handler
480/// let logger = AuditLogger::new().add_handler(TracingHandler::new());
481/// ```
482#[derive(Default, Clone, Copy)]
483pub struct TracingHandler;
484
485impl TracingHandler {
486    /// Create a new tracing handler.
487    ///
488    /// Events are emitted to the `actix_security::audit` target.
489    pub fn new() -> Self {
490        Self
491    }
492}
493
494impl SecurityEventHandler for TracingHandler {
495    fn handle(&self, event: &SecurityEvent) {
496        let username = event.username.as_deref().unwrap_or("-");
497        let ip = event.ip_address.as_deref().unwrap_or("-");
498        let path = event.path.as_deref().unwrap_or("-");
499        let method = event.method.as_deref().unwrap_or("-");
500        let event_type = event.event_type.to_string();
501        let error_msg = event.error.as_deref().unwrap_or("");
502
503        match event.severity {
504            SecurityEventSeverity::Info => {
505                tracing::info!(
506                    target: "actix_security::audit",
507                    event_type = %event_type,
508                    user = %username,
509                    ip = %ip,
510                    path = %path,
511                    method = %method,
512                    "Security event"
513                );
514            }
515            SecurityEventSeverity::Warning => {
516                tracing::warn!(
517                    target: "actix_security::audit",
518                    event_type = %event_type,
519                    user = %username,
520                    ip = %ip,
521                    path = %path,
522                    method = %method,
523                    "Security warning"
524                );
525            }
526            SecurityEventSeverity::Error => {
527                tracing::error!(
528                    target: "actix_security::audit",
529                    event_type = %event_type,
530                    user = %username,
531                    ip = %ip,
532                    path = %path,
533                    method = %method,
534                    error = %error_msg,
535                    "Security error"
536                );
537            }
538            SecurityEventSeverity::Critical => {
539                tracing::error!(
540                    target: "actix_security::audit",
541                    event_type = %event_type,
542                    user = %username,
543                    ip = %ip,
544                    path = %path,
545                    method = %method,
546                    error = %error_msg,
547                    severity = "CRITICAL",
548                    "Critical security event"
549                );
550            }
551        }
552    }
553}
554
555/// Handler that calls a closure.
556pub struct ClosureHandler<F>
557where
558    F: Fn(&SecurityEvent) + Send + Sync,
559{
560    handler: F,
561}
562
563impl<F> ClosureHandler<F>
564where
565    F: Fn(&SecurityEvent) + Send + Sync,
566{
567    /// Create a new closure handler.
568    pub fn new(handler: F) -> Self {
569        Self { handler }
570    }
571}
572
573impl<F> SecurityEventHandler for ClosureHandler<F>
574where
575    F: Fn(&SecurityEvent) + Send + Sync,
576{
577    fn handle(&self, event: &SecurityEvent) {
578        (self.handler)(event);
579    }
580}
581
582/// In-memory event store for testing and debugging.
583#[derive(Clone)]
584pub struct InMemoryEventStore {
585    events: Arc<RwLock<Vec<SecurityEvent>>>,
586    max_events: usize,
587}
588
589impl Default for InMemoryEventStore {
590    fn default() -> Self {
591        Self::new()
592    }
593}
594
595impl InMemoryEventStore {
596    /// Create a new in-memory store.
597    pub fn new() -> Self {
598        Self {
599            events: Arc::new(RwLock::new(Vec::new())),
600            max_events: 10000,
601        }
602    }
603
604    /// Set maximum events to keep.
605    pub fn max_events(mut self, max: usize) -> Self {
606        self.max_events = max;
607        self
608    }
609
610    /// Get all stored events.
611    pub async fn get_events(&self) -> Vec<SecurityEvent> {
612        self.events.read().await.clone()
613    }
614
615    /// Get events filtered by type.
616    pub async fn get_events_by_type(&self, event_type: &SecurityEventType) -> Vec<SecurityEvent> {
617        self.events
618            .read()
619            .await
620            .iter()
621            .filter(|e| &e.event_type == event_type)
622            .cloned()
623            .collect()
624    }
625
626    /// Get events for a specific user.
627    pub async fn get_events_by_user(&self, username: &str) -> Vec<SecurityEvent> {
628        self.events
629            .read()
630            .await
631            .iter()
632            .filter(|e| e.username.as_deref() == Some(username))
633            .cloned()
634            .collect()
635    }
636
637    /// Clear all events.
638    pub async fn clear(&self) {
639        self.events.write().await.clear();
640    }
641}
642
643impl SecurityEventHandler for InMemoryEventStore {
644    fn handle(&self, event: &SecurityEvent) {
645        // Use blocking lock for sync trait implementation
646        let rt = tokio::runtime::Handle::try_current();
647        if let Ok(handle) = rt {
648            let events = Arc::clone(&self.events);
649            let event = event.clone();
650            let max = self.max_events;
651            handle.spawn(async move {
652                let mut guard = events.write().await;
653                guard.push(event);
654                if guard.len() > max {
655                    guard.remove(0);
656                }
657            });
658        }
659    }
660}
661
662/// The main audit logger.
663#[derive(Clone)]
664pub struct AuditLogger {
665    handlers: Arc<Vec<Arc<dyn SecurityEventHandler>>>,
666    enabled: bool,
667}
668
669impl Default for AuditLogger {
670    fn default() -> Self {
671        Self::new()
672    }
673}
674
675impl AuditLogger {
676    /// Create a new audit logger with no handlers.
677    pub fn new() -> Self {
678        Self {
679            handlers: Arc::new(Vec::new()),
680            enabled: true,
681        }
682    }
683
684    /// Create an audit logger with stdout logging.
685    pub fn with_stdout() -> Self {
686        Self::new().add_handler(StdoutHandler::new())
687    }
688
689    /// Add an event handler.
690    pub fn add_handler<H: SecurityEventHandler + 'static>(mut self, handler: H) -> Self {
691        let handlers = Arc::make_mut(&mut self.handlers);
692        handlers.push(Arc::new(handler));
693        self
694    }
695
696    /// Add a closure as event handler.
697    pub fn with_handler<F>(self, handler: F) -> Self
698    where
699        F: Fn(&SecurityEvent) + Send + Sync + 'static,
700    {
701        self.add_handler(ClosureHandler::new(handler))
702    }
703
704    /// Enable or disable the logger.
705    pub fn enabled(mut self, enabled: bool) -> Self {
706        self.enabled = enabled;
707        self
708    }
709
710    /// Log a security event.
711    pub fn log(&self, event: SecurityEvent) {
712        if !self.enabled {
713            return;
714        }
715
716        for handler in self.handlers.iter() {
717            handler.handle(&event);
718        }
719    }
720
721    /// Log a login success event.
722    pub fn log_login_success(&self, username: &str, ip: &str) {
723        self.log(SecurityEvent::login_success(username, ip));
724    }
725
726    /// Log a login failure event.
727    pub fn log_login_failure(&self, username: &str, ip: &str, reason: &str) {
728        self.log(SecurityEvent::login_failure(username, ip, reason));
729    }
730
731    /// Log an access denied event.
732    pub fn log_access_denied(&self, username: &str, path: &str, ip: &str) {
733        self.log(SecurityEvent::access_denied(username, path, ip));
734    }
735
736    /// Log a rate limit exceeded event.
737    pub fn log_rate_limit_exceeded(&self, ip: &str, path: &str) {
738        self.log(SecurityEvent::rate_limit_exceeded(ip, path));
739    }
740}
741
742/// Global audit logger instance.
743static GLOBAL_LOGGER: std::sync::OnceLock<AuditLogger> = std::sync::OnceLock::new();
744
745/// Initialize the global audit logger.
746pub fn init_global_logger(logger: AuditLogger) {
747    let _ = GLOBAL_LOGGER.set(logger);
748}
749
750/// Get the global audit logger.
751pub fn global_logger() -> &'static AuditLogger {
752    GLOBAL_LOGGER.get_or_init(AuditLogger::new)
753}
754
755/// Log a security event using the global logger.
756pub fn audit_log(event: SecurityEvent) {
757    global_logger().log(event);
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763
764    #[test]
765    fn test_event_creation() {
766        let event = SecurityEvent::login_success("admin", "192.168.1.1");
767        assert_eq!(event.event_type, SecurityEventType::AuthenticationSuccess);
768        assert_eq!(event.username, Some("admin".to_string()));
769        assert_eq!(event.ip_address, Some("192.168.1.1".to_string()));
770        assert_eq!(event.severity, SecurityEventSeverity::Info);
771    }
772
773    #[test]
774    fn test_event_builder() {
775        let event = SecurityEvent::new(SecurityEventType::AccessDenied)
776            .username("user1")
777            .ip_address("10.0.0.1")
778            .path("/admin")
779            .detail("reason", "missing role")
780            .error("Access denied");
781
782        assert_eq!(event.username, Some("user1".to_string()));
783        assert_eq!(event.path, Some("/admin".to_string()));
784        assert!(event.details.contains_key("reason"));
785        assert_eq!(event.error, Some("Access denied".to_string()));
786    }
787
788    #[test]
789    fn test_severity_ordering() {
790        assert!(SecurityEventSeverity::Info < SecurityEventSeverity::Warning);
791        assert!(SecurityEventSeverity::Warning < SecurityEventSeverity::Error);
792        assert!(SecurityEventSeverity::Error < SecurityEventSeverity::Critical);
793    }
794
795    #[test]
796    fn test_event_type_display() {
797        assert_eq!(
798            SecurityEventType::AuthenticationSuccess.to_string(),
799            "AUTHENTICATION_SUCCESS"
800        );
801        assert_eq!(
802            SecurityEventType::Custom("test".to_string()).to_string(),
803            "CUSTOM_TEST"
804        );
805    }
806
807    #[test]
808    fn test_log_line_format() {
809        let event = SecurityEvent::login_failure("admin", "192.168.1.1", "Invalid password");
810        let log_line = event.to_log_line();
811
812        assert!(log_line.contains("[ERROR]"));
813        assert!(log_line.contains("[AUTHENTICATION_FAILURE]"));
814        assert!(log_line.contains("user=admin"));
815        assert!(log_line.contains("ip=192.168.1.1"));
816    }
817
818    #[test]
819    fn test_default_severity() {
820        assert_eq!(
821            SecurityEventType::AuthenticationSuccess.default_severity(),
822            SecurityEventSeverity::Info
823        );
824        assert_eq!(
825            SecurityEventType::BruteForceDetected.default_severity(),
826            SecurityEventSeverity::Critical
827        );
828    }
829
830    #[test]
831    fn test_audit_logger_with_closure() {
832        use std::sync::atomic::{AtomicUsize, Ordering};
833        let counter = Arc::new(AtomicUsize::new(0));
834        let counter_clone = counter.clone();
835
836        let logger = AuditLogger::new().with_handler(move |_event| {
837            counter_clone.fetch_add(1, Ordering::SeqCst);
838        });
839
840        logger.log_login_success("admin", "127.0.0.1");
841        logger.log_login_failure("user", "127.0.0.1", "Bad password");
842
843        assert_eq!(counter.load(Ordering::SeqCst), 2);
844    }
845
846    #[test]
847    fn test_disabled_logger() {
848        use std::sync::atomic::{AtomicUsize, Ordering};
849        let counter = Arc::new(AtomicUsize::new(0));
850        let counter_clone = counter.clone();
851
852        let logger = AuditLogger::new()
853            .with_handler(move |_event| {
854                counter_clone.fetch_add(1, Ordering::SeqCst);
855            })
856            .enabled(false);
857
858        logger.log_login_success("admin", "127.0.0.1");
859
860        assert_eq!(counter.load(Ordering::SeqCst), 0);
861    }
862}