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