use std::collections::HashSet;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Arc;
use std::time::SystemTime;
pub use crate::event_types::EVENT_TYPE_VALUES;
pub type EventHandler = Arc<dyn Fn(&SecurityEvent) + Send + Sync>;
pub const MIDDLEWARE_HANDLER_NAME: &str = "middleware";
pub const RATE_LIMIT_HANDLER_NAME: &str = "rate_limit";
pub const IP_BAN_HANDLER_NAME: &str = "ip_ban";
#[derive(Default)]
struct BusInner {
handlers: Vec<EventHandler>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SecurityEvent {
pub timestamp: SystemTime,
pub event_type: String,
pub ip_address: String,
pub country: Option<String>,
pub user_agent: Option<String>,
pub action_taken: String,
pub reason: String,
pub endpoint: Option<String>,
pub method: Option<String>,
pub response_time: Option<f64>,
pub decorator_type: Option<String>,
pub rule_type: Option<String>,
pub handler_name: Option<String>,
pub metadata: serde_json::Map<String, serde_json::Value>,
}
impl SecurityEvent {
#[must_use]
pub fn new(
event_type: &str,
ip_address: &str,
action_taken: &str,
reason: &str,
handler_name: &str,
) -> Self {
Self {
timestamp: SystemTime::now(),
event_type: event_type.to_owned(),
ip_address: ip_address.to_owned(),
country: None,
user_agent: None,
action_taken: action_taken.to_owned(),
reason: reason.to_owned(),
endpoint: None,
method: None,
response_time: None,
decorator_type: None,
rule_type: None,
handler_name: Some(handler_name.to_owned()),
metadata: serde_json::Map::new(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct EventFilter {
pub muted_event_types: HashSet<String>,
}
impl EventFilter {
#[must_use]
pub fn is_event_allowed(&self, event_type: &str) -> bool {
!self.muted_event_types.contains(event_type)
}
}
#[derive(Clone, Default)]
pub struct SecurityEventBus {
enabled: bool,
filter: Option<EventFilter>,
inner: Arc<BusInner>,
}
impl SecurityEventBus {
#[must_use]
pub fn new(enabled: bool) -> Self {
Self {
enabled,
filter: None,
inner: Arc::default(),
}
}
#[must_use]
pub fn with_filter(mut self, filter: EventFilter) -> Self {
self.filter = Some(filter);
self
}
#[must_use]
pub fn on_event(self, handler: EventHandler) -> Self {
let mut handlers: Vec<EventHandler> = Arc::try_unwrap(self.inner)
.map_or_else(|shared| shared.handlers.clone(), |inner| inner.handlers);
handlers.push(handler);
Self {
enabled: self.enabled,
filter: self.filter,
inner: Arc::new(BusInner { handlers }),
}
}
pub fn send_event(&self, event: &SecurityEvent) {
if !self.enabled {
return;
}
if let Some(filter) = &self.filter
&& !filter.is_event_allowed(&event.event_type)
{
return;
}
for handler in &self.inner.handlers {
let _ = catch_unwind(AssertUnwindSafe(|| handler(event)));
}
}
pub fn send_middleware_event(
&self,
event_type: &str,
ip_address: &str,
action_taken: &str,
reason: &str,
) {
self.send_event(&SecurityEvent::new(
event_type,
ip_address,
action_taken,
reason,
MIDDLEWARE_HANDLER_NAME,
));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event_types::{
EVENT_IP_BANNED, EVENT_PATTERN_ANOMALY_STATISTICAL_ANOMALY, EVENT_PENETRATION_ATTEMPT,
EVENT_RATE_LIMITED,
};
use std::sync::Mutex;
fn collect() -> (Arc<Mutex<Vec<String>>>, SecurityEventBus) {
let seen = Arc::new(Mutex::new(Vec::new()));
let sink = seen.clone();
let bus = SecurityEventBus::new(true).on_event(Arc::new(move |event: &SecurityEvent| {
sink.lock().expect("sink").push(event.event_type.clone());
}));
(seen, bus)
}
#[test]
fn middleware_event_carries_the_reference_fields() {
let (seen, bus) = collect();
bus.send_middleware_event(
EVENT_PENETRATION_ATTEMPT,
"192.0.2.7",
"request_blocked",
"Penetration attempt detected: xss",
);
let events = seen.lock().expect("sink").clone();
assert_eq!(events.len(), 1);
assert_eq!(events[0], "penetration_attempt");
}
#[test]
fn disabled_bus_sends_nothing() {
let bus = SecurityEventBus::new(false);
let seen = Arc::new(Mutex::new(Vec::new()));
let sink = seen.clone();
let bus = bus.on_event(Arc::new(move |_: &SecurityEvent| {
sink.lock().expect("sink").push("fired".to_owned());
}));
bus.send_middleware_event(EVENT_RATE_LIMITED, "192.0.2.1", "request_blocked", "r");
assert!(seen.lock().expect("sink").is_empty());
}
#[test]
fn muted_types_skip_handlers() {
let seen = Arc::new(Mutex::new(Vec::new()));
let sink = seen.clone();
let bus = SecurityEventBus::new(true)
.with_filter(EventFilter {
muted_event_types: HashSet::from([EVENT_PENETRATION_ATTEMPT.to_owned()]),
})
.on_event(Arc::new(move |event: &SecurityEvent| {
sink.lock().expect("sink").push(event.event_type.clone());
}));
bus.send_middleware_event(EVENT_PENETRATION_ATTEMPT, "192.0.2.1", "logged_only", "r");
bus.send_middleware_event(EVENT_RATE_LIMITED, "192.0.2.1", "request_blocked", "r");
assert_eq!(seen.lock().expect("sink").as_slice(), ["rate_limited"]);
}
#[test]
fn handler_panics_do_not_break_the_bus() {
let seen = Arc::new(Mutex::new(Vec::new()));
let sink = seen.clone();
let panicky: EventHandler = Arc::new(|_: &SecurityEvent| panic!("handler blew up"));
let good: EventHandler = Arc::new(move |event: &SecurityEvent| {
sink.lock().expect("sink").push(event.event_type.clone());
});
let bus = SecurityEventBus::new(true).on_event(panicky).on_event(good);
bus.send_middleware_event(EVENT_IP_BANNED, "192.0.2.1", "banned", "threshold");
assert_eq!(seen.lock().expect("sink").as_slice(), ["ip_banned"]);
}
#[test]
fn event_type_values_cover_the_reference_set() {
assert_eq!(EVENT_TYPE_VALUES.len(), 39);
assert!(EVENT_TYPE_VALUES.contains(&EVENT_PENETRATION_ATTEMPT));
assert!(EVENT_TYPE_VALUES.contains(&EVENT_PATTERN_ANOMALY_STATISTICAL_ANOMALY));
}
}