// Role-based access control module use crate::config::Config; use crate::models::{Permission, QueryAction}; use std::collections::HashMap; #[derive(Clone)] pub struct RBACManager { permissions: HashMap>, } impl RBACManager { pub fn new(config: &Config) -> Self { let mut permissions = HashMap::new(); // Parse permissions from new config format for (power_str, power_perms) in &config.permissions.power_levels { if let Ok(power) = power_str.parse::() { let mut table_permissions = HashMap::new(); for perm in &power_perms.basic_rules { let parts: Vec<&str> = perm.split(':').collect(); if parts.len() == 2 { let table = parts[0]; let permission = Permission::from_str(parts[1]); if table == "*" { // Grant permission to all known tables from config let all_tables = config.get_known_tables(); for table_name in all_tables { // Don't overwrite existing specific permissions if !table_permissions.contains_key(&table_name) { table_permissions.insert(table_name, permission.clone()); } } } else { table_permissions.insert(table.to_string(), permission); } } } permissions.insert(power, table_permissions); } } Self { permissions } } fn base_table_name(table: &str) -> String { // Accept formats: "table", "table alias", "table AS alias" (AS case-insensitive) let parts: Vec<&str> = table.trim().split_whitespace().collect(); if parts.len() >= 3 && parts[1].eq_ignore_ascii_case("AS") { parts[0].to_string() } else { // For simple "table alias" or just "table", take the first token parts.get(0).cloned().unwrap_or("").to_string() } } pub fn check_permission( &self, config: &Config, power: i32, table: &str, action: &QueryAction, ) -> bool { // Normalize potential alias usage to the base table name for permission lookup let base_table = Self::base_table_name(table); if let Some(table_permissions) = self.permissions.get(&power) { if let Some(permission) = table_permissions.get(&base_table) { // Check if table is read-only through config and enforce read-only constraint if config.is_read_only_table(&base_table) && !matches!(action, QueryAction::Select) { return false; // Write operations not allowed on read-only tables } match action { QueryAction::Select | QueryAction::Count => permission.can_read(), QueryAction::Insert | QueryAction::Update | QueryAction::Delete => { permission.can_write() } } } else { false // No permission for this table } } else { false // No permissions for this power level } } pub fn get_table_permissions(&self, config: &Config, power: i32) -> HashMap { let mut result = HashMap::new(); if let Some(table_permissions) = self.permissions.get(&power) { for (table, permission) in table_permissions { let perm_str = match permission { Permission::Read => "r", Permission::Write => "w", Permission::ReadWrite => "rw", Permission::None => "", }; if !perm_str.is_empty() { result.insert(table.clone(), perm_str.to_string()); } } } // Ensure read-only tables from config are marked as read-only for table in &config.get_known_tables() { if config.is_read_only_table(table) && result.contains_key(table) { result.insert(table.clone(), "r".to_string()); } } result } }