use std::convert::Infallible;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
Decision, FactId, KnownFacts, Locale, ObligationId, PartialFacts, PolicyHash, PolicyId,
Presence, ResidualPolicy, SubjectRef, TenantId, Trace,
};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Context {
pub tenant: TenantId,
pub principal: SubjectRef,
pub locale: Locale,
pub request_id: Option<crate::RequestId>,
}
#[async_trait]
pub trait FactResolver: Send + Sync {
type Error: std::error::Error + Send + Sync + 'static;
async fn resolve_for_decision(
&self,
required: &[FactId],
cx: &Context,
) -> Result<KnownFacts, ResolveError<Self::Error>>;
async fn resolve_for_query(
&self,
required: &[FactId],
cx: &Context,
) -> Result<PartialFacts, ResolveError<Self::Error>>;
}
#[derive(Debug, Error)]
pub enum ResolveError<E> {
#[error("fact backend failed")]
Backend(#[from] E),
#[error("required fact is missing: {0}")]
MissingFact(FactId),
#[error("fact resolution timed out")]
Timeout,
}
pub trait PolicyObserver: Send + Sync {
fn observe(&self, decision_summary: &DecisionSummary);
}
#[derive(Default)]
pub struct NoopPolicyObserver;
impl PolicyObserver for NoopPolicyObserver {
fn observe(&self, _decision_summary: &DecisionSummary) {}
}
pub trait AuditSink: Send + Sync {
type Error: std::error::Error + Send + Sync + 'static;
fn record(&self, entry: &AuditEntry) -> Result<(), Self::Error>;
}
#[derive(Default)]
pub struct NoopAuditSink;
impl AuditSink for NoopAuditSink {
type Error = Infallible;
fn record(&self, _entry: &AuditEntry) -> Result<(), Self::Error> {
Ok(())
}
}
pub trait QueryLowering<O> {
type Filter;
type Projection;
fn lower(
&self,
residual: &ResidualPolicy<O>,
cx: &Context,
) -> Result<Lowered<Self::Filter, Self::Projection>, LowerError>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Lowered<F, P> {
pub filter: F,
pub grade: P,
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum LowerError {
#[error("residual fact cannot be lowered: {0}")]
Unlowerable(FactId),
#[error("graded projection requires a total order")]
NonTotalGrade,
}
pub trait ReasonCatalog {
fn render(&self, reason: &crate::DenialReason, locale: &Locale) -> String;
}
#[derive(Default)]
pub struct IdentityReasonCatalog;
impl ReasonCatalog for IdentityReasonCatalog {
fn render(&self, reason: &crate::DenialReason, _locale: &Locale) -> String {
reason.code.as_str().to_owned()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PolicyAnchor {
pub policy_id: PolicyId,
pub policy_hash: PolicyHash,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum EffectKind {
Permit,
Deny,
}
impl<O> From<&Decision<O>> for EffectKind {
fn from(decision: &Decision<O>) -> Self {
match decision.effect {
crate::Effect::Permit(_) => Self::Permit,
crate::Effect::Deny => Self::Deny,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecisionSummary {
pub anchor: PolicyAnchor,
pub effect: EffectKind,
pub obligations: Vec<ObligationId>,
pub consulted: Vec<(FactId, Presence)>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuditEntry {
pub anchor: PolicyAnchor,
pub trace: Trace,
pub effect: EffectKind,
pub obligations: Vec<ObligationId>,
pub tenant: Option<TenantId>,
pub principal: Option<SubjectRef>,
}