Skip to content

Commit

Permalink
feat: added enforcer context to enable multiple section type (#317)
Browse files Browse the repository at this point in the history
* feat: added EnforcerContext + enforce_with_context function

* fix: debug cannot derive on DefaultEffectStream on expl which has (feature = 'explain')

* fix: fixed wrong macro checking for ctx

* fix: pub EnforceContext to be able to import

* fix: fixed nightly and beta ubuntu Error:   --> src/cached_enforcer.rs:77:1 and Error:   --> src/effector.rs:33:9

docs: doc on enforce_with_context

* fix: clippy cleaning
  • Loading branch information
worapolw authored Nov 16, 2023
1 parent b429e4d commit 49f090e
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 12 deletions.
19 changes: 19 additions & 0 deletions examples/multi_section_model.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[request_definition]
r = sub, act, obj
r2 = sub, act

[policy_definition]
p = sub, act, obj
p2 = sub, act

[role_definition]
g = _, _
g2 = _,_

[policy_effect]
e = some(where (p.eft == allow))
e2 = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && g(p.act, r.act) && r.obj == p.obj
m2 = r2.sub == p2.sub && g(p2.act, r2.act)
7 changes: 7 additions & 0 deletions examples/multi_section_policy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
p, alice , admin, project1
p, bob , user, project2
p2, james, execute

g, admin, read
g, admin, write
g, user, read
54 changes: 54 additions & 0 deletions src/cached_enforcer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
core_api::CoreApi,
effector::Effector,
emitter::{clear_cache, Event, EventData, EventEmitter},
enforcer::EnforceContext,
enforcer::Enforcer,
model::Model,
rbac::RoleManager,
Expand Down Expand Up @@ -71,6 +72,21 @@ impl CachedEnforcer {
(authorized, false, indices)
})
}
pub(crate) fn private_enforce_with_context(
&self,
ctx: EnforceContext,
rvals: &[Dynamic],
cache_key: u64,
) -> Result<(bool, bool, Option<Vec<usize>>)> {
Ok(if let Some(authorized) = self.cache.get(&cache_key) {
(authorized, true, None)
} else {
let (authorized, indices) =
self.enforcer.private_enforce_with_context(ctx, &rvals)?;
self.cache.set(cache_key, authorized);
(authorized, false, indices)
})
}
}

#[async_trait]
Expand Down Expand Up @@ -226,6 +242,44 @@ impl CoreApi for CachedEnforcer {
Ok(authorized)
}

fn enforce_with_context<ARGS: EnforceArgs>(
&self,
ctx: EnforceContext,
rvals: ARGS,
) -> Result<bool> {
let cache_key = rvals.cache_key();
let rvals = rvals.try_into_vec()?;
#[allow(unused_variables)]
let (authorized, cached, indices) =
self.private_enforce_with_context(ctx, &rvals, cache_key)?;

#[cfg(feature = "logging")]
{
self.enforcer.get_logger().print_enforce_log(
rvals.iter().map(|x| x.to_string()).collect(),
authorized,
cached,
);

#[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.enforcer.get_logger().print_explain_log(rules);
}
}

Ok(authorized)
}

#[inline]
fn enforce_mut<ARGS: EnforceArgs>(&mut self, rvals: ARGS) -> Result<bool> {
self.enforce(rvals)
Expand Down
3 changes: 1 addition & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,7 @@ impl Config {
if section.is_empty() {
section = DEFAULT_SECTION.to_owned();
}
let section_value =
self.data.entry(section).or_insert_with(HashMap::new);
let section_value = self.data.entry(section).or_default();

// if key not exists then insert, else update
let key_value = section_value.get_mut(&option);
Expand Down
12 changes: 10 additions & 2 deletions src/core_api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
Adapter, Effector, EnforceArgs, Event, EventEmitter, Filter, Model, Result,
RoleManager, TryIntoAdapter, TryIntoModel,
enforcer::EnforceContext, Adapter, Effector, EnforceArgs, Event,
EventEmitter, Filter, Model, Result, RoleManager, TryIntoAdapter,
TryIntoModel,
};

#[cfg(feature = "watcher")]
Expand Down Expand Up @@ -64,6 +65,13 @@ pub trait CoreApi: Send + Sync {
Self: Sized;
fn set_effector(&mut self, e: Box<dyn Effector>);
fn enforce<ARGS: EnforceArgs>(&self, rvals: ARGS) -> Result<bool>
where
Self: Sized;
fn enforce_with_context<ARGS: EnforceArgs>(
&self,
ctx: EnforceContext,
rvals: ARGS,
) -> Result<bool>
where
Self: Sized;
fn enforce_mut<ARGS: EnforceArgs>(&mut self, rvals: ARGS) -> Result<bool>
Expand Down
228 changes: 225 additions & 3 deletions src/enforcer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
effector::{DefaultEffector, EffectKind, Effector},
emitter::{Event, EventData, EventEmitter},
error::{ModelError, PolicyError, RequestError},
get_or_err,
get_or_err, get_or_err_with_context,
management_api::MgmtApi,
model::{FunctionMap, Model},
rbac::{DefaultRoleManager, RoleManager},
Expand Down Expand Up @@ -74,9 +74,33 @@ pub struct Enforcer {
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_insert_with(Vec::new).push(f)
self.events.entry(e).or_default().push(f)
}

fn off(&mut self, e: Event) {
Expand Down Expand Up @@ -127,7 +151,136 @@ impl Enforcer {
self.eft.new_stream(&e_ast.value, max(policy_len, 1));
let m_ast_compiled = self
.engine
.compile_expression(&escape_eval(&m_ast.value))
.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 {
Expand Down Expand Up @@ -426,6 +579,75 @@ impl CoreApi for Enforcer {

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)
Expand Down
Loading

0 comments on commit 49f090e

Please sign in to comment.