pub use crate::access::permission::PermissionAction;
use async_trait::async_trait;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::{Duration, Instant};
static PATH_TRAVERSAL_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\.\.|%2e%2e|%252e%252e|\\/|\\\\").expect("Regex pattern should be valid"));
fn is_safe_config_path(path: &str) -> bool {
if path.is_empty() {
return false;
}
if PATH_TRAVERSAL_REGEX.is_match(path) {
return false;
}
let path_buf = std::path::Path::new(path);
if path_buf.is_absolute() {
let allowed_prefixes = ["/etc/dbnexus/", "/opt/dbnexus/config/", "./config/", "./"];
if allowed_prefixes.iter().any(|prefix| path.starts_with(prefix)) {
return true;
}
let temp_dir = std::env::temp_dir();
return path.starts_with(temp_dir.to_str().unwrap_or(""));
}
!path.contains("..") && !path.contains('\\')
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PermissionResource {
pub name: String,
#[serde(default)]
pub resource_type: String,
}
impl PermissionResource {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
resource_type: "table".to_string(),
}
}
pub fn with_type(name: &str, resource_type: &str) -> Self {
Self {
name: name.to_string(),
resource_type: resource_type.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PermissionSubject {
pub id: String,
#[serde(default)]
pub subject_type: SubjectType,
}
impl PermissionSubject {
pub fn user(id: &str) -> Self {
Self {
id: id.to_string(),
subject_type: SubjectType::User,
}
}
pub fn role(id: &str) -> Self {
Self {
id: id.to_string(),
subject_type: SubjectType::Role,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SubjectType {
#[default]
User,
Role,
Group,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PermissionDecision {
Allow,
Deny,
NotApplicable,
Error(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionContext {
pub subject: PermissionSubject,
pub resource: PermissionResource,
pub action: PermissionAction,
#[serde(default)]
pub attributes: HashMap<String, String>,
#[serde(default)]
pub environment: HashMap<String, String>,
}
impl PermissionContext {
pub fn new(subject: PermissionSubject, resource: PermissionResource, action: PermissionAction) -> Self {
Self {
subject,
resource,
action,
attributes: HashMap::new(),
environment: HashMap::new(),
}
}
pub fn with_attribute(mut self, key: &str, value: &str) -> Self {
self.attributes.insert(key.to_string(), value.to_string());
self
}
pub fn with_environment(mut self, key: &str, value: &str) -> Self {
self.environment.insert(key.to_string(), value.to_string());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRule {
pub name: String,
#[serde(default)]
pub priority: i32,
pub subject: String,
pub resource: String,
pub allow: Vec<PermissionAction>,
#[serde(default)]
pub deny: Vec<PermissionAction>,
#[serde(default)]
pub condition: Option<String>,
#[serde(default = "default_enabled")]
pub enabled: bool,
}
fn default_enabled() -> bool {
true
}
fn matches_rule(rule: &PermissionRule, context: &PermissionContext) -> bool {
if rule.subject != "*" && rule.subject != context.subject.id {
return false;
}
if rule.resource != "*" && rule.resource != context.resource.name {
return false;
}
let in_allow = rule.allow.contains(&context.action);
let in_deny = rule.deny.contains(&context.action);
if !in_allow && !in_deny {
return false;
}
true
}
fn get_subject_roles<V>(
mapping: &HashMap<String, Vec<String>>,
roles: &HashMap<String, V>,
subject: &str,
) -> Vec<String> {
if let Some(roles_list) = mapping.get(subject) {
return roles_list.clone();
}
if roles.contains_key(subject) {
return vec![subject.to_string()];
}
Vec::new()
}
#[async_trait]
pub trait PermissionProvider: Send + Sync + Debug {
async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision;
async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource>;
async fn get_allowed_actions(&self, subject: &str, resource: &str) -> Vec<PermissionAction>;
async fn refresh(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
fn name(&self) -> &str;
}
#[derive(Debug, Clone)]
struct CachedDecision {
decision: PermissionDecision,
cached_at: Instant,
}
impl CachedDecision {
fn new(decision: PermissionDecision) -> Self {
Self {
decision,
cached_at: Instant::now(),
}
}
fn is_expired(&self, ttl_seconds: u64) -> bool {
self.cached_at.elapsed().as_secs() >= ttl_seconds
}
}
#[derive(Debug, Clone)]
struct RateLimitEntry {
count: u32,
window_start: Instant,
}
const DEFAULT_CACHE_TTL_SECONDS: u64 = 300;
const DEFAULT_RATE_LIMIT_MAX_REQUESTS: u32 = 100;
const DEFAULT_RATE_LIMIT_WINDOW_SECONDS: u32 = 60;
#[derive(Debug)]
pub struct PolicyDecisionPoint {
provider: Arc<dyn PermissionProvider>,
cache: DashMap<String, CachedDecision>,
cache_ttl_seconds: u64,
cache_enabled: bool,
rate_limit_max_requests: u32,
rate_limit_window_seconds: u32,
rate_limit_store: DashMap<String, RateLimitEntry>,
default_decision: PermissionDecision,
log_denied: bool,
}
pub struct PolicyDecisionPointBuilder {
provider: Option<Arc<dyn PermissionProvider>>,
cache_ttl_seconds: Option<u64>,
cache_enabled: Option<bool>,
rate_limit_max_requests: Option<u32>,
rate_limit_window_seconds: Option<u32>,
default_decision: Option<PermissionDecision>,
log_denied: Option<bool>,
}
impl PolicyDecisionPointBuilder {
fn new() -> Self {
Self {
provider: None,
cache_ttl_seconds: None,
cache_enabled: None,
rate_limit_max_requests: None,
rate_limit_window_seconds: None,
default_decision: None,
log_denied: None,
}
}
pub fn provider(mut self, provider: Arc<dyn PermissionProvider>) -> Self {
self.provider = Some(provider);
self
}
pub fn cache_ttl_seconds(mut self, seconds: u64) -> Self {
self.cache_ttl_seconds = Some(seconds);
self
}
pub fn cache_enabled(mut self, enabled: bool) -> Self {
self.cache_enabled = Some(enabled);
self
}
pub fn rate_limit(mut self, max_requests: u32, window_seconds: u32) -> Self {
self.rate_limit_max_requests = Some(max_requests);
self.rate_limit_window_seconds = Some(window_seconds);
self
}
pub fn default_decision(mut self, decision: PermissionDecision) -> Self {
self.default_decision = Some(decision);
self
}
pub fn log_denied(mut self, enabled: bool) -> Self {
self.log_denied = Some(enabled);
self
}
pub fn build(self) -> PolicyDecisionPoint {
let provider = self.provider.expect("Provider is required for PolicyDecisionPoint");
PolicyDecisionPoint {
provider,
cache: DashMap::new(),
cache_ttl_seconds: self.cache_ttl_seconds.unwrap_or(DEFAULT_CACHE_TTL_SECONDS),
cache_enabled: self.cache_enabled.unwrap_or(true),
rate_limit_max_requests: self.rate_limit_max_requests.unwrap_or(DEFAULT_RATE_LIMIT_MAX_REQUESTS),
rate_limit_window_seconds: self
.rate_limit_window_seconds
.unwrap_or(DEFAULT_RATE_LIMIT_WINDOW_SECONDS),
rate_limit_store: DashMap::new(),
default_decision: self.default_decision.unwrap_or(PermissionDecision::NotApplicable),
log_denied: self.log_denied.unwrap_or(false),
}
}
}
impl PolicyDecisionPoint {
pub fn new(provider: Arc<dyn PermissionProvider>) -> Self {
Self {
provider,
cache: DashMap::new(),
cache_ttl_seconds: DEFAULT_CACHE_TTL_SECONDS,
cache_enabled: true,
rate_limit_max_requests: DEFAULT_RATE_LIMIT_MAX_REQUESTS,
rate_limit_window_seconds: DEFAULT_RATE_LIMIT_WINDOW_SECONDS,
rate_limit_store: DashMap::new(),
default_decision: PermissionDecision::NotApplicable,
log_denied: false,
}
}
pub fn builder() -> PolicyDecisionPointBuilder {
PolicyDecisionPointBuilder::new()
}
pub fn with_dependencies(provider: Arc<dyn PermissionProvider>) -> Self {
Self::new(provider)
}
pub fn with_cache(provider: Arc<dyn PermissionProvider>, cache_ttl_seconds: u64) -> Self {
Self {
provider,
cache: DashMap::new(),
cache_ttl_seconds,
cache_enabled: true,
rate_limit_max_requests: DEFAULT_RATE_LIMIT_MAX_REQUESTS,
rate_limit_window_seconds: DEFAULT_RATE_LIMIT_WINDOW_SECONDS,
rate_limit_store: DashMap::new(),
default_decision: PermissionDecision::NotApplicable,
log_denied: false,
}
}
pub fn with_rate_limit(provider: Arc<dyn PermissionProvider>, max_requests: u32, window_seconds: u32) -> Self {
Self {
provider,
cache: DashMap::new(),
cache_ttl_seconds: DEFAULT_CACHE_TTL_SECONDS,
cache_enabled: true,
rate_limit_max_requests: max_requests,
rate_limit_window_seconds: window_seconds,
rate_limit_store: DashMap::new(),
default_decision: PermissionDecision::NotApplicable,
log_denied: false,
}
}
pub fn with_config(provider: Arc<dyn PermissionProvider>, config: PolicyDecisionPointConfig) -> Self {
Self {
provider,
cache: DashMap::new(),
cache_ttl_seconds: config.cache_ttl_seconds,
cache_enabled: config.cache_enabled,
rate_limit_max_requests: DEFAULT_RATE_LIMIT_MAX_REQUESTS,
rate_limit_window_seconds: DEFAULT_RATE_LIMIT_WINDOW_SECONDS,
rate_limit_store: DashMap::new(),
default_decision: config.default_decision,
log_denied: config.log_denied,
}
}
fn check_rate_limit(&self, subject_id: &str) -> bool {
let key = subject_id.to_string();
let now = Instant::now();
let window_duration = Duration::from_secs(self.rate_limit_window_seconds as u64);
let mut entry = self.rate_limit_store.entry(key.clone()).or_insert(RateLimitEntry {
count: 0,
window_start: now,
});
if now.duration_since(entry.window_start) >= window_duration {
entry.count = 0;
entry.window_start = now;
}
if entry.count >= self.rate_limit_max_requests {
false
} else {
entry.count += 1;
true
}
}
pub async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision {
if !self.check_rate_limit(&context.subject.id) {
return PermissionDecision::Deny;
}
let cache_key = self.generate_cache_key(context);
if self.cache_enabled {
if let Some(decision) = self.get_cached_decision(&cache_key) {
self.maybe_log_denied(&decision, context);
return decision;
}
}
let decision = self.provider.check_permission(context).await;
let decision = match decision {
PermissionDecision::NotApplicable => self.default_decision.clone(),
other => other,
};
if self.cache_enabled {
self.update_cache(&cache_key, decision.clone());
}
self.maybe_log_denied(&decision, context);
decision
}
fn maybe_log_denied(&self, decision: &PermissionDecision, context: &PermissionContext) {
if self.log_denied && matches!(decision, PermissionDecision::Deny) {
eprintln!(
"[permission] 拒绝访问: subject={}, resource={}, action={:?}",
context.subject.id, context.resource.name, context.action
);
}
}
pub async fn check(&self, subject: &str, resource: &str, action: &str) -> PermissionDecision {
let action = match action.to_uppercase().as_str() {
"SELECT" => PermissionAction::Select,
"INSERT" => PermissionAction::Insert,
"UPDATE" => PermissionAction::Update,
"DELETE" => PermissionAction::Delete,
_ => return PermissionDecision::Error(format!("Unknown action: {}", action)),
};
let context = PermissionContext::new(
PermissionSubject::user(subject),
PermissionResource::new(resource),
action,
);
self.check_permission(&context).await
}
pub async fn check_batch(&self, contexts: Vec<PermissionContext>) -> Vec<(PermissionContext, PermissionDecision)> {
let mut results = Vec::with_capacity(contexts.len());
for context in contexts {
let decision = self.check_permission(&context).await;
results.push((context, decision));
}
results
}
pub async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource> {
self.provider.get_allowed_resources(subject).await
}
pub async fn refresh_cache(&self) {
self.provider.refresh().await.ok();
self.cache.clear();
}
pub fn set_cache_enabled(&mut self, enabled: bool) {
self.cache_enabled = enabled;
if !enabled {
self.cache.clear();
}
}
fn generate_cache_key(&self, context: &PermissionContext) -> String {
format!(
"{}:{}:{}:{}",
context.subject.id,
context.resource.name,
context.action,
context
.attributes
.iter()
.fold(String::new(), |acc, (k, v)| format!("{}:{}={}", acc, k, v))
)
}
fn get_cached_decision(&self, key: &str) -> Option<PermissionDecision> {
if let Some(cached) = self.cache.get(key) {
if !cached.is_expired(self.cache_ttl_seconds) {
return Some(cached.decision.clone());
}
}
None
}
fn update_cache(&self, key: &str, decision: PermissionDecision) {
self.cache.insert(key.to_string(), CachedDecision::new(decision));
}
}
#[derive(Debug)]
pub struct YamlPermissionProvider {
config_path: String,
roles: RwLock<HashMap<String, Vec<PermissionRule>>>,
last_refresh: RwLock<Instant>,
name: String,
role_mapping: RwLock<HashMap<String, Vec<String>>>,
}
impl Default for YamlPermissionProvider {
fn default() -> Self {
Self {
config_path: String::new(),
roles: RwLock::new(HashMap::new()),
last_refresh: RwLock::new(Instant::now()),
name: "yaml".to_string(),
role_mapping: RwLock::new(HashMap::new()),
}
}
}
impl YamlPermissionProvider {
pub fn new(config_path: &str) -> Result<Self, String> {
if config_path.is_empty() {
return Err("Config path cannot be empty".to_string());
}
if PATH_TRAVERSAL_REGEX.is_match(config_path) {
return Err("Config path contains invalid parent directory reference".to_string());
}
if !is_safe_config_path(config_path) {
return Err("Config path failed safety validation".to_string());
}
Ok(Self {
config_path: config_path.to_string(),
roles: RwLock::new(HashMap::new()),
last_refresh: RwLock::new(Instant::now()),
name: "yaml".to_string(),
role_mapping: RwLock::new(HashMap::new()),
})
}
async fn load_config(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use serde::Deserialize;
let content = tokio::fs::read_to_string(&self.config_path).await?;
#[derive(Debug, Deserialize)]
struct YamlConfig {
roles: HashMap<String, Vec<PermissionRule>>,
}
#[cfg(feature = "json")]
{
let config: YamlConfig = serde_json::from_str(&content)?;
if let Ok(mut roles) = self.roles.write() {
*roles = config.roles;
}
}
#[cfg(not(feature = "json"))]
{
#[cfg(feature = "yaml")]
{
let config: YamlConfig = serde_yaml_ng::from_str(&content)?;
if let Ok(mut roles) = self.roles.write() {
*roles = config.roles;
}
}
#[cfg(not(feature = "yaml"))]
{
return Err("Cannot parse permission config: neither JSON nor YAML support available".into());
}
}
if let Ok(mut role_mapping) = self.role_mapping.write() {
role_mapping.clear();
}
if let Ok(mut last_refresh) = self.last_refresh.write() {
*last_refresh = Instant::now();
}
Ok(())
}
}
#[async_trait]
impl PermissionProvider for YamlPermissionProvider {
async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision {
let age = self.last_refresh.read().map(|r| r.elapsed()).unwrap_or_default();
if age.as_secs() > 60 {
if let Err(e) = self.load_config().await {
return PermissionDecision::Error(format!("Failed to load config: {}", e));
}
}
let roles = match self.roles.read() {
Ok(r) => r,
Err(_) => return PermissionDecision::Error("Lock error".to_string()),
};
let subject_roles = self.get_subject_roles(&context.subject.id);
let mut matched_rules: Vec<(i32, &PermissionRule)> = Vec::new();
for role_name in &subject_roles {
if let Some(rules) = roles.get(role_name) {
for rule in rules {
if rule.enabled && matches_rule(rule, context) {
matched_rules.push((rule.priority, rule));
}
}
}
}
matched_rules.sort_by_key(|b| std::cmp::Reverse(b.0));
for (_, rule) in matched_rules {
if rule.allow.contains(&context.action) {
return PermissionDecision::Allow;
}
if rule.deny.contains(&context.action) {
return PermissionDecision::Deny;
}
}
PermissionDecision::NotApplicable
}
async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource> {
let roles = match self.roles.read() {
Ok(r) => r,
Err(_) => return Vec::new(),
};
let subject_roles = self.get_subject_roles(subject);
let mut resources = std::collections::HashSet::new();
for role_name in &subject_roles {
if let Some(rules) = roles.get(role_name) {
for rule in rules {
if rule.enabled && (rule.subject == "*" || rule.subject == subject) {
resources.insert(PermissionResource::new(&rule.resource));
}
}
}
}
resources.into_iter().collect()
}
async fn get_allowed_actions(&self, subject: &str, resource: &str) -> Vec<PermissionAction> {
let roles = match self.roles.read() {
Ok(r) => r,
Err(_) => return Vec::new(),
};
let subject_roles = self.get_subject_roles(subject);
let mut actions = std::collections::HashSet::new();
for role_name in &subject_roles {
if let Some(rules) = roles.get(role_name) {
for rule in rules {
if rule.enabled
&& (rule.subject == "*" || rule.subject == subject)
&& (rule.resource == "*" || rule.resource == resource)
{
for action in &rule.allow {
actions.insert(action.clone());
}
}
}
}
}
actions.into_iter().collect()
}
async fn refresh(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.load_config().await
}
fn name(&self) -> &str {
&self.name
}
}
impl YamlPermissionProvider {
fn get_subject_roles(&self, subject: &str) -> Vec<String> {
let mapping = match self.role_mapping.read() {
Ok(m) => m,
Err(_) => return Vec::new(),
};
let roles = match self.roles.read() {
Ok(r) => r,
Err(_) => return Vec::new(),
};
get_subject_roles(&mapping, &roles, subject)
}
}
#[derive(Debug)]
pub struct RbacPermissionProvider {
roles: RwLock<HashMap<String, Role>>,
permissions: RwLock<HashMap<String, Vec<PermissionRule>>>,
role_hierarchy: RwLock<HashMap<String, Vec<String>>>,
last_refresh: RwLock<Instant>,
name: String,
role_mapping: RwLock<HashMap<String, Vec<String>>>,
}
impl Default for RbacPermissionProvider {
fn default() -> Self {
Self {
roles: RwLock::new(HashMap::new()),
permissions: RwLock::new(HashMap::new()),
role_hierarchy: RwLock::new(HashMap::new()),
last_refresh: RwLock::new(Instant::now()),
name: "rbac".to_string(),
role_mapping: RwLock::new(HashMap::new()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Role {
pub name: String,
#[serde(default)]
pub description: String,
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default)]
pub extends: Vec<String>,
}
impl Default for Role {
fn default() -> Self {
Self {
name: String::new(),
description: String::new(),
enabled: true,
extends: Vec::new(),
}
}
}
impl RbacPermissionProvider {
pub fn new() -> Self {
Self {
roles: RwLock::new(HashMap::new()),
permissions: RwLock::new(HashMap::new()),
role_hierarchy: RwLock::new(HashMap::new()),
last_refresh: RwLock::new(Instant::now()),
name: "rbac".to_string(),
role_mapping: RwLock::new(HashMap::new()),
}
}
pub fn add_role(&self, role: Role) {
if let Ok(mut roles) = self.roles.write() {
roles.insert(role.name.clone(), role.clone());
}
if let Ok(mut hierarchy) = self.role_hierarchy.write() {
hierarchy.insert(role.name, role.extends);
}
}
pub fn add_permission(&self, role: &str, rule: PermissionRule) {
if let Ok(mut permissions) = self.permissions.write() {
permissions.entry(role.to_string()).or_default().push(rule);
}
}
pub fn add_role_to_subject(&self, subject: &str, role: &str) {
if let Ok(mut mapping) = self.role_mapping.write() {
mapping.entry(subject.to_string()).or_default().push(role.to_string());
}
}
async fn get_role_permissions(&self, role: &str) -> Vec<PermissionRule> {
let mut all_permissions = Vec::new();
let mut visited = std::collections::HashSet::new();
let mut to_visit = vec![role.to_string()];
let permissions = if let Ok(p) = self.permissions.read() {
p
} else {
return Vec::new();
};
let hierarchy = if let Ok(h) = self.role_hierarchy.read() {
h
} else {
return Vec::new();
};
while let Some(current_role) = to_visit.pop() {
if visited.contains(¤t_role) {
continue;
}
visited.insert(current_role.clone());
if let Some(rules) = permissions.get(¤t_role) {
all_permissions.extend(rules.iter().cloned());
}
if let Some(extends) = hierarchy.get(¤t_role) {
for parent_role in extends {
if !visited.contains(parent_role) {
to_visit.push(parent_role.clone());
}
}
}
}
all_permissions
}
}
#[async_trait]
impl PermissionProvider for RbacPermissionProvider {
async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision {
let subject_roles = self.get_subject_roles(&context.subject.id);
let mut all_rules = Vec::new();
for role in &subject_roles {
let rules = self.get_role_permissions(role).await;
all_rules.extend(rules);
}
all_rules.sort_by_key(|b| std::cmp::Reverse(b.priority));
for rule in all_rules {
if rule.enabled && matches_rule(&rule, context) {
if rule.allow.contains(&context.action) {
return PermissionDecision::Allow;
}
if rule.deny.contains(&context.action) {
return PermissionDecision::Deny;
}
}
}
PermissionDecision::NotApplicable
}
async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource> {
let subject_roles = self.get_subject_roles(subject);
let mut resources = std::collections::HashSet::new();
for role in &subject_roles {
let rules = self.get_role_permissions(role).await;
for rule in rules {
if rule.enabled {
resources.insert(PermissionResource::new(&rule.resource));
}
}
}
resources.into_iter().collect()
}
async fn get_allowed_actions(&self, subject: &str, resource: &str) -> Vec<PermissionAction> {
let subject_roles = self.get_subject_roles(subject);
let mut actions = std::collections::HashSet::new();
for role in &subject_roles {
let rules = self.get_role_permissions(role).await;
for rule in rules {
if rule.enabled && (rule.resource == "*" || rule.resource == resource) {
for action in &rule.allow {
actions.insert(action.clone());
}
}
}
}
actions.into_iter().collect()
}
async fn refresh(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if let Ok(mut last_refresh) = self.last_refresh.write() {
*last_refresh = Instant::now();
}
Ok(())
}
fn name(&self) -> &str {
&self.name
}
}
impl RbacPermissionProvider {
fn get_subject_roles(&self, subject: &str) -> Vec<String> {
let mapping = match self.role_mapping.read() {
Ok(m) => m,
Err(_) => return Vec::new(),
};
let roles = match self.roles.read() {
Ok(r) => r,
Err(_) => return Vec::new(),
};
get_subject_roles(&mapping, &roles, subject)
}
pub fn has_role(&self, role: &str) -> bool {
if let Ok(roles) = self.roles.read() {
roles.contains_key(role) || self.get_subject_roles(role).contains(&role.to_string())
} else {
false
}
}
}
#[derive(Debug, Clone)]
pub struct PolicyDecisionPointConfig {
pub default_decision: PermissionDecision,
pub log_denied: bool,
pub cache_ttl_seconds: u64,
pub cache_enabled: bool,
}
impl Default for PolicyDecisionPointConfig {
fn default() -> Self {
Self {
default_decision: PermissionDecision::Deny,
log_denied: true,
cache_ttl_seconds: 300,
cache_enabled: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_yaml_permission_provider() {
let provider = Arc::new(RbacPermissionProvider::new());
provider.add_role(Role {
name: "admin".to_string(),
description: "管理员角色".to_string(),
enabled: true,
extends: vec![],
});
provider.add_permission(
"admin",
PermissionRule {
name: "admin_select".to_string(),
priority: 100,
subject: "*".to_string(),
resource: "users".to_string(),
allow: vec![PermissionAction::Select],
deny: vec![],
condition: None,
enabled: true,
},
);
provider.add_role_to_subject("admin", "admin");
let pdp = PolicyDecisionPoint::new(provider);
let result = pdp.check("admin", "users", "SELECT").await;
assert_eq!(result, PermissionDecision::Allow);
}
#[tokio::test]
async fn test_rbac_permission_provider() {
let provider = Arc::new(RbacPermissionProvider::new());
provider.add_role(Role {
name: "admin".to_string(),
description: "管理员角色".to_string(),
enabled: true,
extends: vec![],
});
provider.add_permission(
"admin",
PermissionRule {
name: "admin_all".to_string(),
priority: 100,
subject: "*".to_string(),
resource: "*".to_string(),
allow: vec![
PermissionAction::Select,
PermissionAction::Insert,
PermissionAction::Update,
PermissionAction::Delete,
],
deny: vec![],
condition: None,
enabled: true,
},
);
provider.add_role_to_subject("admin", "admin");
let pdp = PolicyDecisionPoint::new(provider);
let result = pdp.check("admin", "users", "SELECT").await;
assert_eq!(result, PermissionDecision::Allow);
let result = pdp.check("admin", "users", "DELETE").await;
assert_eq!(result, PermissionDecision::Allow);
}
#[tokio::test]
async fn test_permission_engine() {
let provider = Arc::new(RbacPermissionProvider::new());
provider.add_role(Role {
name: "admin".to_string(),
description: "管理员角色".to_string(),
enabled: true,
extends: vec![],
});
provider.add_permission(
"admin",
PermissionRule {
name: "admin_all".to_string(),
priority: 100,
subject: "*".to_string(),
resource: "*".to_string(),
allow: vec![
PermissionAction::Select,
PermissionAction::Insert,
PermissionAction::Update,
PermissionAction::Delete,
],
deny: vec![],
condition: None,
enabled: true,
},
);
provider.add_role_to_subject("admin", "admin");
let pdp = PolicyDecisionPoint::new(provider);
let decision = pdp.check("admin", "users", "SELECT").await;
assert_eq!(decision, PermissionDecision::Allow);
}
#[tokio::test]
async fn test_permission_context() {
let context = PermissionContext::new(
PermissionSubject::user("admin"),
PermissionResource::new("users"),
PermissionAction::Select,
)
.with_attribute("ip", "192.168.1.1")
.with_environment("time", "2024-01-01");
assert_eq!(context.subject.id, "admin");
assert_eq!(context.resource.name, "users");
assert_eq!(context.action, PermissionAction::Select);
assert!(context.attributes.contains_key("ip"));
}
#[tokio::test]
async fn test_policy_decision_point_with_rate_limit() {
let provider = Arc::new(RbacPermissionProvider::new());
provider.add_role(Role {
name: "admin".to_string(),
description: "管理员角色".to_string(),
enabled: true,
extends: vec![],
});
provider.add_permission(
"admin",
PermissionRule {
name: "admin_select".to_string(),
priority: 100,
subject: "*".to_string(),
resource: "users".to_string(),
allow: vec![PermissionAction::Select],
deny: vec![],
condition: None,
enabled: true,
},
);
provider.add_role_to_subject("admin", "admin");
let pdp = PolicyDecisionPoint::with_rate_limit(provider, 10, 60);
for i in 0..10 {
let result = pdp.check("admin", "users", "SELECT").await;
assert_eq!(result, PermissionDecision::Allow, "Request {} should be allowed", i);
}
let result = pdp.check("admin", "users", "SELECT").await;
assert_eq!(result, PermissionDecision::Deny);
}
#[tokio::test]
async fn test_permission_subject_creation() {
let user = PermissionSubject::user("test_user");
assert_eq!(user.id, "test_user");
assert_eq!(user.subject_type, SubjectType::User);
let role = PermissionSubject::role("admin");
assert_eq!(role.id, "admin");
assert_eq!(role.subject_type, SubjectType::Role);
}
#[tokio::test]
async fn test_permission_resource_creation() {
let resource = PermissionResource::new("users");
assert_eq!(resource.name, "users");
assert_eq!(resource.resource_type, "table");
let resource_with_type = PermissionResource::with_type("logs", "log");
assert_eq!(resource_with_type.name, "logs");
assert_eq!(resource_with_type.resource_type, "log");
}
#[tokio::test]
async fn test_permission_decision_types() {
assert_eq!(PermissionDecision::Allow, PermissionDecision::Allow);
assert_eq!(PermissionDecision::Deny, PermissionDecision::Deny);
assert_eq!(PermissionDecision::NotApplicable, PermissionDecision::NotApplicable);
let error_decision = PermissionDecision::Error("Test error".to_string());
assert!(matches!(error_decision, PermissionDecision::Error(msg) if msg == "Test error"));
}
#[tokio::test]
async fn test_role_creation() {
let role = Role {
name: "test_role".to_string(),
description: "测试角色".to_string(),
enabled: true,
extends: vec!["base_role".to_string()],
};
assert_eq!(role.name, "test_role");
assert_eq!(role.description, "测试角色");
assert!(role.enabled);
assert_eq!(role.extends.len(), 1);
assert_eq!(role.extends[0], "base_role");
}
#[tokio::test]
async fn test_permission_rule_creation() {
let rule = PermissionRule {
name: "test_rule".to_string(),
priority: 50,
subject: "admin".to_string(),
resource: "users".to_string(),
allow: vec![PermissionAction::Select, PermissionAction::Insert],
deny: vec![PermissionAction::Delete],
condition: Some("active = true".to_string()),
enabled: true,
};
assert_eq!(rule.name, "test_rule");
assert_eq!(rule.priority, 50);
assert_eq!(rule.allow.len(), 2);
assert_eq!(rule.deny.len(), 1);
assert!(rule.enabled);
assert!(rule.condition.is_some());
}
#[tokio::test]
async fn test_role_hierarchy() {
let provider = RbacPermissionProvider::new();
let base_role = Role {
name: "base_user".to_string(),
description: "基础用户角色".to_string(),
enabled: true,
extends: vec![],
};
provider.add_role(base_role);
let child_role = Role {
name: "premium_user".to_string(),
description: "高级用户角色".to_string(),
enabled: true,
extends: vec!["base_user".to_string()],
};
provider.add_role(child_role.clone());
assert!(provider.has_role("base_user"));
assert!(provider.has_role("premium_user"));
}
}