in_ram_rebac/
in_ram_rebac.rs1use async_trait::async_trait;
9use dashmap::DashSet;
10use gatehouse::{
11 EvaluationSession, FactLoadResult, FactRegistry, FactSource, PermissionChecker, PolicyDomain,
12 RebacPolicy, RelationshipQuery,
13};
14use std::fmt;
15use std::sync::Arc;
16use uuid::Uuid;
17
18type RelationshipKey = RelationshipQuery<Uuid, Uuid, Relation>;
19
20#[derive(Debug, Clone)]
21struct User {
22 id: Uuid,
23}
24
25#[derive(Debug, Clone)]
26struct Document {
27 id: Uuid,
28 title: &'static str,
29}
30
31#[derive(Debug, Clone)]
32struct View;
33
34struct DocumentDomain;
35
36impl PolicyDomain for DocumentDomain {
37 type Subject = User;
38 type Action = View;
39 type Resource = Document;
40 type Context = ();
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44enum Relation {
45 Viewer,
46 Editor,
47}
48
49impl fmt::Display for Relation {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::Viewer => f.write_str("viewer"),
53 Self::Editor => f.write_str("editor"),
54 }
55 }
56}
57
58#[derive(Default)]
59struct InRamRelationships {
60 grants: DashSet<RelationshipKey>,
61}
62
63impl InRamRelationships {
64 fn grant(&self, subject_id: Uuid, resource_id: Uuid, relation: Relation) {
65 self.grants.insert(RelationshipKey {
66 subject_id,
67 resource_id,
68 relation,
69 });
70 }
71}
72
73#[async_trait]
74impl FactSource<RelationshipKey> for InRamRelationships {
75 async fn load_many(&self, keys: &[RelationshipKey]) -> Vec<FactLoadResult<bool>> {
76 keys.iter()
77 .map(|key| FactLoadResult::Found(self.grants.contains(key)))
78 .collect()
79 }
80}
81
82fn request_session(registry: &FactRegistry) -> EvaluationSession {
83 registry.session()
84}
85
86fn build_checker() -> PermissionChecker<DocumentDomain> {
87 let mut checker = PermissionChecker::new();
88 checker.add_policy(RebacPolicy::<DocumentDomain, Uuid, Uuid, Relation>::new(
89 |user: &User| user.id,
90 |document: &Document| document.id,
91 Relation::Viewer,
92 ));
93 checker
94}
95
96#[tokio::main]
97async fn main() {
98 let user = User { id: Uuid::new_v4() };
99 let documents = vec![
100 Document {
101 id: Uuid::new_v4(),
102 title: "roadmap",
103 },
104 Document {
105 id: Uuid::new_v4(),
106 title: "incident report",
107 },
108 Document {
109 id: Uuid::new_v4(),
110 title: "finance plan",
111 },
112 ];
113
114 let store = Arc::new(InRamRelationships::default());
115 store.grant(user.id, documents[0].id, Relation::Viewer);
116 store.grant(user.id, documents[1].id, Relation::Viewer);
117 store.grant(user.id, documents[1].id, Relation::Editor);
121 let relationships: Arc<dyn FactSource<RelationshipKey>> = store;
122 let registry = FactRegistry::builder()
123 .with_arc::<RelationshipKey>(Arc::clone(&relationships))
124 .build();
125
126 let checker = build_checker();
127 let context = ();
128
129 let first_request = request_session(®istry);
130 let visible = checker
131 .bind(&first_request, &user, &View, &context)
132 .filter(documents.clone())
133 .await;
134 println!(
135 "batch list — visible documents: {:?}",
136 visible
137 .iter()
138 .map(|document| document.title)
139 .collect::<Vec<_>>()
140 );
141
142 let second_request = request_session(®istry);
145 let can_view_finance = checker
146 .bind(&second_request, &user, &View, &context)
147 .check(&documents[2])
148 .await;
149 println!(
150 "single check — can view '{}'? {}",
151 documents[2].title,
152 if can_view_finance.is_granted() {
153 "yes"
154 } else {
155 "no"
156 }
157 );
158 assert!(!can_view_finance.is_granted());
159
160 let shared = registry.clone();
161 let concurrent_requests = (0..4)
162 .map(|_| {
163 let checker = checker.clone();
164 let user = user.clone();
165 let documents = documents.clone();
166 let registry = shared.clone();
167 tokio::spawn(async move {
168 let session = request_session(®istry);
169 let context = ();
170 checker
171 .bind(&session, &user, &View, &context)
172 .filter(documents)
173 .await
174 .len()
175 })
176 })
177 .collect::<Vec<_>>();
178
179 println!("\n4 concurrent requests sharing one FactSource (each builds its own session):");
180 for (index, request) in concurrent_requests.into_iter().enumerate() {
181 let visible_count = request.await.unwrap();
182 println!(" request {index}: {visible_count} visible document(s)");
183 assert_eq!(visible_count, 2);
184 }
185}