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,
};
#[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);
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 = 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 = 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()?;
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?;
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?;
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;
}
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)
}
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();
assert!(e
.adapter
.add_policy(
"p",
"p",
vec!["alice".into(), "data".into(), "read".into()]
)
.await
.unwrap());
e.set_adapter(mem).await.unwrap();
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: ImmutableString, s2: ImmutableString| {
key_match(&s1, &s2).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()
]]
)
);
}
}