hessra_token_authz/verify.rs
1extern crate biscuit_auth as biscuit;
2
3use biscuit::datalog::RunLimits;
4use biscuit::macros::{authorizer, check, fact};
5use biscuit::Algorithm;
6use chrono::Utc;
7use hessra_token_core::{
8 parse_authorization_failure, parse_check_failure, Biscuit, PublicKey, ServiceChainFailure,
9 TokenError,
10};
11use serde::Deserialize;
12use std::time::Duration;
13
14#[derive(Debug, Deserialize, Clone)]
15pub struct ServiceNode {
16 pub component: String,
17 pub public_key: String,
18}
19
20/// Optional context for token verification restrictions.
21///
22/// This struct bundles optional restrictions that can be applied during
23/// token verification. Using a struct instead of individual parameters
24/// makes it easier to add new restrictions without changing function signatures.
25#[derive(Debug, Clone, Default)]
26pub struct VerificationContext {
27 /// Domain restriction for the verification
28 pub domain: Option<String>,
29 /// Prefix restriction for the verification
30 pub prefix: Option<String>,
31}
32
33/// Verification mode for authorization tokens.
34#[derive(Debug, Clone, PartialEq)]
35enum VerificationMode {
36 /// Identity-based verification: requires an explicit subject parameter.
37 /// The authorizer will check if the token grants rights to this specific subject.
38 Identity { subject: String },
39 /// Capability-based verification: derives the subject from the token's rights.
40 /// The authorizer will accept any token that grants the specified capability
41 /// (resource + operation), regardless of the subject.
42 Capability,
43}
44
45impl VerificationMode {
46 /// Returns the subject if in Identity mode, None for Capability mode.
47 fn subject(&self) -> Option<&str> {
48 match self {
49 VerificationMode::Identity { subject } => Some(subject),
50 VerificationMode::Capability => None,
51 }
52 }
53
54 /// Returns the subject if in Identity mode, "unknown" for Capability mode.
55 /// Used for error reporting.
56 fn subject_or_unknown(&self) -> &str {
57 match self {
58 VerificationMode::Identity { subject } => subject,
59 VerificationMode::Capability => "unknown",
60 }
61 }
62}
63
64/// Builder for verifying Hessra authorization tokens with flexible configuration.
65///
66/// This builder allows you to configure various verification parameters including
67/// optional domain restrictions and service chain attestation.
68///
69/// # Example
70/// ```no_run
71/// use hessra_token_authz::{AuthorizationVerifier, ServiceNode, create_token};
72/// use hessra_token_core::KeyPair;
73///
74/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
75/// // Create a token
76/// let keypair = KeyPair::new();
77/// let public_key = keypair.public();
78/// let token = create_token(
79/// "user123".to_string(),
80/// "resource456".to_string(),
81/// "read".to_string(),
82/// keypair,
83/// )?;
84///
85/// // Basic authorization verification
86/// AuthorizationVerifier::new(
87/// token.clone(),
88/// public_key,
89/// "user123".to_string(),
90/// "resource456".to_string(),
91/// "read".to_string(),
92/// )
93/// .verify()?;
94///
95/// // With domain restriction
96/// AuthorizationVerifier::new(
97/// token.clone(),
98/// public_key,
99/// "user123".to_string(),
100/// "resource456".to_string(),
101/// "read".to_string(),
102/// )
103/// .with_domain("example.com".to_string())
104/// .verify()?;
105///
106/// // With service chain attestation
107/// let service_nodes = vec![
108/// ServiceNode {
109/// component: "api-gateway".to_string(),
110/// public_key: "ed25519/abcd1234...".to_string(),
111/// }
112/// ];
113/// AuthorizationVerifier::new(
114/// token,
115/// public_key,
116/// "user123".to_string(),
117/// "resource456".to_string(),
118/// "read".to_string(),
119/// )
120/// .with_service_chain(service_nodes, Some("api-gateway".to_string()))
121/// .verify()?;
122/// # Ok(())
123/// # }
124/// ```
125pub struct AuthorizationVerifier {
126 token: String,
127 public_key: PublicKey,
128 mode: VerificationMode,
129 resource: String,
130 operation: String,
131 context: VerificationContext,
132 service_chain: Option<(Vec<ServiceNode>, Option<String>)>,
133}
134
135impl AuthorizationVerifier {
136 /// Creates a new authorization verifier for a base64-encoded token.
137 ///
138 /// # Arguments
139 /// * `token` - The base64-encoded authorization token to verify
140 /// * `public_key` - The public key used to verify the token signature
141 /// * `subject` - The subject (user) identifier to verify authorization for
142 /// * `resource` - The resource identifier to verify authorization against
143 /// * `operation` - The operation to verify authorization for
144 pub fn new(
145 token: String,
146 public_key: PublicKey,
147 subject: String,
148 resource: String,
149 operation: String,
150 ) -> Self {
151 Self {
152 token,
153 public_key,
154 mode: VerificationMode::Identity { subject },
155 resource,
156 operation,
157 context: VerificationContext::default(),
158 service_chain: None,
159 }
160 }
161
162 /// Creates a new authorization verifier from raw token bytes.
163 ///
164 /// # Arguments
165 /// * `token` - The raw binary Biscuit token bytes
166 /// * `public_key` - The public key used to verify the token signature
167 /// * `subject` - The subject (user) identifier to verify authorization for
168 /// * `resource` - The resource identifier to verify authorization against
169 /// * `operation` - The operation to verify authorization for
170 pub fn from_bytes(
171 token: Vec<u8>,
172 public_key: PublicKey,
173 subject: String,
174 resource: String,
175 operation: String,
176 ) -> Result<Self, TokenError> {
177 // Convert bytes to base64 for internal storage
178 let biscuit = Biscuit::from(&token, public_key)?;
179 let token_string = biscuit
180 .to_base64()
181 .map_err(|e| TokenError::generic(format!("Failed to encode token: {e}")))?;
182 Ok(Self {
183 token: token_string,
184 public_key,
185 mode: VerificationMode::Identity { subject },
186 resource,
187 operation,
188 context: VerificationContext::default(),
189 service_chain: None,
190 })
191 }
192
193 /// Creates a new capability-based verifier (no subject required).
194 ///
195 /// This verifier will accept any token that grants the specified capability
196 /// (resource + operation), regardless of the subject. The subject is derived
197 /// from the token's rights instead of being provided explicitly.
198 ///
199 /// # Arguments
200 /// * `token` - The base64-encoded authorization token to verify
201 /// * `public_key` - The public key used to verify the token signature
202 /// * `resource` - The resource identifier to verify authorization against
203 /// * `operation` - The operation to verify authorization for
204 pub fn new_capability(
205 token: String,
206 public_key: PublicKey,
207 resource: String,
208 operation: String,
209 ) -> Self {
210 Self {
211 token,
212 public_key,
213 mode: VerificationMode::Capability,
214 resource,
215 operation,
216 context: VerificationContext::default(),
217 service_chain: None,
218 }
219 }
220
221 /// Creates a new capability-based verifier from raw token bytes.
222 ///
223 /// This is the binary token version of `new_capability`. It accepts any token
224 /// that grants the specified capability (resource + operation), regardless of
225 /// the subject.
226 ///
227 /// # Arguments
228 /// * `token` - The raw binary Biscuit token bytes
229 /// * `public_key` - The public key used to verify the token signature
230 /// * `resource` - The resource identifier to verify authorization against
231 /// * `operation` - The operation to verify authorization for
232 pub fn from_bytes_capability(
233 token: Vec<u8>,
234 public_key: PublicKey,
235 resource: String,
236 operation: String,
237 ) -> Result<Self, TokenError> {
238 let biscuit = Biscuit::from(&token, public_key)?;
239 let token_string = biscuit
240 .to_base64()
241 .map_err(|e| TokenError::generic(format!("Failed to encode token: {e}")))?;
242 Ok(Self {
243 token: token_string,
244 public_key,
245 mode: VerificationMode::Capability,
246 resource,
247 operation,
248 context: VerificationContext::default(),
249 service_chain: None,
250 })
251 }
252
253 /// Adds a domain restriction to the verification.
254 ///
255 /// When set, adds a domain fact to the authorizer. This is required for
256 /// verifying domain-restricted tokens.
257 ///
258 /// # Arguments
259 /// * `domain` - The domain to verify against (e.g., "example.com")
260 pub fn with_domain(mut self, domain: String) -> Self {
261 self.context.domain = Some(domain);
262 self
263 }
264
265 /// Adds a prefix restriction to the verification.
266 ///
267 /// When set, adds a prefix fact to the authorizer. This is required for
268 /// verifying prefix-restricted tokens.
269 ///
270 /// # Arguments
271 /// * `prefix` - The prefix to verify against (e.g., "tenant/TENANTID/user/USERID/")
272 pub fn with_prefix(mut self, prefix: String) -> Self {
273 self.context.prefix = Some(prefix);
274 self
275 }
276
277 /// Adds service chain attestation verification.
278 ///
279 /// When set, verifies that the token has been properly attested by the
280 /// specified service chain nodes.
281 ///
282 /// # Arguments
283 /// * `service_nodes` - The list of service nodes in the chain
284 /// * `component` - Optional specific component to verify in the chain
285 pub fn with_service_chain(
286 mut self,
287 service_nodes: Vec<ServiceNode>,
288 component: Option<String>,
289 ) -> Self {
290 self.service_chain = Some((service_nodes, component));
291 self
292 }
293
294 /// Performs the token verification with the configured parameters.
295 ///
296 /// # Returns
297 /// * `Ok(())` - If the token is valid and meets all verification requirements
298 /// * `Err(TokenError)` - If verification fails for any reason
299 ///
300 /// # Errors
301 /// Returns an error if:
302 /// - The token is malformed or cannot be parsed
303 /// - The token signature is invalid
304 /// - The token has expired
305 /// - The token does not grant the required access rights
306 /// - The domain doesn't match (if domain restriction is set on token)
307 /// - Service chain attestation fails (if service chain is configured)
308 pub fn verify(self) -> Result<(), TokenError> {
309 let biscuit = Biscuit::from_base64(&self.token, self.public_key)?;
310
311 if let Some((service_nodes, component)) = self.service_chain {
312 // Service chain verification
313 verify_raw_service_chain_biscuit(
314 biscuit,
315 self.mode,
316 self.resource,
317 self.operation,
318 service_nodes,
319 component,
320 self.context,
321 )
322 } else {
323 // Basic verification
324 verify_raw_biscuit(
325 biscuit,
326 self.mode,
327 self.resource,
328 self.operation,
329 self.context,
330 )
331 }
332 }
333}
334
335pub(crate) fn build_base_authorizer(
336 subject: String,
337 resource: String,
338 operation: String,
339 context: &VerificationContext,
340) -> Result<biscuit::AuthorizerBuilder, TokenError> {
341 let now = Utc::now().timestamp();
342
343 let mut authz = authorizer!(
344 r#"
345 time({now});
346 resource({resource});
347 subject({subject});
348 operation({operation});
349 allow if true;
350 "#
351 );
352
353 // Add domain fact if specified
354 if let Some(domain) = context.domain.clone() {
355 authz = authz.fact(fact!(r#"domain({domain});"#))?;
356 }
357
358 // Add prefix fact if specified
359 if let Some(prefix) = context.prefix.clone() {
360 authz = authz.fact(fact!(r#"prefix({prefix});"#))?;
361 }
362
363 Ok(authz)
364}
365
366/// Build a capability-based authorizer that derives the subject from the token's rights.
367///
368/// Unlike `build_base_authorizer`, this function does not require a subject parameter.
369/// Instead, it uses a Datalog rule to derive the subject from any `right(subject, resource, operation)`
370/// facts present in the token. This allows verification based solely on capability (resource + operation)
371/// without needing to know the identity.
372pub(crate) fn build_capability_authorizer(
373 resource: String,
374 operation: String,
375 context: &VerificationContext,
376) -> Result<biscuit::AuthorizerBuilder, TokenError> {
377 let now = Utc::now().timestamp();
378
379 let mut authz = authorizer!(
380 r#"
381 time({now});
382 resource({resource});
383 operation({operation});
384 // Derive subject from the token's rights instead of providing it explicitly
385 subject($sub) <- right($sub, {resource}, {operation});
386 allow if true;
387 "#
388 );
389
390 // Add domain fact if specified
391 if let Some(domain) = context.domain.clone() {
392 authz = authz.fact(fact!(r#"domain({domain});"#))?;
393 }
394
395 // Add prefix fact if specified
396 if let Some(prefix) = context.prefix.clone() {
397 authz = authz.fact(fact!(r#"prefix({prefix});"#))?;
398 }
399
400 Ok(authz)
401}
402
403fn verify_raw_biscuit(
404 biscuit: Biscuit,
405 mode: VerificationMode,
406 resource: String,
407 operation: String,
408 context: VerificationContext,
409) -> Result<(), TokenError> {
410 let authz = match &mode {
411 VerificationMode::Identity { subject } => build_base_authorizer(
412 subject.clone(),
413 resource.clone(),
414 operation.clone(),
415 &context,
416 )?,
417 VerificationMode::Capability => {
418 build_capability_authorizer(resource.clone(), operation.clone(), &context)?
419 }
420 };
421
422 match authz.build(&biscuit)?.authorize() {
423 Ok(_) => Ok(()),
424 Err(e) => Err(convert_authorization_error(
425 e,
426 mode.subject(),
427 Some(&resource),
428 Some(&operation),
429 &context,
430 )),
431 }
432}
433
434/// Verifies a Biscuit authorization token locally without contacting the authorization server.
435///
436/// This function performs local verification of a Biscuit token using the provided public key.
437/// It validates that the token grants access to the specified resource for the given subject.
438///
439/// # Arguments
440///
441/// * `token` - The binary Biscuit token bytes (typically decoded from Base64)
442/// * `public_key` - The public key used to verify the token signature
443/// * `subject` - The subject (user) identifier to verify authorization for
444/// * `resource` - The resource identifier to verify authorization against
445/// * `operation` - The operation to verify authorization for
446///
447/// # Returns
448///
449/// * `Ok(())` - If the token is valid and grants access to the resource
450/// * `Err(TokenError)` - If verification fails for any reason
451///
452/// # Errors
453///
454/// Returns an error if:
455/// - The token is malformed or cannot be parsed
456/// - The token signature is invalid
457/// - The token does not grant the required access rights
458/// - The token has expired or other authorization checks fail
459pub fn verify_biscuit_local(
460 token: Vec<u8>,
461 public_key: PublicKey,
462 subject: String,
463 resource: String,
464 operation: String,
465) -> Result<(), TokenError> {
466 AuthorizationVerifier::from_bytes(token, public_key, subject, resource, operation)?.verify()
467}
468
469/// Verifies a Biscuit authorization token locally without contacting the authorization server.
470///
471/// This function performs local verification of a Biscuit token using the provided public key.
472/// It validates that the token grants access to the specified resource for the given subject.
473///
474/// # Arguments
475///
476/// * `token` - The base64-encoded Biscuit token string
477/// * `public_key` - The public key used to verify the token signature
478/// * `subject` - The subject (user) identifier to verify authorization for
479/// * `resource` - The resource identifier to verify authorization against
480/// * `operation` - The operation to verify authorization for
481///
482/// # Returns
483///
484/// * `Ok(())` - If the token is valid and grants access to the resource
485/// * `Err(TokenError)` - If verification fails for any reason
486///
487/// # Errors
488///
489/// Returns an error if:
490/// - The token is malformed or cannot be parsed
491/// - The token signature is invalid
492/// - The token does not grant the required access rights
493/// - The token has expired or other authorization checks fail
494pub fn verify_token_local(
495 token: &str,
496 public_key: PublicKey,
497 subject: &str,
498 resource: &str,
499 operation: &str,
500) -> Result<(), TokenError> {
501 AuthorizationVerifier::new(
502 token.to_string(),
503 public_key,
504 subject.to_string(),
505 resource.to_string(),
506 operation.to_string(),
507 )
508 .verify()
509}
510
511/// Takes a public key encoded as a string in the format "ed25519/..." or "secp256r1/..."
512/// and returns a PublicKey.
513pub fn biscuit_key_from_string(key: String) -> Result<PublicKey, TokenError> {
514 let parts = key.split('/').collect::<Vec<&str>>();
515 if parts.len() != 2 {
516 return Err(TokenError::invalid_key_format(
517 "Key must be in format 'algorithm/hexkey'",
518 ));
519 }
520
521 let alg = match parts[0] {
522 "ed25519" => Algorithm::Ed25519,
523 "secp256r1" => Algorithm::Secp256r1,
524 _ => {
525 return Err(TokenError::invalid_key_format(
526 "Unsupported algorithm, must be ed25519 or secp256r1",
527 ))
528 }
529 };
530
531 // decode the key from hex
532 let key_bytes = hex::decode(parts[1])?;
533
534 // construct the public key
535 let key = PublicKey::from_bytes(&key_bytes, alg)
536 .map_err(|e| TokenError::invalid_key_format(e.to_string()))?;
537
538 Ok(key)
539}
540
541fn verify_raw_service_chain_biscuit(
542 biscuit: Biscuit,
543 mode: VerificationMode,
544 resource: String,
545 operation: String,
546 service_nodes: Vec<ServiceNode>,
547 component: Option<String>,
548 context: VerificationContext,
549) -> Result<(), TokenError> {
550 let mut authz = match &mode {
551 VerificationMode::Identity { subject } => build_base_authorizer(
552 subject.clone(),
553 resource.clone(),
554 operation.clone(),
555 &context,
556 )?,
557 VerificationMode::Capability => {
558 build_capability_authorizer(resource.clone(), operation.clone(), &context)?
559 }
560 };
561
562 let mut component_found = false;
563 if component.is_none() {
564 component_found = true;
565 }
566 for service_node in &service_nodes {
567 if let Some(ref component) = component {
568 if component == &service_node.component {
569 component_found = true;
570 break;
571 }
572 }
573 let service = resource.clone();
574 let node_name = service_node.component.clone();
575 let node_key = biscuit_key_from_string(service_node.public_key.clone())?;
576 authz = authz.check(check!(
577 r#"
578 check if node({service}, {node_name}) trusting authority, {node_key};
579 "#
580 ))?;
581 }
582
583 if let Some(ref component_name) = component {
584 if !component_found {
585 return Err(TokenError::ServiceChainFailed {
586 component: component_name.clone(),
587 reason: ServiceChainFailure::ComponentNotFound,
588 });
589 }
590 }
591
592 // Service chain verification evaluates additional Datalog checks for each
593 // node in the chain. This can exceed default time limits on slower systems,
594 // so we use extended limits here (default is 1ms, we use 10ms).
595 let service_chain_limits = RunLimits {
596 max_time: Duration::from_millis(10),
597 ..Default::default()
598 };
599 match authz
600 .build(&biscuit)?
601 .authorize_with_limits(service_chain_limits)
602 {
603 Ok(_) => Ok(()),
604 Err(e) => Err(convert_service_chain_error(
605 e,
606 mode.subject_or_unknown(),
607 &resource,
608 &operation,
609 service_nodes,
610 &context,
611 )),
612 }
613}
614
615pub fn verify_service_chain_biscuit_local(
616 token: Vec<u8>,
617 public_key: PublicKey,
618 subject: String,
619 resource: String,
620 operation: String,
621 service_nodes: Vec<ServiceNode>,
622 component: Option<String>,
623) -> Result<(), TokenError> {
624 AuthorizationVerifier::from_bytes(token, public_key, subject, resource, operation)?
625 .with_service_chain(service_nodes, component)
626 .verify()
627}
628
629pub fn verify_service_chain_token_local(
630 token: &str,
631 public_key: PublicKey,
632 subject: &str,
633 resource: &str,
634 operation: &str,
635 service_nodes: Vec<ServiceNode>,
636 component: Option<String>,
637) -> Result<(), TokenError> {
638 AuthorizationVerifier::new(
639 token.to_string(),
640 public_key,
641 subject.to_string(),
642 resource.to_string(),
643 operation.to_string(),
644 )
645 .with_service_chain(service_nodes, component)
646 .verify()
647}
648
649/// Verifies a Biscuit authorization token based on capability (resource + operation) only.
650///
651/// This function performs capability-based verification without requiring a subject parameter.
652/// The subject is derived from the token's rights - any subject that has the specified right
653/// for the resource and operation will satisfy verification.
654///
655/// This is useful for services that only care about authorization for an action, not identity.
656/// For example, a telemetry service that only needs to verify write permission, not who is writing.
657///
658/// # Arguments
659///
660/// * `token` - The base64-encoded Biscuit token string
661/// * `public_key` - The public key used to verify the token signature
662/// * `resource` - The resource identifier to verify authorization against
663/// * `operation` - The operation to verify authorization for
664///
665/// # Returns
666///
667/// * `Ok(())` - If the token is valid and grants access to the resource+operation
668/// * `Err(TokenError)` - If verification fails
669///
670/// # Example
671///
672/// ```no_run
673/// use hessra_token_authz::{verify_capability_token_local, create_token};
674/// use hessra_token_core::{KeyPair, TokenError};
675///
676/// let keypair = KeyPair::new();
677/// let public_key = keypair.public();
678///
679/// // Create a token for alice to read resource1
680/// let token = create_token(
681/// "alice".to_string(),
682/// "resource1".to_string(),
683/// "read".to_string(),
684/// keypair,
685/// )
686/// .map_err(|e| TokenError::Generic(e.to_string()))?;
687///
688/// // Verify capability without caring about the subject
689/// verify_capability_token_local(&token, public_key, "resource1", "read")?;
690/// # Ok::<(), hessra_token_core::TokenError>(())
691/// ```
692pub fn verify_capability_token_local(
693 token: &str,
694 public_key: PublicKey,
695 resource: &str,
696 operation: &str,
697) -> Result<(), TokenError> {
698 AuthorizationVerifier::new_capability(
699 token.to_string(),
700 public_key,
701 resource.to_string(),
702 operation.to_string(),
703 )
704 .verify()
705}
706
707/// Verifies a Biscuit authorization token based on capability (resource + operation) only.
708///
709/// This is the binary token version of `verify_capability_token_local`.
710///
711/// # Arguments
712///
713/// * `token` - The binary Biscuit token bytes
714/// * `public_key` - The public key used to verify the token signature
715/// * `resource` - The resource identifier to verify authorization against
716/// * `operation` - The operation to verify authorization for
717///
718/// # Returns
719///
720/// * `Ok(())` - If the token is valid and grants access to the resource+operation
721/// * `Err(TokenError)` - If verification fails
722pub fn verify_capability_biscuit_local(
723 token: Vec<u8>,
724 public_key: PublicKey,
725 resource: String,
726 operation: String,
727) -> Result<(), TokenError> {
728 AuthorizationVerifier::from_bytes_capability(token, public_key, resource, operation)?.verify()
729}
730
731/// Verifies a service chain token based on capability without requiring subject.
732///
733/// This combines service chain attestation verification with capability-based verification.
734/// The token must have the required service chain attestations and grant the specified
735/// capability (resource + operation), but the subject is derived from the token rather
736/// than being provided explicitly.
737///
738/// # Arguments
739///
740/// * `token` - The base64-encoded Biscuit token string
741/// * `public_key` - The public key used to verify the token signature
742/// * `resource` - The resource identifier to verify authorization against
743/// * `operation` - The operation to verify authorization for
744/// * `service_nodes` - The list of service nodes in the chain
745/// * `component` - Optional specific component to verify in the chain
746///
747/// # Returns
748///
749/// * `Ok(())` - If the token is valid and grants access
750/// * `Err(TokenError)` - If verification fails
751pub fn verify_service_chain_capability_token_local(
752 token: &str,
753 public_key: PublicKey,
754 resource: &str,
755 operation: &str,
756 service_nodes: Vec<ServiceNode>,
757 component: Option<String>,
758) -> Result<(), TokenError> {
759 AuthorizationVerifier::new_capability(
760 token.to_string(),
761 public_key,
762 resource.to_string(),
763 operation.to_string(),
764 )
765 .with_service_chain(service_nodes, component)
766 .verify()
767}
768
769/// Binary version of `verify_service_chain_capability_token_local`.
770///
771/// Verifies a service chain token from raw bytes based on capability without requiring subject.
772///
773/// # Arguments
774///
775/// * `token` - The binary Biscuit token bytes
776/// * `public_key` - The public key used to verify the token signature
777/// * `resource` - The resource identifier to verify authorization against
778/// * `operation` - The operation to verify authorization for
779/// * `service_nodes` - The list of service nodes in the chain
780/// * `component` - Optional specific component to verify in the chain
781///
782/// # Returns
783///
784/// * `Ok(())` - If the token is valid and grants access
785/// * `Err(TokenError)` - If verification fails
786pub fn verify_service_chain_capability_biscuit_local(
787 token: Vec<u8>,
788 public_key: PublicKey,
789 resource: String,
790 operation: String,
791 service_nodes: Vec<ServiceNode>,
792 component: Option<String>,
793) -> Result<(), TokenError> {
794 AuthorizationVerifier::from_bytes_capability(token, public_key, resource, operation)?
795 .with_service_chain(service_nodes, component)
796 .verify()
797}
798
799/// Convert biscuit authorization errors to detailed authorization errors
800fn convert_authorization_error(
801 err: biscuit::error::Token,
802 subject: Option<&str>,
803 resource: Option<&str>,
804 operation: Option<&str>,
805 context: &VerificationContext,
806) -> TokenError {
807 use biscuit::error::{Logic, Token};
808
809 match err {
810 Token::FailedLogic(logic_err) => match &logic_err {
811 Logic::Unauthorized { checks, .. } | Logic::NoMatchingPolicy { checks } => {
812 // Try to parse each failed check for more specific errors
813 for failed_check in checks.iter() {
814 let (block_id, check_id, rule) = match failed_check {
815 biscuit::error::FailedCheck::Block(block_check) => (
816 block_check.block_id,
817 block_check.check_id,
818 block_check.rule.clone(),
819 ),
820 biscuit::error::FailedCheck::Authorizer(auth_check) => {
821 (0, auth_check.check_id, auth_check.rule.clone())
822 }
823 };
824
825 // Try to parse the check for specific error types
826 let parsed_error = parse_check_failure(block_id, check_id, &rule);
827
828 // Enhance domain errors with context we have
829 match parsed_error {
830 TokenError::DomainMismatch {
831 expected,
832 block_id,
833 check_id,
834 ..
835 } => {
836 return TokenError::DomainMismatch {
837 expected,
838 provided: context.domain.clone(),
839 block_id,
840 check_id,
841 };
842 }
843 TokenError::Expired { .. } => return parsed_error,
844 _ => {}
845 }
846 }
847
848 // Check if this looks like a rights denial (no matching policy)
849 if matches!(logic_err, Logic::NoMatchingPolicy { .. }) {
850 return parse_authorization_failure(
851 subject,
852 resource,
853 operation,
854 &format!("{checks:?}"),
855 );
856 }
857
858 // If we couldn't parse any specific error, use generic conversion
859 TokenError::from(Token::FailedLogic(logic_err))
860 }
861 other => TokenError::from(Token::FailedLogic(other.clone())),
862 },
863 other => TokenError::from(other),
864 }
865}
866
867/// Convert biscuit authorization errors to service chain specific errors
868fn convert_service_chain_error(
869 err: biscuit::error::Token,
870 subject: &str,
871 resource: &str,
872 operation: &str,
873 service_nodes: Vec<ServiceNode>,
874 context: &VerificationContext,
875) -> TokenError {
876 use biscuit::error::{Logic, Token};
877
878 match err {
879 Token::FailedLogic(logic_err) => match &logic_err {
880 Logic::Unauthorized { checks, .. } | Logic::NoMatchingPolicy { checks } => {
881 // Check if any failure is service chain related
882 for failed_check in checks.iter() {
883 let (block_id, check_id, rule) = match failed_check {
884 biscuit::error::FailedCheck::Block(block_check) => (
885 block_check.block_id,
886 block_check.check_id,
887 block_check.rule.clone(),
888 ),
889 biscuit::error::FailedCheck::Authorizer(auth_check) => {
890 (0, auth_check.check_id, auth_check.rule.clone())
891 }
892 };
893
894 // Check if this is a service chain check (contains "node(")
895 if rule.contains("node(") {
896 // Try to extract component name from the rule
897 for service_node in &service_nodes {
898 if rule.contains(&service_node.component) {
899 return TokenError::ServiceChainFailed {
900 component: service_node.component.clone(),
901 reason: ServiceChainFailure::MissingAttestation,
902 };
903 }
904 }
905
906 // Generic service chain failure
907 return TokenError::ServiceChainFailed {
908 component: resource.to_string(),
909 reason: ServiceChainFailure::Other(
910 "Service chain attestation check failed".to_string(),
911 ),
912 };
913 }
914
915 // Check for other specific error types
916 let parsed_error = parse_check_failure(block_id, check_id, &rule);
917 match parsed_error {
918 TokenError::DomainMismatch {
919 expected,
920 block_id,
921 check_id,
922 ..
923 } => {
924 return TokenError::DomainMismatch {
925 expected,
926 provided: context.domain.clone(),
927 block_id,
928 check_id,
929 };
930 }
931 TokenError::Expired { .. } => return parsed_error,
932 _ => {}
933 }
934 }
935
936 // Fallback to authorization error
937 parse_authorization_failure(
938 Some(subject),
939 Some(resource),
940 Some(operation),
941 &format!("{checks:?}"),
942 )
943 }
944 other => TokenError::from(Token::FailedLogic(other.clone())),
945 },
946 other => TokenError::from(other),
947 }
948}