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")
66 .when(
67 |user: &StaffUser, action: &AdminAction, org: &Organization, _ctx: &()| {
68 user.permissions
69 .iter()
70 .any(|p| p.scope == action.required_scope() && p.entity == org.id)
71 },
72 )
73 .build()
74}
75
76fn global_admin_policy() -> Box<dyn Policy<AdminDomain>> {
79 PolicyBuilder::<AdminDomain>::new("GlobalAdmin")
80 .subjects(|user: &StaffUser| user.permissions.iter().any(|p| p.scope == "global_admin"))
81 .build()
82}
83
84#[tokio::main]
85async fn main() {
86 let mut checker = PermissionChecker::<AdminDomain>::new();
87 checker.add_policy(scoped_permission_policy());
88 checker.add_policy(global_admin_policy());
89
90 let org1 = Organization { id: "org-1".into() };
91 let org2 = Organization { id: "org-2".into() };
92
93 let org1_admin = StaffUser {
94 name: "org1-admin",
95 permissions: vec![GroupPermission {
96 scope: "edit_user_settings",
97 entity: "org-1".into(),
98 }],
99 };
100 let org2_admin = StaffUser {
101 name: "org2-admin",
102 permissions: vec![GroupPermission {
103 scope: "edit_user_settings",
104 entity: "org-2".into(),
105 }],
106 };
107 let no_grants = StaffUser {
108 name: "no-grants",
109 permissions: vec![],
110 };
111 let global_admin = StaffUser {
112 name: "global-admin",
113 permissions: vec![GroupPermission {
114 scope: "global_admin",
115 entity: String::new(),
116 }],
117 };
118
119 let cases = [
121 (&org1_admin, AdminAction::EditUserSettings, &org1, true),
123 (&org2_admin, AdminAction::EditUserSettings, &org1, false),
125 (&org1_admin, AdminAction::EditOrgSettings, &org1, false),
126 (&org2_admin, AdminAction::EditUserSettings, &org2, true),
127 (&no_grants, AdminAction::EditUserSettings, &org1, false),
128 (&global_admin, AdminAction::EditOrgSettings, &org1, true),
130 ];
131
132 for (user, action, org, expected_granted) in cases {
133 let session = EvaluationSession::empty();
134 let decision = checker.bind(&session, user, &action, &()).check(org).await;
135 println!(
136 "{:<12} {:?} on {}: {}",
137 user.name,
138 action,
139 org.id,
140 if decision.is_granted() {
141 "GRANTED"
142 } else {
143 "DENIED"
144 }
145 );
146 assert_eq!(decision.is_granted(), expected_granted);
147 }
148
149 println!("\nWhy org2-admin cannot edit user settings on org-1:");
152 let session = EvaluationSession::empty();
153 let decision = checker
154 .bind(&session, &org2_admin, &AdminAction::EditUserSettings, &())
155 .check(&org1)
156 .await;
157 println!("{}", decision.display_trace());
158}