1use crate::errors::{AuthError, Result};
59use crate::security::secure_jwt::{SecureJwtClaims, SecureJwtValidator};
60use crate::server::token_exchange::token_exchange_common::{
61 ServiceComplexityLevel, TokenExchangeCapabilities, TokenExchangeService, TokenValidationResult,
62 ValidationUtils,
63};
64use async_trait::async_trait;
65use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
66use chrono::{Duration, Utc};
67use serde::{Deserialize, Serialize};
68use std::collections::HashMap;
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct TokenExchangeRequest {
73 pub grant_type: String,
75
76 pub subject_token: String,
78
79 pub subject_token_type: String,
81
82 pub actor_token: Option<String>,
84
85 pub actor_token_type: Option<String>,
87
88 pub requested_token_type: Option<String>,
90
91 pub audience: Option<String>,
93
94 pub scope: Option<String>,
96
97 pub resource: Option<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct TokenExchangeResponse {
104 pub access_token: String,
106
107 pub token_type: String,
109
110 pub expires_in: Option<i64>,
112
113 pub refresh_token: Option<String>,
115
116 pub scope: Option<String>,
118
119 pub issued_token_type: Option<String>,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub enum TokenType {
126 #[serde(rename = "urn:ietf:params:oauth:token-type:access_token")]
128 AccessToken,
129
130 #[serde(rename = "urn:ietf:params:oauth:token-type:refresh_token")]
132 RefreshToken,
133
134 #[serde(rename = "urn:ietf:params:oauth:token-type:id_token")]
136 IdToken,
137
138 #[serde(rename = "urn:ietf:params:oauth:token-type:saml2")]
140 Saml2,
141
142 #[serde(rename = "urn:ietf:params:oauth:token-type:saml1")]
144 Saml1,
145
146 #[serde(rename = "urn:ietf:params:oauth:token-type:jwt")]
148 Jwt,
149}
150
151#[derive(Debug, Clone)]
153pub struct TokenExchangeContext {
154 pub subject_claims: SecureJwtClaims,
156
157 pub actor_claims: Option<SecureJwtClaims>,
159
160 pub client_id: String,
162
163 pub audience: Option<String>,
165
166 pub scope: Option<Vec<String>>,
168
169 pub resource: Option<String>,
171}
172
173#[derive(Debug, Clone)]
175pub struct TokenExchangePolicy {
176 pub allowed_subject_token_types: Vec<TokenType>,
178
179 pub allowed_actor_token_types: Vec<TokenType>,
181
182 pub allowed_scenarios: Vec<ExchangeScenario>,
184
185 pub max_token_lifetime: Duration,
187
188 pub require_actor_for_delegation: bool,
190
191 pub allowed_audiences: Vec<String>,
193
194 pub scope_mapping: HashMap<String, Vec<String>>,
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
200pub enum ExchangeScenario {
201 ActingAs,
203
204 OnBehalfOf,
206
207 TokenConversion,
209
210 AudienceRestriction,
212
213 ScopeReduction,
215}
216
217#[derive(Debug, Clone)]
219struct SamlClaims {
220 pub subject: String,
222 pub issuer: String,
224 pub audience: Option<String>,
226 pub expiry: Option<i64>,
228 pub not_before: Option<i64>,
230 pub session_id: Option<String>,
232 pub scopes: Vec<String>,
234}
235
236pub struct TokenExchangeManager {
238 jwt_validator: SecureJwtValidator,
240
241 policies: tokio::sync::RwLock<HashMap<String, TokenExchangePolicy>>,
243
244 active_exchanges: tokio::sync::RwLock<HashMap<String, TokenExchangeContext>>,
246}
247
248impl TokenExchangeManager {
249 const SUBJECT_TOKEN_TYPES: &'static [&'static str] = &[
251 "urn:ietf:params:oauth:token-type:jwt",
252 "urn:ietf:params:oauth:token-type:access_token",
253 "urn:ietf:params:oauth:token-type:id_token",
254 "urn:ietf:params:oauth:token-type:saml2",
255 ];
256
257 const REQUESTED_TOKEN_TYPES: &'static [&'static str] = &[
259 "urn:ietf:params:oauth:token-type:jwt",
260 "urn:ietf:params:oauth:token-type:access_token",
261 "urn:ietf:params:oauth:token-type:refresh_token",
262 ];
263
264 pub fn new(jwt_validator: SecureJwtValidator) -> Self {
266 Self {
267 jwt_validator,
268 policies: tokio::sync::RwLock::new(HashMap::new()),
269 active_exchanges: tokio::sync::RwLock::new(HashMap::new()),
270 }
271 }
272
273 pub async fn register_policy(&self, client_id: String, policy: TokenExchangePolicy) {
275 let mut policies = self.policies.write().await;
276 policies.insert(client_id, policy);
277 }
278
279 pub async fn exchange_token(
281 &self,
282 request: TokenExchangeRequest,
283 client_id: &str,
284 ) -> Result<TokenExchangeResponse> {
285 if request.grant_type != "urn:ietf:params:oauth:grant-type:token-exchange" {
287 return Err(AuthError::auth_method(
288 "token_exchange",
289 "Invalid grant type for token exchange",
290 ));
291 }
292
293 let policies = self.policies.read().await;
295 let policy = policies.get(client_id).ok_or_else(|| {
296 AuthError::auth_method("token_exchange", "No token exchange policy for client")
297 })?;
298
299 let subject_claims = self.validate_subject_token(&request, policy).await?;
301
302 let actor_claims = if let Some(ref actor_token) = request.actor_token {
304 Some(
305 self.validate_actor_token(actor_token, &request.actor_token_type, policy)
306 .await?,
307 )
308 } else {
309 None
310 };
311
312 let context = TokenExchangeContext {
314 subject_claims,
315 actor_claims,
316 client_id: client_id.to_string(),
317 audience: request.audience.clone(),
318 scope: request
319 .scope
320 .as_ref()
321 .map(|s| s.split(' ').map(String::from).collect()),
322 resource: request.resource.clone(),
323 };
324
325 let scenario = self.determine_exchange_scenario(&context, policy)?;
327 self.validate_exchange_scenario(&scenario, &context, policy)?;
328
329 let response = self
331 .generate_exchanged_token(&context, &request, policy)
332 .await?;
333
334 let exchange_id = uuid::Uuid::new_v4().to_string();
336 let mut exchanges = self.active_exchanges.write().await;
337 exchanges.insert(exchange_id, context);
338
339 Ok(response)
340 }
341
342 async fn validate_subject_token(
344 &self,
345 request: &TokenExchangeRequest,
346 policy: &TokenExchangePolicy,
347 ) -> Result<SecureJwtClaims> {
348 let token_type = self.parse_token_type(&request.subject_token_type)?;
350
351 if !policy.allowed_subject_token_types.contains(&token_type) {
353 return Err(AuthError::auth_method(
354 "token_exchange",
355 "Subject token type not allowed",
356 ));
357 }
358
359 match token_type {
361 TokenType::AccessToken
362 | TokenType::RefreshToken
363 | TokenType::IdToken
364 | TokenType::Jwt => {
365 self.validate_jwt_token(&request.subject_token).await
368 }
369 TokenType::Saml2 | TokenType::Saml1 => {
370 self.validate_saml_token(&request.subject_token, &token_type)
372 .await
373 }
374 }
375 }
376
377 async fn validate_actor_token(
379 &self,
380 actor_token: &str,
381 actor_token_type: &Option<String>,
382 policy: &TokenExchangePolicy,
383 ) -> Result<SecureJwtClaims> {
384 let token_type_str = actor_token_type
385 .as_ref()
386 .ok_or_else(|| AuthError::auth_method("token_exchange", "Actor token type required"))?;
387
388 let token_type = self.parse_token_type(token_type_str)?;
389
390 if !policy.allowed_actor_token_types.contains(&token_type) {
391 return Err(AuthError::auth_method(
392 "token_exchange",
393 "Actor token type not allowed",
394 ));
395 }
396
397 self.validate_jwt_token(actor_token).await
398 }
399
400 async fn validate_jwt_token(&self, token: &str) -> Result<SecureJwtClaims> {
402 let decoding_key = self.jwt_validator.get_decoding_key();
405
406 self.jwt_validator
408 .validate_token(token, &decoding_key, true)
409 .map_err(|e| {
410 AuthError::auth_method("token_exchange", format!("JWT validation failed: {}", e))
411 })
412 }
413
414 async fn validate_saml_token(
416 &self,
417 token: &str,
418 token_type: &TokenType,
419 ) -> Result<SecureJwtClaims> {
420 if token.trim().is_empty() {
422 return Err(AuthError::auth_method(
423 "token_exchange",
424 "Empty SAML token provided",
425 ));
426 }
427
428 let has_saml_markers = token.contains("<saml:")
430 || token.contains("<saml2:")
431 || token.contains("urn:oasis:names:tc:SAML");
432
433 if !has_saml_markers {
434 return Err(AuthError::auth_method(
435 "token_exchange",
436 "Invalid SAML token format - missing SAML namespace markers",
437 ));
438 }
439
440 let saml_claims = SamlClaims {
442 subject: token
443 .find("<saml:NameID")
444 .and_then(|start| {
445 let content_start = token[start..].find('>').map(|pos| start + pos + 1)?;
446 let content_end = token[content_start..]
447 .find("</saml:NameID>")
448 .map(|pos| content_start + pos)?;
449 Some(token[content_start..content_end].trim().to_string())
450 })
451 .unwrap_or_else(|| "saml_subject".to_string()),
452 issuer: token
453 .find("<saml:Issuer")
454 .and_then(|start| {
455 let content_start = token[start..].find('>').map(|pos| start + pos + 1)?;
456 let content_end = token[content_start..]
457 .find("</saml:Issuer>")
458 .map(|pos| content_start + pos)?;
459 Some(token[content_start..content_end].trim().to_string())
460 })
461 .unwrap_or_else(|| "saml_identity_provider".to_string()),
462 audience: token.find("<saml:Audience").and_then(|start| {
463 let content_start = token[start..].find('>').map(|pos| start + pos + 1)?;
464 let content_end = token[content_start..]
465 .find("</saml:Audience>")
466 .map(|pos| content_start + pos)?;
467 Some(token[content_start..content_end].trim().to_string())
468 }),
469 expiry: Some(chrono::Utc::now().timestamp() + 3600), not_before: Some(chrono::Utc::now().timestamp()),
471 session_id: Some(format!("saml_session_{}", uuid::Uuid::new_v4())),
472 scopes: {
473 let mut scopes = Vec::new();
474 if token.contains("emailaddress") {
475 scopes.push("email".to_string());
476 }
477 if token.contains("identity/claims/name") {
478 scopes.push("profile".to_string());
479 }
480 if token.contains("claims/groups") || token.contains("role") {
481 scopes.push("groups".to_string());
482 }
483 if scopes.is_empty() {
484 scopes.push("saml_authenticated".to_string());
485 }
486 scopes
487 },
488 };
489
490 let now = chrono::Utc::now().timestamp();
491 let claims = SecureJwtClaims {
492 iss: saml_claims.issuer,
493 sub: saml_claims.subject,
494 aud: saml_claims
495 .audience
496 .unwrap_or_else(|| "target_audience".to_string()),
497 exp: saml_claims.expiry.unwrap_or(now + 3600), nbf: saml_claims.not_before.unwrap_or(now),
499 iat: now,
500 jti: format!("saml_token_{}", uuid::Uuid::new_v4()),
501 scope: saml_claims.scopes.join(" "),
502 typ: match token_type {
503 TokenType::Saml2 => "urn:ietf:params:oauth:token-type:saml2",
504 TokenType::Saml1 => "urn:ietf:params:oauth:token-type:saml1",
505 _ => "urn:ietf:params:oauth:token-type:saml2",
506 }
507 .to_string(),
508 sid: saml_claims.session_id,
509 client_id: None,
510 auth_ctx_hash: Some(format!("saml_ctx_{}", uuid::Uuid::new_v4())),
511 };
512
513 tracing::info!(
514 "SAML token validation completed - parsed subject: {}, issuer: {}, scopes: {}",
515 claims.sub,
516 claims.iss,
517 claims.scope
518 );
519 Ok(claims)
520 }
521
522 fn parse_token_type(&self, token_type: &str) -> Result<TokenType> {
524 match token_type {
525 "urn:ietf:params:oauth:token-type:access_token" => Ok(TokenType::AccessToken),
526 "urn:ietf:params:oauth:token-type:refresh_token" => Ok(TokenType::RefreshToken),
527 "urn:ietf:params:oauth:token-type:id_token" => Ok(TokenType::IdToken),
528 "urn:ietf:params:oauth:token-type:saml2" => Ok(TokenType::Saml2),
529 "urn:ietf:params:oauth:token-type:saml1" => Ok(TokenType::Saml1),
530 "urn:ietf:params:oauth:token-type:jwt" => Ok(TokenType::Jwt),
531 _ => Err(AuthError::auth_method(
532 "token_exchange",
533 "Unknown token type",
534 )),
535 }
536 }
537
538 fn determine_exchange_scenario(
540 &self,
541 context: &TokenExchangeContext,
542 _policy: &TokenExchangePolicy,
543 ) -> Result<ExchangeScenario> {
544 if context.actor_claims.is_some() {
546 return Ok(ExchangeScenario::OnBehalfOf);
547 }
548
549 if context.audience.is_some()
551 && context.audience.as_ref() != Some(&context.subject_claims.aud)
552 {
553 return Ok(ExchangeScenario::AudienceRestriction);
554 }
555
556 if let Some(requested_scope) = &context.scope {
558 let current_scope: Vec<&str> = context.subject_claims.scope.split(' ').collect();
559 if requested_scope.len() < current_scope.len() {
560 return Ok(ExchangeScenario::ScopeReduction);
561 }
562 }
563
564 Ok(ExchangeScenario::ActingAs)
566 }
567
568 fn validate_exchange_scenario(
570 &self,
571 scenario: &ExchangeScenario,
572 context: &TokenExchangeContext,
573 policy: &TokenExchangePolicy,
574 ) -> Result<()> {
575 if !policy.allowed_scenarios.contains(scenario) {
576 return Err(AuthError::auth_method(
577 "token_exchange",
578 "Exchange scenario not allowed",
579 ));
580 }
581
582 match scenario {
583 ExchangeScenario::OnBehalfOf => {
584 if policy.require_actor_for_delegation && context.actor_claims.is_none() {
585 return Err(AuthError::auth_method(
586 "token_exchange",
587 "Actor token required for delegation",
588 ));
589 }
590 }
591 ExchangeScenario::AudienceRestriction => {
592 if let Some(ref audience) = context.audience
593 && !policy.allowed_audiences.is_empty()
594 && !policy.allowed_audiences.contains(audience)
595 {
596 return Err(AuthError::auth_method(
597 "token_exchange",
598 "Audience not allowed",
599 ));
600 }
601 }
602 _ => {
603 }
605 }
606
607 Ok(())
608 }
609
610 async fn generate_exchanged_token(
612 &self,
613 context: &TokenExchangeContext,
614 request: &TokenExchangeRequest,
615 policy: &TokenExchangePolicy,
616 ) -> Result<TokenExchangeResponse> {
617 let now = Utc::now();
618 let expires_in = policy.max_token_lifetime.num_seconds();
619 let exp = now + policy.max_token_lifetime;
620
621 let mut new_claims = context.subject_claims.clone();
623
624 new_claims.exp = exp.timestamp();
626 new_claims.iat = now.timestamp();
627 new_claims.jti = uuid::Uuid::new_v4().to_string();
628
629 if let Some(ref audience) = request.audience {
631 new_claims.aud = audience.clone();
632 }
633
634 if let Some(ref requested_scope) = request.scope {
636 if let Some(mapped_scopes) = policy.scope_mapping.get(requested_scope) {
637 new_claims.scope = mapped_scopes.join(" ");
638 } else {
639 new_claims.scope = requested_scope.clone();
640 }
641 }
642
643 if let Some(ref actor_claims) = context.actor_claims {
645 new_claims.client_id = Some(actor_claims.sub.clone());
646 }
647
648 let access_token = format!(
650 "exchanged_token_{}_{}",
651 new_claims.jti,
652 URL_SAFE_NO_PAD.encode(&new_claims.sub)
653 );
654
655 let issued_token_type = request
657 .requested_token_type
658 .clone()
659 .unwrap_or_else(|| "urn:ietf:params:oauth:token-type:access_token".to_string());
660
661 Ok(TokenExchangeResponse {
662 access_token,
663 token_type: "Bearer".to_string(),
664 expires_in: Some(expires_in),
665 refresh_token: None, scope: Some(new_claims.scope),
667 issued_token_type: Some(issued_token_type),
668 })
669 }
670
671 fn get_jwt_decoding_key(&self, token: &str) -> Result<jsonwebtoken::DecodingKey> {
673 use jsonwebtoken::DecodingKey;
674
675 let token_parts: Vec<&str> = token.split('.').collect();
677 if token_parts.len() < 2 {
678 return Err(AuthError::InvalidToken("Invalid JWT format".to_string()));
679 }
680
681 let header_b64 = token_parts[0];
682 let header_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
683 .decode(header_b64)
684 .map_err(|_| AuthError::InvalidToken("Invalid JWT header encoding".to_string()))?;
685
686 let header: serde_json::Value = serde_json::from_slice(&header_bytes)
687 .map_err(|_| AuthError::InvalidToken("Invalid JWT header JSON".to_string()))?;
688
689 let algorithm = header
695 .get("alg")
696 .and_then(|a| a.as_str())
697 .unwrap_or("HS256");
698
699 match algorithm {
700 "HS256" => {
701 let secret = std::env::var("JWT_HMAC_SECRET")
703 .unwrap_or_else(|_| "default_hmac_secret_for_development".to_string());
704 Ok(DecodingKey::from_secret(secret.as_bytes()))
705 }
706 "RS256" => {
707 let public_key_pem = std::env::var("JWT_RSA_PUBLIC_KEY")
709 .unwrap_or_else(|_| include_str!("../../../public.pem").to_string());
710 DecodingKey::from_rsa_pem(public_key_pem.as_bytes())
711 .map_err(|e| AuthError::InvalidToken(format!("Invalid RSA key: {}", e)))
712 }
713 _ => {
714 Ok(DecodingKey::from_secret("fallback_secret".as_bytes()))
716 }
717 }
718 }
719}
720
721impl Default for TokenExchangePolicy {
722 fn default() -> Self {
723 Self {
724 allowed_subject_token_types: vec![
725 TokenType::AccessToken,
726 TokenType::RefreshToken,
727 TokenType::IdToken,
728 ],
729 allowed_actor_token_types: vec![TokenType::AccessToken, TokenType::IdToken],
730 allowed_scenarios: vec![
731 ExchangeScenario::ActingAs,
732 ExchangeScenario::OnBehalfOf,
733 ExchangeScenario::AudienceRestriction,
734 ExchangeScenario::ScopeReduction,
735 ],
736 max_token_lifetime: Duration::hours(1),
737 require_actor_for_delegation: true,
738 allowed_audiences: Vec::new(), scope_mapping: HashMap::new(),
740 }
741 }
742}
743
744#[async_trait]
746impl TokenExchangeService for TokenExchangeManager {
747 type Request = (TokenExchangeRequest, String); type Response = TokenExchangeResponse;
749 type Config = SecureJwtValidator; async fn exchange_token(&self, request: Self::Request) -> Result<Self::Response> {
753 let (token_request, client_id) = request;
754 self.exchange_token(token_request, &client_id).await
755 }
756
757 async fn validate_token(&self, token: &str, token_type: &str) -> Result<TokenValidationResult> {
759 let supported_types = self.supported_subject_token_types();
761 ValidationUtils::validate_token_type(token_type, &supported_types)?;
762
763 match self.parse_token_type(token_type)? {
764 TokenType::Jwt | TokenType::AccessToken | TokenType::IdToken => {
765 let decoding_key = self.get_jwt_decoding_key(token)?;
767
768 match self
769 .jwt_validator
770 .validate_token(token, &decoding_key, true)
771 {
772 Ok(claims) => {
773 use chrono::{TimeZone, Utc};
775 let expires_at = Utc.timestamp_opt(claims.exp, 0).single();
776
777 let audience = if claims.aud.is_empty() {
779 Vec::new()
780 } else {
781 vec![claims.aud.clone()]
782 };
783
784 let scopes = if claims.scope.is_empty() {
786 Vec::new()
787 } else {
788 claims
789 .scope
790 .split_whitespace()
791 .map(|s| s.to_string())
792 .collect()
793 };
794
795 let mut metadata = HashMap::new();
797 metadata.insert(
798 "sub".to_string(),
799 serde_json::Value::String(claims.sub.clone()),
800 );
801 metadata.insert(
802 "iss".to_string(),
803 serde_json::Value::String(claims.iss.clone()),
804 );
805 metadata.insert(
806 "aud".to_string(),
807 serde_json::Value::String(claims.aud.clone()),
808 );
809 metadata.insert(
810 "scope".to_string(),
811 serde_json::Value::String(claims.scope.clone()),
812 );
813 metadata.insert(
814 "typ".to_string(),
815 serde_json::Value::String(claims.typ.clone()),
816 );
817 if let Some(ref sid) = claims.sid {
818 metadata
819 .insert("sid".to_string(), serde_json::Value::String(sid.clone()));
820 }
821 if let Some(ref client_id) = claims.client_id {
822 metadata.insert(
823 "client_id".to_string(),
824 serde_json::Value::String(client_id.clone()),
825 );
826 }
827
828 Ok(TokenValidationResult {
829 is_valid: true,
830 subject: Some(claims.sub),
831 issuer: Some(claims.iss),
832 audience,
833 scopes,
834 expires_at,
835 metadata,
836 validation_messages: Vec::new(),
837 })
838 }
839 Err(e) => Ok(TokenValidationResult {
840 is_valid: false,
841 subject: None,
842 issuer: None,
843 audience: Vec::new(),
844 scopes: Vec::new(),
845 expires_at: None,
846 metadata: HashMap::new(),
847 validation_messages: vec![format!("JWT validation failed: {}", e)],
848 }),
849 }
850 }
851 TokenType::Saml2 | TokenType::Saml1 => {
852 Ok(TokenValidationResult {
854 is_valid: true, subject: None, issuer: None, audience: Vec::new(),
858 scopes: Vec::new(),
859 expires_at: None,
860 metadata: HashMap::new(),
861 validation_messages: vec!["SAML validation not fully implemented".to_string()],
862 })
863 }
864 _ => Err(AuthError::InvalidRequest(format!(
865 "Token validation not supported for type: {}",
866 token_type
867 ))),
868 }
869 }
870
871 fn supported_subject_token_types(&self) -> Vec<String> {
873 Self::SUBJECT_TOKEN_TYPES
874 .iter()
875 .map(|s| s.to_string())
876 .collect()
877 }
878
879 fn supported_requested_token_types(&self) -> Vec<String> {
881 Self::REQUESTED_TOKEN_TYPES
882 .iter()
883 .map(|s| s.to_string())
884 .collect()
885 }
886
887 fn capabilities(&self) -> TokenExchangeCapabilities {
889 TokenExchangeCapabilities {
890 basic_exchange: true,
891 multi_party_chains: false,
892 context_preservation: false,
893 audit_trail: false,
894 session_integration: false,
895 jwt_operations: false,
896 policy_control: true,
897 cross_domain_exchange: false,
898 max_delegation_depth: 3,
899 complexity_level: ServiceComplexityLevel::Basic,
900 }
901 }
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907 use crate::security::secure_jwt::SecureJwtConfig;
908
909 fn create_test_manager() -> TokenExchangeManager {
910 let jwt_config = SecureJwtConfig::default();
911 let jwt_validator = SecureJwtValidator::new(jwt_config);
912 TokenExchangeManager::new(jwt_validator)
913 }
914
915 fn create_test_request() -> TokenExchangeRequest {
916 TokenExchangeRequest {
917 grant_type: "urn:ietf:params:oauth:grant-type:token-exchange".to_string(),
918 subject_token: "dummy.jwt.token".to_string(),
919 subject_token_type: "urn:ietf:params:oauth:token-type:access_token".to_string(),
920 actor_token: None,
921 actor_token_type: None,
922 requested_token_type: Some("urn:ietf:params:oauth:token-type:access_token".to_string()),
923 audience: Some("api.example.com".to_string()),
924 scope: Some("read write".to_string()),
925 resource: None,
926 }
927 }
928
929 #[tokio::test]
930 async fn test_token_exchange_manager_creation() {
931 let manager = create_test_manager();
932
933 let policy = TokenExchangePolicy::default();
935 manager
936 .register_policy("test_client".to_string(), policy)
937 .await;
938 }
939
940 #[test]
941 fn test_token_type_parsing() {
942 let manager = create_test_manager();
943
944 assert_eq!(
945 manager
946 .parse_token_type("urn:ietf:params:oauth:token-type:access_token")
947 .unwrap(),
948 TokenType::AccessToken
949 );
950
951 assert_eq!(
952 manager
953 .parse_token_type("urn:ietf:params:oauth:token-type:id_token")
954 .unwrap(),
955 TokenType::IdToken
956 );
957
958 assert!(manager.parse_token_type("invalid_token_type").is_err());
959 }
960
961 #[test]
962 fn test_exchange_scenario_determination() {
963 let manager = create_test_manager();
964 let policy = TokenExchangePolicy::default();
965
966 let context = TokenExchangeContext {
968 subject_claims: SecureJwtClaims {
969 sub: "user123".to_string(),
970 iss: "auth.example.com".to_string(),
971 aud: "api.example.com".to_string(),
972 exp: chrono::Utc::now().timestamp() + 3600,
973 nbf: chrono::Utc::now().timestamp(),
974 iat: chrono::Utc::now().timestamp(),
975 jti: "token123".to_string(),
976 scope: "read write".to_string(),
977 typ: "access".to_string(),
978 sid: None,
979 client_id: None,
980 auth_ctx_hash: None,
981 },
982 actor_claims: None,
983 client_id: "test_client".to_string(),
984 audience: Some("different.api.com".to_string()),
985 scope: None,
986 resource: None,
987 };
988
989 let scenario = manager
990 .determine_exchange_scenario(&context, &policy)
991 .unwrap();
992 assert_eq!(scenario, ExchangeScenario::AudienceRestriction);
993 }
994
995 #[tokio::test]
996 async fn test_invalid_grant_type() {
997 let manager = create_test_manager();
998 let policy = TokenExchangePolicy::default();
999 manager
1000 .register_policy("test_client".to_string(), policy)
1001 .await;
1002
1003 let mut request = create_test_request();
1004 request.grant_type = "invalid_grant_type".to_string();
1005
1006 let result = manager.exchange_token(request, "test_client").await;
1007 assert!(result.is_err());
1008 }
1009}
1010
1011