use super::event::{AuditAction, AuditEvent, AuditSeverity};
use super::store::{AuditResult, AuditStore};
#[derive(Debug, Clone, Default)]
pub struct AuditQuery {
pub actor_id: Option<String>,
pub action: Option<AuditAction>,
pub resource_type: Option<String>,
pub resource_id: Option<String>,
pub success: Option<bool>,
pub min_severity: Option<AuditSeverity>,
pub from_timestamp: Option<u64>,
pub to_timestamp: Option<u64>,
pub request_id: Option<String>,
pub session_id: Option<String>,
pub involves_personal_data: Option<bool>,
pub ip_address: Option<String>,
pub limit: Option<usize>,
pub offset: Option<usize>,
pub newest_first: bool,
}
impl AuditQuery {
pub fn new() -> Self {
Self {
newest_first: true,
..Default::default()
}
}
pub fn matches(&self, event: &AuditEvent) -> bool {
if let Some(ref actor) = self.actor_id {
if event.actor_id.as_ref() != Some(actor) {
return false;
}
}
if let Some(ref action) = self.action {
if &event.action != action {
return false;
}
}
if let Some(ref rt) = self.resource_type {
if event.resource_type.as_ref() != Some(rt) {
return false;
}
}
if let Some(ref rid) = self.resource_id {
if event.resource_id.as_ref() != Some(rid) {
return false;
}
}
if let Some(success) = self.success {
if event.success != success {
return false;
}
}
if let Some(min_sev) = self.min_severity {
if event.severity < min_sev {
return false;
}
}
if let Some(from) = self.from_timestamp {
if event.timestamp < from {
return false;
}
}
if let Some(to) = self.to_timestamp {
if event.timestamp > to {
return false;
}
}
if let Some(ref req_id) = self.request_id {
if event.request_id.as_ref() != Some(req_id) {
return false;
}
}
if let Some(ref sess_id) = self.session_id {
if event.session_id.as_ref() != Some(sess_id) {
return false;
}
}
if let Some(personal) = self.involves_personal_data {
if event.compliance.involves_personal_data != personal {
return false;
}
}
if let Some(ref ip) = self.ip_address {
if event.ip_address.as_ref() != Some(ip) {
return false;
}
}
true
}
}
pub struct AuditQueryBuilder<'a> {
store: &'a dyn AuditStore,
query: AuditQuery,
}
impl<'a> AuditQueryBuilder<'a> {
pub fn new(store: &'a dyn AuditStore) -> Self {
Self {
store,
query: AuditQuery::new(),
}
}
pub fn actor(mut self, actor_id: impl Into<String>) -> Self {
self.query.actor_id = Some(actor_id.into());
self
}
pub fn action(mut self, action: AuditAction) -> Self {
self.query.action = Some(action);
self
}
pub fn resource_type(mut self, resource_type: impl Into<String>) -> Self {
self.query.resource_type = Some(resource_type.into());
self
}
pub fn resource_id(mut self, resource_id: impl Into<String>) -> Self {
self.query.resource_id = Some(resource_id.into());
self
}
pub fn resource(
mut self,
resource_type: impl Into<String>,
resource_id: impl Into<String>,
) -> Self {
self.query.resource_type = Some(resource_type.into());
self.query.resource_id = Some(resource_id.into());
self
}
pub fn success(mut self, success: bool) -> Self {
self.query.success = Some(success);
self
}
pub fn failures_only(self) -> Self {
self.success(false)
}
pub fn min_severity(mut self, severity: AuditSeverity) -> Self {
self.query.min_severity = Some(severity);
self
}
pub fn from_timestamp(mut self, timestamp: u64) -> Self {
self.query.from_timestamp = Some(timestamp);
self
}
pub fn to_timestamp(mut self, timestamp: u64) -> Self {
self.query.to_timestamp = Some(timestamp);
self
}
pub fn time_range(mut self, from: u64, to: u64) -> Self {
self.query.from_timestamp = Some(from);
self.query.to_timestamp = Some(to);
self
}
pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
self.query.request_id = Some(request_id.into());
self
}
pub fn session_id(mut self, session_id: impl Into<String>) -> Self {
self.query.session_id = Some(session_id.into());
self
}
pub fn personal_data(mut self, involves: bool) -> Self {
self.query.involves_personal_data = Some(involves);
self
}
pub fn ip_address(mut self, ip: impl Into<String>) -> Self {
self.query.ip_address = Some(ip.into());
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.query.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.query.offset = Some(offset);
self
}
pub fn newest_first(mut self) -> Self {
self.query.newest_first = true;
self
}
pub fn oldest_first(mut self) -> Self {
self.query.newest_first = false;
self
}
pub fn execute(self) -> AuditResult<Vec<AuditEvent>> {
self.store.execute_query(&self.query)
}
pub fn count(self) -> AuditResult<usize> {
self.store.count(&self.query)
}
pub fn build(self) -> AuditQuery {
self.query
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_matches() {
let event = AuditEvent::new(AuditAction::Create)
.resource("users", "user-123")
.actor("admin")
.success(true);
let query = AuditQuery {
action: Some(AuditAction::Create),
resource_type: Some("users".to_string()),
..Default::default()
};
assert!(query.matches(&event));
let query = AuditQuery {
action: Some(AuditAction::Delete),
..Default::default()
};
assert!(!query.matches(&event));
}
#[test]
fn test_query_severity_filter() {
let info_event = AuditEvent::new(AuditAction::Read).severity(AuditSeverity::Info);
let warning_event =
AuditEvent::new(AuditAction::LoginFailed).severity(AuditSeverity::Warning);
let query = AuditQuery {
min_severity: Some(AuditSeverity::Warning),
..Default::default()
};
assert!(!query.matches(&info_event));
assert!(query.matches(&warning_event));
}
}