delegating_policy/
delegating_policy.rs1use async_trait::async_trait;
20use gatehouse::{
21 DelegatingPolicy, EvalCtx, EvaluationSession, PermissionChecker, Policy, PolicyBuilder,
22 PolicyDomain, PolicyEvalResult,
23};
24use std::borrow::Cow;
25use uuid::Uuid;
26
27#[derive(Debug, Clone)]
30struct DocUser {
31 id: Uuid,
32 is_admin: bool,
33}
34
35#[derive(Debug, Clone)]
36struct Document {
37 owner_id: Uuid,
38}
39
40#[derive(Debug, Clone)]
41struct EditDoc;
42
43struct DocumentDomain;
44
45impl PolicyDomain for DocumentDomain {
46 type Subject = DocUser;
47 type Action = EditDoc;
48 type Resource = Document;
49 type Context = ();
50}
51
52fn document_checker() -> PermissionChecker<DocumentDomain> {
56 let owner = PolicyBuilder::<DocumentDomain>::new("DocumentOwner")
57 .when(|user, _action, document, _ctx| user.id == document.owner_id)
58 .build();
59 let admin = PolicyBuilder::<DocumentDomain>::new("DocumentAdmin")
60 .subjects(|user| user.is_admin)
61 .build();
62
63 let mut checker = PermissionChecker::named("DocumentChecker");
64 checker.add_policy(owner);
65 checker.add_policy(admin);
66 checker
67}
68
69#[derive(Debug, Clone)]
72struct Principal {
73 user_id: Uuid,
74 is_admin: bool,
75}
76
77#[derive(Debug, Clone)]
78struct Comment {
79 author_id: Uuid,
80 document: Document,
83}
84
85#[derive(Debug, Clone)]
86struct EditComment;
87
88struct CommentDomain;
89
90impl PolicyDomain for CommentDomain {
91 type Subject = Principal;
92 type Action = EditComment;
93 type Resource = Comment;
94 type Context = ();
95}
96
97struct AuthorCanEditComment;
99
100#[async_trait]
101impl Policy<CommentDomain> for AuthorCanEditComment {
102 async fn evaluate(&self, ctx: &EvalCtx<'_, CommentDomain>) -> PolicyEvalResult {
103 if ctx.subject.user_id == ctx.resource.author_id {
104 ctx.grant("subject is the comment author")
105 } else {
106 ctx.not_applicable("subject is not the comment author")
107 }
108 }
109 fn policy_type(&self) -> Cow<'static, str> {
110 Cow::Borrowed("AuthorCanEditComment")
111 }
112}
113
114fn comment_checker() -> PermissionChecker<CommentDomain> {
117 let inherit_from_document = DelegatingPolicy::new(
121 "InheritDocumentEdit",
122 document_checker(),
123 |principal: &Principal| DocUser {
124 id: principal.user_id,
125 is_admin: principal.is_admin,
126 },
127 |_action: &EditComment| EditDoc,
128 |_subject: &Principal, _action: &EditComment, comment: &Comment, _ctx: &()| {
129 comment.document.clone()
130 },
131 |_subject, _action, _ctx| (),
132 );
133
134 let mut checker = PermissionChecker::named("CommentChecker");
135 checker.add_policy(AuthorCanEditComment);
136 checker.add_policy(inherit_from_document);
137 checker
138}
139
140#[tokio::main]
143async fn main() {
144 let owner_id = Uuid::new_v4();
145 let author_id = Uuid::new_v4();
146
147 let comment = Comment {
148 author_id,
149 document: Document { owner_id },
150 };
151
152 let author = Principal {
153 user_id: author_id,
154 is_admin: false,
155 };
156 let document_owner = Principal {
157 user_id: owner_id,
158 is_admin: false,
159 };
160 let admin = Principal {
161 user_id: Uuid::new_v4(),
162 is_admin: true,
163 };
164 let stranger = Principal {
165 user_id: Uuid::new_v4(),
166 is_admin: false,
167 };
168
169 let checker = comment_checker();
170 let session = EvaluationSession::empty();
171 let action = EditComment;
172 let context = ();
173
174 let cases = [
175 ("author", &author),
176 ("document owner (not author)", &document_owner),
177 ("admin (not author/owner)", &admin),
178 ("unrelated user", &stranger),
179 ];
180 for (who, principal) in cases {
181 let granted = checker
182 .bind(&session, principal, &action, &context)
183 .check(&comment)
184 .await
185 .is_granted();
186 println!(
187 "{who:<28} can edit the comment? {}",
188 if granted { "yes" } else { "no" }
189 );
190 }
191
192 println!("\nTrace — document owner (not the author) editing the comment:");
196 let decision = checker
197 .bind(&session, &document_owner, &action, &context)
198 .check(&comment)
199 .await;
200 println!("{}", decision.display_trace());
201
202 assert!(checker
203 .bind(&session, &author, &action, &context)
204 .check(&comment)
205 .await
206 .is_granted());
207 assert!(checker
208 .bind(&session, &document_owner, &action, &context)
209 .check(&comment)
210 .await
211 .is_granted());
212 assert!(checker
213 .bind(&session, &admin, &action, &context)
214 .check(&comment)
215 .await
216 .is_granted());
217 assert!(!checker
218 .bind(&session, &stranger, &action, &context)
219 .check(&comment)
220 .await
221 .is_granted());
222}