1use async_trait::async_trait;
20use gatehouse::*;
21use std::collections::HashSet;
22use std::fmt;
23use uuid::Uuid;
24
25#[derive(Debug, Clone)]
26struct User {
27 id: Uuid,
28 name: &'static str,
29}
30
31#[derive(Debug, Clone)]
32struct Project {
33 id: Uuid,
34 name: &'static str,
35}
36
37#[derive(Debug, Clone)]
38struct EditAction;
39
40struct ProjectDomain;
41
42impl PolicyDomain for ProjectDomain {
43 type Subject = User;
44 type Action = EditAction;
45 type Resource = Project;
46 type Context = ();
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50enum Relation {
51 Owner,
52 Contributor,
53 Viewer,
54}
55
56impl fmt::Display for Relation {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 Relation::Owner => write!(f, "owner"),
60 Relation::Contributor => write!(f, "contributor"),
61 Relation::Viewer => write!(f, "viewer"),
62 }
63 }
64}
65
66type ProjectRelationship = RelationshipQuery<Uuid, Uuid, Relation>;
67
68#[derive(Debug, Clone)]
72struct ProjectRelationshipSource {
73 relationships: HashSet<ProjectRelationship>,
74 fail: bool,
75}
76
77impl ProjectRelationshipSource {
78 fn new(relationships: HashSet<ProjectRelationship>) -> Self {
79 Self {
80 relationships,
81 fail: false,
82 }
83 }
84
85 fn with_error(mut self) -> Self {
87 self.fail = true;
88 self
89 }
90}
91
92#[async_trait]
93impl FactSource<ProjectRelationship> for ProjectRelationshipSource {
94 async fn load_many(&self, keys: &[ProjectRelationship]) -> Vec<FactLoadResult<bool>> {
95 keys.iter()
96 .map(|key| {
97 println!(
98 " loading fact: subject={} relation={} resource={}",
99 key.subject_id, key.relation, key.resource_id
100 );
101 if self.fail {
102 FactLoadResult::Error(FactLoadError::backend_message(
103 "simulated relationship store error",
104 ))
105 } else {
106 FactLoadResult::Found(self.relationships.contains(key))
107 }
108 })
109 .collect()
110 }
111}
112
113#[tokio::main]
114async fn main() {
115 println!("=== ReBAC Policy Example ===\n");
116
117 let owner = User {
118 id: Uuid::new_v4(),
119 name: "Alice",
120 };
121 let contributor = User {
122 id: Uuid::new_v4(),
123 name: "Bob",
124 };
125 let viewer = User {
126 id: Uuid::new_v4(),
127 name: "Charlie",
128 };
129 let outsider = User {
130 id: Uuid::new_v4(),
131 name: "Dave",
132 };
133 let project = Project {
134 id: Uuid::new_v4(),
135 name: "Sample Project",
136 };
137
138 let relationships = HashSet::from([
139 ProjectRelationship {
140 subject_id: owner.id,
141 resource_id: project.id,
142 relation: Relation::Owner,
143 },
144 ProjectRelationship {
145 subject_id: contributor.id,
146 resource_id: project.id,
147 relation: Relation::Contributor,
148 },
149 ProjectRelationship {
150 subject_id: viewer.id,
151 resource_id: project.id,
152 relation: Relation::Viewer,
153 },
154 ]);
155
156 let registry = FactRegistry::builder()
157 .with::<ProjectRelationship, _>(ProjectRelationshipSource::new(relationships.clone()))
158 .build();
159 let session = registry.session();
160
161 let mut checker = PermissionChecker::<ProjectDomain>::new();
164 checker.add_policy(RebacPolicy::<ProjectDomain, Uuid, Uuid, Relation>::new(
165 |user: &User| user.id,
166 |project: &Project| project.id,
167 Relation::Owner,
168 ));
169 checker.add_policy(RebacPolicy::<ProjectDomain, Uuid, Uuid, Relation>::new(
170 |user: &User| user.id,
171 |project: &Project| project.id,
172 Relation::Contributor,
173 ));
174
175 let cases = [
177 (&owner, "owner", true),
178 (&contributor, "contributor", true),
179 (&viewer, "viewer", false),
180 (&outsider, "none", false),
181 ];
182 for (user, held, expected_granted) in cases {
183 println!("Can {} ({held}) edit {}?", user.name, project.name);
184 let decision = checker
185 .bind(&session, user, &EditAction, &())
186 .check(&project)
187 .await;
188 println!(
189 " -> {}\n",
190 if decision.is_granted() {
191 "GRANTED"
192 } else {
193 "DENIED"
194 }
195 );
196 assert_eq!(decision.is_granted(), expected_granted);
197 }
198
199 println!("Why {} is denied:", viewer.name);
205 let decision = checker
206 .bind(&session, &viewer, &EditAction, &())
207 .check(&project)
208 .await;
209 println!("{}\n", decision.display_trace());
210
211 println!("=== Error During Relationship Loading ===\n");
212
213 let error_registry = FactRegistry::builder()
216 .with::<ProjectRelationship, _>(ProjectRelationshipSource::new(relationships).with_error())
217 .build();
218 let error_session = error_registry.session();
219 let decision = checker
220 .bind(&error_session, &owner, &EditAction, &())
221 .check(&project)
222 .await;
223 println!("{}", decision.display_trace());
224 decision.assert_denied();
225 decision.assert_trace_contains("simulated relationship store error");
226}