use crate::{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,
predicate: Box<dyn Fn(&S, &A, &R, &C) -> bool + Send + Sync>,
}
#[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 {
if (self.predicate)(ctx.subject, ctx.action, ctx.resource, ctx.context) {
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")
}
}
fn policy_type(&self) -> &str {
&self.name
}
}
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>> {
let effect = self.effect;
let subject_pred = self.subject_pred;
let action_pred = self.action_pred;
let resource_pred = self.resource_pred;
let context_pred = self.context_pred;
let extra_condition = self.extra_condition;
let predicate = Box::new(move |s: &Subject, a: &Action, r: &Resource, c: &Context| {
subject_pred.as_ref().is_none_or(|f| f(s))
&& action_pred.as_ref().is_none_or(|f| f(a))
&& resource_pred.as_ref().is_none_or(|f| f(r))
&& context_pred.as_ref().is_none_or(|f| f(c))
&& extra_condition.as_ref().is_none_or(|f| f(s, a, r, c))
});
Box::new(InternalPolicy {
name: self.name,
effect,
predicate,
})
}
}