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