1use super::mandate_store::{AuthzError, AuthzReceipt, MandateStore};
13use chrono::{DateTime, Utc};
14use thiserror::Error;
15
16#[path = "authorizer_internal/mod.rs"]
17mod authorizer_internal;
18
19#[cfg(test)]
21pub(crate) use authorizer_internal::policy::glob_matches_impl;
22
23pub const DEFAULT_CLOCK_SKEW_SECONDS: i64 = 30;
25
26#[derive(Debug, Clone)]
28pub struct AuthzConfig {
29 pub clock_skew_seconds: i64,
31 pub expected_audience: String,
33 pub trusted_issuers: Vec<String>,
35}
36
37impl Default for AuthzConfig {
38 fn default() -> Self {
39 Self {
40 clock_skew_seconds: DEFAULT_CLOCK_SKEW_SECONDS,
41 expected_audience: String::new(),
42 trusted_issuers: Vec::new(),
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
49pub enum OperationClass {
50 Read = 0,
51 Write = 1,
52 Commit = 2,
53}
54
55impl OperationClass {
56 pub fn as_str(&self) -> &'static str {
57 match self {
58 Self::Read => "read",
59 Self::Write => "write",
60 Self::Commit => "commit",
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum MandateKind {
68 Intent,
69 Transaction,
70}
71
72impl MandateKind {
73 pub fn as_str(&self) -> &'static str {
74 match self {
75 Self::Intent => "intent",
76 Self::Transaction => "transaction",
77 }
78 }
79
80 pub fn max_operation_class(&self) -> OperationClass {
82 match self {
83 Self::Intent => OperationClass::Write, Self::Transaction => OperationClass::Commit, }
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct MandateData {
92 pub mandate_id: String,
93 pub mandate_kind: MandateKind,
94 pub audience: String,
95 pub issuer: String,
96 pub tool_patterns: Vec<String>,
97 pub operation_class: Option<OperationClass>,
98 pub transaction_ref: Option<String>,
99 pub not_before: Option<DateTime<Utc>>,
100 pub expires_at: Option<DateTime<Utc>>,
101 pub single_use: bool,
102 pub max_uses: Option<u32>,
103 pub nonce: Option<String>,
104 pub canonical_digest: String,
105 pub key_id: String,
106}
107
108#[derive(Debug, Clone)]
110pub struct ToolCallData {
111 pub tool_call_id: String,
112 pub tool_name: String,
113 pub operation_class: OperationClass,
114 pub transaction_object: Option<serde_json::Value>,
115 pub source_run_id: Option<String>,
116}
117
118#[derive(Debug, Error, PartialEq, Eq)]
120#[non_exhaustive]
121pub enum PolicyError {
122 #[error("Mandate expired: expires_at={expires_at}, now={now}")]
123 Expired {
124 expires_at: DateTime<Utc>,
125 now: DateTime<Utc>,
126 },
127
128 #[error("Mandate not yet valid: not_before={not_before}, now={now}")]
129 NotYetValid {
130 not_before: DateTime<Utc>,
131 now: DateTime<Utc>,
132 },
133
134 #[error("Tool '{tool}' not in mandate scope")]
135 ToolNotInScope { tool: String },
136
137 #[error("Mandate kind '{kind}' does not allow operation class '{op_class}'")]
138 KindMismatch { kind: String, op_class: String },
139
140 #[error("Audience mismatch: expected '{expected}', got '{actual}'")]
141 AudienceMismatch { expected: String, actual: String },
142
143 #[error("Issuer '{issuer}' not in trusted issuers")]
144 IssuerNotTrusted { issuer: String },
145
146 #[error("Missing transaction object for commit tool")]
147 MissingTransactionObject,
148
149 #[error("Transaction ref mismatch: expected '{expected}', got '{actual}'")]
150 TransactionRefMismatch { expected: String, actual: String },
151}
152
153#[derive(Debug, Error)]
155#[non_exhaustive]
156pub enum AuthorizeError {
157 #[error("Policy error: {0}")]
158 Policy(#[from] PolicyError),
159
160 #[error("Store error: {0}")]
161 Store(#[from] AuthzError),
162
163 #[error("Failed to compute transaction ref: {0}")]
164 TransactionRef(String),
165}
166
167pub struct Authorizer {
169 store: MandateStore,
170 config: AuthzConfig,
171}
172
173impl Authorizer {
174 pub fn new(store: MandateStore, config: AuthzConfig) -> Self {
176 Self { store, config }
177 }
178
179 pub fn authorize_and_consume(
190 &self,
191 mandate: &MandateData,
192 tool_call: &ToolCallData,
193 ) -> Result<AuthzReceipt, AuthorizeError> {
194 authorizer_internal::run::authorize_and_consume_impl(self, mandate, tool_call)
195 }
196
197 pub fn authorize_at(
200 &self,
201 now: DateTime<Utc>,
202 mandate: &MandateData,
203 tool_call: &ToolCallData,
204 ) -> Result<AuthzReceipt, AuthorizeError> {
205 authorizer_internal::run::authorize_at_impl(self, now, mandate, tool_call)
206 }
207}