1use crate::{core::UserPermissions, metrics::RoleSystemMetrics};
4use chrono::{DateTime, Utc};
5use dashmap::DashMap;
6use std::collections::HashSet;
7use std::sync::Arc;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum CacheTag {
12 Subject(String),
14 Role(String),
16 ResourceType(String),
18 Action(String),
20 ContextDependent,
22}
23
24#[derive(Debug, Clone)]
26pub struct CacheEntry {
27 pub permissions: UserPermissions,
29 pub tags: HashSet<CacheTag>,
31 pub created_at: DateTime<Utc>,
33}
34
35impl CacheEntry {
36 pub fn new(permissions: UserPermissions, tags: HashSet<CacheTag>) -> Self {
38 Self {
39 permissions,
40 tags,
41 created_at: Utc::now(),
42 }
43 }
44
45 pub fn has_tag(&self, tag: &CacheTag) -> bool {
47 self.tags.contains(tag)
48 }
49
50 pub fn is_expired(&self, ttl_seconds: u64) -> bool {
52 self.permissions.is_expired(ttl_seconds)
53 }
54}
55
56#[derive(Debug)]
58pub struct CacheManager {
59 cache: DashMap<(String, String), CacheEntry>,
61 tag_index: DashMap<CacheTag, HashSet<(String, String)>>,
63 metrics: Arc<RoleSystemMetrics>,
65}
66
67impl CacheManager {
68 pub fn new(metrics: Arc<RoleSystemMetrics>) -> Self {
70 Self {
71 cache: DashMap::new(),
72 tag_index: DashMap::new(),
73 metrics,
74 }
75 }
76
77 pub fn insert(
79 &self,
80 key: (String, String),
81 permissions: UserPermissions,
82 tags: HashSet<CacheTag>,
83 ) {
84 let entry = CacheEntry::new(permissions, tags.clone());
85
86 self.cache.insert(key.clone(), entry);
88
89 for tag in tags {
91 self.tag_index.entry(tag).or_default().insert(key.clone());
92 }
93 }
94
95 pub fn get(&self, key: &(String, String), ttl_seconds: u64) -> Option<UserPermissions> {
97 if let Some(entry) = self.cache.get(key) {
98 if !entry.is_expired(ttl_seconds) {
99 self.metrics.record_cache_hit();
100 return Some(entry.permissions.clone());
101 } else {
102 drop(entry);
104 self.remove_expired_entry(key);
105 }
106 }
107
108 self.metrics.record_cache_miss();
109 None
110 }
111
112 pub fn invalidate_by_tag(&self, tag: &CacheTag) {
114 if let Some(keys) = self.tag_index.get(tag) {
115 let keys_to_remove: Vec<_> = keys.iter().cloned().collect();
116 drop(keys); for key in keys_to_remove {
119 self.remove_entry(&key);
120 }
121 }
122 }
123
124 pub fn invalidate_subject(&self, subject_id: &str) {
126 self.invalidate_by_tag(&CacheTag::Subject(subject_id.to_string()));
127 }
128
129 pub fn invalidate_role(&self, role_name: &str) {
131 self.invalidate_by_tag(&CacheTag::Role(role_name.to_string()));
132 }
133
134 pub fn invalidate_resource_type(&self, resource_type: &str) {
136 self.invalidate_by_tag(&CacheTag::ResourceType(resource_type.to_string()));
137 }
138
139 pub fn invalidate_context_dependent(&self) {
141 self.invalidate_by_tag(&CacheTag::ContextDependent);
142 }
143
144 pub fn cleanup_expired(&self, ttl_seconds: u64) {
146 let expired_keys: Vec<_> = self
147 .cache
148 .iter()
149 .filter(|entry| entry.value().is_expired(ttl_seconds))
150 .map(|entry| entry.key().clone())
151 .collect();
152
153 for key in expired_keys {
154 self.remove_expired_entry(&key);
155 }
156 }
157
158 pub fn stats(&self) -> CacheStats {
160 let total_entries = self.cache.len();
161 let tag_count = self.tag_index.len();
162
163 let mut tags_per_entry = 0;
164 for entry in self.cache.iter() {
165 tags_per_entry += entry.value().tags.len();
166 }
167
168 let avg_tags_per_entry = if total_entries > 0 {
169 tags_per_entry as f64 / total_entries as f64
170 } else {
171 0.0
172 };
173
174 CacheStats {
175 total_entries,
176 tag_count,
177 avg_tags_per_entry,
178 }
179 }
180
181 pub fn clear(&self) {
183 self.cache.clear();
184 self.tag_index.clear();
185 }
186
187 pub fn generate_tags(
189 subject_id: &str,
190 action: &str,
191 resource_type: &str,
192 roles: &[String],
193 has_context: bool,
194 ) -> HashSet<CacheTag> {
195 let mut tags = HashSet::new();
196
197 tags.insert(CacheTag::Subject(subject_id.to_string()));
199
200 tags.insert(CacheTag::Action(action.to_string()));
202
203 tags.insert(CacheTag::ResourceType(resource_type.to_string()));
205
206 for role in roles {
208 tags.insert(CacheTag::Role(role.clone()));
209 }
210
211 if has_context {
213 tags.insert(CacheTag::ContextDependent);
214 }
215
216 tags
217 }
218
219 fn remove_entry(&self, key: &(String, String)) {
222 if let Some((_, entry)) = self.cache.remove(key) {
223 for tag in &entry.tags {
225 if let Some(mut keys) = self.tag_index.get_mut(tag) {
226 keys.remove(key);
227 if keys.is_empty() {
228 drop(keys);
229 self.tag_index.remove(tag);
230 }
231 }
232 }
233 }
234 }
235
236 fn remove_expired_entry(&self, key: &(String, String)) {
237 self.remove_entry(key);
238 }
239}
240
241#[derive(Debug, Clone)]
243pub struct CacheStats {
244 pub total_entries: usize,
246 pub tag_count: usize,
248 pub avg_tags_per_entry: f64,
250}
251
252pub trait CacheInvalidation {
254 fn invalidate_subject_cache(&self, subject_id: &str);
256
257 fn invalidate_role_cache(&self, role_name: &str);
259
260 fn invalidate_resource_type_cache(&self, resource_type: &str);
262
263 fn cleanup_expired_cache(&self, ttl_seconds: u64);
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use crate::metrics::RoleSystemMetrics;
271 use std::collections::HashMap;
272 use std::sync::Arc;
273
274 #[test]
275 fn test_cache_manager_basic_operations() {
276 let metrics = Arc::new(RoleSystemMetrics::new());
277 let cache = CacheManager::new(metrics.clone());
278
279 let key = ("user1".to_string(), "read:documents".to_string());
280 let mut permissions_map = HashMap::new();
281 permissions_map.insert("read".to_string(), crate::core::AccessResult::Granted);
282 let permissions = UserPermissions::new(permissions_map);
283
284 let mut tags = HashSet::new();
285 tags.insert(CacheTag::Subject("user1".to_string()));
286 tags.insert(CacheTag::Action("read".to_string()));
287 tags.insert(CacheTag::ResourceType("documents".to_string()));
288
289 cache.insert(key.clone(), permissions.clone(), tags);
291
292 let retrieved = cache.get(&key, 300).unwrap();
294 assert_eq!(
295 retrieved.computed_permissions.len(),
296 permissions.computed_permissions.len()
297 );
298
299 let stats = cache.stats();
301 assert_eq!(stats.total_entries, 1);
302 assert_eq!(stats.tag_count, 3);
303 }
304
305 #[test]
306 fn test_cache_invalidation_by_tag() {
307 let metrics = Arc::new(RoleSystemMetrics::new());
308 let cache = CacheManager::new(metrics);
309
310 let key1 = ("user1".to_string(), "read:documents".to_string());
311 let key2 = ("user2".to_string(), "read:documents".to_string());
312
313 let permissions = UserPermissions::new(HashMap::new());
314
315 let mut tags1 = HashSet::new();
316 tags1.insert(CacheTag::Subject("user1".to_string()));
317 tags1.insert(CacheTag::ResourceType("documents".to_string()));
318
319 let mut tags2 = HashSet::new();
320 tags2.insert(CacheTag::Subject("user2".to_string()));
321 tags2.insert(CacheTag::ResourceType("documents".to_string()));
322
323 cache.insert(key1.clone(), permissions.clone(), tags1);
324 cache.insert(key2.clone(), permissions.clone(), tags2);
325
326 assert_eq!(cache.stats().total_entries, 2);
327
328 cache.invalidate_resource_type("documents");
330
331 assert_eq!(cache.stats().total_entries, 0);
332 }
333
334 #[test]
335 fn test_cache_tag_generation() {
336 let tags = CacheManager::generate_tags(
337 "user1",
338 "read",
339 "documents",
340 &["reader".to_string(), "user".to_string()],
341 true,
342 );
343
344 assert!(tags.contains(&CacheTag::Subject("user1".to_string())));
345 assert!(tags.contains(&CacheTag::Action("read".to_string())));
346 assert!(tags.contains(&CacheTag::ResourceType("documents".to_string())));
347 assert!(tags.contains(&CacheTag::Role("reader".to_string())));
348 assert!(tags.contains(&CacheTag::Role("user".to_string())));
349 assert!(tags.contains(&CacheTag::ContextDependent));
350 }
351
352 #[test]
353 fn test_expired_cache_cleanup() {
354 let metrics = Arc::new(RoleSystemMetrics::new());
355 let cache = CacheManager::new(metrics);
356
357 let key = ("user1".to_string(), "read:documents".to_string());
358
359 let mut permissions_map = HashMap::new();
361 permissions_map.insert("read".to_string(), crate::core::AccessResult::Granted);
362 let mut permissions = UserPermissions::new(permissions_map);
363 permissions.last_updated = Utc::now() - chrono::Duration::seconds(400); let tags = HashSet::new();
366 cache.insert(key.clone(), permissions, tags);
367
368 assert_eq!(cache.stats().total_entries, 1);
369
370 cache.cleanup_expired(300);
372
373 assert_eq!(cache.stats().total_entries, 0);
374 }
375}