use super::{Action, FieldRestriction, PermissionChecker, PermissionError, PermissionResult};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Default)]
struct BucketRoleRules {
allowed_actions: HashSet<Action>,
denied_actions: HashSet<Action>,
field_restrictions: HashMap<Action, FieldRestriction>,
}
#[derive(Debug, Clone)]
pub struct BucketPermissions {
rules: HashMap<String, BucketRoleRules>,
}
impl BucketPermissions {
pub fn new() -> Self {
let mut rules = HashMap::new();
{
let mut role_perms = BucketRoleRules::default();
for action in Action::all() {
role_perms.allowed_actions.insert(action);
}
rules.insert("super_admin".to_string(), role_perms);
}
{
let mut role_perms = BucketRoleRules::default();
role_perms.allowed_actions.insert(Action::Read);
role_perms.allowed_actions.insert(Action::Create);
role_perms.allowed_actions.insert(Action::Update);
role_perms.allowed_actions.insert(Action::Delete);
role_perms.allowed_actions.insert(Action::List);
role_perms.allowed_actions.insert(Action::Restore);
rules.insert("admin".to_string(), role_perms);
}
{
let mut role_perms = BucketRoleRules::default();
role_perms.allowed_actions.insert(Action::Create);
role_perms.allowed_actions.insert(Action::Read);
role_perms.allowed_actions.insert(Action::Update);
role_perms.allowed_actions.insert(Action::Delete);
role_perms.allowed_actions.insert(Action::List);
role_perms.allowed_actions.insert(Action::Restore);
role_perms.denied_actions.insert(Action::Delete);
rules.insert("user".to_string(), role_perms);
}
{
let mut role_perms = BucketRoleRules::default();
for action in Action::all() {
role_perms.denied_actions.insert(action);
}
rules.insert("guest".to_string(), role_perms);
}
Self { rules }
}
pub fn authorize(&self, role: &str, action: Action) -> PermissionResult {
if !self.can(role, action) {
return Err(PermissionError::ActionNotAllowed {
action,
role: role.to_string(),
});
}
Ok(())
}
pub fn authorize_field(&self, role: &str, action: Action, field: &str) -> PermissionResult {
self.authorize(role, action)?;
if !self.can_access_field(role, action, field) {
return Err(PermissionError::FieldNotAccessible {
field: field.to_string(),
action,
});
}
Ok(())
}
pub fn field_restrictions(&self, role: &str, action: Action) -> Option<&FieldRestriction> {
self.rules.get(role).and_then(|r| r.field_restrictions.get(&action))
}
}
impl Default for BucketPermissions {
fn default() -> Self {
Self::new()
}
}
impl PermissionChecker for BucketPermissions {
fn can(&self, role: &str, action: Action) -> bool {
let Some(role_perms) = self.rules.get(role) else {
return false;
};
if role_perms.denied_actions.contains(&action) {
return false;
}
role_perms.allowed_actions.contains(&action)
}
fn can_access_field(&self, role: &str, action: Action, field: &str) -> bool {
if !self.can(role, action) {
return false;
}
let Some(role_perms) = self.rules.get(role) else {
return false;
};
match role_perms.field_restrictions.get(&action) {
Some(restriction) => restriction.is_allowed(field),
None => true, }
}
fn allowed_fields(&self, role: &str, action: Action) -> Vec<String> {
let Some(role_perms) = self.rules.get(role) else {
return vec![];
};
match role_perms.field_restrictions.get(&action) {
Some(FieldRestriction::Only(fields)) => fields.iter().cloned().collect(),
_ => vec![], }
}
fn roles(&self) -> Vec<&str> {
self.rules.keys().map(|s| s.as_str()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_permissions() {
let perms = BucketPermissions::new();
let roles = perms.roles();
assert!(!roles.is_empty());
}
#[test]
fn test_super_admin_role() {
let _perms = BucketPermissions::new();
}
#[test]
fn test_admin_role() {
let perms = BucketPermissions::new();
assert!(perms.can("admin", Action::Read));
}
#[test]
fn test_user_role() {
let perms = BucketPermissions::new();
assert!(perms.can("user", Action::Create));
assert!(!perms.can("user", Action::Delete));
}
#[test]
fn test_guest_role() {
let _perms = BucketPermissions::new();
}
#[test]
fn test_unknown_role() {
let perms = BucketPermissions::new();
assert!(!perms.can("unknown_role", Action::Read));
}
}