use crate::{BatchEvalCtx, EvalCtx, Policy, PolicyEvalResult};
use async_trait::async_trait;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Effect {
Allow,
Deny,
}
struct InternalPolicy<S, R, A, C> {
name: String,
effect: Effect,
subject_pred: Option<Box<dyn Fn(&S) -> bool + Send + Sync>>,
action_pred: Option<Box<dyn Fn(&A) -> bool + Send + Sync>>,
resource_pred: Option<Box<dyn Fn(&R) -> bool + Send + Sync>>,
context_pred: Option<Box<dyn Fn(&C) -> bool + Send + Sync>>,
when_pred: Option<Box<dyn Fn(&S, &A, &R, &C) -> bool + Send + Sync>>,
}
impl<S, R, A, C> InternalPolicy<S, R, A, C> {
fn build_result(&self, all_axes_pass: bool) -> PolicyEvalResult {
if all_axes_pass {
match self.effect {
Effect::Allow => PolicyEvalResult::granted(
self.name.clone(),
Some("Policy allowed access".into()),
),
Effect::Deny => PolicyEvalResult::denied(self.name.clone(), "Policy denied access"),
}
} else {
PolicyEvalResult::denied(self.name.clone(), "Policy predicate did not match")
}
}
}
#[async_trait]
impl<S, R, A, C> Policy<S, R, A, C> for InternalPolicy<S, R, A, C>
where
S: Send + Sync,
R: Send + Sync,
A: Send + Sync,
C: Send + Sync,
{
async fn evaluate(&self, ctx: &EvalCtx<'_, S, R, A, C>) -> PolicyEvalResult {
let pass = self.subject_pred.as_ref().is_none_or(|f| f(ctx.subject))
&& self.action_pred.as_ref().is_none_or(|f| f(ctx.action))
&& self.resource_pred.as_ref().is_none_or(|f| f(ctx.resource))
&& self.context_pred.as_ref().is_none_or(|f| f(ctx.context))
&& self
.when_pred
.as_ref()
.is_none_or(|f| f(ctx.subject, ctx.action, ctx.resource, ctx.context));
self.build_result(pass)
}
async fn evaluate_batch<'item>(
&self,
ctx: &BatchEvalCtx<'item, S, R, A, C>,
) -> Vec<PolicyEvalResult> {
let n = ctx.items.len();
let subject_ok = self.subject_pred.as_ref().is_none_or(|f| f(ctx.subject));
let action_ok = self.action_pred.as_ref().is_none_or(|f| f(ctx.action));
if !subject_ok || !action_ok {
let result = self.build_result(false);
return std::iter::repeat_with(|| result.clone()).take(n).collect();
}
if self.resource_pred.is_none() && self.context_pred.is_none() && self.when_pred.is_none() {
let result = self.build_result(true);
return std::iter::repeat_with(|| result.clone()).take(n).collect();
}
ctx.items
.iter()
.map(|item| {
let resource_ok = self.resource_pred.as_ref().is_none_or(|f| f(item.resource));
let context_ok = self.context_pred.as_ref().is_none_or(|f| f(item.context));
let when_ok = self
.when_pred
.as_ref()
.is_none_or(|f| f(ctx.subject, ctx.action, item.resource, item.context));
self.build_result(resource_ok && context_ok && when_ok)
})
.collect()
}
fn policy_type(&self) -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Owned(self.name.clone())
}
}
pub struct PolicyBuilder<S, R, A, C>
where
S: Send + Sync + 'static,
R: Send + Sync + 'static,
A: Send + Sync + 'static,
C: Send + Sync + 'static,
{
name: String,
effect: Effect,
subject_pred: Option<Box<dyn Fn(&S) -> bool + Send + Sync>>,
action_pred: Option<Box<dyn Fn(&A) -> bool + Send + Sync>>,
resource_pred: Option<Box<dyn Fn(&R) -> bool + Send + Sync>>,
context_pred: Option<Box<dyn Fn(&C) -> bool + Send + Sync>>,
extra_condition: Option<Box<dyn Fn(&S, &A, &R, &C) -> bool + Send + Sync>>,
}
impl<Subject, Resource, Action, Context> PolicyBuilder<Subject, Resource, Action, Context>
where
Subject: Send + Sync + 'static,
Resource: Send + Sync + 'static,
Action: Send + Sync + 'static,
Context: Send + Sync + 'static,
{
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
effect: Effect::Allow,
subject_pred: None,
action_pred: None,
resource_pred: None,
context_pred: None,
extra_condition: None,
}
}
pub fn effect(mut self, effect: Effect) -> Self {
self.effect = effect;
self
}
pub fn subjects<F>(mut self, pred: F) -> Self
where
F: Fn(&Subject) -> bool + Send + Sync + 'static,
{
self.subject_pred = Some(Box::new(pred));
self
}
pub fn actions<F>(mut self, pred: F) -> Self
where
F: Fn(&Action) -> bool + Send + Sync + 'static,
{
self.action_pred = Some(Box::new(pred));
self
}
pub fn resources<F>(mut self, pred: F) -> Self
where
F: Fn(&Resource) -> bool + Send + Sync + 'static,
{
self.resource_pred = Some(Box::new(pred));
self
}
pub fn context<F>(mut self, pred: F) -> Self
where
F: Fn(&Context) -> bool + Send + Sync + 'static,
{
self.context_pred = Some(Box::new(pred));
self
}
pub fn when<F>(mut self, pred: F) -> Self
where
F: Fn(&Subject, &Action, &Resource, &Context) -> bool + Send + Sync + 'static,
{
self.extra_condition = Some(Box::new(pred));
self
}
pub fn build(self) -> Box<dyn Policy<Subject, Resource, Action, Context>> {
Box::new(InternalPolicy {
name: self.name,
effect: self.effect,
subject_pred: self.subject_pred,
action_pred: self.action_pred,
resource_pred: self.resource_pred,
context_pred: self.context_pred,
when_pred: self.extra_condition,
})
}
}