| use crate::{ |
| adapter::{Adapter, Filter}, |
| convert::{EnforceArgs, TryIntoAdapter, TryIntoModel}, |
| core_api::CoreApi, |
| effector::{DefaultEffector, EffectKind, Effector}, |
| emitter::{Event, EventData, EventEmitter}, |
| error::{ModelError, PolicyError, RequestError}, |
| get_or_err, get_or_err_with_context, |
| management_api::MgmtApi, |
| model::{FunctionMap, Model, OperatorFunction}, |
| rbac::{DefaultRoleManager, RoleManager}, |
| register_g_function, |
| util::{escape_assertion, escape_eval}, |
| Result, |
| }; |
| |
| use crate::model::DefaultModel; |
| |
| #[cfg(any(feature = "logging", feature = "watcher"))] |
| use crate::emitter::notify_logger_and_watcher; |
| |
| #[cfg(feature = "watcher")] |
| use crate::watcher::Watcher; |
| |
| #[cfg(feature = "logging")] |
| use crate::{DefaultLogger, Logger}; |
| |
| use async_trait::async_trait; |
| use once_cell::sync::Lazy; |
| use parking_lot::RwLock; |
| use rhai::{ |
| def_package, |
| packages::{ |
| ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage, |
| Package, |
| }, |
| Dynamic, Engine, EvalAltResult, ImmutableString, Scope, |
| }; |
| |
| def_package! { |
| pub CasbinPackage(lib) { |
| ArithmeticPackage::init(lib); |
| LogicPackage::init(lib); |
| BasicArrayPackage::init(lib); |
| BasicMapPackage::init(lib); |
| |
| lib.set_native_fn("escape_assertion", |s: ImmutableString| { |
| Ok(escape_assertion(&s)) |
| }); |
| } |
| } |
| |
| static CASBIN_PACKAGE: Lazy<CasbinPackage> = Lazy::new(CasbinPackage::new); |
| |
| use std::{cmp::max, collections::HashMap, sync::Arc}; |
| |
| type EventCallback = fn(&mut Enforcer, EventData); |
| |
| /// Enforcer is the main interface for authorization enforcement and policy management. |
| pub struct Enforcer { |
| model: Box<dyn Model>, |
| adapter: Box<dyn Adapter>, |
| fm: FunctionMap, |
| eft: Box<dyn Effector>, |
| rm: Arc<RwLock<dyn RoleManager>>, |
| enabled: bool, |
| auto_save: bool, |
| auto_build_role_links: bool, |
| #[cfg(feature = "watcher")] |
| auto_notify_watcher: bool, |
| #[cfg(feature = "watcher")] |
| watcher: Option<Box<dyn Watcher>>, |
| events: HashMap<Event, Vec<EventCallback>>, |
| engine: Engine, |
| #[cfg(feature = "logging")] |
| logger: Box<dyn Logger>, |
| } |
| |
| pub struct EnforceContext { |
| pub r_type: String, |
| pub p_type: String, |
| pub e_type: String, |
| pub m_type: String, |
| } |
| |
| impl EnforceContext { |
| pub fn new(suffix: &str) -> Self { |
| Self { |
| r_type: format!("r{}", suffix), |
| p_type: format!("p{}", suffix), |
| e_type: format!("e{}", suffix), |
| m_type: format!("m{}", suffix), |
| } |
| } |
| pub fn get_cache_key(&self) -> String { |
| format!( |
| "EnforceContext{{{}-{}-{}-{}}}", |
| &self.r_type, &self.p_type, &self.e_type, &self.m_type, |
| ) |
| } |
| } |
| |
| impl EventEmitter<Event> for Enforcer { |
| fn on(&mut self, e: Event, f: fn(&mut Self, EventData)) { |
| self.events.entry(e).or_default().push(f) |
| } |
| |
| fn off(&mut self, e: Event) { |
| self.events.remove(&e); |
| } |
| |
| fn emit(&mut self, e: Event, d: EventData) { |
| if let Some(cbs) = self.events.get(&e) { |
| for cb in cbs.clone().iter() { |
| cb(self, d.clone()) |
| } |
| } |
| } |
| } |
| |
| impl Enforcer { |
| pub(crate) fn private_enforce( |
| &self, |
| rvals: &[Dynamic], |
| ) -> Result<(bool, Option<Vec<usize>>)> { |
| if !self.enabled { |
| return Ok((true, None)); |
| } |
| |
| let mut scope: Scope = Scope::new(); |
| |
| let r_ast = get_or_err!(self, "r", ModelError::R, "request"); |
| let p_ast = get_or_err!(self, "p", ModelError::P, "policy"); |
| let m_ast = get_or_err!(self, "m", ModelError::M, "matcher"); |
| let e_ast = get_or_err!(self, "e", ModelError::E, "effector"); |
| |
| if r_ast.tokens.len() != rvals.len() { |
| return Err(RequestError::UnmatchRequestDefinition( |
| r_ast.tokens.len(), |
| rvals.len(), |
| ) |
| .into()); |
| } |
| |
| for (rtoken, rval) in r_ast.tokens.iter().zip(rvals.iter()) { |
| scope.push_constant_dynamic(rtoken, rval.to_owned()); |
| } |
| |
| let policies = p_ast.get_policy(); |
| let (policy_len, scope_len) = (policies.len(), scope.len()); |
| |
| let mut eft_stream = |
| self.eft.new_stream(&e_ast.value, max(policy_len, 1)); |
| let m_ast_compiled = if let Some(default_model) = |
| self.model.as_any().downcast_ref::<DefaultModel>() |
| { |
| default_model.get_compiled_matcher("m").ok_or_else(|| { |
| crate::error::Error::ModelError(crate::error::ModelError::M( |
| "Matcher 'm' not compiled".to_string(), |
| )) |
| })? |
| } else { |
| // Fallback to original compilation (for other Model implementations) |
| &self |
| .engine |
| .compile_expression(escape_eval(&m_ast.value)) |
| .map_err(Into::<Box<EvalAltResult>>::into)? |
| }; |
| |
| if policy_len == 0 { |
| for token in p_ast.tokens.iter() { |
| scope.push_constant(token, String::new()); |
| } |
| |
| let eval_result = self |
| .engine |
| .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?; |
| let eft = if eval_result { |
| EffectKind::Allow |
| } else { |
| EffectKind::Indeterminate |
| }; |
| |
| eft_stream.push_effect(eft); |
| |
| return Ok((eft_stream.next(), None)); |
| } |
| |
| for pvals in policies { |
| scope.rewind(scope_len); |
| |
| if p_ast.tokens.len() != pvals.len() { |
| return Err(PolicyError::UnmatchPolicyDefinition( |
| p_ast.tokens.len(), |
| pvals.len(), |
| ) |
| .into()); |
| } |
| for (ptoken, pval) in p_ast.tokens.iter().zip(pvals.iter()) { |
| scope.push_constant(ptoken, pval.to_owned()); |
| } |
| |
| let eval_result = self |
| .engine |
| .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?; |
| let eft = match p_ast.tokens.iter().position(|x| x == "p_eft") { |
| Some(j) if eval_result => { |
| let p_eft = &pvals[j]; |
| if p_eft == "deny" { |
| EffectKind::Deny |
| } else if p_eft == "allow" { |
| EffectKind::Allow |
| } else { |
| EffectKind::Indeterminate |
| } |
| } |
| None if eval_result => EffectKind::Allow, |
| _ => EffectKind::Indeterminate, |
| }; |
| |
| if eft_stream.push_effect(eft) { |
| break; |
| } |
| } |
| |
| Ok((eft_stream.next(), { |
| #[cfg(feature = "explain")] |
| { |
| eft_stream.explain() |
| } |
| #[cfg(not(feature = "explain"))] |
| { |
| None |
| } |
| })) |
| } |
| |
| pub(crate) fn private_enforce_with_context( |
| &self, |
| ctx: EnforceContext, |
| rvals: &[Dynamic], |
| ) -> Result<(bool, Option<Vec<usize>>)> { |
| if !self.enabled { |
| return Ok((true, None)); |
| } |
| |
| let mut scope: Scope = Scope::new(); |
| let r_ast = get_or_err_with_context!( |
| self, |
| "r", |
| &ctx.r_type, |
| ModelError::R, |
| "request" |
| ); |
| let p_ast = get_or_err_with_context!( |
| self, |
| "p", |
| &ctx.p_type, |
| ModelError::P, |
| "policy" |
| ); |
| let m_ast = get_or_err_with_context!( |
| self, |
| "m", |
| &ctx.m_type, |
| ModelError::M, |
| "matcher" |
| ); |
| let e_ast = get_or_err_with_context!( |
| self, |
| "e", |
| &ctx.e_type, |
| ModelError::E, |
| "effector" |
| ); |
| |
| if r_ast.tokens.len() != rvals.len() { |
| return Err(RequestError::UnmatchRequestDefinition( |
| r_ast.tokens.len(), |
| rvals.len(), |
| ) |
| .into()); |
| } |
| |
| for (rtoken, rval) in r_ast.tokens.iter().zip(rvals.iter()) { |
| scope.push_constant_dynamic(rtoken, rval.to_owned()); |
| } |
| |
| let policies = p_ast.get_policy(); |
| let (policy_len, scope_len) = (policies.len(), scope.len()); |
| |
| let mut eft_stream = |
| self.eft.new_stream(&e_ast.value, max(policy_len, 1)); |
| let m_ast_compiled = if let Some(default_model) = |
| self.model.as_any().downcast_ref::<DefaultModel>() |
| { |
| default_model.get_compiled_matcher(&ctx.m_type).ok_or_else( |
| || { |
| crate::error::Error::ModelError( |
| crate::error::ModelError::M(format!( |
| "Matcher '{}' not compiled", |
| ctx.m_type |
| )), |
| ) |
| }, |
| )? |
| } else { |
| // Fallback to original compilation (for other Model implementations) |
| &self |
| .engine |
| .compile_expression(escape_eval(&m_ast.value)) |
| .map_err(Into::<Box<EvalAltResult>>::into)? |
| }; |
| |
| if policy_len == 0 { |
| for token in p_ast.tokens.iter() { |
| scope.push_constant(token, String::new()); |
| } |
| |
| let eval_result = self |
| .engine |
| .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?; |
| let eft = if eval_result { |
| EffectKind::Allow |
| } else { |
| EffectKind::Indeterminate |
| }; |
| |
| eft_stream.push_effect(eft); |
| |
| return Ok((eft_stream.next(), None)); |
| } |
| |
| for pvals in policies { |
| scope.rewind(scope_len); |
| |
| if p_ast.tokens.len() != pvals.len() { |
| return Err(PolicyError::UnmatchPolicyDefinition( |
| p_ast.tokens.len(), |
| pvals.len(), |
| ) |
| .into()); |
| } |
| for (ptoken, pval) in p_ast.tokens.iter().zip(pvals.iter()) { |
| scope.push_constant(ptoken, pval.to_owned()); |
| } |
| |
| let eval_result = self |
| .engine |
| .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?; |
| let eft = match p_ast.tokens.iter().position(|x| x == "p_eft") { |
| Some(j) if eval_result => { |
| let p_eft = &pvals[j]; |
| if p_eft == "deny" { |
| EffectKind::Deny |
| } else if p_eft == "allow" { |
| EffectKind::Allow |
| } else { |
| EffectKind::Indeterminate |
| } |
| } |
| None if eval_result => EffectKind::Allow, |
| _ => EffectKind::Indeterminate, |
| }; |
| |
| if eft_stream.push_effect(eft) { |
| break; |
| } |
| } |
| |
| Ok((eft_stream.next(), { |
| #[cfg(feature = "explain")] |
| { |
| eft_stream.explain() |
| } |
| #[cfg(not(feature = "explain"))] |
| { |
| None |
| } |
| })) |
| } |
| |
| fn register_function(engine: &mut Engine, key: &str, f: OperatorFunction) { |
| match f { |
| OperatorFunction::Arg0(func) => { |
| engine.register_fn(key, func); |
| } |
| OperatorFunction::Arg1(func) => { |
| engine.register_fn(key, func); |
| } |
| OperatorFunction::Arg2(func) => { |
| engine.register_fn(key, func); |
| } |
| OperatorFunction::Arg3(func) => { |
| engine.register_fn(key, func); |
| } |
| OperatorFunction::Arg4(func) => { |
| engine.register_fn(key, func); |
| } |
| OperatorFunction::Arg5(func) => { |
| engine.register_fn(key, func); |
| } |
| OperatorFunction::Arg6(func) => { |
| engine.register_fn(key, func); |
| } |
| } |
| } |
| |
| pub(crate) fn register_g_functions(&mut self) -> Result<()> { |
| if let Some(ast_map) = self.model.get_model().get("g") { |
| for (fname, ast) in ast_map { |
| register_g_function!(self, fname, ast); |
| } |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| #[async_trait] |
| impl CoreApi for Enforcer { |
| #[allow(clippy::box_default)] |
| async fn new_raw<M: TryIntoModel, A: TryIntoAdapter>( |
| m: M, |
| a: A, |
| ) -> Result<Self> { |
| let model = m.try_into_model().await?; |
| let adapter = a.try_into_adapter().await?; |
| let fm = FunctionMap::default(); |
| let eft = Box::new(DefaultEffector); |
| let rm = Arc::new(RwLock::new(DefaultRoleManager::new(10))); |
| |
| let mut engine = Engine::new_raw(); |
| |
| engine.register_global_module(CASBIN_PACKAGE.as_shared_module()); |
| |
| for (key, &func) in fm.get_functions() { |
| Self::register_function(&mut engine, key, func); |
| } |
| |
| let mut e = Self { |
| model, |
| adapter, |
| fm, |
| eft, |
| rm, |
| enabled: true, |
| auto_save: true, |
| auto_build_role_links: true, |
| #[cfg(feature = "watcher")] |
| auto_notify_watcher: true, |
| #[cfg(feature = "watcher")] |
| watcher: None, |
| events: HashMap::new(), |
| engine, |
| #[cfg(feature = "logging")] |
| logger: Box::new(DefaultLogger::default()), |
| }; |
| |
| #[cfg(any(feature = "logging", feature = "watcher"))] |
| e.on(Event::PolicyChange, notify_logger_and_watcher); |
| |
| e.register_g_functions()?; |
| |
| // If using DefaultModel, compile matcher expressions |
| if let Some(default_model) = |
| e.model.as_any_mut().downcast_mut::<DefaultModel>() |
| { |
| default_model.compile_matchers(&e.engine)?; |
| } |
| |
| Ok(e) |
| } |
| |
| #[inline] |
| async fn new<M: TryIntoModel, A: TryIntoAdapter>( |
| m: M, |
| a: A, |
| ) -> Result<Self> { |
| let mut e = Self::new_raw(m, a).await?; |
| |
| // Do not initialize the full policy when using a filtered adapter |
| if !e.adapter.is_filtered() { |
| e.load_policy().await?; |
| } |
| Ok(e) |
| } |
| |
| #[inline] |
| fn add_function(&mut self, fname: &str, f: OperatorFunction) { |
| self.fm.add_function(fname, f); |
| Self::register_function(&mut self.engine, fname, f); |
| } |
| |
| #[inline] |
| fn get_model(&self) -> &dyn Model { |
| &*self.model |
| } |
| |
| #[inline] |
| fn get_mut_model(&mut self) -> &mut dyn Model { |
| &mut *self.model |
| } |
| |
| #[inline] |
| fn get_adapter(&self) -> &dyn Adapter { |
| &*self.adapter |
| } |
| |
| #[inline] |
| fn get_mut_adapter(&mut self) -> &mut dyn Adapter { |
| &mut *self.adapter |
| } |
| |
| #[cfg(feature = "watcher")] |
| #[inline] |
| fn set_watcher(&mut self, w: Box<dyn Watcher>) { |
| self.watcher = Some(w); |
| } |
| |
| #[cfg(feature = "logging")] |
| #[inline] |
| fn get_logger(&self) -> &dyn Logger { |
| &*self.logger |
| } |
| |
| #[cfg(feature = "logging")] |
| #[inline] |
| fn set_logger(&mut self, l: Box<dyn Logger>) { |
| self.logger = l; |
| } |
| |
| #[cfg(feature = "watcher")] |
| #[inline] |
| fn get_watcher(&self) -> Option<&dyn Watcher> { |
| if let Some(ref watcher) = self.watcher { |
| Some(&**watcher) |
| } else { |
| None |
| } |
| } |
| |
| #[cfg(feature = "watcher")] |
| #[inline] |
| fn get_mut_watcher(&mut self) -> Option<&mut dyn Watcher> { |
| if let Some(ref mut watcher) = self.watcher { |
| Some(&mut **watcher) |
| } else { |
| None |
| } |
| } |
| |
| #[inline] |
| fn get_role_manager(&self) -> Arc<RwLock<dyn RoleManager>> { |
| Arc::clone(&self.rm) |
| } |
| |
| #[inline] |
| fn set_role_manager( |
| &mut self, |
| rm: Arc<RwLock<dyn RoleManager>>, |
| ) -> Result<()> { |
| self.rm = rm; |
| if self.auto_build_role_links { |
| self.build_role_links()?; |
| } |
| |
| self.register_g_functions() |
| } |
| |
| async fn set_model<M: TryIntoModel>(&mut self, m: M) -> Result<()> { |
| self.model = m.try_into_model().await?; |
| |
| // If using DefaultModel, recompile matcher expressions |
| if let Some(default_model) = |
| self.model.as_any_mut().downcast_mut::<DefaultModel>() |
| { |
| default_model.compile_matchers(&self.engine)?; |
| } |
| |
| self.load_policy().await?; |
| Ok(()) |
| } |
| |
| async fn set_adapter<A: TryIntoAdapter>(&mut self, a: A) -> Result<()> { |
| self.adapter = a.try_into_adapter().await?; |
| self.load_policy().await?; |
| Ok(()) |
| } |
| |
| #[inline] |
| fn set_effector(&mut self, e: Box<dyn Effector>) { |
| self.eft = e; |
| } |
| |
| /// Enforce decides whether a "subject" can access a "object" with the operation "action", |
| /// input parameters are usually: (sub, obj, act). |
| /// |
| /// # Examples |
| /// ``` |
| /// use casbin::prelude::*; |
| /// #[cfg(feature = "runtime-async-std")] |
| /// #[async_std::main] |
| /// async fn main() -> Result<()> { |
| /// let mut e = Enforcer::new("examples/basic_model.conf", "examples/basic_policy.csv").await?; |
| /// assert_eq!(true, e.enforce(("alice", "data1", "read"))?); |
| /// Ok(()) |
| /// } |
| /// |
| /// #[cfg(feature = "runtime-tokio")] |
| /// #[tokio::main] |
| /// async fn main() -> Result<()> { |
| /// let mut e = Enforcer::new("examples/basic_model.conf", "examples/basic_policy.csv").await?; |
| /// assert_eq!(true, e.enforce(("alice", "data1", "read"))?); |
| /// |
| /// Ok(()) |
| /// } |
| /// #[cfg(all(not(feature = "runtime-async-std"), not(feature = "runtime-tokio")))] |
| /// fn main() {} |
| /// ``` |
| fn enforce<ARGS: EnforceArgs>(&self, rvals: ARGS) -> Result<bool> { |
| let rvals = rvals.try_into_vec()?; |
| #[allow(unused_variables)] |
| let (authorized, indices) = self.private_enforce(&rvals)?; |
| |
| #[cfg(feature = "logging")] |
| { |
| self.logger.print_enforce_log( |
| rvals.iter().map(|x| x.to_string()).collect(), |
| authorized, |
| false, |
| ); |
| |
| #[cfg(feature = "explain")] |
| if let Some(indices) = indices { |
| let all_rules = get_or_err!(self, "p", ModelError::P, "policy") |
| .get_policy(); |
| |
| let rules: Vec<String> = indices |
| .into_iter() |
| .filter_map(|y| { |
| all_rules.iter().nth(y).map(|x| x.join(", ")) |
| }) |
| .collect(); |
| |
| self.logger.print_explain_log(rules); |
| } |
| } |
| |
| Ok(authorized) |
| } |
| /// Enforce decides whether a "subject" can access a "object" with the operation "action", |
| /// input parameters are usually: (sub, obj, act). |
| /// this function will add suffix to each model eg. r2, p2, e2, m2, g2, |
| /// |
| /// # Examples |
| /// ``` |
| /// use casbin::prelude::*; |
| /// use casbin::EnforceContext; |
| /// |
| /// #[cfg(feature = "runtime-async-std")] |
| /// #[async_std::main] |
| /// async fn main() -> Result<()> { |
| /// let mut e = Enforcer::new("examples/multi_section_model.conf", "examples/multi_section_policy.csv").await?; |
| /// assert_eq!(true, e.enforce(("alice", "read", "project1"))?); |
| /// let ctx = EnforceContext::new("2"); |
| /// assert_eq!(true, e.enforce_with_context(ctx, ("james", "execute"))?); |
| /// Ok(()) |
| /// } |
| /// |
| /// #[cfg(feature = "runtime-tokio")] |
| /// #[tokio::main] |
| /// async fn main() -> Result<()> { |
| /// let mut e = Enforcer::new("examples/multi_section_model.conf", "examples/multi_section_policy.csv").await?; |
| /// assert_eq!(true, e.enforce(("alice", "read", "project1"))?); |
| /// let ctx = EnforceContext::new("2"); |
| /// assert_eq!(true, e.enforce_with_context(ctx, ("james", "execute"))?); |
| /// |
| /// Ok(()) |
| /// } |
| /// #[cfg(all(not(feature = "runtime-async-std"), not(feature = "runtime-tokio")))] |
| /// fn main() {} |
| /// ``` |
| fn enforce_with_context<ARGS: EnforceArgs>( |
| &self, |
| ctx: EnforceContext, |
| rvals: ARGS, |
| ) -> Result<bool> { |
| let rvals = rvals.try_into_vec()?; |
| #[allow(unused_variables)] |
| let (authorized, indices) = |
| self.private_enforce_with_context(ctx, &rvals)?; |
| |
| #[cfg(feature = "logging")] |
| { |
| self.logger.print_enforce_log( |
| rvals.iter().map(|x| x.to_string()).collect(), |
| authorized, |
| false, |
| ); |
| |
| #[cfg(feature = "explain")] |
| if let Some(indices) = indices { |
| let all_rules = get_or_err!(self, "p", ModelError::P, "policy") |
| .get_policy(); |
| |
| let rules: Vec<String> = indices |
| .into_iter() |
| .filter_map(|y| { |
| all_rules.iter().nth(y).map(|x| x.join(", ")) |
| }) |
| .collect(); |
| |
| self.logger.print_explain_log(rules); |
| } |
| } |
| |
| Ok(authorized) |
| } |
| |
| fn enforce_mut<ARGS: EnforceArgs>(&mut self, rvals: ARGS) -> Result<bool> { |
| self.enforce(rvals) |
| } |
| |
| #[cfg(feature = "explain")] |
| fn enforce_ex<ARGS: EnforceArgs>( |
| &self, |
| rvals: ARGS, |
| ) -> Result<(bool, Vec<Vec<String>>)> { |
| let rvals = rvals.try_into_vec()?; |
| #[allow(unused_variables)] |
| let (authorized, indices) = self.private_enforce(&rvals)?; |
| |
| let rules = match indices { |
| Some(indices) => { |
| let all_rules = get_or_err!(self, "p", ModelError::P, "policy") |
| .get_policy(); |
| |
| indices |
| .into_iter() |
| .filter_map(|y| all_rules.iter().nth(y).cloned()) |
| .collect::<Vec<_>>() |
| } |
| None => vec![], |
| }; |
| |
| Ok((authorized, rules)) |
| } |
| |
| fn build_role_links(&mut self) -> Result<()> { |
| self.rm.write().clear(); |
| self.model.build_role_links(Arc::clone(&self.rm))?; |
| |
| Ok(()) |
| } |
| |
| #[cfg(feature = "incremental")] |
| fn build_incremental_role_links(&mut self, d: EventData) -> Result<()> { |
| self.model |
| .build_incremental_role_links(Arc::clone(&self.rm), d)?; |
| |
| Ok(()) |
| } |
| |
| async fn load_policy(&mut self) -> Result<()> { |
| self.model.clear_policy(); |
| self.adapter.load_policy(&mut *self.model).await?; |
| |
| if self.auto_build_role_links { |
| self.build_role_links()?; |
| } |
| |
| Ok(()) |
| } |
| |
| async fn load_filtered_policy<'a>(&mut self, f: Filter<'a>) -> Result<()> { |
| self.model.clear_policy(); |
| self.adapter |
| .load_filtered_policy(&mut *self.model, f) |
| .await?; |
| |
| if self.auto_build_role_links { |
| self.build_role_links()?; |
| } |
| |
| Ok(()) |
| } |
| |
| #[inline] |
| fn is_filtered(&self) -> bool { |
| self.adapter.is_filtered() |
| } |
| |
| #[inline] |
| fn is_enabled(&self) -> bool { |
| self.enabled |
| } |
| |
| async fn save_policy(&mut self) -> Result<()> { |
| assert!(!self.is_filtered(), "cannot save filtered policy"); |
| |
| self.adapter.save_policy(&mut *self.model).await?; |
| |
| let mut policies = self.get_all_policy(); |
| let gpolicies = self.get_all_grouping_policy(); |
| |
| policies.extend(gpolicies); |
| |
| #[cfg(any(feature = "logging", feature = "watcher"))] |
| self.emit(Event::PolicyChange, EventData::SavePolicy(policies)); |
| |
| Ok(()) |
| } |
| |
| #[inline] |
| async fn clear_policy(&mut self) -> Result<()> { |
| if self.auto_save { |
| self.adapter.clear_policy().await?; |
| } |
| self.model.clear_policy(); |
| |
| #[cfg(any(feature = "logging", feature = "watcher"))] |
| self.emit(Event::PolicyChange, EventData::ClearPolicy); |
| |
| Ok(()) |
| } |
| |
| #[inline] |
| fn enable_enforce(&mut self, enabled: bool) { |
| self.enabled = enabled; |
| |
| #[cfg(feature = "logging")] |
| self.logger.print_status_log(enabled); |
| } |
| |
| #[cfg(feature = "logging")] |
| #[inline] |
| fn enable_log(&mut self, enabled: bool) { |
| self.logger.enable_log(enabled); |
| } |
| |
| #[inline] |
| fn enable_auto_save(&mut self, auto_save: bool) { |
| self.auto_save = auto_save; |
| } |
| |
| #[inline] |
| fn enable_auto_build_role_links(&mut self, auto_build_role_links: bool) { |
| self.auto_build_role_links = auto_build_role_links; |
| } |
| |
| #[cfg(feature = "watcher")] |
| #[inline] |
| fn enable_auto_notify_watcher(&mut self, auto_notify_watcher: bool) { |
| if !auto_notify_watcher { |
| self.off(Event::PolicyChange); |
| } else { |
| self.on(Event::PolicyChange, notify_logger_and_watcher); |
| } |
| |
| self.auto_notify_watcher = auto_notify_watcher; |
| } |
| |
| #[inline] |
| fn has_auto_save_enabled(&self) -> bool { |
| self.auto_save |
| } |
| |
| #[cfg(feature = "watcher")] |
| #[inline] |
| fn has_auto_notify_watcher_enabled(&self) -> bool { |
| self.auto_notify_watcher |
| } |
| |
| #[inline] |
| fn has_auto_build_role_links_enabled(&self) -> bool { |
| self.auto_build_role_links |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::prelude::*; |
| |
| fn is_send<T: Send>() -> bool { |
| true |
| } |
| |
| fn is_sync<T: Sync>() -> bool { |
| true |
| } |
| |
| #[test] |
| fn test_send_sync() { |
| assert!(is_send::<Enforcer>()); |
| assert!(is_sync::<Enforcer>()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_enforcer_swap_adapter_type() { |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub, obj, act"); |
| m.add_def("e", "e", "some(where (p.eft == allow))"); |
| m.add_def( |
| "m", |
| "m", |
| "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)", |
| ); |
| |
| let file = FileAdapter::new("examples/basic_policy.csv"); |
| let mem = MemoryAdapter::default(); |
| let mut e = Enforcer::new(m, file).await.unwrap(); |
| // this should fail since FileAdapter has basically no add_policy |
| assert!(e |
| .adapter |
| .add_policy( |
| "p", |
| "p", |
| vec!["alice".into(), "data".into(), "read".into()] |
| ) |
| .await |
| .unwrap()); |
| e.set_adapter(mem).await.unwrap(); |
| // this passes since our MemoryAdapter has a working add_policy method |
| assert!(e |
| .adapter |
| .add_policy( |
| "p", |
| "p", |
| vec!["alice".into(), "data".into(), "read".into()] |
| ) |
| .await |
| .unwrap()) |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_key_match_model_in_memory() { |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub, obj, act"); |
| m.add_def("e", "e", "some(where (p.eft == allow))"); |
| m.add_def( |
| "m", |
| "m", |
| "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)", |
| ); |
| |
| let adapter = FileAdapter::new("examples/keymatch_policy.csv"); |
| let e = Enforcer::new(m, adapter).await.unwrap(); |
| assert_eq!( |
| true, |
| e.enforce(("alice", "/alice_data/resource1", "GET")) |
| .unwrap() |
| ); |
| assert_eq!( |
| true, |
| e.enforce(("alice", "/alice_data/resource1", "POST")) |
| .unwrap() |
| ); |
| assert_eq!( |
| true, |
| e.enforce(("alice", "/alice_data/resource2", "GET")) |
| .unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("alice", "/alice_data/resource2", "POST")) |
| .unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("alice", "/bob_data/resource1", "GET")).unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("alice", "/bob_data/resource1", "POST")).unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("alice", "/bob_data/resource2", "GET")).unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("alice", "/bob_data/resource2", "POST")).unwrap() |
| ); |
| |
| assert_eq!( |
| false, |
| e.enforce(("bob", "/alice_data/resource1", "GET")).unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("bob", "/alice_data/resource1", "POST")).unwrap() |
| ); |
| assert_eq!( |
| true, |
| e.enforce(("bob", "/alice_data/resource2", "GET")).unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("bob", "/alice_data/resource2", "POST")).unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("bob", "/bob_data/resource1", "GET")).unwrap() |
| ); |
| assert_eq!( |
| true, |
| e.enforce(("bob", "/bob_data/resource1", "POST")).unwrap() |
| ); |
| assert_eq!( |
| false, |
| e.enforce(("bob", "/bob_data/resource2", "GET")).unwrap() |
| ); |
| assert_eq!( |
| true, |
| e.enforce(("bob", "/bob_data/resource2", "POST")).unwrap() |
| ); |
| |
| assert_eq!(true, e.enforce(("cathy", "/cathy_data", "GET")).unwrap()); |
| assert_eq!(true, e.enforce(("cathy", "/cathy_data", "POST")).unwrap()); |
| assert_eq!( |
| false, |
| e.enforce(("cathy", "/cathy_data", "DELETE")).unwrap() |
| ); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_key_match_model_in_memory_deny() { |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub, obj, act"); |
| m.add_def("e", "e", "!some(where (p.eft == deny))"); |
| m.add_def( |
| "m", |
| "m", |
| "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)", |
| ); |
| |
| let adapter = FileAdapter::new("examples/keymatch_policy.csv"); |
| let e = Enforcer::new(m, adapter).await.unwrap(); |
| assert_eq!( |
| true, |
| e.enforce(("alice", "/alice_data/resource2", "POST")) |
| .unwrap() |
| ); |
| } |
| |
| use crate::RbacApi; |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_rbac_model_in_memory_indeterminate() { |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub, obj, act"); |
| m.add_def("g", "g", "_, _"); |
| m.add_def("e", "e", "some(where (p.eft == allow))"); |
| m.add_def( |
| "m", |
| "m", |
| "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act", |
| ); |
| |
| let adapter = MemoryAdapter::default(); |
| let mut e = Enforcer::new(m, adapter).await.unwrap(); |
| e.add_permission_for_user( |
| "alice", |
| vec!["data1", "invalid"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| assert_eq!(false, e.enforce(("alice", "data1", "read")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_rbac_model_in_memory() { |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub, obj, act"); |
| m.add_def("g", "g", "_, _"); |
| m.add_def("e", "e", "some(where (p.eft == allow))"); |
| m.add_def( |
| "m", |
| "m", |
| "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act", |
| ); |
| |
| let adapter = MemoryAdapter::default(); |
| let mut e = Enforcer::new(m, adapter).await.unwrap(); |
| e.add_permission_for_user( |
| "alice", |
| vec!["data1", "read"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| e.add_permission_for_user( |
| "bob", |
| vec!["data2", "write"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| e.add_permission_for_user( |
| "data2_admin", |
| vec!["data2", "read"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| e.add_permission_for_user( |
| "data2_admin", |
| vec!["data2", "write"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| e.add_role_for_user("alice", "data2_admin", None) |
| .await |
| .unwrap(); |
| |
| assert_eq!(true, e.enforce(("alice", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data1", "write")).unwrap()); |
| assert_eq!(true, e.enforce(("alice", "data2", "read")).unwrap()); |
| assert_eq!(true, e.enforce(("alice", "data2", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data2", "read")).unwrap()); |
| assert_eq!(true, e.enforce(("bob", "data2", "write")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_not_used_rbac_model_in_memory() { |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub, obj, act"); |
| m.add_def("g", "g", "_, _"); |
| m.add_def("e", "e", "some(where (p.eft == allow))"); |
| m.add_def( |
| "m", |
| "m", |
| "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act", |
| ); |
| |
| let adapter = MemoryAdapter::default(); |
| let mut e = Enforcer::new(m, adapter).await.unwrap(); |
| e.add_permission_for_user( |
| "alice", |
| vec!["data1", "read"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| e.add_permission_for_user( |
| "bob", |
| vec!["data2", "write"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| |
| assert_eq!(true, e.enforce(("alice", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data2", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data2", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data2", "read")).unwrap()); |
| assert_eq!(true, e.enforce(("bob", "data2", "write")).unwrap()); |
| } |
| |
| #[cfg(feature = "ip")] |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_ip_match_model() { |
| let m = DefaultModel::from_file("examples/ipmatch_model.conf") |
| .await |
| .unwrap(); |
| |
| let adapter = FileAdapter::new("examples/ipmatch_policy.csv"); |
| let e = Enforcer::new(m, adapter).await.unwrap(); |
| |
| assert!(e.enforce(("192.168.2.123", "data1", "read")).unwrap()); |
| |
| assert!(e.enforce(("10.0.0.5", "data2", "write")).unwrap()); |
| |
| assert!(!e.enforce(("192.168.2.123", "data1", "write")).unwrap()); |
| assert!(!e.enforce(("192.168.2.123", "data2", "read")).unwrap()); |
| assert!(!e.enforce(("192.168.2.123", "data2", "write")).unwrap()); |
| |
| assert!(!e.enforce(("192.168.0.123", "data1", "read")).unwrap()); |
| assert!(!e.enforce(("192.168.0.123", "data1", "write")).unwrap()); |
| assert!(!e.enforce(("192.168.0.123", "data2", "read")).unwrap()); |
| assert!(!e.enforce(("192.168.0.123", "data2", "write")).unwrap()); |
| |
| assert!(!e.enforce(("10.0.0.5", "data1", "read")).unwrap()); |
| assert!(!e.enforce(("10.0.0.5", "data1", "write")).unwrap()); |
| assert!(!e.enforce(("10.0.0.5", "data2", "read")).unwrap()); |
| |
| assert!(!e.enforce(("192.168.0.1", "data1", "read")).unwrap()); |
| assert!(!e.enforce(("192.168.0.1", "data1", "write")).unwrap()); |
| assert!(!e.enforce(("192.168.0.1", "data2", "read")).unwrap()); |
| assert!(!e.enforce(("192.168.0.1", "data2", "write")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_enable_auto_save() { |
| let m = DefaultModel::from_file("examples/basic_model.conf") |
| .await |
| .unwrap(); |
| |
| let adapter = FileAdapter::new("examples/basic_policy.csv"); |
| let mut e = Enforcer::new(m, adapter).await.unwrap(); |
| e.enable_auto_save(false); |
| e.remove_policy( |
| vec!["alice", "data1", "read"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| e.load_policy().await.unwrap(); |
| |
| assert_eq!(true, e.enforce(("alice", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data2", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data2", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data2", "read")).unwrap()); |
| assert_eq!(true, e.enforce(("bob", "data2", "write")).unwrap()); |
| |
| e.enable_auto_save(true); |
| e.remove_policy( |
| vec!["alice", "data1", "read"] |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| e.load_policy().await.unwrap(); |
| assert_eq!(true, e.enforce(("alice", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data2", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data2", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data2", "read")).unwrap()); |
| assert_eq!(true, e.enforce(("bob", "data2", "write")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_role_links() { |
| let m = DefaultModel::from_file("examples/rbac_model.conf") |
| .await |
| .unwrap(); |
| |
| let adapter = MemoryAdapter::default(); |
| let mut e = Enforcer::new(m, adapter).await.unwrap(); |
| e.enable_auto_build_role_links(false); |
| e.build_role_links().unwrap(); |
| assert_eq!(false, e.enforce(("user501", "data9", "read")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_get_and_set_model() { |
| let m1 = DefaultModel::from_file("examples/basic_model.conf") |
| .await |
| .unwrap(); |
| let adapter1 = FileAdapter::new("examples/basic_policy.csv"); |
| let mut e = Enforcer::new(m1, adapter1).await.unwrap(); |
| |
| assert_eq!(false, e.enforce(("root", "data1", "read")).unwrap()); |
| |
| let m2 = DefaultModel::from_file("examples/basic_with_root_model.conf") |
| .await |
| .unwrap(); |
| let adapter2 = FileAdapter::new("examples/basic_policy.csv"); |
| let e2 = Enforcer::new(m2, adapter2).await.unwrap(); |
| |
| e.model = e2.model; |
| assert_eq!(true, e.enforce(("root", "data1", "read")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_get_and_set_adapter_in_mem() { |
| let m1 = DefaultModel::from_file("examples/basic_model.conf") |
| .await |
| .unwrap(); |
| let adapter1 = FileAdapter::new("examples/basic_policy.csv"); |
| let mut e = Enforcer::new(m1, adapter1).await.unwrap(); |
| |
| assert_eq!(true, e.enforce(("alice", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data1", "write")).unwrap()); |
| |
| let m2 = DefaultModel::from_file("examples/basic_model.conf") |
| .await |
| .unwrap(); |
| let adapter2 = FileAdapter::new("examples/basic_inverse_policy.csv"); |
| let e2 = Enforcer::new(m2, adapter2).await.unwrap(); |
| |
| e.adapter = e2.adapter; |
| e.load_policy().await.unwrap(); |
| assert_eq!(false, e.enforce(("alice", "data1", "read")).unwrap()); |
| assert_eq!(true, e.enforce(("alice", "data1", "write")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_keymatch_custom_model() { |
| use crate::model::key_match; |
| |
| let m1 = DefaultModel::from_file("examples/keymatch_custom_model.conf") |
| .await |
| .unwrap(); |
| let adapter1 = FileAdapter::new("examples/keymatch_policy.csv"); |
| let mut e = Enforcer::new(m1, adapter1).await.unwrap(); |
| |
| e.add_function( |
| "keyMatchCustom", |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| let s1_str = s1.to_string(); |
| let s2_str = s2.to_string(); |
| key_match(&s1_str, &s2_str).into() |
| }), |
| ); |
| |
| assert_eq!( |
| true, |
| e.enforce(("alice", "/alice_data/123", "GET")).unwrap() |
| ); |
| assert_eq!( |
| true, |
| e.enforce(("alice", "/alice_data/resource1", "POST")) |
| .unwrap() |
| ); |
| |
| assert_eq!( |
| true, |
| e.enforce(("bob", "/alice_data/resource2", "GET")).unwrap() |
| ); |
| |
| assert_eq!( |
| true, |
| e.enforce(("bob", "/bob_data/resource1", "POST")).unwrap() |
| ); |
| |
| assert_eq!(true, e.enforce(("cathy", "/cathy_data", "GET")).unwrap()); |
| assert_eq!(true, e.enforce(("cathy", "/cathy_data", "POST")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_filtered_file_adapter() { |
| let adapter = FileAdapter::new_filtered_adapter( |
| "examples/rbac_with_domains_policy.csv", |
| ); |
| let mut e = |
| Enforcer::new("examples/rbac_with_domains_model.conf", adapter) |
| .await |
| .unwrap(); |
| |
| let filter = Filter { |
| p: vec!["", "domain1"], |
| g: vec!["", "", "domain1"], |
| }; |
| |
| e.load_filtered_policy(filter).await.unwrap(); |
| assert_eq!( |
| e.enforce(("alice", "domain1", "data1", "read")).unwrap(), |
| true |
| ); |
| assert!(e.enforce(("alice", "domain1", "data1", "write")).unwrap()); |
| assert!(!e.enforce(("alice", "domain1", "data2", "read")).unwrap()); |
| assert!(!e.enforce(("alice", "domain1", "data2", "write")).unwrap()); |
| assert!(!e.enforce(("bob", "domain2", "data2", "read")).unwrap()); |
| assert!(!e.enforce(("bob", "domain2", "data2", "write")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_set_role_manager() { |
| let mut e = Enforcer::new( |
| "examples/rbac_with_domains_model.conf", |
| "examples/rbac_with_domains_policy.csv", |
| ) |
| .await |
| .unwrap(); |
| |
| let new_rm = Arc::new(RwLock::new(DefaultRoleManager::new(10))); |
| |
| e.set_role_manager(new_rm).unwrap(); |
| |
| assert!(e.enforce(("alice", "domain1", "data1", "read")).unwrap(),); |
| assert!(e.enforce(("alice", "domain1", "data1", "write")).unwrap()); |
| assert!(e.enforce(("bob", "domain2", "data2", "read")).unwrap()); |
| assert!(e.enforce(("bob", "domain2", "data2", "write")).unwrap()); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_policy_abac1() { |
| use serde::Serialize; |
| |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub_rule, obj, act"); |
| m.add_def("e", "e", "some(where (p.eft == allow))"); |
| m.add_def( |
| "m", |
| "m", |
| "eval(p.sub_rule) && r.obj == p.obj && r.act == p.act", |
| ); |
| |
| let a = MemoryAdapter::default(); |
| |
| let mut e = Enforcer::new(m, a).await.unwrap(); |
| |
| e.add_policy( |
| vec!["r.sub.age > 18", "/data1", "read"] |
| .into_iter() |
| .map(|x| x.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| |
| #[derive(Serialize, Hash)] |
| pub struct Person<'a> { |
| name: &'a str, |
| age: u8, |
| } |
| |
| assert_eq!( |
| e.enforce(( |
| Person { |
| name: "alice", |
| age: 16 |
| }, |
| "/data1", |
| "read" |
| )) |
| .unwrap(), |
| false |
| ); |
| assert_eq!( |
| e.enforce(( |
| Person { |
| name: "bob", |
| age: 19 |
| }, |
| "/data1", |
| "read" |
| )) |
| .unwrap(), |
| true |
| ); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_policy_abac2() { |
| use serde::Serialize; |
| |
| let mut m = DefaultModel::default(); |
| m.add_def("r", "r", "sub, obj, act"); |
| m.add_def("p", "p", "sub, obj, act"); |
| m.add_def("e", "e", "some(where (p.eft == allow))"); |
| m.add_def("g", "g", "_, _"); |
| m.add_def( |
| "m", |
| "m", |
| "(g(r.sub, p.sub) || eval(p.sub) == true) && r.act == p.act", |
| ); |
| |
| let a = MemoryAdapter::default(); |
| |
| let mut e = Enforcer::new(m, a).await.unwrap(); |
| |
| e.add_policy( |
| vec![r#""admin""#, "post", "write"] |
| .into_iter() |
| .map(|x| x.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| |
| e.add_policy( |
| vec!["r.sub == r.obj.author", "post", "write"] |
| .into_iter() |
| .map(|x| x.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| |
| e.add_grouping_policy( |
| vec!["alice", r#""admin""#] |
| .into_iter() |
| .map(|x| x.to_string()) |
| .collect(), |
| ) |
| .await |
| .unwrap(); |
| |
| #[derive(Serialize, Hash)] |
| pub struct Post<'a> { |
| author: &'a str, |
| } |
| |
| assert_eq!( |
| e.enforce(("alice", Post { author: "bob" }, "write")) |
| .unwrap(), |
| true |
| ); |
| |
| assert_eq!( |
| e.enforce(("bob", Post { author: "bob" }, "write")).unwrap(), |
| true |
| ); |
| } |
| |
| #[cfg(feature = "explain")] |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_enforce_ex() { |
| use crate::adapter; |
| |
| let model = DefaultModel::from_file("examples/basic_model.conf") |
| .await |
| .unwrap(); |
| |
| let adapter = adapter::FileAdapter::new("examples/basic_policy.csv"); |
| |
| let e = Enforcer::new(model, adapter).await.unwrap(); |
| |
| assert_eq!( |
| e.enforce_ex(("alice", "data1", "read")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "alice".to_string(), |
| "data1".to_string(), |
| "read".to_string() |
| ]] |
| ) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data1", "write")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data2", "read")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data2", "write")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data1", "read")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data1", "write")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data2", "read")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data2", "write")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "bob".to_string(), |
| "data2".to_string(), |
| "write".to_string() |
| ]] |
| ) |
| ); |
| |
| let e = Enforcer::new( |
| "examples/rbac_model.conf", |
| "examples/rbac_policy.csv", |
| ) |
| .await |
| .unwrap(); |
| |
| assert_eq!( |
| e.enforce_ex(("alice", "data1", "read")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "alice".to_string(), |
| "data1".to_string(), |
| "read".to_string() |
| ]] |
| ) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data1", "write")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data2", "read")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "data2_admin".to_string(), |
| "data2".to_string(), |
| "read".to_string() |
| ]] |
| ) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data2", "write")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "data2_admin".to_string(), |
| "data2".to_string(), |
| "write".to_string() |
| ]] |
| ) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data1", "read")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data1", "write")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data2", "read")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data2", "write")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "bob".to_string(), |
| "data2".to_string(), |
| "write".to_string() |
| ]] |
| ) |
| ); |
| |
| let e = Enforcer::new( |
| "examples/priority_model.conf", |
| "examples/priority_policy.csv", |
| ) |
| .await |
| .unwrap(); |
| |
| assert_eq!( |
| e.enforce_ex(("alice", "data1", "read")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "alice".to_string(), |
| "data1".to_string(), |
| "read".to_string(), |
| "allow".to_string() |
| ]] |
| ) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data1", "write")).unwrap(), |
| ( |
| false, |
| vec![vec![ |
| "data1_deny_group".to_string(), |
| "data1".to_string(), |
| "write".to_string(), |
| "deny".to_string() |
| ]] |
| ) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data2", "read")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("alice", "data2", "write")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data1", "write")).unwrap(), |
| (false, vec![]) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data2", "read")).unwrap(), |
| ( |
| true, |
| vec![vec![ |
| "data2_allow_group".to_string(), |
| "data2".to_string(), |
| "read".to_string(), |
| "allow".to_string() |
| ]] |
| ) |
| ); |
| assert_eq!( |
| e.enforce_ex(("bob", "data2", "write")).unwrap(), |
| ( |
| false, |
| vec![vec![ |
| "bob".to_string(), |
| "data2".to_string(), |
| "write".to_string(), |
| "deny".to_string() |
| ]] |
| ) |
| ); |
| } |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| #[cfg_attr( |
| all(feature = "runtime-async-std", not(target_arch = "wasm32")), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(feature = "runtime-tokio", not(target_arch = "wasm32")), |
| tokio::test |
| )] |
| async fn test_custom_function_with_dynamic_types() { |
| use crate::prelude::*; |
| |
| let m = DefaultModel::from_str( |
| r#" |
| [request_definition] |
| r = sub, obj, act |
| |
| [policy_definition] |
| p = sub, obj, act |
| |
| [policy_effect] |
| e = some(where (p.eft == allow)) |
| |
| [matchers] |
| m = r.sub == p.sub && r.obj == p.obj && r.act == p.act |
| "#, |
| ) |
| .await |
| .unwrap(); |
| |
| let adapter = MemoryAdapter::default(); |
| let mut e = Enforcer::new(m, adapter).await.unwrap(); |
| |
| // Test 1: Custom function that takes integer arguments |
| e.add_function( |
| "greaterThan", |
| OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { |
| // Dynamic can hold integers - extract and compare |
| let a_int = a.as_int().unwrap_or(0); |
| let b_int = b.as_int().unwrap_or(0); |
| (a_int > b_int).into() |
| }), |
| ); |
| |
| // Test 2: Custom function that works with booleans |
| e.add_function( |
| "customAnd", |
| OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { |
| // Dynamic can hold booleans - extract and perform logic |
| let a_bool = a.as_bool().unwrap_or(false); |
| let b_bool = b.as_bool().unwrap_or(false); |
| (a_bool && b_bool).into() |
| }), |
| ); |
| |
| // Test 3: Custom function that works with strings |
| e.add_function( |
| "stringContains", |
| OperatorFunction::Arg2(|haystack: Dynamic, needle: Dynamic| { |
| // Dynamic can hold strings - convert and check |
| let haystack_str = haystack.to_string(); |
| let needle_str = needle.to_string(); |
| haystack_str.contains(&needle_str).into() |
| }), |
| ); |
| |
| // Test 4: Custom function with 3 arguments |
| e.add_function( |
| "between", |
| OperatorFunction::Arg3( |
| |val: Dynamic, min: Dynamic, max: Dynamic| { |
| // Check if val is between min and max (inclusive) |
| let val_int = val.as_int().unwrap_or(0); |
| let min_int = min.as_int().unwrap_or(0); |
| let max_int = max.as_int().unwrap_or(0); |
| (val_int >= min_int && val_int <= max_int).into() |
| }, |
| ), |
| ); |
| |
| // Verify that custom functions are registered without errors |
| // In real usage, these would be called from policy matchers |
| |
| // Test basic enforcement still works with Dynamic-based functions |
| e.add_policy(vec![ |
| "alice".to_owned(), |
| "data1".to_owned(), |
| "read".to_owned(), |
| ]) |
| .await |
| .unwrap(); |
| |
| assert_eq!(true, e.enforce(("alice", "data1", "read")).unwrap()); |
| assert_eq!(false, e.enforce(("alice", "data1", "write")).unwrap()); |
| assert_eq!(false, e.enforce(("bob", "data1", "read")).unwrap()); |
| } |
| } |