1use crate::errors::{AuthError, Result};
64#[cfg(feature = "saml")]
65use crate::methods::saml::SamlSignatureValidator;
66use crate::security::secure_jwt::{SecureJwtClaims, SecureJwtValidator};
67use crate::server::token_exchange::token_exchange_common::{
68 ServiceComplexityLevel, TokenExchangeCapabilities, TokenExchangeService, TokenValidationResult,
69 ValidationUtils,
70};
71use async_trait::async_trait;
72use base64::Engine as _;
73use chrono::{Duration, Utc};
74use jsonwebtoken::{Algorithm, Header, encode};
75use serde::{Deserialize, Serialize};
76use std::collections::HashMap;
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct TokenExchangeRequest {
81 pub grant_type: String,
83
84 pub subject_token: String,
86
87 pub subject_token_type: String,
89
90 pub actor_token: Option<String>,
92
93 pub actor_token_type: Option<String>,
95
96 pub requested_token_type: Option<String>,
98
99 pub audience: Option<String>,
101
102 pub scope: Option<String>,
104
105 pub resource: Option<String>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct TokenExchangeResponse {
112 pub access_token: String,
114
115 pub token_type: String,
117
118 pub expires_in: Option<i64>,
120
121 pub refresh_token: Option<String>,
123
124 pub scope: Option<String>,
126
127 pub issued_token_type: Option<String>,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub enum TokenType {
134 #[serde(rename = "urn:ietf:params:oauth:token-type:access_token")]
136 AccessToken,
137
138 #[serde(rename = "urn:ietf:params:oauth:token-type:refresh_token")]
140 RefreshToken,
141
142 #[serde(rename = "urn:ietf:params:oauth:token-type:id_token")]
144 IdToken,
145
146 #[serde(rename = "urn:ietf:params:oauth:token-type:saml2")]
148 Saml2,
149
150 #[serde(rename = "urn:ietf:params:oauth:token-type:saml1")]
152 Saml1,
153
154 #[serde(rename = "urn:ietf:params:oauth:token-type:jwt")]
156 Jwt,
157}
158
159#[derive(Debug, Clone)]
161pub struct TokenExchangeContext {
162 pub subject_claims: SecureJwtClaims,
164
165 pub actor_claims: Option<SecureJwtClaims>,
167
168 pub client_id: String,
170
171 pub audience: Option<String>,
173
174 pub scope: Option<Vec<String>>,
176
177 pub resource: Option<String>,
179}
180
181#[derive(Debug, Clone)]
183pub struct TokenExchangePolicy {
184 pub allowed_subject_token_types: Vec<TokenType>,
186
187 pub allowed_actor_token_types: Vec<TokenType>,
189
190 pub allowed_scenarios: Vec<ExchangeScenario>,
192
193 pub max_token_lifetime: Duration,
195
196 pub require_actor_for_delegation: bool,
198
199 pub allowed_audiences: Vec<String>,
201
202 pub scope_mapping: HashMap<String, Vec<String>>,
204}
205
206impl TokenExchangePolicy {
207 pub fn builder() -> TokenExchangePolicyBuilder {
220 TokenExchangePolicyBuilder {
221 inner: Self::default(),
222 }
223 }
224
225 pub fn jwt_only() -> Self {
230 Self {
231 allowed_subject_token_types: vec![TokenType::Jwt, TokenType::AccessToken],
232 allowed_actor_token_types: vec![TokenType::AccessToken],
233 allowed_scenarios: vec![
234 ExchangeScenario::OnBehalfOf,
235 ExchangeScenario::AudienceRestriction,
236 ],
237 max_token_lifetime: Duration::hours(1),
238 require_actor_for_delegation: true,
239 allowed_audiences: Vec::new(),
240 scope_mapping: HashMap::new(),
241 }
242 }
243}
244
245pub struct TokenExchangePolicyBuilder {
247 inner: TokenExchangePolicy,
248}
249
250impl TokenExchangePolicyBuilder {
251 pub fn subject_token_types(mut self, types: Vec<TokenType>) -> Self {
253 self.inner.allowed_subject_token_types = types;
254 self
255 }
256
257 pub fn actor_token_types(mut self, types: Vec<TokenType>) -> Self {
259 self.inner.allowed_actor_token_types = types;
260 self
261 }
262
263 pub fn scenarios(mut self, scenarios: Vec<ExchangeScenario>) -> Self {
265 self.inner.allowed_scenarios = scenarios;
266 self
267 }
268
269 pub fn max_token_lifetime(mut self, lifetime: Duration) -> Self {
271 self.inner.max_token_lifetime = lifetime;
272 self
273 }
274
275 pub fn require_actor_for_delegation(mut self, required: bool) -> Self {
277 self.inner.require_actor_for_delegation = required;
278 self
279 }
280
281 pub fn audience(mut self, aud: impl Into<String>) -> Self {
283 self.inner.allowed_audiences.push(aud.into());
284 self
285 }
286
287 pub fn audiences(mut self, auds: Vec<String>) -> Self {
289 self.inner.allowed_audiences = auds;
290 self
291 }
292
293 pub fn scope_map(mut self, source: impl Into<String>, targets: Vec<String>) -> Self {
295 self.inner.scope_mapping.insert(source.into(), targets);
296 self
297 }
298
299 pub fn build(self) -> TokenExchangePolicy {
301 self.inner
302 }
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
307pub enum ExchangeScenario {
308 ActingAs,
310
311 OnBehalfOf,
313
314 TokenConversion,
316
317 AudienceRestriction,
319
320 ScopeReduction,
322}
323
324pub struct TokenExchangeManager {
326 jwt_validator: SecureJwtValidator,
328
329 policies: tokio::sync::RwLock<HashMap<String, TokenExchangePolicy>>,
331
332 active_exchanges: tokio::sync::RwLock<HashMap<String, TokenExchangeContext>>,
334}
335
336impl TokenExchangeManager {
337 const SUBJECT_TOKEN_TYPES: &'static [&'static str] = &[
339 "urn:ietf:params:oauth:token-type:jwt",
340 "urn:ietf:params:oauth:token-type:access_token",
341 "urn:ietf:params:oauth:token-type:id_token",
342 "urn:ietf:params:oauth:token-type:saml1",
343 "urn:ietf:params:oauth:token-type:saml2",
344 ];
345
346 const REQUESTED_TOKEN_TYPES: &'static [&'static str] = &[
348 "urn:ietf:params:oauth:token-type:jwt",
349 "urn:ietf:params:oauth:token-type:access_token",
350 "urn:ietf:params:oauth:token-type:refresh_token",
351 ];
352
353 pub fn new(jwt_validator: SecureJwtValidator) -> Self {
355 Self {
356 jwt_validator,
357 policies: tokio::sync::RwLock::new(HashMap::new()),
358 active_exchanges: tokio::sync::RwLock::new(HashMap::new()),
359 }
360 }
361
362 pub async fn register_policy(&self, client_id: String, policy: TokenExchangePolicy) {
364 let mut policies = self.policies.write().await;
365 policies.insert(client_id, policy);
366 }
367
368 pub async fn exchange_token(
370 &self,
371 request: TokenExchangeRequest,
372 client_id: &str,
373 ) -> Result<TokenExchangeResponse> {
374 if request.grant_type != "urn:ietf:params:oauth:grant-type:token-exchange" {
376 return Err(AuthError::auth_method(
377 "token_exchange",
378 "Invalid grant type for token exchange",
379 ));
380 }
381
382 let policies = self.policies.read().await;
384 let policy = policies.get(client_id).ok_or_else(|| {
385 AuthError::auth_method("token_exchange", "No token exchange policy for client")
386 })?;
387
388 let subject_claims = self.validate_subject_token(&request, policy).await?;
390
391 let actor_claims = if let Some(ref actor_token) = request.actor_token {
393 Some(
394 self.validate_actor_token(actor_token, &request.actor_token_type, policy)
395 .await?,
396 )
397 } else {
398 None
399 };
400
401 let context = TokenExchangeContext {
403 subject_claims,
404 actor_claims,
405 client_id: client_id.to_string(),
406 audience: request.audience.clone(),
407 scope: request
408 .scope
409 .as_ref()
410 .map(|s| s.split(' ').map(String::from).collect()),
411 resource: request.resource.clone(),
412 };
413
414 let scenario = self.determine_exchange_scenario(&context, policy)?;
416 self.validate_exchange_scenario(&scenario, &context, policy)?;
417
418 let response = self
420 .generate_exchanged_token(&context, &request, policy)
421 .await?;
422
423 let exchange_id = uuid::Uuid::new_v4().to_string();
425 let mut exchanges = self.active_exchanges.write().await;
426 exchanges.insert(exchange_id, context);
427
428 Ok(response)
429 }
430
431 async fn validate_subject_token(
433 &self,
434 request: &TokenExchangeRequest,
435 policy: &TokenExchangePolicy,
436 ) -> Result<SecureJwtClaims> {
437 let token_type = self.parse_token_type(&request.subject_token_type)?;
439
440 if !policy.allowed_subject_token_types.contains(&token_type) {
442 return Err(AuthError::auth_method(
443 "token_exchange",
444 "Subject token type not allowed",
445 ));
446 }
447
448 match token_type {
450 TokenType::AccessToken
451 | TokenType::RefreshToken
452 | TokenType::IdToken
453 | TokenType::Jwt => {
454 self.validate_jwt_token(&request.subject_token).await
457 }
458 TokenType::Saml2 | TokenType::Saml1 => {
459 self.validate_saml_token(&request.subject_token, &token_type)
461 .await
462 }
463 }
464 }
465
466 async fn validate_actor_token(
468 &self,
469 actor_token: &str,
470 actor_token_type: &Option<String>,
471 policy: &TokenExchangePolicy,
472 ) -> Result<SecureJwtClaims> {
473 let token_type_str = actor_token_type
474 .as_ref()
475 .ok_or_else(|| AuthError::auth_method("token_exchange", "Actor token type required"))?;
476
477 let token_type = self.parse_token_type(token_type_str)?;
478
479 if !policy.allowed_actor_token_types.contains(&token_type) {
480 return Err(AuthError::auth_method(
481 "token_exchange",
482 "Actor token type not allowed",
483 ));
484 }
485
486 self.validate_jwt_token(actor_token).await
487 }
488
489 async fn validate_jwt_token(&self, token: &str) -> Result<SecureJwtClaims> {
491 self.jwt_validator.validate(token).map_err(|e| {
494 AuthError::auth_method("token_exchange", format!("JWT validation failed: {}", e))
495 })
496 }
497
498 fn extract_saml_xml(&self, token: &str) -> Result<String> {
499 if token.trim().is_empty() {
500 return Err(AuthError::auth_method(
501 "token_exchange",
502 "Empty SAML token provided",
503 ));
504 }
505
506 let decoded = if token.trim_start().starts_with('<') {
507 token.to_string()
508 } else {
509 String::from_utf8(
510 base64::engine::general_purpose::STANDARD
511 .decode(token)
512 .map_err(|e| {
513 AuthError::auth_method(
514 "token_exchange",
515 format!("Invalid base64-encoded SAML token: {}", e),
516 )
517 })?,
518 )
519 .map_err(|e| {
520 AuthError::auth_method(
521 "token_exchange",
522 format!("Invalid UTF-8 in SAML token: {}", e),
523 )
524 })?
525 };
526
527 let has_saml_markers = decoded.contains("<saml:")
528 || decoded.contains("<saml2:")
529 || decoded.contains("<Assertion")
530 || decoded.contains("<Response")
531 || decoded.contains("urn:oasis:names:tc:SAML");
532
533 if !has_saml_markers {
534 return Err(AuthError::auth_method(
535 "token_exchange",
536 "Invalid SAML token format - missing SAML namespace markers",
537 ));
538 }
539
540 Ok(decoded)
541 }
542
543 #[cfg(feature = "saml")]
544 fn extract_xml_text(&self, xml: &str, local_name: &str) -> Option<String> {
545 let patterns = [
546 format!("<{local_name}>"),
547 format!("<saml:{local_name}>"),
548 format!("<saml2:{local_name}>"),
549 format!("<{local_name} "),
550 format!("<saml:{local_name} "),
551 format!("<saml2:{local_name} "),
552 ];
553
554 for pattern in patterns {
555 if let Some(start) = xml.find(&pattern) {
556 let content_start = xml[start..].find('>').map(|index| start + index + 1)?;
557 let end_patterns = [
558 format!("</{local_name}>"),
559 format!("</saml:{local_name}>"),
560 format!("</saml2:{local_name}>"),
561 ];
562
563 for end_pattern in end_patterns {
564 if let Some(relative_end) = xml[content_start..].find(&end_pattern) {
565 return Some(
566 xml[content_start..content_start + relative_end]
567 .trim()
568 .to_string(),
569 );
570 }
571 }
572 }
573 }
574
575 None
576 }
577
578 #[cfg(feature = "saml")]
579 fn extract_xml_attribute(&self, xml: &str, attribute_name: &str) -> Option<String> {
580 for pattern in [
581 format!("{attribute_name}=\""),
582 format!("{attribute_name}='"),
583 ] {
584 if let Some(start) = xml.find(&pattern) {
585 let value_start = start + pattern.len();
586 if let Some(relative_end) = xml[value_start..].find(['"', '\'']) {
587 return Some(xml[value_start..value_start + relative_end].to_string());
588 }
589 }
590 }
591
592 None
593 }
594
595 #[cfg(feature = "saml")]
596 fn parse_saml_timestamp(&self, timestamp: &str) -> Result<i64> {
597 chrono::DateTime::parse_from_rfc3339(timestamp)
598 .or_else(|_| chrono::DateTime::parse_from_str(timestamp, "%Y-%m-%dT%H:%M:%S%.fZ"))
599 .or_else(|_| chrono::DateTime::parse_from_str(timestamp, "%Y-%m-%dT%H:%M:%SZ"))
600 .map(|dt| dt.timestamp())
601 .map_err(|_| {
602 AuthError::auth_method(
603 "token_exchange",
604 format!("Invalid SAML timestamp: {}", timestamp),
605 )
606 })
607 }
608
609 #[cfg(feature = "saml")]
610 fn extract_saml_timestamp(&self, xml: &str, attribute_name: &str) -> Result<Option<i64>> {
611 match self.extract_xml_attribute(xml, attribute_name) {
612 Some(timestamp) => self.parse_saml_timestamp(×tamp).map(Some),
613 None => Ok(None),
614 }
615 }
616
617 #[cfg(feature = "saml")]
618 fn validate_saml_assertion_xml(
619 &self,
620 xml: &str,
621 token_type: &TokenType,
622 ) -> Result<SecureJwtClaims> {
623 let validator = SamlSignatureValidator;
624 let certificate = validator.extract_embedded_certificate(xml).map_err(|e| {
625 AuthError::auth_method(
626 "token_exchange",
627 format!(
628 "SAML assertion is missing a usable embedded certificate: {}",
629 e
630 ),
631 )
632 })?;
633
634 let signature_valid = validator
635 .validate_xml_signature(xml, &certificate)
636 .map_err(|e| {
637 AuthError::auth_method(
638 "token_exchange",
639 format!("SAML signature validation failed: {}", e),
640 )
641 })?;
642
643 if !signature_valid {
644 return Err(AuthError::auth_method(
645 "token_exchange",
646 "SAML signature validation failed",
647 ));
648 }
649
650 let subject = self.extract_xml_text(xml, "NameID").ok_or_else(|| {
651 AuthError::auth_method("token_exchange", "SAML assertion is missing NameID")
652 })?;
653 let issuer = self.extract_xml_text(xml, "Issuer").ok_or_else(|| {
654 AuthError::auth_method("token_exchange", "SAML assertion is missing Issuer")
655 })?;
656 let audience = self.extract_xml_text(xml, "Audience");
657 let issue_instant = self.extract_saml_timestamp(xml, "IssueInstant")?;
658 let not_before = self.extract_saml_timestamp(xml, "NotBefore")?;
659 let not_on_or_after = self.extract_saml_timestamp(xml, "NotOnOrAfter")?;
660 let session_id = self.extract_xml_attribute(xml, "SessionIndex");
661
662 let now = chrono::Utc::now().timestamp();
663 if let Some(value) = issue_instant {
664 if value > now + 30 {
665 return Err(AuthError::auth_method(
666 "token_exchange",
667 "SAML assertion issue instant is in the future",
668 ));
669 }
670 if now - value > 300 {
671 return Err(AuthError::auth_method(
672 "token_exchange",
673 "SAML assertion is too old",
674 ));
675 }
676 }
677 if let Some(value) = not_before
678 && now < value
679 {
680 return Err(AuthError::auth_method(
681 "token_exchange",
682 "SAML assertion is not yet valid",
683 ));
684 }
685 if let Some(value) = not_on_or_after
686 && now >= value
687 {
688 return Err(AuthError::auth_method(
689 "token_exchange",
690 "SAML assertion has expired",
691 ));
692 }
693
694 let mut scopes = Vec::new();
695 if xml.contains("emailaddress") {
696 scopes.push("email".to_string());
697 }
698 if xml.contains("identity/claims/name") || xml.contains("givenname") {
699 scopes.push("profile".to_string());
700 }
701 if xml.contains("claims/groups") || xml.contains("role") || xml.contains("Role") {
702 scopes.push("groups".to_string());
703 }
704 if scopes.is_empty() {
705 scopes.push("saml_authenticated".to_string());
706 }
707
708 Ok(SecureJwtClaims {
709 iss: issuer,
710 sub: subject,
711 aud: audience.unwrap_or_else(|| "target_audience".to_string()),
712 exp: not_on_or_after.unwrap_or(now + 3600),
713 nbf: not_before.unwrap_or(now),
714 iat: issue_instant.unwrap_or(now),
715 jti: format!("saml_token_{}", uuid::Uuid::new_v4()),
716 scope: scopes.join(" "),
717 typ: match token_type {
718 TokenType::Saml2 => "urn:ietf:params:oauth:token-type:saml2",
719 TokenType::Saml1 => "urn:ietf:params:oauth:token-type:saml1",
720 _ => "urn:ietf:params:oauth:token-type:saml2",
721 }
722 .to_string(),
723 sid: session_id,
724 client_id: None,
725 auth_ctx_hash: Some(format!("saml_ctx_{}", uuid::Uuid::new_v4())),
726 })
727 }
728
729 #[cfg(not(feature = "saml"))]
730 fn validate_saml_assertion_xml(
731 &self,
732 _xml: &str,
733 _token_type: &TokenType,
734 ) -> Result<SecureJwtClaims> {
735 Err(AuthError::auth_method(
736 "token_exchange",
737 "SAML validation requires the 'saml' feature to be enabled",
738 ))
739 }
740
741 async fn validate_saml_token(
743 &self,
744 token: &str,
745 token_type: &TokenType,
746 ) -> Result<SecureJwtClaims> {
747 let xml = self.extract_saml_xml(token)?;
748 let claims = self.validate_saml_assertion_xml(&xml, token_type)?;
749
750 tracing::info!(
751 "SAML token validation completed - parsed subject: {}, issuer: {}, scopes: {}",
752 claims.sub,
753 claims.iss,
754 claims.scope
755 );
756 Ok(claims)
757 }
758
759 fn parse_token_type(&self, token_type: &str) -> Result<TokenType> {
761 match token_type {
762 "urn:ietf:params:oauth:token-type:access_token" => Ok(TokenType::AccessToken),
763 "urn:ietf:params:oauth:token-type:refresh_token" => Ok(TokenType::RefreshToken),
764 "urn:ietf:params:oauth:token-type:id_token" => Ok(TokenType::IdToken),
765 "urn:ietf:params:oauth:token-type:saml2" => Ok(TokenType::Saml2),
766 "urn:ietf:params:oauth:token-type:saml1" => Ok(TokenType::Saml1),
767 "urn:ietf:params:oauth:token-type:jwt" => Ok(TokenType::Jwt),
768 _ => Err(AuthError::auth_method(
769 "token_exchange",
770 "Unknown token type",
771 )),
772 }
773 }
774
775 fn determine_exchange_scenario(
777 &self,
778 context: &TokenExchangeContext,
779 _policy: &TokenExchangePolicy,
780 ) -> Result<ExchangeScenario> {
781 if context.actor_claims.is_some() {
782 return Ok(ExchangeScenario::OnBehalfOf);
783 }
784
785 if let Some(audience) = context.audience.as_ref() {
786 if audience != &context.subject_claims.aud {
787 return Ok(ExchangeScenario::AudienceRestriction);
788 }
789 }
790
791 if let Some(requested_scope) = &context.scope {
792 let current_scope: Vec<&str> = context.subject_claims.scope.split(' ').collect();
793 if requested_scope.len() < current_scope.len() {
794 return Ok(ExchangeScenario::ScopeReduction);
795 }
796 }
797
798 Ok(ExchangeScenario::ActingAs)
799 }
800
801 fn validate_exchange_scenario(
802 &self,
803 scenario: &ExchangeScenario,
804 context: &TokenExchangeContext,
805 policy: &TokenExchangePolicy,
806 ) -> Result<()> {
807 if !policy.allowed_scenarios.is_empty() && !policy.allowed_scenarios.contains(scenario) {
808 return Err(AuthError::auth_method(
809 "token_exchange",
810 "Exchange scenario is not permitted by policy",
811 ));
812 }
813
814 match scenario {
815 ExchangeScenario::OnBehalfOf => {
816 if policy.require_actor_for_delegation && context.actor_claims.is_none() {
817 return Err(AuthError::auth_method(
818 "token_exchange",
819 "Actor token required for delegation",
820 ));
821 }
822 }
823 ExchangeScenario::AudienceRestriction => {
824 if let Some(audience) = context.audience.as_ref() {
825 if !policy.allowed_audiences.is_empty()
826 && !policy.allowed_audiences.contains(audience)
827 {
828 return Err(AuthError::auth_method(
829 "token_exchange",
830 "Audience not allowed",
831 ));
832 }
833 }
834 }
835 _ => {}
836 }
837
838 Ok(())
839 }
840
841 async fn generate_exchanged_token(
843 &self,
844 context: &TokenExchangeContext,
845 request: &TokenExchangeRequest,
846 policy: &TokenExchangePolicy,
847 ) -> Result<TokenExchangeResponse> {
848 let now = Utc::now();
849 let expires_in = policy.max_token_lifetime.num_seconds();
850 let exp = now + policy.max_token_lifetime;
851
852 let mut new_claims = context.subject_claims.clone();
854
855 new_claims.exp = exp.timestamp();
857 new_claims.iat = now.timestamp();
858 new_claims.jti = uuid::Uuid::new_v4().to_string();
859
860 if let Some(ref audience) = request.audience {
862 new_claims.aud = audience.clone();
863 }
864
865 if let Some(ref requested_scope) = request.scope {
867 if let Some(mapped_scopes) = policy.scope_mapping.get(requested_scope) {
868 new_claims.scope = mapped_scopes.join(" ");
869 } else {
870 new_claims.scope = requested_scope.clone();
871 }
872 }
873
874 if let Some(ref actor_claims) = context.actor_claims {
876 new_claims.client_id = Some(actor_claims.sub.clone());
877 }
878
879 let access_token = encode(
881 &Header::new(Algorithm::HS256),
882 &new_claims,
883 &self.jwt_validator.get_encoding_key(),
884 )
885 .map_err(|e| AuthError::internal(format!("Failed to sign exchanged token: {}", e)))?;
886
887 let issued_token_type = request
889 .requested_token_type
890 .clone()
891 .unwrap_or_else(|| "urn:ietf:params:oauth:token-type:access_token".to_string());
892
893 Ok(TokenExchangeResponse {
894 access_token,
895 token_type: "Bearer".to_string(),
896 expires_in: Some(expires_in),
897 refresh_token: None, scope: Some(new_claims.scope),
899 issued_token_type: Some(issued_token_type),
900 })
901 }
902}
903
904impl Default for TokenExchangePolicy {
905 fn default() -> Self {
906 Self {
907 allowed_subject_token_types: vec![
908 TokenType::AccessToken,
909 TokenType::RefreshToken,
910 TokenType::IdToken,
911 ],
912 allowed_actor_token_types: vec![TokenType::AccessToken, TokenType::IdToken],
913 allowed_scenarios: vec![
914 ExchangeScenario::ActingAs,
915 ExchangeScenario::OnBehalfOf,
916 ExchangeScenario::AudienceRestriction,
917 ExchangeScenario::ScopeReduction,
918 ],
919 max_token_lifetime: Duration::hours(1),
920 require_actor_for_delegation: true,
921 allowed_audiences: Vec::new(), scope_mapping: HashMap::new(),
923 }
924 }
925}
926
927#[async_trait]
929impl TokenExchangeService for TokenExchangeManager {
930 type Request = (TokenExchangeRequest, String); type Response = TokenExchangeResponse;
932 type Config = SecureJwtValidator; async fn exchange_token(&self, request: Self::Request) -> Result<Self::Response> {
936 let (token_request, client_id) = request;
937 self.exchange_token(token_request, &client_id).await
938 }
939
940 async fn validate_token(&self, token: &str, token_type: &str) -> Result<TokenValidationResult> {
942 let supported_types = self.supported_subject_token_types();
944 ValidationUtils::validate_token_type(token_type, &supported_types)?;
945
946 match self.parse_token_type(token_type)? {
947 TokenType::Jwt | TokenType::AccessToken | TokenType::IdToken => {
948 match self.jwt_validator.validate(token) {
951 Ok(claims) => {
952 use chrono::{TimeZone, Utc};
954 let expires_at = Utc.timestamp_opt(claims.exp, 0).single();
955
956 let audience = if claims.aud.is_empty() {
958 Vec::new()
959 } else {
960 vec![claims.aud.clone()]
961 };
962
963 let scopes = if claims.scope.is_empty() {
965 Vec::new()
966 } else {
967 claims
968 .scope
969 .split_whitespace()
970 .map(|s| s.to_string())
971 .collect()
972 };
973
974 let mut metadata = HashMap::new();
976 metadata.insert(
977 "sub".to_string(),
978 serde_json::Value::String(claims.sub.clone()),
979 );
980 metadata.insert(
981 "iss".to_string(),
982 serde_json::Value::String(claims.iss.clone()),
983 );
984 metadata.insert(
985 "aud".to_string(),
986 serde_json::Value::String(claims.aud.clone()),
987 );
988 metadata.insert(
989 "scope".to_string(),
990 serde_json::Value::String(claims.scope.clone()),
991 );
992 metadata.insert(
993 "typ".to_string(),
994 serde_json::Value::String(claims.typ.clone()),
995 );
996 if let Some(ref sid) = claims.sid {
997 metadata
998 .insert("sid".to_string(), serde_json::Value::String(sid.clone()));
999 }
1000 if let Some(ref client_id) = claims.client_id {
1001 metadata.insert(
1002 "client_id".to_string(),
1003 serde_json::Value::String(client_id.clone()),
1004 );
1005 }
1006
1007 Ok(TokenValidationResult {
1008 is_valid: true,
1009 subject: Some(claims.sub),
1010 issuer: Some(claims.iss),
1011 audience,
1012 scopes,
1013 expires_at,
1014 metadata,
1015 validation_messages: Vec::new(),
1016 })
1017 }
1018 Err(e) => Ok(TokenValidationResult {
1019 is_valid: false,
1020 subject: None,
1021 issuer: None,
1022 audience: Vec::new(),
1023 scopes: Vec::new(),
1024 expires_at: None,
1025 metadata: HashMap::new(),
1026 validation_messages: vec![format!("JWT validation failed: {}", e)],
1027 }),
1028 }
1029 }
1030 TokenType::Saml2 | TokenType::Saml1 => {
1031 match self
1032 .validate_saml_token(token, &self.parse_token_type(token_type)?)
1033 .await
1034 {
1035 Ok(claims) => {
1036 use chrono::{TimeZone, Utc};
1037 let expires_at = Utc.timestamp_opt(claims.exp, 0).single();
1038 let audience = if claims.aud.is_empty() {
1039 Vec::new()
1040 } else {
1041 vec![claims.aud.clone()]
1042 };
1043 let scopes = if claims.scope.is_empty() {
1044 Vec::new()
1045 } else {
1046 claims
1047 .scope
1048 .split_whitespace()
1049 .map(|scope| scope.to_string())
1050 .collect()
1051 };
1052
1053 let mut metadata = HashMap::new();
1054 metadata.insert(
1055 "sub".to_string(),
1056 serde_json::Value::String(claims.sub.clone()),
1057 );
1058 metadata.insert(
1059 "iss".to_string(),
1060 serde_json::Value::String(claims.iss.clone()),
1061 );
1062 metadata.insert(
1063 "aud".to_string(),
1064 serde_json::Value::String(claims.aud.clone()),
1065 );
1066 metadata.insert(
1067 "scope".to_string(),
1068 serde_json::Value::String(claims.scope.clone()),
1069 );
1070 metadata.insert(
1071 "typ".to_string(),
1072 serde_json::Value::String(claims.typ.clone()),
1073 );
1074 if let Some(ref sid) = claims.sid {
1075 metadata
1076 .insert("sid".to_string(), serde_json::Value::String(sid.clone()));
1077 }
1078
1079 Ok(TokenValidationResult {
1080 is_valid: true,
1081 subject: Some(claims.sub),
1082 issuer: Some(claims.iss),
1083 audience,
1084 scopes,
1085 expires_at,
1086 metadata,
1087 validation_messages: Vec::new(),
1088 })
1089 }
1090 Err(error) => Ok(TokenValidationResult {
1091 is_valid: false,
1092 subject: None,
1093 issuer: None,
1094 audience: Vec::new(),
1095 scopes: Vec::new(),
1096 expires_at: None,
1097 metadata: HashMap::new(),
1098 validation_messages: vec![error.to_string()],
1099 }),
1100 }
1101 }
1102 _ => Err(AuthError::InvalidRequest(format!(
1103 "Token validation not supported for type: {}",
1104 token_type
1105 ))),
1106 }
1107 }
1108
1109 fn supported_subject_token_types(&self) -> Vec<String> {
1111 Self::SUBJECT_TOKEN_TYPES
1112 .iter()
1113 .map(|s| s.to_string())
1114 .collect()
1115 }
1116
1117 fn supported_requested_token_types(&self) -> Vec<String> {
1119 Self::REQUESTED_TOKEN_TYPES
1120 .iter()
1121 .map(|s| s.to_string())
1122 .collect()
1123 }
1124
1125 fn capabilities(&self) -> TokenExchangeCapabilities {
1127 TokenExchangeCapabilities {
1128 basic_exchange: true,
1129 multi_party_chains: false,
1130 context_preservation: false,
1131 audit_trail: false,
1132 session_integration: false,
1133 jwt_operations: false,
1134 policy_control: true,
1135 cross_domain_exchange: false,
1136 max_delegation_depth: 3,
1137 complexity_level: ServiceComplexityLevel::Basic,
1138 }
1139 }
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144 use super::*;
1145 use crate::security::secure_jwt::SecureJwtConfig;
1146
1147 fn create_test_manager() -> TokenExchangeManager {
1148 let jwt_config = SecureJwtConfig::default();
1149 let jwt_validator = SecureJwtValidator::new(jwt_config).expect("test JWT config");
1150 TokenExchangeManager::new(jwt_validator)
1151 }
1152
1153 fn create_test_request() -> TokenExchangeRequest {
1154 TokenExchangeRequest {
1155 grant_type: "urn:ietf:params:oauth:grant-type:token-exchange".to_string(),
1156 subject_token: "dummy.jwt.token".to_string(),
1157 subject_token_type: "urn:ietf:params:oauth:token-type:access_token".to_string(),
1158 actor_token: None,
1159 actor_token_type: None,
1160 requested_token_type: Some("urn:ietf:params:oauth:token-type:access_token".to_string()),
1161 audience: Some("api.example.com".to_string()),
1162 scope: Some("read write".to_string()),
1163 resource: None,
1164 }
1165 }
1166
1167 #[tokio::test]
1168 async fn test_token_exchange_manager_creation() {
1169 let manager = create_test_manager();
1170
1171 let policy = TokenExchangePolicy::default();
1173 manager
1174 .register_policy("test_client".to_string(), policy)
1175 .await;
1176 }
1177
1178 #[test]
1179 fn test_token_type_parsing() {
1180 let manager = create_test_manager();
1181
1182 assert_eq!(
1183 manager
1184 .parse_token_type("urn:ietf:params:oauth:token-type:access_token")
1185 .unwrap(),
1186 TokenType::AccessToken
1187 );
1188
1189 assert_eq!(
1190 manager
1191 .parse_token_type("urn:ietf:params:oauth:token-type:id_token")
1192 .unwrap(),
1193 TokenType::IdToken
1194 );
1195
1196 assert!(manager.parse_token_type("invalid_token_type").is_err());
1197 }
1198
1199 #[test]
1200 fn test_exchange_scenario_determination() {
1201 let manager = create_test_manager();
1202 let policy = TokenExchangePolicy::default();
1203
1204 let context = TokenExchangeContext {
1206 subject_claims: SecureJwtClaims {
1207 sub: "user123".to_string(),
1208 iss: "auth.example.com".to_string(),
1209 aud: "api.example.com".to_string(),
1210 exp: chrono::Utc::now().timestamp() + 3600,
1211 nbf: chrono::Utc::now().timestamp(),
1212 iat: chrono::Utc::now().timestamp(),
1213 jti: "token123".to_string(),
1214 scope: "read write".to_string(),
1215 typ: "access".to_string(),
1216 sid: None,
1217 client_id: None,
1218 auth_ctx_hash: None,
1219 },
1220 actor_claims: None,
1221 client_id: "test_client".to_string(),
1222 audience: Some("different.api.com".to_string()),
1223 scope: None,
1224 resource: None,
1225 };
1226
1227 let scenario = manager
1228 .determine_exchange_scenario(&context, &policy)
1229 .unwrap();
1230 assert_eq!(scenario, ExchangeScenario::AudienceRestriction);
1231 }
1232
1233 #[tokio::test]
1234 async fn test_invalid_grant_type() {
1235 let manager = create_test_manager();
1236 let policy = TokenExchangePolicy::default();
1237 manager
1238 .register_policy("test_client".to_string(), policy)
1239 .await;
1240
1241 let mut request = create_test_request();
1242 request.grant_type = "invalid_grant_type".to_string();
1243
1244 let result = manager.exchange_token(request, "test_client").await;
1245 assert!(result.is_err());
1246 }
1247
1248 #[test]
1249 fn test_token_exchange_policy_builder() {
1250 let policy = TokenExchangePolicy::builder()
1251 .max_token_lifetime(Duration::minutes(30))
1252 .require_actor_for_delegation(false)
1253 .audience("api.example.com")
1254 .audience("internal.example.com")
1255 .scope_map("admin", vec!["read".into(), "write".into()])
1256 .build();
1257
1258 assert_eq!(policy.max_token_lifetime, Duration::minutes(30));
1259 assert!(!policy.require_actor_for_delegation);
1260 assert_eq!(policy.allowed_audiences.len(), 2);
1261 assert_eq!(policy.scope_mapping.get("admin").unwrap().len(), 2);
1262 assert!(!policy.allowed_subject_token_types.is_empty());
1264 }
1265
1266 #[test]
1267 fn test_token_exchange_policy_jwt_only_preset() {
1268 let policy = TokenExchangePolicy::jwt_only();
1269 assert_eq!(policy.allowed_subject_token_types.len(), 2);
1270 assert!(policy.allowed_subject_token_types.contains(&TokenType::Jwt));
1271 assert!(policy.allowed_subject_token_types.contains(&TokenType::AccessToken));
1272 assert!(policy.require_actor_for_delegation);
1273 }
1274
1275 #[tokio::test]
1276 async fn test_token_exchange_policy_builder_register() {
1277 let manager = create_test_manager();
1278 let policy = TokenExchangePolicy::builder()
1279 .scenarios(vec![ExchangeScenario::OnBehalfOf, ExchangeScenario::AudienceRestriction])
1280 .max_token_lifetime(Duration::minutes(15))
1281 .build();
1282 manager
1283 .register_policy("test_client".to_string(), policy)
1284 .await;
1285 }
1286}