use anyhow::Result;
use std::collections::HashMap;
pub trait PermissionLike: Clone + Send + Sync {
fn name(&self) -> &str;
fn resource(&self) -> &str;
fn action(&self) -> &str;
fn description(&self) -> Option<&str> { None }
}
pub trait RoleLike: Clone + Send + Sync {
type Permission: PermissionLike;
fn name(&self) -> &str;
fn description(&self) -> Option<&str> { None }
fn permissions(&self) -> &[Self::Permission];
}
pub trait PermissionChecker: Send + Sync {
fn has_permission(&self, user_id: &str, permission: &str) -> Result<bool>;
fn has_role(&self, user_id: &str, role_name: &str) -> Result<bool>;
fn get_user_roles(&self, user_id: &str) -> Vec<String>;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SimplePermission {
pub name: String,
pub resource: String,
pub action: String,
pub description: Option<String>,
}
impl PermissionLike for SimplePermission {
fn name(&self) -> &str { &self.name }
fn resource(&self) -> &str { &self.resource }
fn action(&self) -> &str { &self.action }
fn description(&self) -> Option<&str> { self.description.as_deref() }
}
#[derive(Debug, Clone)]
pub struct SimpleRole {
pub name: String,
pub description: Option<String>,
pub permissions: Vec<SimplePermission>,
}
impl RoleLike for SimpleRole {
type Permission = SimplePermission;
fn name(&self) -> &str { &self.name }
fn description(&self) -> Option<&str> { self.description.as_deref() }
fn permissions(&self) -> &[SimplePermission] { &self.permissions }
}
pub struct InMemoryPermissionService<R: RoleLike = SimpleRole> {
roles: HashMap<String, R>,
user_roles: HashMap<String, Vec<String>>, }
impl InMemoryPermissionService<SimpleRole> {
pub fn new() -> Self {
let mut roles = HashMap::new();
roles.insert("admin".to_string(), SimpleRole {
name: "admin".to_string(),
description: Some("Administrator with full access".to_string()),
permissions: vec![
SimplePermission {
name: "admin:all".to_string(),
description: Some("Full administrative access".to_string()),
resource: "*".to_string(),
action: "*".to_string(),
},
],
});
roles.insert("user".to_string(), SimpleRole {
name: "user".to_string(),
description: Some("Regular user with basic permissions".to_string()),
permissions: vec![
SimplePermission {
name: "user:read".to_string(),
description: Some("Read own user data".to_string()),
resource: "user".to_string(),
action: "read".to_string(),
},
SimplePermission {
name: "user:write".to_string(),
description: Some("Update own user data".to_string()),
resource: "user".to_string(),
action: "write".to_string(),
},
],
});
Self {
roles,
user_roles: HashMap::new(),
}
}
}
impl<R: RoleLike> InMemoryPermissionService<R> {
pub fn empty() -> Self {
Self {
roles: HashMap::new(),
user_roles: HashMap::new(),
}
}
fn check_permission_internal(&self, user_id: &str, permission: &str) -> Result<bool> {
let user_role_names = match self.user_roles.get(user_id) {
Some(roles) => roles,
None => return Ok(false),
};
for role_name in user_role_names {
if let Some(role) = self.roles.get(role_name) {
for perm in role.permissions() {
if perm.name() == permission {
return Ok(true);
}
if perm.resource() == "*" && perm.action() == "*" {
return Ok(true);
}
let required_parts: Vec<&str> = permission.split(':').collect();
let perm_parts: Vec<&str> = perm.name().split(':').collect();
if required_parts.len() == 2 && perm_parts.len() == 2 {
let resource_match = perm_parts[0] == "*" || perm_parts[0] == required_parts[0];
let action_match = perm_parts[1] == "*" || perm_parts[1] == required_parts[1];
if resource_match && action_match {
return Ok(true);
}
}
}
}
}
Ok(false)
}
pub fn get_user_permissions(&self, user_id: &str) -> Result<Vec<SimplePermission>> {
let mut permissions = Vec::new();
let mut permission_names = std::collections::HashSet::new();
let user_role_names = match self.user_roles.get(user_id) {
Some(roles) => roles,
None => return Ok(permissions),
};
for role_name in user_role_names {
if let Some(role) = self.roles.get(role_name) {
for perm in role.permissions() {
if permission_names.insert(perm.name().to_string()) {
permissions.push(SimplePermission {
name: perm.name().to_string(),
resource: perm.resource().to_string(),
action: perm.action().to_string(),
description: perm.description().map(String::from),
});
}
}
}
}
Ok(permissions)
}
pub fn add_role(&mut self, role: R) -> Result<()> {
self.roles.insert(role.name().to_string(), role);
Ok(())
}
pub fn get_role(&self, role_name: &str) -> Option<&R> {
self.roles.get(role_name)
}
pub fn list_roles(&self) -> Vec<&R> {
self.roles.values().collect()
}
pub fn assign_role(&mut self, user_id: &str, role_name: &str) -> Result<()> {
if !self.roles.contains_key(role_name) {
return Err(anyhow::anyhow!("Role '{}' does not exist", role_name));
}
let user_roles = self.user_roles.entry(user_id.to_string()).or_default();
if user_roles.contains(&role_name.to_string()) {
return Err(anyhow::anyhow!("User already has role '{}'", role_name));
}
user_roles.push(role_name.to_string());
Ok(())
}
pub fn remove_role(&mut self, user_id: &str, role_name: &str) -> Result<()> {
let user_roles = self.user_roles.get_mut(user_id)
.ok_or_else(|| anyhow::anyhow!("User has no roles assigned"))?;
let original_len = user_roles.len();
user_roles.retain(|r| r != role_name);
if user_roles.len() == original_len {
return Err(anyhow::anyhow!("User does not have role '{}'", role_name));
}
if user_roles.is_empty() {
self.user_roles.remove(user_id);
}
Ok(())
}
fn has_role_internal(&self, user_id: &str, role_name: &str) -> bool {
self.user_roles.get(user_id)
.map(|roles| roles.contains(&role_name.to_string()))
.unwrap_or(false)
}
fn get_user_roles_internal(&self, user_id: &str) -> Vec<String> {
self.user_roles.get(user_id)
.cloned()
.unwrap_or_default()
}
}
impl<R: RoleLike> PermissionChecker for InMemoryPermissionService<R> {
fn has_permission(&self, user_id: &str, permission: &str) -> Result<bool> {
self.check_permission_internal(user_id, permission)
}
fn has_role(&self, user_id: &str, role_name: &str) -> Result<bool> {
Ok(self.has_role_internal(user_id, role_name))
}
fn get_user_roles(&self, user_id: &str) -> Vec<String> {
self.get_user_roles_internal(user_id)
}
}
impl Default for InMemoryPermissionService<SimpleRole> {
fn default() -> Self {
Self::new()
}
}
pub type PermissionService = InMemoryPermissionService<SimpleRole>;
pub type Permission = SimplePermission;
pub type Role = SimpleRole;