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::Instant;
static PATH_TRAVERSAL_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\.\.|%2e%2e|%252e%252e|\\/|\\\\").expect("Regex pattern should be valid"));
static DANGEROUS_PATH_PATTERNS: &[&str] = &[
"/etc/passwd",
"/etc/shadow",
"/etc/sudoers",
"/root/.ssh",
"/proc/self",
"/sys/kernel",
"C:\\Windows\\System32",
"..\\..\\",
];
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PermissionAction {
Select,
Insert,
Update,
Delete,
All,
}
impl std::fmt::Display for PermissionAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PermissionAction::Select => write!(f, "SELECT"),
PermissionAction::Insert => write!(f, "INSERT"),
PermissionAction::Update => write!(f, "UPDATE"),
PermissionAction::Delete => write!(f, "DELETE"),
PermissionAction::All => write!(f, "*"),
}
}
}
#[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
}
#[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)]
pub struct PolicyDecisionPoint {
provider: Arc<dyn PermissionProvider>,
cache: DashMap<String, CachedDecision>,
cache_ttl_seconds: u64,
cache_enabled: bool,
}
impl PolicyDecisionPoint {
pub fn new(provider: Arc<dyn PermissionProvider>) -> Self {
Self {
provider,
cache: DashMap::new(),
cache_ttl_seconds: 300, cache_enabled: true,
}
}
pub fn with_cache(provider: Arc<dyn PermissionProvider>, cache_ttl_seconds: u64) -> Self {
Self {
provider,
cache: DashMap::new(),
cache_ttl_seconds,
cache_enabled: true,
}
}
pub async fn check_permission(&self, context: &PermissionContext) -> PermissionDecision {
let cache_key = self.generate_cache_key(context);
if self.cache_enabled {
if let Some(decision) = self.get_cached_decision(&cache_key) {
return decision;
}
}
let decision = self.provider.check_permission(context).await;
if self.cache_enabled {
self.update_cache(&cache_key, decision.clone());
}
decision
}
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> {
let path = std::path::Path::new(config_path);
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 config_path.as_bytes().contains(&0) {
return Err("Config path contains null byte".to_string());
}
if !path.is_absolute() {
let canonical = std::fs::canonicalize(path)
.map_err(|_| "Cannot resolve config path, it may not exist or is inaccessible".to_string())?;
let canonical_str = canonical.to_string_lossy();
if canonical_str.contains("..") {
return Err("Config path resolves to invalid parent directory reference".to_string());
}
let current_dir = std::env::current_dir().map_err(|_| "Cannot determine current directory".to_string())?;
let allowed_dirs = [
current_dir.clone(),
current_dir.join("config"),
current_dir.join("permissions"),
current_dir.join("etc"),
];
let mut is_allowed = false;
for allowed in &allowed_dirs {
if let Ok(allowed_canonical) = std::fs::canonicalize(allowed) {
if canonical.starts_with(&allowed_canonical) {
is_allowed = true;
break;
}
}
}
if !is_allowed {
return Err("Config path is not in allowed directory".to_string());
}
} else {
let forbidden_prefixes = [
std::path::Path::new("/etc"),
std::path::Path::new("/usr"),
std::path::Path::new("/var"),
std::path::Path::new("/root"),
std::path::Path::new("/boot"),
std::path::Path::new("/sys"),
std::path::Path::new("/proc"),
];
let canonical = std::fs::canonicalize(path).map_err(|_| "Cannot resolve config path".to_string())?;
for prefix in &forbidden_prefixes {
if canonical.starts_with(prefix) {
return Err("Config path is in system directory, which is not allowed".to_string());
}
}
}
if path.is_symlink() {
return Err("Config path cannot be a symbolic link".to_string());
}
if !path.exists() {
return Err("Config file does not exist".to_string());
}
if !path.is_file() {
return Err("Config path must point to a file".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>> {
let content = tokio::fs::read_to_string(&self.config_path).await?;
#[derive(Debug, Deserialize)]
struct YamlConfig {
roles: HashMap<String, Vec<PermissionRule>>,
#[serde(default)]
subjects: HashMap<String, Vec<String>>,
}
let config: YamlConfig = serde_yaml::from_str(&content)?;
if let Ok(mut roles) = self.roles.write() {
*roles = config.roles;
}
if let Ok(mut role_mapping) = self.role_mapping.write() {
role_mapping.clear();
for (subject, roles_list) in config.subjects {
role_mapping.insert(subject, roles_list);
}
}
if let Ok(mut last_refresh) = self.last_refresh.write() {
*last_refresh = Instant::now();
}
Ok(())
}
fn matches_rule(&self, 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) || rule.allow.contains(&PermissionAction::All);
let in_deny = rule.deny.contains(&context.action) || rule.deny.contains(&PermissionAction::All);
if !in_allow && !in_deny && context.action != PermissionAction::All {
return false;
}
true
}
}
#[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 && self.matches_rule(rule, context) {
matched_rules.push((rule.priority, rule));
}
}
}
}
matched_rules.sort_by(|a, b| b.0.cmp(&a.0));
for (_, rule) in matched_rules {
if rule.allow.contains(&context.action) || rule.allow.contains(&PermissionAction::All) {
return PermissionDecision::Allow;
}
if rule.deny.contains(&context.action) || rule.deny.contains(&PermissionAction::All) {
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> {
if let Ok(mapping) = self.role_mapping.read() {
if let Some(roles) = mapping.get(subject) {
return roles.clone();
}
}
if let Ok(roles) = self.roles.read() {
if roles.contains_key(subject) {
return vec![subject.to_string()];
}
}
Vec::new()
}
}
#[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(|a, b| b.priority.cmp(&a.priority));
for rule in all_rules {
if rule.enabled && self.matches_rule(&rule, context) {
if rule.allow.contains(&context.action) || rule.allow.contains(&PermissionAction::All) {
return PermissionDecision::Allow;
}
if rule.deny.contains(&context.action) || rule.deny.contains(&PermissionAction::All) {
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> {
if let Ok(mapping) = self.role_mapping.read() {
if let Some(roles) = mapping.get(subject) {
return roles.clone();
}
}
if let Ok(roles) = self.roles.read() {
if roles.contains_key(subject) {
return vec![subject.to_string()];
}
}
Vec::new()
}
fn matches_rule(&self, 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;
}
true
}
}
#[derive(Debug, Clone)]
pub struct PermissionEngineConfig {
pub default_decision: PermissionDecision,
pub log_denied: bool,
pub cache_ttl_seconds: u64,
pub cache_enabled: bool,
}
impl Default for PermissionEngineConfig {
fn default() -> Self {
Self {
default_decision: PermissionDecision::Deny,
log_denied: true,
cache_ttl_seconds: 300,
cache_enabled: true,
}
}
}
#[derive(Debug)]
pub struct PermissionEngine {
pdp: PolicyDecisionPoint,
config: PermissionEngineConfig,
}
impl PermissionEngine {
pub fn new(provider: Arc<dyn PermissionProvider>) -> Self {
let config = PermissionEngineConfig::default();
Self {
pdp: PolicyDecisionPoint::with_cache(provider, config.cache_ttl_seconds),
config,
}
}
pub fn with_config(provider: Arc<dyn PermissionProvider>, config: PermissionEngineConfig) -> Self {
Self {
pdp: PolicyDecisionPoint::with_cache(provider, config.cache_ttl_seconds),
config,
}
}
pub async fn check(&self, subject: &str, resource: &str, action: &str) -> bool {
let decision = self.pdp.check(subject, resource, action).await;
decision == PermissionDecision::Allow
}
pub async fn check_with_decision(&self, subject: &str, resource: &str, action: &str) -> PermissionDecision {
self.pdp.check(subject, resource, action).await
}
pub async fn get_allowed_resources(&self, subject: &str) -> Vec<PermissionResource> {
self.pdp.get_allowed_resources(subject).await
}
pub async fn refresh(&self) {
self.pdp.refresh_cache().await;
}
}
#[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 engine = PermissionEngine::new(provider);
let allowed = engine.check("admin", "users", "SELECT").await;
assert!(allowed);
}
#[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"));
}
}