use gatehouse::*;
#[derive(Debug, Clone)]
pub struct GroupPermission {
pub scope: &'static str,
pub entity: String,
}
#[derive(Debug, Clone)]
pub struct StaffUser {
pub name: &'static str,
pub permissions: Vec<GroupPermission>,
}
#[derive(Debug, Clone)]
pub struct Organization {
pub id: String,
}
#[derive(Debug, Clone, Copy)]
pub enum AdminAction {
EditUserSettings,
EditOrgSettings,
}
impl AdminAction {
fn required_scope(self) -> &'static str {
match self {
Self::EditUserSettings => "edit_user_settings",
Self::EditOrgSettings => "edit_org_settings",
}
}
}
struct AdminDomain;
impl PolicyDomain for AdminDomain {
type Subject = StaffUser;
type Action = AdminAction;
type Resource = Organization;
type Context = ();
}
fn scoped_permission_policy() -> Box<dyn Policy<AdminDomain>> {
PolicyBuilder::<AdminDomain>::new("ScopedPermission")
.when(
|user: &StaffUser, action: &AdminAction, org: &Organization, _ctx: &()| {
user.permissions
.iter()
.any(|p| p.scope == action.required_scope() && p.entity == org.id)
},
)
.build()
}
fn global_admin_policy() -> Box<dyn Policy<AdminDomain>> {
PolicyBuilder::<AdminDomain>::new("GlobalAdmin")
.subjects(|user: &StaffUser| user.permissions.iter().any(|p| p.scope == "global_admin"))
.build()
}
#[tokio::main]
async fn main() {
let mut checker = PermissionChecker::<AdminDomain>::new();
checker.add_policy(scoped_permission_policy());
checker.add_policy(global_admin_policy());
let org1 = Organization { id: "org-1".into() };
let org2 = Organization { id: "org-2".into() };
let org1_admin = StaffUser {
name: "org1-admin",
permissions: vec![GroupPermission {
scope: "edit_user_settings",
entity: "org-1".into(),
}],
};
let org2_admin = StaffUser {
name: "org2-admin",
permissions: vec![GroupPermission {
scope: "edit_user_settings",
entity: "org-2".into(),
}],
};
let no_grants = StaffUser {
name: "no-grants",
permissions: vec![],
};
let global_admin = StaffUser {
name: "global-admin",
permissions: vec![GroupPermission {
scope: "global_admin",
entity: String::new(),
}],
};
let cases = [
(&org1_admin, AdminAction::EditUserSettings, &org1, true),
(&org2_admin, AdminAction::EditUserSettings, &org1, false),
(&org1_admin, AdminAction::EditOrgSettings, &org1, false),
(&org2_admin, AdminAction::EditUserSettings, &org2, true),
(&no_grants, AdminAction::EditUserSettings, &org1, false),
(&global_admin, AdminAction::EditOrgSettings, &org1, true),
];
for (user, action, org, expected_granted) in cases {
let session = EvaluationSession::empty();
let decision = checker.bind(&session, user, &action, &()).check(org).await;
println!(
"{:<12} {:?} on {}: {}",
user.name,
action,
org.id,
if decision.is_granted() {
"GRANTED"
} else {
"DENIED"
}
);
assert_eq!(decision.is_granted(), expected_granted);
}
println!("\nWhy org2-admin cannot edit user settings on org-1:");
let session = EvaluationSession::empty();
let decision = checker
.bind(&session, &org2_admin, &AdminAction::EditUserSettings, &())
.check(&org1)
.await;
println!("{}", decision.display_trace());
}