use std::sync::Arc;
use gatekeep::{
AuditSink, Authorizer, Context, Decision, DecisionAuditOccurrence, DecisiveClause, DenyShape,
Effect, FactResolver, IdentityReasonCatalog, Lattice, NoopAuditSink, NoopPolicyObserver,
Policy, PolicyId, PolicyObserver, PreparedPolicy, ReasonCatalog,
};
use serde::Serialize;
use crate::{DenialResponseConfig, GatekeepAxumError, GatekeepRejection};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Authorized<O> {
pub outcome: O,
pub decision: Decision<O>,
pub audit_occurrence: DecisionAuditOccurrence,
}
pub struct Gatekeeper<R, A = NoopAuditSink, C = IdentityReasonCatalog, W = NoopPolicyObserver> {
authorizer: Authorizer<R, A, W>,
reason_catalog: Arc<C>,
denial_response: DenialResponseConfig,
}
impl<R, A, C, W> Clone for Gatekeeper<R, A, C, W> {
fn clone(&self) -> Self {
Self {
authorizer: self.authorizer.clone(),
reason_catalog: Arc::clone(&self.reason_catalog),
denial_response: self.denial_response.clone(),
}
}
}
impl<R> Gatekeeper<R> {
#[must_use]
pub fn unaudited(resolver: R) -> Self {
Self {
authorizer: Authorizer::unaudited(resolver),
reason_catalog: Arc::new(IdentityReasonCatalog),
denial_response: DenialResponseConfig::default(),
}
}
}
impl<R, A> Gatekeeper<R, A> {
#[must_use]
pub fn new(resolver: R, audit_sink: A) -> Self {
Self {
authorizer: Authorizer::new(resolver, audit_sink),
reason_catalog: Arc::new(IdentityReasonCatalog),
denial_response: DenialResponseConfig::default(),
}
}
}
impl<R, A, C, W> Gatekeeper<R, A, C, W> {
#[must_use]
pub fn with_audit_sink<NextAudit>(
self,
audit_sink: NextAudit,
) -> Gatekeeper<R, NextAudit, C, W> {
Gatekeeper {
authorizer: self.authorizer.with_audit_sink(audit_sink),
reason_catalog: self.reason_catalog,
denial_response: self.denial_response,
}
}
#[must_use]
pub fn with_reason_catalog<NextCatalog>(
self,
reason_catalog: NextCatalog,
) -> Gatekeeper<R, A, NextCatalog, W> {
Gatekeeper {
authorizer: self.authorizer,
reason_catalog: Arc::new(reason_catalog),
denial_response: self.denial_response,
}
}
#[must_use]
pub fn with_observer<NextObserver>(
self,
observer: NextObserver,
) -> Gatekeeper<R, A, C, NextObserver> {
Gatekeeper {
authorizer: self.authorizer.with_observer(observer),
reason_catalog: self.reason_catalog,
denial_response: self.denial_response,
}
}
#[must_use]
pub fn with_denial_response(mut self, denial_response: DenialResponseConfig) -> Self {
self.denial_response = denial_response;
self
}
#[must_use]
pub fn with_clock<F>(mut self, clock: F) -> Self
where
F: gatekeep::Clock + 'static,
{
self.authorizer = self.authorizer.with_clock(clock);
self
}
}
impl<R, A, C, W> Gatekeeper<R, A, C, W>
where
R: FactResolver,
A: AuditSink,
C: ReasonCatalog + Send + Sync,
W: PolicyObserver,
{
pub async fn authorize<O>(
&self,
policy_id: PolicyId,
policy: &Policy<O>,
context: Context,
) -> Result<Authorized<O>, GatekeepRejection<R::Error, A::Error>>
where
O: Lattice + Serialize + Send + Sync,
{
let result = self
.authorizer
.authorize_policy(policy_id, policy, &context)
.await
.map_err(GatekeepRejection::from_error)?;
self.present(result, &context)
}
pub async fn authorize_prepared<O>(
&self,
policy: &PreparedPolicy<O>,
context: Context,
) -> Result<Authorized<O>, GatekeepRejection<R::Error, A::Error>>
where
O: Lattice + Serialize + Send + Sync,
{
let result = self
.authorizer
.authorize(policy, &context)
.await
.map_err(GatekeepRejection::from_error)?;
self.present(result, &context)
}
}
impl<R, A, C: ReasonCatalog, W> Gatekeeper<R, A, C, W> {
fn present<O: Serialize + Clone, Resolve, Audit>(
&self,
result: gatekeep::AuthorizationDecision<O>,
context: &Context,
) -> Result<Authorized<O>, GatekeepRejection<Resolve, Audit>> {
let gatekeep::AuthorizationDecision {
decision,
audit_occurrence,
} = result;
match decision.effect.clone() {
Effect::Permit(outcome) => Ok(Authorized {
outcome,
decision,
audit_occurrence,
}),
Effect::Deny => {
let reason = decision
.denial_reason()
.map_err(GatekeepAxumError::Trace)
.map_err(GatekeepRejection::from_error)?;
let response = self.denial_response.denied(
denial_shape(&decision),
reason.as_ref(),
context.locale(),
self.reason_catalog.as_ref(),
);
Err(response.into())
}
}
}
}
const fn denial_shape<O>(decision: &Decision<O>) -> DenyShape {
match &decision.trace.decisive {
DecisiveClause::Deny { shape, .. } => *shape,
DecisiveClause::Permit { .. } => DenyShape::Forbidden,
}
}
impl<R, A, C, W> Gatekeeper<R, A, C, W>
where
R: gatekeep::ResourcePolicy,
A: AuditSink,
C: ReasonCatalog + Send + Sync,
W: PolicyObserver,
{
pub async fn authorize_resource(
&self,
action: &R::Action,
principal: &R::Principal,
resource: &R::Resource,
context: Context,
) -> Result<Authorized<R::Outcome>, GatekeepRejection<R::Error, A::Error>> {
let result = self
.authorizer
.authorize_resource(action, principal, resource, &context)
.await
.map_err(GatekeepRejection::from_error)?;
self.present(result, &context)
}
}