Skip to main content

oxigdal_security/audit/
storage.rs

1//! Audit log storage.
2
3use crate::audit::AuditLogEntry;
4use crate::error::Result;
5use dashmap::DashMap;
6use std::sync::Arc;
7
8/// Audit log storage trait.
9pub trait AuditStorage: Send + Sync {
10    /// Store an audit log entry.
11    fn store(&self, entry: AuditLogEntry) -> Result<()>;
12
13    /// Query audit logs.
14    fn query(&self, query: &StorageQuery) -> Result<Vec<AuditLogEntry>>;
15
16    /// Count audit logs matching query.
17    fn count(&self, query: &StorageQuery) -> Result<usize>;
18}
19
20/// In-memory audit storage.
21pub struct InMemoryAuditStorage {
22    entries: Arc<DashMap<String, AuditLogEntry>>,
23}
24
25impl InMemoryAuditStorage {
26    /// Create new in-memory storage.
27    pub fn new() -> Self {
28        Self {
29            entries: Arc::new(DashMap::new()),
30        }
31    }
32
33    /// Get number of stored entries.
34    pub fn len(&self) -> usize {
35        self.entries.len()
36    }
37
38    /// Check if storage is empty.
39    pub fn is_empty(&self) -> bool {
40        self.entries.is_empty()
41    }
42
43    /// Clear all entries.
44    pub fn clear(&self) {
45        self.entries.clear();
46    }
47}
48
49impl Default for InMemoryAuditStorage {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl AuditStorage for InMemoryAuditStorage {
56    fn store(&self, entry: AuditLogEntry) -> Result<()> {
57        self.entries.insert(entry.id.clone(), entry);
58        Ok(())
59    }
60
61    fn query(&self, query: &StorageQuery) -> Result<Vec<AuditLogEntry>> {
62        let mut results: Vec<AuditLogEntry> = self
63            .entries
64            .iter()
65            .map(|e| e.value().clone())
66            .filter(|entry| query.matches(entry))
67            .collect();
68
69        // Sort by timestamp (most recent first)
70        results.sort_by_key(|x| std::cmp::Reverse(x.timestamp));
71
72        // Apply limit
73        if let Some(limit) = query.limit {
74            results.truncate(limit);
75        }
76
77        Ok(results)
78    }
79
80    fn count(&self, query: &StorageQuery) -> Result<usize> {
81        Ok(self
82            .entries
83            .iter()
84            .filter(|e| query.matches(e.value()))
85            .count())
86    }
87}
88
89/// Storage query.
90#[derive(Debug, Clone, Default)]
91pub struct StorageQuery {
92    /// Filter by subject.
93    pub subject: Option<String>,
94    /// Filter by resource.
95    pub resource: Option<String>,
96    /// Filter by event type.
97    pub event_type: Option<crate::audit::AuditEventType>,
98    /// Filter by result.
99    pub result: Option<crate::audit::AuditResult>,
100    /// Filter by tenant ID.
101    pub tenant_id: Option<String>,
102    /// Time range start.
103    pub start_time: Option<chrono::DateTime<chrono::Utc>>,
104    /// Time range end.
105    pub end_time: Option<chrono::DateTime<chrono::Utc>>,
106    /// Maximum number of results.
107    pub limit: Option<usize>,
108}
109
110impl StorageQuery {
111    /// Create new storage query.
112    pub fn new() -> Self {
113        Self::default()
114    }
115
116    /// Filter by subject.
117    pub fn with_subject(mut self, subject: String) -> Self {
118        self.subject = Some(subject);
119        self
120    }
121
122    /// Filter by resource.
123    pub fn with_resource(mut self, resource: String) -> Self {
124        self.resource = Some(resource);
125        self
126    }
127
128    /// Filter by event type.
129    pub fn with_event_type(mut self, event_type: crate::audit::AuditEventType) -> Self {
130        self.event_type = Some(event_type);
131        self
132    }
133
134    /// Filter by result.
135    pub fn with_result(mut self, result: crate::audit::AuditResult) -> Self {
136        self.result = Some(result);
137        self
138    }
139
140    /// Filter by tenant ID.
141    pub fn with_tenant_id(mut self, tenant_id: String) -> Self {
142        self.tenant_id = Some(tenant_id);
143        self
144    }
145
146    /// Set time range.
147    pub fn with_time_range(
148        mut self,
149        start: chrono::DateTime<chrono::Utc>,
150        end: chrono::DateTime<chrono::Utc>,
151    ) -> Self {
152        self.start_time = Some(start);
153        self.end_time = Some(end);
154        self
155    }
156
157    /// Set limit.
158    pub fn with_limit(mut self, limit: usize) -> Self {
159        self.limit = Some(limit);
160        self
161    }
162
163    /// Check if entry matches query.
164    pub fn matches(&self, entry: &AuditLogEntry) -> bool {
165        if let Some(ref subject) = self.subject {
166            if entry.subject.as_ref() != Some(subject) {
167                return false;
168            }
169        }
170
171        if let Some(ref resource) = self.resource {
172            if entry.resource.as_ref() != Some(resource) {
173                return false;
174            }
175        }
176
177        if let Some(event_type) = self.event_type {
178            if entry.event_type != event_type {
179                return false;
180            }
181        }
182
183        if let Some(result) = self.result {
184            if entry.result != result {
185                return false;
186            }
187        }
188
189        if let Some(ref tenant_id) = self.tenant_id {
190            if entry.tenant_id.as_ref() != Some(tenant_id) {
191                return false;
192            }
193        }
194
195        if let Some(start) = self.start_time {
196            if entry.timestamp < start {
197                return false;
198            }
199        }
200
201        if let Some(end) = self.end_time {
202            if entry.timestamp > end {
203                return false;
204            }
205        }
206
207        true
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use crate::audit::{AuditEventType, AuditResult};
215
216    #[test]
217    fn test_in_memory_storage() {
218        let storage = InMemoryAuditStorage::new();
219
220        let entry = AuditLogEntry::new(AuditEventType::Authentication, AuditResult::Success)
221            .with_subject("user-123".to_string());
222
223        storage.store(entry.clone()).expect("Failed to store");
224        assert_eq!(storage.len(), 1);
225
226        let query = StorageQuery::new().with_subject("user-123".to_string());
227        let results = storage.query(&query).expect("Query failed");
228        assert_eq!(results.len(), 1);
229        assert_eq!(results[0].id, entry.id);
230    }
231
232    #[test]
233    fn test_storage_query() {
234        let storage = InMemoryAuditStorage::new();
235
236        let entry1 = AuditLogEntry::new(AuditEventType::Authentication, AuditResult::Success)
237            .with_subject("user-1".to_string());
238        let entry2 = AuditLogEntry::new(AuditEventType::DataAccess, AuditResult::Success)
239            .with_subject("user-2".to_string());
240
241        storage.store(entry1).expect("Failed to store");
242        storage.store(entry2).expect("Failed to store");
243
244        let query = StorageQuery::new().with_event_type(AuditEventType::Authentication);
245        let results = storage.query(&query).expect("Query failed");
246        assert_eq!(results.len(), 1);
247    }
248}