Skip to main content

aura_guards/guards/
biscuit_evaluator.rs

1//! Biscuit guard evaluator for capability and flow budget enforcement.
2//!
3//! Combines Biscuit authorization with flow budget checking to provide
4//! atomic capability verification and budget charging for guard chains.
5
6use crate::authorization::BiscuitAuthorizationBridge;
7use crate::guards::types::CapabilityId;
8use aura_authorization::{BiscuitError, ResourceScope};
9use aura_core::{AuthorityId, FlowBudget, FlowCost};
10use biscuit_auth::Biscuit;
11
12pub struct BiscuitGuardEvaluator {
13    bridge: BiscuitAuthorizationBridge,
14}
15
16impl BiscuitGuardEvaluator {
17    pub fn new(bridge: BiscuitAuthorizationBridge) -> Self {
18        Self { bridge }
19    }
20
21    /// Get the authority ID from the underlying bridge
22    pub fn authority_id(&self) -> AuthorityId {
23        self.bridge.authority_id()
24    }
25
26    /// Backwards compatible wrapper for evaluate_guard without explicit time
27    /// Uses default time value for testing/mock scenarios
28    pub fn evaluate_guard_default_time(
29        &self,
30        token: &Biscuit,
31        guard_capability: &CapabilityId,
32        resource: &ResourceScope,
33        flow_cost: FlowCost,
34        budget: &mut FlowBudget,
35    ) -> Result<GuardResult, GuardError> {
36        self.evaluate_guard(token, guard_capability, resource, flow_cost, budget, 0)
37    }
38
39    /// Backwards compatible wrapper for check_guard without explicit time
40    /// Uses default time value for testing/mock scenarios
41    pub fn check_guard_default_time(
42        &self,
43        token: &Biscuit,
44        guard_capability: &CapabilityId,
45        resource: &ResourceScope,
46    ) -> Result<bool, GuardError> {
47        self.check_guard(token, guard_capability, resource, 0)
48    }
49
50    pub fn evaluate_guard(
51        &self,
52        token: &Biscuit,
53        guard_capability: &CapabilityId,
54        resource: &ResourceScope,
55        flow_cost: FlowCost,
56        budget: &mut FlowBudget,
57        current_time_seconds: u64,
58    ) -> Result<GuardResult, GuardError> {
59        let can_charge =
60            budget
61                .can_charge(flow_cost)
62                .map_err(|e| GuardError::FlowBudgetEvaluationFailed {
63                    detail: e.to_string(),
64                })?;
65        if !can_charge {
66            return Err(GuardError::BudgetExceeded {
67                required: u64::from(flow_cost),
68                available: budget.headroom(),
69            });
70        }
71
72        let auth_result = self.bridge.authorize(
73            token,
74            guard_capability.as_str(),
75            resource,
76            current_time_seconds,
77        )?;
78
79        if !auth_result.authorized {
80            return Err(GuardError::MissingCapability {
81                capability: guard_capability.to_string(),
82            });
83        }
84
85        if let Err(e) = budget.record_charge(flow_cost) {
86            return Err(GuardError::FlowBudgetChargeFailed {
87                detail: e.to_string(),
88            });
89        }
90
91        Ok(GuardResult {
92            authorized: true,
93            flow_consumed: u64::from(flow_cost),
94            delegation_depth: auth_result.delegation_depth,
95        })
96    }
97
98    pub fn check_guard(
99        &self,
100        token: &Biscuit,
101        guard_capability: &CapabilityId,
102        resource: &ResourceScope,
103        current_time_seconds: u64,
104    ) -> Result<bool, GuardError> {
105        let auth_result = self.bridge.authorize(
106            token,
107            guard_capability.as_str(),
108            resource,
109            current_time_seconds,
110        )?;
111        Ok(auth_result.authorized)
112    }
113}
114
115#[derive(Debug, Clone)]
116pub struct GuardResult {
117    pub authorized: bool,
118    pub flow_consumed: u64,
119    pub delegation_depth: Option<u32>,
120}
121
122#[derive(Debug, thiserror::Error)]
123pub enum GuardError {
124    #[error("Budget exceeded: required {required}, available {available}")]
125    BudgetExceeded { required: u64, available: u64 },
126
127    #[error("authorization failed: missing capability {capability}")]
128    MissingCapability { capability: String },
129
130    #[error("Biscuit error: {0}")]
131    Biscuit(#[from] BiscuitError),
132
133    #[error("flow budget evaluation failed: {detail}")]
134    FlowBudgetEvaluationFailed { detail: String },
135
136    #[error("flow budget charge failed: {detail}")]
137    FlowBudgetChargeFailed { detail: String },
138}
139
140impl aura_core::ProtocolErrorCode for GuardError {
141    fn code(&self) -> &'static str {
142        match self {
143            GuardError::BudgetExceeded { .. } => "budget_exceeded",
144            GuardError::MissingCapability { .. } => "unauthorized",
145            GuardError::Biscuit(_) => "biscuit_error",
146            GuardError::FlowBudgetEvaluationFailed { .. } => "flow_budget_evaluation_failed",
147            GuardError::FlowBudgetChargeFailed { .. } => "flow_budget_charge_failed",
148        }
149    }
150}