policy_builder/
policy_builder.rs1use gatehouse::*;
15
16#[derive(Debug, Clone)]
18pub struct GroupPermission {
19 pub scope: &'static str,
21 pub entity: String,
23}
24
25#[derive(Debug, Clone)]
26pub struct StaffUser {
27 pub name: &'static str,
28 pub permissions: Vec<GroupPermission>,
29}
30
31#[derive(Debug, Clone)]
33pub struct Organization {
34 pub id: String,
35}
36
37#[derive(Debug, Clone, Copy)]
38pub enum AdminAction {
39 EditUserSettings,
40 EditOrgSettings,
41}
42
43impl AdminAction {
44 fn required_scope(self) -> &'static str {
45 match self {
46 Self::EditUserSettings => "edit_user_settings",
47 Self::EditOrgSettings => "edit_org_settings",
48 }
49 }
50}
51
52struct AdminDomain;
53
54impl PolicyDomain for AdminDomain {
55 type Subject = StaffUser;
56 type Action = AdminAction;
57 type Resource = Organization;
58 type Context = ();
59}
60
61fn scoped_permission_policy() -> Box<dyn Policy<AdminDomain>> {
65 PolicyBuilder::<AdminDomain>::new("ScopedPermission")
67 .when(
68 |user: &StaffUser, action: &AdminAction, org: &Organization, _ctx: &()| {
69 user.permissions
70 .iter()
71 .any(|p| p.scope == action.required_scope() && p.entity == org.id)
72 },
73 )
74 .build()
75}
76
77fn global_admin_policy() -> Box<dyn Policy<AdminDomain>> {
80 PolicyBuilder::<AdminDomain>::new("GlobalAdmin")
81 .subjects(|user: &StaffUser| user.permissions.iter().any(|p| p.scope == "global_admin"))
82 .build()
83}
84
85#[tokio::main]
86async fn main() {
87 let mut checker = PermissionChecker::<AdminDomain>::new();
88 checker.add_policy(scoped_permission_policy());
89 checker.add_policy(global_admin_policy());
90
91 let org1 = Organization { id: "org-1".into() };
92 let org2 = Organization { id: "org-2".into() };
93
94 let org1_admin = StaffUser {
95 name: "org1-admin",
96 permissions: vec![GroupPermission {
97 scope: "edit_user_settings",
98 entity: "org-1".into(),
99 }],
100 };
101 let org2_admin = StaffUser {
102 name: "org2-admin",
103 permissions: vec![GroupPermission {
104 scope: "edit_user_settings",
105 entity: "org-2".into(),
106 }],
107 };
108 let no_grants = StaffUser {
109 name: "no-grants",
110 permissions: vec![],
111 };
112 let global_admin = StaffUser {
113 name: "global-admin",
114 permissions: vec![GroupPermission {
115 scope: "global_admin",
116 entity: String::new(),
117 }],
118 };
119
120 let cases = [
122 (&org1_admin, AdminAction::EditUserSettings, &org1, true),
124 (&org2_admin, AdminAction::EditUserSettings, &org1, false),
126 (&org1_admin, AdminAction::EditOrgSettings, &org1, false),
127 (&org2_admin, AdminAction::EditUserSettings, &org2, true),
128 (&no_grants, AdminAction::EditUserSettings, &org1, false),
129 (&global_admin, AdminAction::EditOrgSettings, &org1, true),
131 ];
132
133 for (user, action, org, expected_granted) in cases {
134 let session = EvaluationSession::empty();
135 let decision = checker.bind(&session, user, &action, &()).check(org).await;
136 println!(
137 "{:<12} {:?} on {}: {}",
138 user.name,
139 action,
140 org.id,
141 if decision.is_granted() {
142 "GRANTED"
143 } else {
144 "DENIED"
145 }
146 );
147 assert_eq!(decision.is_granted(), expected_granted);
148 }
149
150 println!("\nWhy org2-admin cannot edit user settings on org-1:");
153 let session = EvaluationSession::empty();
154 let decision = checker
155 .bind(&session, &org2_admin, &AdminAction::EditUserSettings, &())
156 .check(&org1)
157 .await;
158 println!("{}", decision.display_trace());
159}