1use crate::*;
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12
13pub struct InMemoryRebacManager {
16 tuples_by_subject: Arc<RwLock<HashMap<String, Vec<RelationTuple>>>>,
18
19 tuples_by_object: Arc<RwLock<HashMap<String, Vec<RelationTuple>>>>,
21
22 graph: Arc<RwLock<RelationshipGraph>>,
24}
25
26#[derive(Debug, Default)]
28struct RelationshipGraph {
29 edges: HashMap<(String, String), Vec<String>>,
31}
32
33impl RelationshipGraph {
34 fn add_edge(&mut self, subject: String, relation: String, object: String) {
35 self.edges
36 .entry((subject, relation))
37 .or_default()
38 .push(object);
39 }
40
41 fn remove_edge(&mut self, subject: &str, relation: &str, object: &str) {
42 if let Some(objects) = self
43 .edges
44 .get_mut(&(subject.to_string(), relation.to_string()))
45 {
46 objects.retain(|o| o != object);
47 }
48 }
49
50 fn has_path(&self, subject: &str, relation: &str, object: &str) -> bool {
52 if let Some(objects) = self.edges.get(&(subject.to_string(), relation.to_string())) {
54 if objects.contains(&object.to_string()) {
55 return true;
56 }
57 }
58
59 false
62 }
63}
64
65impl InMemoryRebacManager {
66 pub fn new() -> Self {
68 Self {
69 tuples_by_subject: Arc::new(RwLock::new(HashMap::new())),
70 tuples_by_object: Arc::new(RwLock::new(HashMap::new())),
71 graph: Arc::new(RwLock::new(RelationshipGraph::default())),
72 }
73 }
74
75 pub async fn with_tuples(tuples: Vec<RelationTuple>) -> Result<Self> {
77 let manager = Self::new();
78 for tuple in tuples {
79 manager.add_tuple(tuple).await?;
80 }
81 Ok(manager)
82 }
83
84 pub async fn add_tuple(&self, tuple: RelationTuple) -> Result<()> {
86 let subject_key = tuple.subject.to_string();
87 let object_key = format!("{}:{}", tuple.namespace, tuple.object_id);
88
89 {
91 let mut tuples_by_subject = self.tuples_by_subject.write().await;
92 let subject_tuples = tuples_by_subject
93 .entry(subject_key.clone())
94 .or_insert_with(Vec::new);
95
96 if !subject_tuples.iter().any(|t| {
98 t.namespace == tuple.namespace
99 && t.object_id == tuple.object_id
100 && t.relation == tuple.relation
101 && t.subject == tuple.subject
102 }) {
103 subject_tuples.push(tuple.clone());
104 }
105 }
106
107 {
109 let mut tuples_by_object = self.tuples_by_object.write().await;
110 let object_tuples = tuples_by_object
111 .entry(object_key.clone())
112 .or_insert_with(Vec::new);
113
114 if !object_tuples.iter().any(|t| {
116 t.namespace == tuple.namespace
117 && t.object_id == tuple.object_id
118 && t.relation == tuple.relation
119 && t.subject == tuple.subject
120 }) {
121 object_tuples.push(tuple.clone());
122 }
123 }
124
125 {
127 let mut graph = self.graph.write().await;
128 graph.add_edge(subject_key, tuple.relation.clone(), object_key);
129 }
130
131 Ok(())
132 }
133
134 pub async fn remove_tuple(&self, tuple: &RelationTuple) -> Result<()> {
136 let subject_key = tuple.subject.to_string();
137 let object_key = format!("{}:{}", tuple.namespace, tuple.object_id);
138
139 {
141 let mut tuples_by_subject = self.tuples_by_subject.write().await;
142 if let Some(tuples) = tuples_by_subject.get_mut(&subject_key) {
143 tuples.retain(|t| {
144 !(t.namespace == tuple.namespace
145 && t.object_id == tuple.object_id
146 && t.relation == tuple.relation
147 && t.subject == tuple.subject)
148 });
149 }
150 }
151
152 {
154 let mut tuples_by_object = self.tuples_by_object.write().await;
155 if let Some(tuples) = tuples_by_object.get_mut(&object_key) {
156 tuples.retain(|t| {
157 !(t.namespace == tuple.namespace
158 && t.object_id == tuple.object_id
159 && t.relation == tuple.relation
160 && t.subject == tuple.subject)
161 });
162 }
163 }
164
165 {
167 let mut graph = self.graph.write().await;
168 graph.remove_edge(&subject_key, &tuple.relation, &object_key);
169 }
170
171 Ok(())
172 }
173
174 pub async fn check(&self, request: &CheckRequest) -> Result<CheckResponse> {
176 let subject_key = request.subject.to_string();
177 let object_key = format!("{}:{}", request.namespace, request.object_id);
178
179 let graph = self.graph.read().await;
181 let has_relation = graph.has_path(&subject_key, &request.relation, &object_key);
182
183 if !has_relation {
184 return Ok(CheckResponse {
185 allowed: false,
186 cached: false,
187 });
188 }
189
190 let tuples_by_subject = self.tuples_by_subject.read().await;
192 if let Some(tuples) = tuples_by_subject.get(&subject_key) {
193 for tuple in tuples {
194 if tuple.namespace == request.namespace
195 && tuple.object_id == request.object_id
196 && tuple.relation == request.relation
197 && !tuple.is_condition_satisfied()
198 {
199 return Ok(CheckResponse {
200 allowed: false,
201 cached: false,
202 });
203 }
204 }
205 }
206
207 Ok(CheckResponse {
208 allowed: true,
209 cached: false,
210 })
211 }
212
213 pub async fn list_subject_tuples(&self, subject: &Subject) -> Result<Vec<RelationTuple>> {
215 let subject_key = subject.to_string();
216 let tuples_by_subject = self.tuples_by_subject.read().await;
217 Ok(tuples_by_subject
218 .get(&subject_key)
219 .cloned()
220 .unwrap_or_default())
221 }
222
223 pub async fn list_object_tuples(
225 &self,
226 namespace: &str,
227 object_id: &str,
228 ) -> Result<Vec<RelationTuple>> {
229 let object_key = format!("{}:{}", namespace, object_id);
230 let tuples_by_object = self.tuples_by_object.read().await;
231 Ok(tuples_by_object
232 .get(&object_key)
233 .cloned()
234 .unwrap_or_default())
235 }
236
237 pub async fn batch_check(&self, requests: &[CheckRequest]) -> Result<Vec<CheckResponse>> {
239 let mut results = Vec::with_capacity(requests.len());
240 for request in requests {
241 results.push(self.check(request).await?);
242 }
243 Ok(results)
244 }
245}
246
247impl Default for InMemoryRebacManager {
248 fn default() -> Self {
249 Self::new()
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[tokio::test]
258 async fn test_basic_relationship() {
259 let manager = InMemoryRebacManager::new();
260
261 let tuple = RelationTuple::new(
263 "document",
264 "can_read",
265 "public",
266 Subject::User("alice".to_string()),
267 );
268 manager.add_tuple(tuple).await.unwrap();
269
270 let request = CheckRequest {
272 namespace: "document".to_string(),
273 object_id: "public".to_string(),
274 relation: "can_read".to_string(),
275 subject: Subject::User("alice".to_string()),
276 context: None,
277 };
278 let response = manager.check(&request).await.unwrap();
279 assert!(response.allowed);
280
281 let request = CheckRequest {
283 namespace: "document".to_string(),
284 object_id: "public".to_string(),
285 relation: "can_write".to_string(),
286 subject: Subject::User("alice".to_string()),
287 context: None,
288 };
289 let response = manager.check(&request).await.unwrap();
290 assert!(!response.allowed);
291 }
292
293 #[tokio::test]
294 async fn test_list_tuples() {
295 let manager = InMemoryRebacManager::new();
296
297 manager
299 .add_tuple(RelationTuple::new(
300 "document",
301 "can_read",
302 "public",
303 Subject::User("alice".to_string()),
304 ))
305 .await
306 .unwrap();
307 manager
308 .add_tuple(RelationTuple::new(
309 "document",
310 "can_write",
311 "private",
312 Subject::User("alice".to_string()),
313 ))
314 .await
315 .unwrap();
316
317 let tuples = manager
319 .list_subject_tuples(&Subject::User("alice".to_string()))
320 .await
321 .unwrap();
322 assert_eq!(tuples.len(), 2);
323 }
324
325 #[tokio::test]
326 async fn test_time_based_condition() {
327 let manager = InMemoryRebacManager::new();
328
329 let tuple = RelationTuple::with_condition(
331 "document",
332 "temporary",
333 "can_read",
334 Subject::User("alice".to_string()),
335 RelationshipCondition::TimeWindow {
336 not_before: Some(chrono::Utc::now() - chrono::Duration::hours(2)),
337 not_after: Some(chrono::Utc::now() - chrono::Duration::hours(1)),
338 },
339 );
340 manager.add_tuple(tuple).await.unwrap();
341
342 let request = CheckRequest {
344 namespace: "document".to_string(),
345 object_id: "temporary".to_string(),
346 relation: "can_read".to_string(),
347 subject: Subject::User("alice".to_string()),
348 context: None,
349 };
350 let response = manager.check(&request).await.unwrap();
351 assert!(!response.allowed);
352 }
353
354 #[tokio::test]
355 async fn test_remove_tuple() {
356 let manager = InMemoryRebacManager::new();
357
358 let tuple = RelationTuple::new(
360 "document",
361 "can_read",
362 "public",
363 Subject::User("alice".to_string()),
364 );
365 manager.add_tuple(tuple.clone()).await.unwrap();
366
367 let request = CheckRequest {
369 namespace: "document".to_string(),
370 object_id: "public".to_string(),
371 relation: "can_read".to_string(),
372 subject: Subject::User("alice".to_string()),
373 context: None,
374 };
375 let response = manager.check(&request).await.unwrap();
376 assert!(response.allowed);
377
378 manager.remove_tuple(&tuple).await.unwrap();
380
381 let response = manager.check(&request).await.unwrap();
383 assert!(!response.allowed);
384 }
385
386 #[tokio::test]
387 async fn test_batch_check() {
388 let manager = InMemoryRebacManager::new();
389
390 manager
392 .add_tuple(RelationTuple::new(
393 "document",
394 "can_read",
395 "doc1",
396 Subject::User("alice".to_string()),
397 ))
398 .await
399 .unwrap();
400 manager
401 .add_tuple(RelationTuple::new(
402 "document",
403 "can_read",
404 "doc2",
405 Subject::User("alice".to_string()),
406 ))
407 .await
408 .unwrap();
409
410 let requests = vec![
412 CheckRequest {
413 namespace: "document".to_string(),
414 object_id: "doc1".to_string(),
415 relation: "can_read".to_string(),
416 subject: Subject::User("alice".to_string()),
417 context: None,
418 },
419 CheckRequest {
420 namespace: "document".to_string(),
421 object_id: "doc2".to_string(),
422 relation: "can_read".to_string(),
423 subject: Subject::User("alice".to_string()),
424 context: None,
425 },
426 CheckRequest {
427 namespace: "document".to_string(),
428 object_id: "doc3".to_string(),
429 relation: "can_read".to_string(),
430 subject: Subject::User("alice".to_string()),
431 context: None,
432 },
433 ];
434
435 let responses = manager.batch_check(&requests).await.unwrap();
436 assert_eq!(responses.len(), 3);
437 assert!(responses[0].allowed);
438 assert!(responses[1].allowed);
439 assert!(!responses[2].allowed);
440 }
441}