use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Resource {
Organization,
Member,
Invitation,
}
impl Resource {
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"organization" => Some(Self::Organization),
"member" => Some(Self::Member),
"invitation" => Some(Self::Invitation),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Action {
Create,
Read,
Update,
Delete,
Cancel,
}
impl Action {
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"create" => Some(Self::Create),
"read" => Some(Self::Read),
"update" => Some(Self::Update),
"delete" => Some(Self::Delete),
"cancel" => Some(Self::Cancel),
_ => None,
}
}
}
pub type Permissions = HashMap<Resource, Vec<Action>>;
#[derive(Debug, Clone)]
pub struct Role {
pub name: String,
pub permissions: Permissions,
}
pub fn default_roles() -> HashMap<String, Role> {
let mut roles = HashMap::new();
roles.insert(
"owner".to_string(),
Role {
name: "owner".to_string(),
permissions: {
let mut p = HashMap::new();
p.insert(Resource::Organization, vec![Action::Update, Action::Delete]);
p.insert(
Resource::Member,
vec![Action::Create, Action::Update, Action::Delete],
);
p.insert(Resource::Invitation, vec![Action::Create, Action::Cancel]);
p
},
},
);
roles.insert(
"admin".to_string(),
Role {
name: "admin".to_string(),
permissions: {
let mut p = HashMap::new();
p.insert(Resource::Organization, vec![Action::Update]);
p.insert(
Resource::Member,
vec![Action::Create, Action::Update, Action::Delete],
);
p.insert(Resource::Invitation, vec![Action::Create, Action::Cancel]);
p
},
},
);
roles.insert(
"member".to_string(),
Role {
name: "member".to_string(),
permissions: HashMap::new(),
},
);
roles
}
pub fn has_permission(
role: &str,
resource: &Resource,
action: &Action,
custom_roles: &HashMap<String, crate::plugins::organization::RolePermissions>,
) -> bool {
let default = default_roles();
if let Some(custom_role) = custom_roles.get(role) {
let actions = match resource {
Resource::Organization => &custom_role.organization,
Resource::Member => &custom_role.member,
Resource::Invitation => &custom_role.invitation,
};
let action_str = match action {
Action::Create => "create",
Action::Read => "read",
Action::Update => "update",
Action::Delete => "delete",
Action::Cancel => "cancel",
};
if actions.iter().any(|a| a == action_str) {
return true;
}
}
if let Some(role_def) = default.get(role)
&& let Some(actions) = role_def.permissions.get(resource)
{
return actions.contains(action);
}
false
}
pub fn has_permission_any(
roles_str: &str,
resource: &Resource,
action: &Action,
custom_roles: &HashMap<String, crate::plugins::organization::RolePermissions>,
) -> bool {
for role in roles_str.split(',').map(|s| s.trim()) {
if has_permission(role, resource, action, custom_roles) {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_owner_has_full_permissions() {
let custom = HashMap::new();
assert!(has_permission(
"owner",
&Resource::Organization,
&Action::Update,
&custom
));
assert!(has_permission(
"owner",
&Resource::Organization,
&Action::Delete,
&custom
));
assert!(has_permission(
"owner",
&Resource::Member,
&Action::Create,
&custom
));
assert!(has_permission(
"owner",
&Resource::Invitation,
&Action::Cancel,
&custom
));
}
#[test]
fn test_admin_cannot_delete_organization() {
let custom = HashMap::new();
assert!(has_permission(
"admin",
&Resource::Organization,
&Action::Update,
&custom
));
assert!(!has_permission(
"admin",
&Resource::Organization,
&Action::Delete,
&custom
));
}
#[test]
fn test_member_has_no_permissions() {
let custom = HashMap::new();
assert!(!has_permission(
"member",
&Resource::Organization,
&Action::Update,
&custom
));
assert!(!has_permission(
"member",
&Resource::Member,
&Action::Create,
&custom
));
}
#[test]
fn test_composite_roles() {
let custom = HashMap::new();
assert!(has_permission_any(
"member,admin",
&Resource::Organization,
&Action::Update,
&custom
));
assert!(!has_permission_any(
"member",
&Resource::Organization,
&Action::Update,
&custom
));
}
}