use crate::permission_engine::{
PermissionAction, PermissionContext, PermissionDecision, PermissionProvider as PermissionProviderTrait,
PermissionResource,
};
use dashmap::DashMap;
use std::sync::Arc;
#[derive(Debug)]
pub struct RbacProvider {
roles: Arc<DashMap<String, RolePolicy>>,
}
#[derive(Debug, Clone)]
pub struct RolePolicy {
pub tables: Vec<TablePermission>,
}
#[derive(Debug, Clone)]
pub struct TablePermission {
pub table_name: String,
pub actions: Vec<String>,
}
impl RbacProvider {
pub fn new() -> Self {
let roles = DashMap::new();
roles.insert(
"admin".to_string(),
RolePolicy {
tables: vec![TablePermission {
table_name: "*".to_string(),
actions: vec![
"SELECT".to_string(),
"INSERT".to_string(),
"UPDATE".to_string(),
"DELETE".to_string(),
],
}],
},
);
Self { roles: Arc::new(roles) }
}
}
#[async_trait::async_trait]
impl PermissionProviderTrait for RbacProvider {
async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision {
let subject_id = match &context.subject {
crate::permission_engine::PermissionSubject { id, .. } => id.clone(),
};
if let Some(role_policy) = self.roles.get(&subject_id) {
let resource_name = context.resource.name.clone();
let action_is_select = matches!(context.action, PermissionAction::Select);
for table_perm in &role_policy.tables {
if table_perm.table_name == "*" || table_perm.table_name == resource_name {
if table_perm.actions.contains(&"SELECT".to_string()) && action_is_select {
return PermissionDecision::Allow;
}
let action_str = context.action.to_string();
if table_perm.actions.contains(&action_str) || table_perm.actions.contains(&"*".to_string()) {
return PermissionDecision::Allow;
}
}
}
}
PermissionDecision::Deny
}
async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource> {
if let Some(role_policy) = self.roles.get(subject) {
role_policy
.tables
.iter()
.map(|t| PermissionResource::new(&t.table_name))
.collect()
} else {
Vec::new()
}
}
async fn get_allowed_actions(&self, subject: &str, resource: &str) -> Vec<PermissionAction> {
if let Some(role_policy) = self.roles.get(subject) {
for table_perm in &role_policy.tables {
if table_perm.table_name == "*" || table_perm.table_name == resource {
return table_perm
.actions
.iter()
.filter_map(|op| {
match op.to_uppercase().as_str() {
"SELECT" => Some(PermissionAction::Select),
"INSERT" => Some(PermissionAction::Insert),
"UPDATE" => Some(PermissionAction::Update),
"DELETE" => Some(PermissionAction::Delete),
_ => None, }
})
.collect();
}
}
}
Vec::new()
}
async fn refresh(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
fn name(&self) -> &str {
"rbac"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::permission_engine::{PermissionAction, PermissionResource, PermissionSubject};
#[tokio::test]
async fn test_rbac_provider_new() {
let provider = RbacProvider::new();
assert_eq!(provider.name(), "rbac");
}
#[tokio::test]
async fn test_rbac_provider_check_permission_admin() {
let provider = RbacProvider::new();
let context = PermissionContext::new(
PermissionSubject::role("admin"),
PermissionResource::new("users"),
PermissionAction::Select,
);
let decision = provider.check_permission(&context).await;
assert_eq!(decision, PermissionDecision::Allow);
}
#[tokio::test]
async fn test_rbac_provider_check_permission_deny() {
let provider = RbacProvider::new();
let context = PermissionContext::new(
PermissionSubject::role("unknown"),
PermissionResource::new("users"),
PermissionAction::Select,
);
let decision = provider.check_permission(&context).await;
assert_eq!(decision, PermissionDecision::Deny);
}
}