use thiserror::Error;
use crate::decision::DenyReason;
#[derive(Debug, Error)]
pub enum PolicyError {
#[error("policy load failed: {0}")]
LoadFailed(String),
#[error("policy evaluation error: {0}")]
EvaluationError(String),
}
impl From<PolicyError> for DenyReason {
fn from(_: PolicyError) -> Self {
DenyReason::EngineError
}
}
mod private {
pub trait Sealed {}
}
pub trait PolicyEngine: private::Sealed + Send + Sync {
fn evaluate(
&self,
subject: &str,
resource: &str,
action: &str,
) -> impl std::future::Future<Output = Result<bool, PolicyError>> + Send;
fn evaluate_bulk<'a>(
&'a self,
requests: &'a [(&'a str, &'a str, &'a str)],
) -> impl std::future::Future<Output = Vec<Result<bool, PolicyError>>> + Send
where
Self: Sized,
{
async move {
let mut decisions = Vec::with_capacity(requests.len());
for (subject, resource, action) in requests {
decisions.push(self.evaluate(subject, resource, action).await);
}
decisions
}
}
fn policy_version(&self) -> u64;
}
const RBAC_MODEL_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures/rbac_model.conf");
const EMPTY_POLICY_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures/empty_policy.csv");
use casbin::{CoreApi, MgmtApi};
use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
use tokio::sync::Mutex;
pub struct DefaultPolicyEngine {
enforcer: Mutex<casbin::Enforcer>,
version: Arc<AtomicU64>,
}
impl private::Sealed for DefaultPolicyEngine {}
impl DefaultPolicyEngine {
pub async fn new_empty() -> Result<Self, PolicyError> {
let model = casbin::DefaultModel::from_file(RBAC_MODEL_PATH)
.await
.map_err(|e| PolicyError::LoadFailed(e.to_string()))?;
let adapter = casbin::FileAdapter::new(EMPTY_POLICY_PATH);
let enforcer = casbin::Enforcer::new(model, adapter)
.await
.map_err(|e| PolicyError::LoadFailed(e.to_string()))?;
Ok(Self {
enforcer: Mutex::new(enforcer),
version: Arc::new(AtomicU64::new(1)),
})
}
pub async fn add_policy(&self, sub: &str, obj: &str, act: &str) -> Result<(), PolicyError> {
let mut enforcer = self.enforcer.lock().await;
enforcer
.add_policy(vec![sub.to_owned(), obj.to_owned(), act.to_owned()])
.await
.map_err(|e| PolicyError::LoadFailed(e.to_string()))?;
drop(enforcer);
self.version.fetch_add(1, Ordering::SeqCst);
Ok(())
}
pub async fn get_policies(&self) -> Vec<Vec<String>> {
let enforcer = self.enforcer.lock().await;
let policies = enforcer.get_policy();
drop(enforcer);
policies
}
}
impl PolicyEngine for DefaultPolicyEngine {
fn evaluate(
&self,
subject: &str,
resource: &str,
action: &str,
) -> impl std::future::Future<Output = Result<bool, PolicyError>> + Send {
let subject = subject.to_owned();
let resource = resource.to_owned();
let action = action.to_owned();
let enforcer = &self.enforcer;
async move {
let guard = enforcer.lock().await;
let result = guard
.enforce((subject.as_str(), resource.as_str(), action.as_str()))
.map_err(|e| PolicyError::EvaluationError(e.to_string()));
drop(guard);
result
}
}
fn policy_version(&self) -> u64 {
self.version.load(Ordering::SeqCst)
}
}