oxigdal_security/audit/
storage.rs1use crate::audit::AuditLogEntry;
4use crate::error::Result;
5use dashmap::DashMap;
6use std::sync::Arc;
7
8pub trait AuditStorage: Send + Sync {
10 fn store(&self, entry: AuditLogEntry) -> Result<()>;
12
13 fn query(&self, query: &StorageQuery) -> Result<Vec<AuditLogEntry>>;
15
16 fn count(&self, query: &StorageQuery) -> Result<usize>;
18}
19
20pub struct InMemoryAuditStorage {
22 entries: Arc<DashMap<String, AuditLogEntry>>,
23}
24
25impl InMemoryAuditStorage {
26 pub fn new() -> Self {
28 Self {
29 entries: Arc::new(DashMap::new()),
30 }
31 }
32
33 pub fn len(&self) -> usize {
35 self.entries.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
40 self.entries.is_empty()
41 }
42
43 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 results.sort_by_key(|x| std::cmp::Reverse(x.timestamp));
71
72 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#[derive(Debug, Clone, Default)]
91pub struct StorageQuery {
92 pub subject: Option<String>,
94 pub resource: Option<String>,
96 pub event_type: Option<crate::audit::AuditEventType>,
98 pub result: Option<crate::audit::AuditResult>,
100 pub tenant_id: Option<String>,
102 pub start_time: Option<chrono::DateTime<chrono::Utc>>,
104 pub end_time: Option<chrono::DateTime<chrono::Utc>>,
106 pub limit: Option<usize>,
108}
109
110impl StorageQuery {
111 pub fn new() -> Self {
113 Self::default()
114 }
115
116 pub fn with_subject(mut self, subject: String) -> Self {
118 self.subject = Some(subject);
119 self
120 }
121
122 pub fn with_resource(mut self, resource: String) -> Self {
124 self.resource = Some(resource);
125 self
126 }
127
128 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 pub fn with_result(mut self, result: crate::audit::AuditResult) -> Self {
136 self.result = Some(result);
137 self
138 }
139
140 pub fn with_tenant_id(mut self, tenant_id: String) -> Self {
142 self.tenant_id = Some(tenant_id);
143 self
144 }
145
146 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 pub fn with_limit(mut self, limit: usize) -> Self {
159 self.limit = Some(limit);
160 self
161 }
162
163 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}