Skip to main content

auth_framework/server/token_exchange/
core.rs

1//! OAuth 2.0 Token Exchange (RFC 8693) - Basic Implementation
2//!
3//! This module implements RFC 8693, which defines a protocol for exchanging
4//! one security token for another, enabling delegation and acting-as scenarios.
5//!
6//! This is the **basic** implementation suitable for simple token exchange scenarios.
7//! For enterprise-grade features like multi-party chains, audit trails, and session
8//! integration, use `AdvancedTokenExchangeManager` instead.
9//!
10//! ## When to Use This Manager
11//!
12//! Use `TokenExchangeManager` when you need:
13//! - Simple RFC 8693 compliant token exchange
14//! - Lightweight implementation with minimal dependencies
15//! - Basic delegation scenarios (OnBehalfOf, ActingAs)
16//! - Client-specific policies
17//! - Standard token validation (JWT, SAML)
18//!
19//! ## When to Use Advanced Manager
20//!
21//! Use `AdvancedTokenExchangeManager` when you need:
22//! - Multi-party delegation chains
23//! - Context preservation across exchanges
24//! - Comprehensive audit trails
25//! - Session integration and step-up authentication
26//! - Policy-driven exchange control
27//! - Cross-domain exchanges
28//! - JWT cryptographic operations
29//!
30//! ## Example Usage
31//!
32//! ```rust,no_run
33//! use auth_framework::server::token_exchange::{TokenExchangeManager, TokenExchangeRequest};
34//! use auth_framework::{SecureJwtValidator, SecureJwtConfig};
35//!
36//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
37//! // Default generates a cryptographically random per-instance secret.
38//! // For stable keys shared across nodes, set `jwt_secret` explicitly.
39//! let jwt_validator = SecureJwtValidator::new(SecureJwtConfig {
40//!     jwt_secret: std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"),
41//!     ..SecureJwtConfig::default()
42//! })?;
43//! let mut manager = TokenExchangeManager::new(jwt_validator);
44//!
45//! let request = TokenExchangeRequest {
46//!     grant_type: "urn:ietf:params:oauth:grant-type:token-exchange".to_string(),
47//!     subject_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...".to_string(),
48//!     subject_token_type: "urn:ietf:params:oauth:token-type:jwt".to_string(),
49//!     requested_token_type: Some("urn:ietf:params:oauth:token-type:access_token".to_string()),
50//!     // ... other fields
51//!     # actor_token: None,
52//!     # actor_token_type: None,
53//!     # audience: None,
54//!     # scope: None,
55//!     # resource: None,
56//! };
57//!
58//! let response = manager.exchange_token(request, "client_123").await?;
59//! # Ok(())
60//! # }
61//! ```
62
63use 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/// Token Exchange Request (RFC 8693)
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct TokenExchangeRequest {
81    /// Grant type (must be "urn:ietf:params:oauth:grant-type:token-exchange")
82    pub grant_type: String,
83
84    /// Security token to be exchanged
85    pub subject_token: String,
86
87    /// Type of the subject token
88    pub subject_token_type: String,
89
90    /// Optional actor token (for delegation scenarios)
91    pub actor_token: Option<String>,
92
93    /// Type of the actor token
94    pub actor_token_type: Option<String>,
95
96    /// Type of the requested security token
97    pub requested_token_type: Option<String>,
98
99    /// Intended audience for the requested token
100    pub audience: Option<String>,
101
102    /// Scope of the access token
103    pub scope: Option<String>,
104
105    /// Requested resource for the exchanged token
106    pub resource: Option<String>,
107}
108
109/// Token Exchange Response (RFC 8693)
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct TokenExchangeResponse {
112    /// The security token issued by the authorization server
113    pub access_token: String,
114
115    /// Token type (typically "Bearer")
116    pub token_type: String,
117
118    /// Expires in seconds
119    pub expires_in: Option<i64>,
120
121    /// Refresh token (optional)
122    pub refresh_token: Option<String>,
123
124    /// Scope of the access token
125    pub scope: Option<String>,
126
127    /// Type of the issued token
128    pub issued_token_type: Option<String>,
129}
130
131/// Token types defined in RFC 8693
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub enum TokenType {
134    /// JWT access token
135    #[serde(rename = "urn:ietf:params:oauth:token-type:access_token")]
136    AccessToken,
137
138    /// JWT refresh token
139    #[serde(rename = "urn:ietf:params:oauth:token-type:refresh_token")]
140    RefreshToken,
141
142    /// OIDC ID token
143    #[serde(rename = "urn:ietf:params:oauth:token-type:id_token")]
144    IdToken,
145
146    /// SAML 2.0 assertion
147    #[serde(rename = "urn:ietf:params:oauth:token-type:saml2")]
148    Saml2,
149
150    /// SAML 1.1 assertion
151    #[serde(rename = "urn:ietf:params:oauth:token-type:saml1")]
152    Saml1,
153
154    /// JWT token (generic)
155    #[serde(rename = "urn:ietf:params:oauth:token-type:jwt")]
156    Jwt,
157}
158
159/// Token exchange context for validation
160#[derive(Debug, Clone)]
161pub struct TokenExchangeContext {
162    /// Subject token claims
163    pub subject_claims: SecureJwtClaims,
164
165    /// Actor token claims (if present)
166    pub actor_claims: Option<SecureJwtClaims>,
167
168    /// Client identifier
169    pub client_id: String,
170
171    /// Requested audience
172    pub audience: Option<String>,
173
174    /// Requested scope
175    pub scope: Option<Vec<String>>,
176
177    /// Resource parameter
178    pub resource: Option<String>,
179}
180
181/// Token exchange policy
182#[derive(Debug, Clone)]
183pub struct TokenExchangePolicy {
184    /// Allowed subject token types
185    pub allowed_subject_token_types: Vec<TokenType>,
186
187    /// Allowed actor token types
188    pub allowed_actor_token_types: Vec<TokenType>,
189
190    /// Allowed token exchange scenarios
191    pub allowed_scenarios: Vec<ExchangeScenario>,
192
193    /// Maximum token lifetime for exchanged tokens
194    pub max_token_lifetime: Duration,
195
196    /// Whether to require actor tokens for delegation
197    pub require_actor_for_delegation: bool,
198
199    /// Allowed audience values
200    pub allowed_audiences: Vec<String>,
201
202    /// Scope mapping rules
203    pub scope_mapping: HashMap<String, Vec<String>>,
204}
205
206impl TokenExchangePolicy {
207    /// Create a builder for `TokenExchangePolicy` starting from defaults.
208    ///
209    /// # Example
210    /// ```rust
211    /// use auth_framework::server::token_exchange::TokenExchangePolicy;
212    ///
213    /// let policy = TokenExchangePolicy::builder()
214    ///     .max_token_lifetime(chrono::Duration::minutes(30))
215    ///     .require_actor_for_delegation(false)
216    ///     .audience("api.example.com")
217    ///     .build();
218    /// ```
219    pub fn builder() -> TokenExchangePolicyBuilder {
220        TokenExchangePolicyBuilder {
221            inner: Self::default(),
222        }
223    }
224
225    /// Preset: JWT-only token exchange policy.
226    ///
227    /// Only allows JWT and access-token subject types, JWT requested types,
228    /// and delegation/audience-restriction scenarios.
229    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
245/// Builder for [`TokenExchangePolicy`].
246pub struct TokenExchangePolicyBuilder {
247    inner: TokenExchangePolicy,
248}
249
250impl TokenExchangePolicyBuilder {
251    /// Set the allowed subject token types.
252    pub fn subject_token_types(mut self, types: Vec<TokenType>) -> Self {
253        self.inner.allowed_subject_token_types = types;
254        self
255    }
256
257    /// Set the allowed actor token types.
258    pub fn actor_token_types(mut self, types: Vec<TokenType>) -> Self {
259        self.inner.allowed_actor_token_types = types;
260        self
261    }
262
263    /// Set the allowed exchange scenarios.
264    pub fn scenarios(mut self, scenarios: Vec<ExchangeScenario>) -> Self {
265        self.inner.allowed_scenarios = scenarios;
266        self
267    }
268
269    /// Set the maximum token lifetime for exchanged tokens.
270    pub fn max_token_lifetime(mut self, lifetime: Duration) -> Self {
271        self.inner.max_token_lifetime = lifetime;
272        self
273    }
274
275    /// Set whether actor tokens are required for delegation.
276    pub fn require_actor_for_delegation(mut self, required: bool) -> Self {
277        self.inner.require_actor_for_delegation = required;
278        self
279    }
280
281    /// Add a single allowed audience.
282    pub fn audience(mut self, aud: impl Into<String>) -> Self {
283        self.inner.allowed_audiences.push(aud.into());
284        self
285    }
286
287    /// Set all allowed audiences.
288    pub fn audiences(mut self, auds: Vec<String>) -> Self {
289        self.inner.allowed_audiences = auds;
290        self
291    }
292
293    /// Add a scope mapping entry (source scope → allowed target scopes).
294    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    /// Build the [`TokenExchangePolicy`].
300    pub fn build(self) -> TokenExchangePolicy {
301        self.inner
302    }
303}
304
305/// Token exchange scenarios
306#[derive(Debug, Clone, PartialEq, Eq)]
307pub enum ExchangeScenario {
308    /// Acting as the subject (impersonation)
309    ActingAs,
310
311    /// Acting on behalf of the subject (delegation)
312    OnBehalfOf,
313
314    /// Token format conversion
315    TokenConversion,
316
317    /// Audience restriction
318    AudienceRestriction,
319
320    /// Scope reduction
321    ScopeReduction,
322}
323
324/// Token Exchange Manager
325pub struct TokenExchangeManager {
326    /// JWT validator for token validation
327    jwt_validator: SecureJwtValidator,
328
329    /// Token exchange policies
330    policies: tokio::sync::RwLock<HashMap<String, TokenExchangePolicy>>,
331
332    /// Active exchanges for tracking
333    active_exchanges: tokio::sync::RwLock<HashMap<String, TokenExchangeContext>>,
334}
335
336impl TokenExchangeManager {
337    /// Supported subject token types
338    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    /// Supported requested token types
347    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    /// Create a new token exchange manager
354    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    /// Register a token exchange policy for a client
363    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    /// Process a token exchange request
369    pub async fn exchange_token(
370        &self,
371        request: TokenExchangeRequest,
372        client_id: &str,
373    ) -> Result<TokenExchangeResponse> {
374        // Validate grant type
375        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        // Get client policy
383        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        // Validate and parse subject token
389        let subject_claims = self.validate_subject_token(&request, policy).await?;
390
391        // Validate and parse actor token (if present)
392        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        // Create exchange context
402        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        // Validate exchange scenario
415        let scenario = self.determine_exchange_scenario(&context, policy)?;
416        self.validate_exchange_scenario(&scenario, &context, policy)?;
417
418        // Generate new token
419        let response = self
420            .generate_exchanged_token(&context, &request, policy)
421            .await?;
422
423        // Track the exchange
424        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    /// Validate subject token
432    async fn validate_subject_token(
433        &self,
434        request: &TokenExchangeRequest,
435        policy: &TokenExchangePolicy,
436    ) -> Result<SecureJwtClaims> {
437        // Parse token type
438        let token_type = self.parse_token_type(&request.subject_token_type)?;
439
440        // Check if token type is allowed
441        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        // Validate JWT token (simplified - would need different validation for different types)
449        match token_type {
450            TokenType::AccessToken
451            | TokenType::RefreshToken
452            | TokenType::IdToken
453            | TokenType::Jwt => {
454                // For JWT tokens, validate using JWT validator
455                // In a real implementation, you'd need appropriate decoding keys
456                self.validate_jwt_token(&request.subject_token).await
457            }
458            TokenType::Saml2 | TokenType::Saml1 => {
459                // For SAML tokens, perform basic validation
460                self.validate_saml_token(&request.subject_token, &token_type)
461                    .await
462            }
463        }
464    }
465
466    /// Validate actor token
467    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    /// Validate JWT token using SECURE cryptographic verification
490    async fn validate_jwt_token(&self, token: &str) -> Result<SecureJwtClaims> {
491        // Delegate entirely to SecureJwtValidator::validate which handles
492        // algorithm allow-list enforcement, key selection, and full claims validation.
493        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(&timestamp).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    /// Validate SAML token structure and basic properties
742    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    /// Parse token type from string
760    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    /// Determine exchange scenario based on context
776    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    /// Generate exchanged token
842    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        // Create new claims based on exchange scenario
853        let mut new_claims = context.subject_claims.clone();
854
855        // Update expiration
856        new_claims.exp = exp.timestamp();
857        new_claims.iat = now.timestamp();
858        new_claims.jti = uuid::Uuid::new_v4().to_string();
859
860        // Update audience if specified
861        if let Some(ref audience) = request.audience {
862            new_claims.aud = audience.clone();
863        }
864
865        // Update scope if specified and apply mapping
866        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        // Add actor information for delegation
875        if let Some(ref actor_claims) = context.actor_claims {
876            new_claims.client_id = Some(actor_claims.sub.clone());
877        }
878
879        // Generate a proper HMAC-signed JWT from the exchange claims
880        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        // Determine issued token type
888        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, // Could generate refresh token in some scenarios
898            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(), // Empty means all audiences allowed
922            scope_mapping: HashMap::new(),
923        }
924    }
925}
926
927/// Implementation of the common TokenExchangeService trait
928#[async_trait]
929impl TokenExchangeService for TokenExchangeManager {
930    type Request = (TokenExchangeRequest, String); // Request + client_id
931    type Response = TokenExchangeResponse;
932    type Config = SecureJwtValidator; // Configuration is the JWT validator
933
934    /// Exchange a token following RFC 8693 (basic implementation)
935    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    /// Validate a token using the internal JWT validator
941    async fn validate_token(&self, token: &str, token_type: &str) -> Result<TokenValidationResult> {
942        // Use shared validation utilities
943        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                // Use the secure validate path which handles algorithm checking
949                // and key selection internally — no caller-supplied key needed.
950                match self.jwt_validator.validate(token) {
951                    Ok(claims) => {
952                        // Convert timestamp to DateTime
953                        use chrono::{TimeZone, Utc};
954                        let expires_at = Utc.timestamp_opt(claims.exp, 0).single();
955
956                        // Convert audience string to vector
957                        let audience = if claims.aud.is_empty() {
958                            Vec::new()
959                        } else {
960                            vec![claims.aud.clone()]
961                        };
962
963                        // Extract scopes from scope string
964                        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                        // Create metadata from available claims
975                        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    /// Get supported subject token types
1110    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    /// Get supported requested token types
1118    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    /// Get service capabilities
1126    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        // Register a policy
1172        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        // Test audience restriction scenario
1205        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        // Builder starts from default so subject types are populated
1263        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}