use crate::permission_engine::{
PermissionAction, PermissionContext, PermissionDecision, PermissionProvider as PermissionProviderTrait,
PermissionResource,
};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SimplePermissionConfig {
pub roles: HashMap<String, SimpleRolePolicy>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SimpleRolePolicy {
pub tables: Vec<SimpleTablePermission>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SimpleTablePermission {
pub name: String,
pub operations: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct SimplePermissionProvider {
config: Arc<SimplePermissionConfig>,
}
impl SimplePermissionProvider {
pub fn new(config: SimplePermissionConfig) -> Self {
Self {
config: Arc::new(config),
}
}
pub fn deny_all() -> Self {
Self::new(SimplePermissionConfig { roles: HashMap::new() })
}
pub fn allow_all() -> Self {
let mut admin_tables = vec![SimpleTablePermission {
name: "*".to_string(),
operations: vec![
"SELECT".to_string(),
"INSERT".to_string(),
"UPDATE".to_string(),
"DELETE".to_string(),
],
}];
admin_tables.extend(vec![
SimpleTablePermission {
name: "sqlite_master".to_string(),
operations: vec!["SELECT".to_string()],
},
SimpleTablePermission {
name: "information_schema.tables".to_string(),
operations: vec!["SELECT".to_string()],
},
]);
let system_tables = admin_tables.clone();
Self::new(SimplePermissionConfig {
roles: HashMap::from([
("admin".to_string(), SimpleRolePolicy { tables: admin_tables }),
("system".to_string(), SimpleRolePolicy { tables: system_tables }),
]),
})
}
fn check_access(&self, role: &str, table: &str, operation: &str) -> bool {
if let Some(policy) = self.config.roles.get(role) {
let normalized_table = table.to_lowercase();
let is_system_table = Self::is_system_table(&normalized_table);
for perm in &policy.tables {
if perm.name == "*" {
if is_system_table {
continue;
}
if perm.operations.iter().any(|op| op.eq_ignore_ascii_case(operation)) {
return true;
}
} else if perm.name.eq_ignore_ascii_case(table) {
if perm.operations.iter().any(|op| op.eq_ignore_ascii_case(operation)) {
return true;
}
}
}
}
false
}
fn is_system_table(table: &str) -> bool {
const SYSTEM_TABLES: &[&str] = &[
"sqlite_master",
"sqlite_sequence",
"sqlite_stat1",
"sqlite_stat2",
"sqlite_stat3",
"sqlite_stat4",
"information_schema",
"pg_catalog",
"pg_toast",
"mysql",
"performance_schema",
"sys",
];
SYSTEM_TABLES.iter().any(|system_table| {
table.eq_ignore_ascii_case(system_table) || table.starts_with(&format!("{}.", system_table))
})
}
}
#[async_trait]
impl PermissionProviderTrait for SimplePermissionProvider {
async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision {
let subject_id = match &context.subject {
crate::permission_engine::PermissionSubject { id, .. } => id.clone(),
};
let allowed = self.check_access(&subject_id, &context.resource.name, &context.action.to_string());
if allowed {
PermissionDecision::Allow
} else {
PermissionDecision::Deny
}
}
async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource> {
if let Some(policy) = self.config.roles.get(subject) {
policy.tables.iter().map(|t| PermissionResource::new(&t.name)).collect()
} else {
Vec::new()
}
}
async fn get_allowed_actions(&self, subject: &str, resource: &str) -> Vec<PermissionAction> {
if let Some(policy) = self.config.roles.get(subject) {
for table in &policy.tables {
if table.name == "*" || table.name.eq_ignore_ascii_case(resource) {
return table
.operations
.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 {
"SimplePermissionProvider"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::permission_engine::{PermissionAction, PermissionResource, PermissionSubject};
#[test]
fn test_simple_permission_provider_allow_all() {
let provider = SimplePermissionProvider::allow_all();
let ctx = PermissionContext::new(
PermissionSubject::role("admin"),
PermissionResource::new("users"),
PermissionAction::Select,
);
let decision = futures::executor::block_on(provider.check_permission(&ctx));
assert_eq!(decision, PermissionDecision::Allow);
let ctx = PermissionContext::new(
PermissionSubject::role("admin"),
PermissionResource::new("orders"),
PermissionAction::Delete,
);
let decision = futures::executor::block_on(provider.check_permission(&ctx));
assert_eq!(decision, PermissionDecision::Allow);
}
#[test]
fn test_simple_permission_provider_deny_all() {
let provider = SimplePermissionProvider::deny_all();
let ctx = PermissionContext::new(
PermissionSubject::role("admin"),
PermissionResource::new("users"),
PermissionAction::Select,
);
let decision = futures::executor::block_on(provider.check_permission(&ctx));
assert_eq!(decision, PermissionDecision::Deny);
}
}