1use async_trait::async_trait;
2use std::sync::Arc;
3
4use camel_api::security_policy::{
5 AuthorizationDecision, CredentialSource, Principal, SecurityPolicy, principal_from_exchange,
6 store_principal_properties,
7};
8use camel_api::{CamelError, Exchange};
9
10use crate::credential_source::extract_token_from_exchange;
11use crate::token_authenticator::TokenAuthenticator;
12
13pub use camel_api::security_policy::PRINCIPAL_KEY;
18
19async fn authenticate(
33 exchange: &mut Exchange,
34 authenticator: &dyn TokenAuthenticator,
35 trust_upstream_principal: bool,
36 sources: &[CredentialSource],
37) -> Result<Principal, CamelError> {
38 let token = extract_token_from_exchange(exchange, sources).map(|extracted| extracted.token);
40
41 if let Some(token) = token {
42 let principal = authenticator.authenticate_bearer(&token).await?;
43 store_principal_properties(exchange, &principal);
45 return Ok(principal);
46 }
47
48 if trust_upstream_principal {
49 extract_principal_from_exchange(exchange)
50 } else {
51 Err(CamelError::Unauthenticated(
52 "no Bearer token and trust_upstream_principal is false".into(),
53 ))
54 }
55}
56
57fn extract_principal_from_exchange(exchange: &Exchange) -> Result<Principal, CamelError> {
64 principal_from_exchange(exchange)
65 .ok_or_else(|| CamelError::Unauthenticated("no principal in exchange".into()))
66}
67
68pub struct RolePolicy {
75 required_roles: Vec<String>,
76 all_required: bool,
77 trust_upstream_principal: bool,
81 authenticator: Arc<dyn TokenAuthenticator>,
82 credential_sources: Vec<CredentialSource>,
83}
84
85impl RolePolicy {
86 pub fn new(
87 required_roles: Vec<String>,
88 all_required: bool,
89 trust_upstream_principal: bool,
90 authenticator: Arc<dyn TokenAuthenticator>,
91 credential_sources: Vec<CredentialSource>,
92 ) -> Self {
93 Self {
94 required_roles,
95 all_required,
96 trust_upstream_principal,
97 authenticator,
98 credential_sources,
99 }
100 }
101
102 pub fn credential_sources(&self) -> &[CredentialSource] {
104 &self.credential_sources
105 }
106}
107
108#[async_trait]
109impl SecurityPolicy for RolePolicy {
110 async fn evaluate(&self, exchange: &mut Exchange) -> Result<AuthorizationDecision, CamelError> {
111 let principal = authenticate(
112 exchange,
113 &*self.authenticator,
114 self.trust_upstream_principal,
115 &self.credential_sources,
116 )
117 .await?;
118
119 let missing: Vec<String> = self
120 .required_roles
121 .iter()
122 .filter(|r| !principal.has_role(r))
123 .cloned()
124 .collect();
125
126 let granted = if self.all_required {
127 missing.is_empty()
128 } else {
129 self.required_roles.is_empty() || missing.len() < self.required_roles.len()
130 };
131
132 if granted {
133 Ok(AuthorizationDecision::Granted { principal })
134 } else {
135 let actual = principal.roles.clone();
136 Ok(AuthorizationDecision::Denied {
137 reason: format!("missing required role(s): {}", missing.join(", ")), required: self.required_roles.clone(),
139 actual,
140 })
141 }
142 }
143}
144
145pub struct ScopePolicy {
152 required_scopes: Vec<String>,
153 all_required: bool,
154 trust_upstream_principal: bool,
158 authenticator: Arc<dyn TokenAuthenticator>,
159 credential_sources: Vec<CredentialSource>,
160}
161
162impl ScopePolicy {
163 pub fn new(
164 required_scopes: Vec<String>,
165 all_required: bool,
166 trust_upstream_principal: bool,
167 authenticator: Arc<dyn TokenAuthenticator>,
168 credential_sources: Vec<CredentialSource>,
169 ) -> Self {
170 Self {
171 required_scopes,
172 all_required,
173 trust_upstream_principal,
174 authenticator,
175 credential_sources,
176 }
177 }
178
179 pub fn credential_sources(&self) -> &[CredentialSource] {
181 &self.credential_sources
182 }
183}
184
185#[async_trait]
186impl SecurityPolicy for ScopePolicy {
187 async fn evaluate(&self, exchange: &mut Exchange) -> Result<AuthorizationDecision, CamelError> {
188 let principal = authenticate(
189 exchange,
190 &*self.authenticator,
191 self.trust_upstream_principal,
192 &self.credential_sources,
193 )
194 .await?;
195
196 let missing: Vec<String> = self
197 .required_scopes
198 .iter()
199 .filter(|s| !principal.has_scope(s))
200 .cloned()
201 .collect();
202
203 let granted = if self.all_required {
204 missing.is_empty()
205 } else {
206 self.required_scopes.is_empty() || missing.len() < self.required_scopes.len()
207 };
208
209 if granted {
210 Ok(AuthorizationDecision::Granted { principal })
211 } else {
212 let actual = principal.scopes.clone();
213 Ok(AuthorizationDecision::Denied {
214 reason: format!("missing required scope(s): {}", missing.join(", ")),
215 required: self.required_scopes.clone(),
216 actual,
217 })
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::jwt::JwtValidator;
226 use crate::native_auth::{
227 NativeCredential, NativeCredentialSecret, NativeCredentialStore, StaticTokenAuthenticator,
228 };
229 use crate::types::AuthError;
230 use camel_api::Message;
231
232 fn test_principal(roles: Vec<&str>, scopes: Vec<&str>) -> Principal {
233 Principal {
234 subject: "test-user".into(),
235 issuer: "test".into(),
236 audience: vec![],
237 roles: roles.iter().map(|s| s.to_string()).collect(),
238 scopes: scopes.iter().map(|s| s.to_string()).collect(),
239 claims: serde_json::Value::Null,
240 }
241 }
242
243 struct MockJwtValidator {
245 principal: Principal,
246 }
247
248 #[async_trait]
249 impl JwtValidator for MockJwtValidator {
250 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
251 Ok(self.principal.clone())
252 }
253 }
254
255 fn mock_validator(principal: Principal) -> Arc<dyn TokenAuthenticator> {
256 Arc::new(MockJwtValidator { principal })
257 }
258
259 fn store_seeded_authenticator(
261 credential: &str,
262 principal: Principal,
263 ) -> Arc<dyn TokenAuthenticator> {
264 let store = NativeCredentialStore::try_new(vec![NativeCredential {
265 secret: NativeCredentialSecret::Plaintext {
266 value: zeroize::Zeroizing::new(credential.to_string()),
267 },
268 principal,
269 }])
270 .unwrap();
271 Arc::new(StaticTokenAuthenticator::new(store))
272 }
273
274 fn exchange_with_bearer(principal: Principal) -> Exchange {
276 let mut msg = Message::default();
277 msg.set_header(
278 "Authorization",
279 serde_json::Value::String("Bearer mock-token".into()),
280 );
281 let mut ex = Exchange::new(msg);
283 store_principal_properties(&mut ex, &principal);
284 ex
285 }
286
287 fn exchange_with_principal(principal: Principal) -> Exchange {
292 let mut ex = Exchange::new(Message::default());
293 store_principal_properties(&mut ex, &principal);
294 ex
295 }
296
297 #[tokio::test]
298 async fn role_policy_grants_when_role_present() {
299 let principal = test_principal(vec!["admin"], vec![]);
300 let policy = RolePolicy::new(
301 vec!["admin".into()],
302 true,
303 false,
304 mock_validator(principal.clone()),
305 vec![CredentialSource::AuthorizationHeader],
306 );
307 let mut ex = exchange_with_bearer(principal);
308 let decision = policy.evaluate(&mut ex).await.unwrap();
309 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
310 }
311
312 #[tokio::test]
313 async fn role_policy_denies_when_role_missing() {
314 let principal = test_principal(vec!["user"], vec![]);
315 let policy = RolePolicy::new(
316 vec!["admin".into()],
317 true,
318 false,
319 mock_validator(principal.clone()),
320 vec![CredentialSource::AuthorizationHeader],
321 );
322 let mut ex = exchange_with_bearer(principal);
323 let decision = policy.evaluate(&mut ex).await.unwrap();
324 assert!(matches!(decision, AuthorizationDecision::Denied { .. }));
325 }
326
327 #[tokio::test]
328 async fn role_policy_any_required() {
329 let principal = test_principal(vec!["user"], vec![]);
330 let policy = RolePolicy::new(
331 vec!["admin".into(), "user".into()],
332 false,
333 false,
334 mock_validator(principal.clone()),
335 vec![CredentialSource::AuthorizationHeader],
336 );
337 let mut ex = exchange_with_bearer(principal);
338 let decision = policy.evaluate(&mut ex).await.unwrap();
339 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
340 }
341
342 #[tokio::test]
343 async fn scope_policy_grants() {
344 let principal = test_principal(vec![], vec!["read"]);
345 let policy = ScopePolicy::new(
346 vec!["read".into()],
347 true,
348 false,
349 mock_validator(principal.clone()),
350 vec![CredentialSource::AuthorizationHeader],
351 );
352 let mut ex = exchange_with_bearer(principal);
353 let decision = policy.evaluate(&mut ex).await.unwrap();
354 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
355 }
356
357 #[tokio::test]
358 async fn unauthenticated_when_no_principal_and_no_header() {
359 struct FailValidator;
361 #[async_trait]
362 impl JwtValidator for FailValidator {
363 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
364 panic!("should not be called")
365 }
366 }
367 let policy = RolePolicy::new(
368 vec!["admin".into()],
369 true,
370 false,
371 Arc::new(FailValidator),
372 vec![CredentialSource::AuthorizationHeader],
373 );
374 let mut ex = Exchange::new(Message::default());
375 let result = policy.evaluate(&mut ex).await;
376 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
377 }
378
379 #[tokio::test]
380 async fn principal_fallback_denied_by_default() {
381 let principal = test_principal(vec!["admin"], vec![]);
384 let policy = RolePolicy::new(
385 vec!["admin".into()],
386 true,
387 false, mock_validator(principal.clone()),
389 vec![CredentialSource::AuthorizationHeader],
390 );
391 let mut ex = exchange_with_principal(principal); let result = policy.evaluate(&mut ex).await;
393 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
394 }
395
396 #[tokio::test]
397 async fn principal_fallback_allowed_with_opt_in() {
398 let principal = test_principal(vec!["admin"], vec![]);
400 let policy = RolePolicy::new(
401 vec!["admin".into()],
402 true,
403 true, mock_validator(principal.clone()),
405 vec![CredentialSource::AuthorizationHeader],
406 );
407 let mut ex = exchange_with_principal(principal); let decision = policy.evaluate(&mut ex).await.unwrap();
409 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
410 }
411
412 #[tokio::test]
413 async fn scope_policy_fallback_denied_by_default() {
414 let principal = test_principal(vec![], vec!["read"]);
416 let policy = ScopePolicy::new(
417 vec!["read".into()],
418 true,
419 false, mock_validator(principal.clone()),
421 vec![CredentialSource::AuthorizationHeader],
422 );
423 let mut ex = exchange_with_principal(principal);
424 let result = policy.evaluate(&mut ex).await;
425 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
426 }
427
428 #[test]
429 fn role_policy_constructor_accepts_sources() {
430 let authenticator = mock_validator(test_principal(vec![], vec![]));
431 let sources = vec![CredentialSource::Cookie { name: "s".into() }];
432 let policy = RolePolicy::new(
433 vec!["r".into()],
434 true,
435 false,
436 authenticator,
437 sources.clone(),
438 );
439 assert_eq!(policy.credential_sources(), sources.as_slice());
440 }
441
442 #[tokio::test]
445 async fn authenticate_default_equals_bearer_prefix_strip() {
446 let principal = test_principal(vec!["admin"], vec![]);
450 let store = NativeCredentialStore::try_new(vec![NativeCredential {
451 secret: NativeCredentialSecret::Plaintext {
452 value: zeroize::Zeroizing::new("mock-token".to_string()),
453 },
454 principal: principal.clone(),
455 }])
456 .unwrap();
457 let authenticator: Arc<dyn TokenAuthenticator> =
458 Arc::new(StaticTokenAuthenticator::new(store));
459 let policy = RolePolicy::new(
460 vec!["admin".into()],
461 true,
462 false,
463 authenticator,
464 vec![CredentialSource::AuthorizationHeader],
465 );
466 let mut ex = exchange_with_bearer(principal.clone());
467 let decision = policy.evaluate(&mut ex).await.unwrap();
468 match decision {
469 AuthorizationDecision::Granted { principal: granted } => {
470 assert_eq!(granted.subject, principal.subject);
471 }
472 other => panic!("expected Granted, got {other:?}"),
473 }
474 }
475
476 #[tokio::test]
477 async fn authenticate_header_source_reads_authorization() {
478 let principal = test_principal(vec!["admin"], vec![]);
479
480 let policy = RolePolicy::new(
482 vec!["admin".into()],
483 true,
484 false,
485 mock_validator(principal.clone()),
486 vec![CredentialSource::AuthorizationHeader],
487 );
488 let mut ex = exchange_with_bearer(principal.clone());
489 let decision = policy.evaluate(&mut ex).await.unwrap();
490 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
491
492 let policy = RolePolicy::new(
494 vec!["admin".into()],
495 true,
496 false,
497 mock_validator(principal),
498 vec![CredentialSource::AuthorizationHeader],
499 );
500 let mut ex = Exchange::new(Message::default());
501 let result = policy.evaluate(&mut ex).await;
502 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
503 }
504
505 #[tokio::test]
506 async fn cookie_parse_malformed_is_absent_not_error() {
507 let principal = test_principal(vec!["admin"], vec![]);
508 let policy = RolePolicy::new(
509 vec!["admin".into()],
510 true,
511 false,
512 mock_validator(principal),
513 vec![CredentialSource::Cookie {
514 name: "session".into(),
515 }],
516 );
517 let mut ex = Exchange::new(Message::default());
518 ex.input.set_header(
519 "Cookie",
520 serde_json::Value::String("garbage-no-equals".into()),
521 );
522 let result = policy.evaluate(&mut ex).await;
524 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
525 }
526
527 #[tokio::test]
528 async fn query_source_reads_camel_http_query_header() {
529 let principal = test_principal(vec!["admin"], vec![]);
530 let store = NativeCredentialStore::try_new(vec![NativeCredential {
531 secret: NativeCredentialSecret::Plaintext {
532 value: zeroize::Zeroizing::new("TOK".to_string()),
533 },
534 principal: principal.clone(),
535 }])
536 .unwrap();
537 let authenticator: Arc<dyn TokenAuthenticator> =
538 Arc::new(StaticTokenAuthenticator::new(store));
539 let policy = RolePolicy::new(
540 vec!["admin".into()],
541 true,
542 false,
543 authenticator,
544 vec![CredentialSource::QueryParam {
545 param: "token".into(),
546 }],
547 );
548 let mut ex = Exchange::new(Message::default());
549 ex.input.set_header(
550 "CamelHttpQuery",
551 serde_json::Value::String("token=TOK".into()),
552 );
553 let decision = policy.evaluate(&mut ex).await.unwrap();
554 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
555 }
556
557 #[tokio::test]
558 async fn trust_false_preloaded_principal_unauthenticated() {
559 let principal = test_principal(vec!["admin"], vec![]);
560 let policy = RolePolicy::new(
561 vec!["admin".into()],
562 true,
563 false, mock_validator(principal.clone()),
565 vec![CredentialSource::AuthorizationHeader],
566 );
567 let mut ex = exchange_with_principal(principal); let result = policy.evaluate(&mut ex).await;
569 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
570 }
571
572 #[tokio::test]
573 async fn trust_true_preloaded_principal_fallback() {
574 let principal = test_principal(vec!["admin"], vec![]);
575 let policy = RolePolicy::new(
576 vec!["admin".into()],
577 true,
578 true, mock_validator(principal.clone()),
580 vec![CredentialSource::AuthorizationHeader],
581 );
582 let mut ex = exchange_with_principal(principal); let decision = policy.evaluate(&mut ex).await.unwrap();
584 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
585 }
586
587 #[tokio::test]
588 async fn prefix_credential_unauthenticated() {
589 let principal = test_principal(vec!["admin"], vec![]);
592 let store = NativeCredentialStore::try_new(vec![NativeCredential {
593 secret: NativeCredentialSecret::Plaintext {
594 value: zeroize::Zeroizing::new("SENTINEL_FULL_9kq2".to_string()),
595 },
596 principal: principal.clone(),
597 }])
598 .unwrap();
599 let authenticator: Arc<dyn TokenAuthenticator> =
600 Arc::new(StaticTokenAuthenticator::new(store));
601
602 let policy = RolePolicy::new(
604 vec!["admin".into()],
605 true,
606 false,
607 authenticator.clone(),
608 vec![CredentialSource::AuthorizationHeader],
609 );
610 let mut ex = Exchange::new(Message::default());
611 ex.input.set_header(
612 "Authorization",
613 serde_json::Value::String("Bearer SENTINEL_FULL".into()),
614 );
615 let result = policy.evaluate(&mut ex).await;
616 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
617
618 let policy = RolePolicy::new(
620 vec!["admin".into()],
621 true,
622 false,
623 authenticator.clone(),
624 vec![CredentialSource::Cookie {
625 name: "session".into(),
626 }],
627 );
628 let mut ex = Exchange::new(Message::default());
629 ex.input.set_header(
630 "Cookie",
631 serde_json::Value::String("session=SENTINEL_FULL".into()),
632 );
633 let result = policy.evaluate(&mut ex).await;
634 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
635
636 let policy = RolePolicy::new(
638 vec!["admin".into()],
639 true,
640 false,
641 authenticator,
642 vec![CredentialSource::QueryParam {
643 param: "token".into(),
644 }],
645 );
646 let mut ex = Exchange::new(Message::default());
647 ex.input.set_header(
648 "CamelHttpQuery",
649 serde_json::Value::String("token=SENTINEL_FULL".into()),
650 );
651 let result = policy.evaluate(&mut ex).await;
652 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
653 }
654
655 #[tokio::test]
658 async fn header_source_authenticates_api_key() {
659 let principal = test_principal(vec!["admin"], vec![]);
660 let store = NativeCredentialStore::try_new(vec![NativeCredential {
661 secret: NativeCredentialSecret::Plaintext {
662 value: zeroize::Zeroizing::new("SENTINEL_KEY_1".to_string()),
663 },
664 principal: principal.clone(),
665 }])
666 .unwrap();
667 let authenticator: Arc<dyn TokenAuthenticator> =
668 Arc::new(StaticTokenAuthenticator::new(store));
669 let policy = RolePolicy::new(
670 vec!["admin".into()],
671 true,
672 false,
673 authenticator,
674 vec![CredentialSource::Header {
675 name: "x-api-key".into(),
676 }],
677 );
678 let mut ex = Exchange::new(Message::default());
679 ex.input.set_header(
680 "X-API-Key",
681 serde_json::Value::String("SENTINEL_KEY_1".into()),
682 );
683 let decision = policy.evaluate(&mut ex).await.unwrap();
684 match decision {
685 AuthorizationDecision::Granted { principal: granted } => {
686 assert_eq!(granted.subject, principal.subject);
687 }
688 other => panic!("expected Granted, got {other:?}"),
689 }
690 }
691
692 #[tokio::test]
693 async fn header_source_miss_maps_401() {
694 let principal = test_principal(vec!["admin"], vec![]);
695 let policy = RolePolicy::new(
696 vec!["admin".into()],
697 true,
698 false,
699 mock_validator(principal),
700 vec![CredentialSource::Header {
701 name: "x-api-key".into(),
702 }],
703 );
704 let mut ex = Exchange::new(Message::default());
706 let result = policy.evaluate(&mut ex).await;
707 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
708 }
709
710 #[tokio::test]
711 async fn header_lookup_case_insensitive() {
712 let principal = test_principal(vec!["admin"], vec![]);
713 let store = NativeCredentialStore::try_new(vec![NativeCredential {
714 secret: NativeCredentialSecret::Plaintext {
715 value: zeroize::Zeroizing::new("SENTINEL_KEY_1".to_string()),
716 },
717 principal: principal.clone(),
718 }])
719 .unwrap();
720 let authenticator: Arc<dyn TokenAuthenticator> =
721 Arc::new(StaticTokenAuthenticator::new(store));
722 let policy = RolePolicy::new(
723 vec!["admin".into()],
724 true,
725 false,
726 authenticator,
727 vec![CredentialSource::Header {
728 name: "x-api-key".into(),
729 }],
730 );
731 let mut ex = Exchange::new(Message::default());
732 ex.input.set_header(
734 "X-API-KEY",
735 serde_json::Value::String("SENTINEL_KEY_1".into()),
736 );
737 let decision = policy.evaluate(&mut ex).await.unwrap();
738 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
739 }
740
741 #[tokio::test]
744 async fn bearer_scheme_lowercase_grants() {
745 let principal = test_principal(vec!["admin"], vec![]);
746 let policy = RolePolicy::new(
747 vec!["admin".into()],
748 true,
749 false,
750 store_seeded_authenticator("TOK", principal),
751 vec![CredentialSource::AuthorizationHeader],
752 );
753 let mut ex = Exchange::new(Message::default());
754 ex.input.set_header(
755 "Authorization",
756 serde_json::Value::String("bearer TOK".into()),
757 );
758 let decision = policy.evaluate(&mut ex).await.unwrap();
759 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
760 }
761
762 #[tokio::test]
763 async fn bearer_scheme_uppercase_grants() {
764 let principal = test_principal(vec!["admin"], vec![]);
765 let policy = RolePolicy::new(
766 vec!["admin".into()],
767 true,
768 false,
769 store_seeded_authenticator("TOK", principal),
770 vec![CredentialSource::AuthorizationHeader],
771 );
772 let mut ex = Exchange::new(Message::default());
773 ex.input.set_header(
774 "Authorization",
775 serde_json::Value::String("BEARER TOK".into()),
776 );
777 let decision = policy.evaluate(&mut ex).await.unwrap();
778 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
779 }
780
781 #[tokio::test]
782 async fn bearer_leading_whitespace_grants() {
783 let principal = test_principal(vec!["admin"], vec![]);
784 let policy = RolePolicy::new(
785 vec!["admin".into()],
786 true,
787 false,
788 store_seeded_authenticator("TOK", principal),
789 vec![CredentialSource::AuthorizationHeader],
790 );
791 let mut ex = Exchange::new(Message::default());
792 ex.input.set_header(
793 "Authorization",
794 serde_json::Value::String(" Bearer TOK".into()),
795 );
796 let decision = policy.evaluate(&mut ex).await.unwrap();
797 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
798 }
799
800 #[tokio::test]
801 async fn bearer_double_space_yields_trimmed_token() {
802 let principal = test_principal(vec!["admin"], vec![]);
803 let policy = RolePolicy::new(
804 vec!["admin".into()],
805 true,
806 false,
807 store_seeded_authenticator("TOK", principal),
808 vec![CredentialSource::AuthorizationHeader],
809 );
810 let mut ex = Exchange::new(Message::default());
811 ex.input.set_header(
812 "Authorization",
813 serde_json::Value::String("Bearer TOK".into()),
814 );
815 let decision = policy.evaluate(&mut ex).await.unwrap();
816 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
817 }
818
819 #[tokio::test]
820 async fn bearer_empty_token_falls_to_trust_branch() {
821 let principal = test_principal(vec!["admin"], vec![]);
822
823 let policy = RolePolicy::new(
825 vec!["admin".into()],
826 true,
827 false,
828 mock_validator(principal.clone()),
829 vec![CredentialSource::AuthorizationHeader],
830 );
831 let mut ex = exchange_with_principal(principal.clone());
832 ex.input
833 .set_header("Authorization", serde_json::Value::String("Bearer ".into()));
834 let result = policy.evaluate(&mut ex).await;
835 assert!(matches!(result, Err(CamelError::Unauthenticated(_))));
836
837 let policy = RolePolicy::new(
839 vec!["admin".into()],
840 true,
841 true,
842 mock_validator(principal.clone()),
843 vec![CredentialSource::AuthorizationHeader],
844 );
845 let mut ex = exchange_with_principal(principal);
846 ex.input
847 .set_header("Authorization", serde_json::Value::String("Bearer ".into()));
848 let decision = policy.evaluate(&mut ex).await.unwrap();
849 assert!(matches!(decision, AuthorizationDecision::Granted { .. }));
850 }
851}