use crate::errors::{AuthError, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[async_trait]
pub trait TokenExchangeService: Send + Sync {
type Request: Send + Sync;
type Response: Send + Sync;
type Config: Send + Sync;
async fn exchange_token(&self, request: Self::Request) -> Result<Self::Response>;
async fn validate_token(&self, token: &str, token_type: &str) -> Result<TokenValidationResult>;
fn supported_subject_token_types(&self) -> Vec<String>;
fn supported_requested_token_types(&self) -> Vec<String>;
fn capabilities(&self) -> TokenExchangeCapabilities;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenValidationResult {
pub is_valid: bool,
pub subject: Option<String>,
pub issuer: Option<String>,
pub audience: Vec<String>,
pub scopes: Vec<String>,
pub expires_at: Option<DateTime<Utc>>,
pub metadata: HashMap<String, serde_json::Value>,
pub validation_messages: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenExchangeCapabilities {
pub basic_exchange: bool,
pub multi_party_chains: bool,
pub context_preservation: bool,
pub audit_trail: bool,
pub session_integration: bool,
pub jwt_operations: bool,
pub policy_control: bool,
pub cross_domain_exchange: bool,
pub max_delegation_depth: usize,
pub complexity_level: ServiceComplexityLevel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ServiceComplexityLevel {
Basic,
Advanced,
}
pub struct ValidationUtils;
impl ValidationUtils {
pub fn validate_grant_type(grant_type: &str) -> Result<()> {
if grant_type != "urn:ietf:params:oauth:grant-type:token-exchange" {
return Err(AuthError::InvalidRequest(
"Invalid grant type for token exchange".to_string(),
));
}
Ok(())
}
pub fn validate_token_type(token_type: &str, supported_types: &[String]) -> Result<()> {
if !supported_types.contains(&token_type.to_string()) {
return Err(AuthError::InvalidRequest(format!(
"Unsupported token type: {}",
token_type
)));
}
Ok(())
}
pub fn extract_subject(metadata: &HashMap<String, serde_json::Value>) -> Option<String> {
metadata
.get("sub")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
}
pub fn extract_scopes(
metadata: &HashMap<String, serde_json::Value>,
scope_string: Option<&str>,
) -> Vec<String> {
if let Some(scopes) = metadata.get("scope").or_else(|| metadata.get("scopes")) {
if let Some(scope_str) = scopes.as_str() {
return scope_str
.split_whitespace()
.map(|s| s.to_string())
.collect();
} else if let Some(scope_array) = scopes.as_array() {
return scope_array
.iter()
.filter_map(|v| v.as_str())
.map(|s| s.to_string())
.collect();
}
}
scope_string
.map(|s| {
s.split_whitespace()
.map(|scope| scope.to_string())
.collect()
})
.unwrap_or_default()
}
pub fn validate_delegation_depth(current_depth: usize, max_depth: usize) -> Result<()> {
if current_depth > max_depth {
return Err(AuthError::InvalidRequest(
"Maximum delegation depth exceeded".to_string(),
));
}
Ok(())
}
pub fn normalize_token_type(token_type: &str) -> String {
match token_type {
"jwt" => "urn:ietf:params:oauth:token-type:jwt".to_string(),
"access_token" => "urn:ietf:params:oauth:token-type:access_token".to_string(),
"refresh_token" => "urn:ietf:params:oauth:token-type:refresh_token".to_string(),
"id_token" => "urn:ietf:params:oauth:token-type:id_token".to_string(),
"saml2" => "urn:ietf:params:oauth:token-type:saml2".to_string(),
_ => token_type.to_string(),
}
}
pub fn is_jwt_token_type(token_type: &str) -> bool {
matches!(
token_type,
"urn:ietf:params:oauth:token-type:jwt"
| "urn:ietf:params:oauth:token-type:access_token"
| "urn:ietf:params:oauth:token-type:id_token"
)
}
pub fn validate_scope_requirements(
requested_scopes: &[String],
available_scopes: &[String],
require_all: bool,
) -> Result<()> {
if require_all {
for scope in requested_scopes {
if !available_scopes.contains(scope) {
return Err(AuthError::InvalidRequest(format!(
"Required scope not available: {}",
scope
)));
}
}
} else {
let has_any = requested_scopes
.iter()
.any(|scope| available_scopes.contains(scope));
if !has_any && !requested_scopes.is_empty() {
return Err(AuthError::InvalidRequest(
"None of the requested scopes are available".to_string(),
));
}
}
Ok(())
}
}
pub struct TokenExchangeFactory;
impl TokenExchangeFactory {
pub async fn create_manager(
requirements: &ExchangeRequirements,
) -> Result<Box<dyn TokenExchangeService<Request = (), Response = (), Config = ()>>> {
let complexity = Self::determine_manager_type(requirements);
match complexity {
ServiceComplexityLevel::Advanced => Err(AuthError::InvalidRequest(
"Use TokenExchangeFactory::create_advanced_manager() for advanced requirements"
.to_string(),
)),
ServiceComplexityLevel::Basic => Err(AuthError::InvalidRequest(
"Use TokenExchangeFactory::create_basic_manager() for basic requirements"
.to_string(),
)),
}
}
pub fn determine_manager_type(requirements: &ExchangeRequirements) -> ServiceComplexityLevel {
if requirements.needs_audit_trail
|| requirements.needs_session_integration
|| requirements.needs_context_preservation
|| requirements.needs_multi_party_chains
|| requirements.needs_jwt_operations
|| requirements.needs_policy_control
|| requirements.needs_cross_domain
|| requirements.max_delegation_depth > 3
{
ServiceComplexityLevel::Advanced
} else {
ServiceComplexityLevel::Basic
}
}
pub fn get_recommended_config(use_case: &TokenExchangeUseCase) -> ExchangeRequirements {
match use_case {
TokenExchangeUseCase::SimpleServiceToService => ExchangeRequirements {
needs_audit_trail: false,
needs_session_integration: false,
needs_context_preservation: false,
needs_multi_party_chains: false,
needs_jwt_operations: false,
needs_policy_control: false,
needs_cross_domain: false,
max_delegation_depth: 1,
},
TokenExchangeUseCase::MicroserviceChain => ExchangeRequirements {
needs_audit_trail: true,
needs_session_integration: false,
needs_context_preservation: true,
needs_multi_party_chains: true,
needs_jwt_operations: false,
needs_policy_control: true,
needs_cross_domain: false,
max_delegation_depth: 5,
},
TokenExchangeUseCase::EnterpriseIntegration => ExchangeRequirements {
needs_audit_trail: true,
needs_session_integration: true,
needs_context_preservation: true,
needs_multi_party_chains: true,
needs_jwt_operations: true,
needs_policy_control: true,
needs_cross_domain: true,
max_delegation_depth: 10,
},
TokenExchangeUseCase::CrossDomainFederation => ExchangeRequirements {
needs_audit_trail: true,
needs_session_integration: false,
needs_context_preservation: true,
needs_multi_party_chains: false,
needs_jwt_operations: true,
needs_policy_control: true,
needs_cross_domain: true,
max_delegation_depth: 3,
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExchangeRequirements {
pub needs_audit_trail: bool,
pub needs_session_integration: bool,
pub needs_context_preservation: bool,
pub needs_multi_party_chains: bool,
pub needs_jwt_operations: bool,
pub needs_policy_control: bool,
pub needs_cross_domain: bool,
pub max_delegation_depth: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TokenExchangeUseCase {
SimpleServiceToService,
MicroserviceChain,
EnterpriseIntegration,
CrossDomainFederation,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_utils_grant_type() {
assert!(
ValidationUtils::validate_grant_type("urn:ietf:params:oauth:grant-type:token-exchange")
.is_ok()
);
assert!(ValidationUtils::validate_grant_type("authorization_code").is_err());
}
#[test]
fn test_validation_utils_token_type() {
let supported = vec!["urn:ietf:params:oauth:token-type:jwt".to_string()];
assert!(
ValidationUtils::validate_token_type(
"urn:ietf:params:oauth:token-type:jwt",
&supported
)
.is_ok()
);
assert!(ValidationUtils::validate_token_type("unsupported", &supported).is_err());
}
#[test]
fn test_extract_scopes() {
let mut metadata = HashMap::new();
metadata.insert(
"scope".to_string(),
serde_json::Value::String("read write".to_string()),
);
let scopes = ValidationUtils::extract_scopes(&metadata, None);
assert_eq!(scopes, vec!["read", "write"]);
let scopes = ValidationUtils::extract_scopes(&HashMap::new(), Some("admin user"));
assert_eq!(scopes, vec!["admin", "user"]);
}
#[test]
fn test_normalize_token_type() {
assert_eq!(
ValidationUtils::normalize_token_type("jwt"),
"urn:ietf:params:oauth:token-type:jwt"
);
assert_eq!(
ValidationUtils::normalize_token_type("urn:ietf:params:oauth:token-type:jwt"),
"urn:ietf:params:oauth:token-type:jwt"
);
}
#[test]
fn test_factory_manager_type_determination() {
let simple_req = ExchangeRequirements {
needs_audit_trail: false,
needs_session_integration: false,
needs_context_preservation: false,
needs_multi_party_chains: false,
needs_jwt_operations: false,
needs_policy_control: false,
needs_cross_domain: false,
max_delegation_depth: 1,
};
assert_eq!(
TokenExchangeFactory::determine_manager_type(&simple_req),
ServiceComplexityLevel::Basic
);
let complex_req = ExchangeRequirements {
needs_audit_trail: true,
needs_session_integration: true,
needs_context_preservation: true,
needs_multi_party_chains: true,
needs_jwt_operations: true,
needs_policy_control: true,
needs_cross_domain: true,
max_delegation_depth: 10,
};
assert_eq!(
TokenExchangeFactory::determine_manager_type(&complex_req),
ServiceComplexityLevel::Advanced
);
}
#[test]
fn test_use_case_recommendations() {
let simple_config = TokenExchangeFactory::get_recommended_config(
&TokenExchangeUseCase::SimpleServiceToService,
);
assert!(!simple_config.needs_audit_trail);
assert_eq!(simple_config.max_delegation_depth, 1);
let enterprise_config = TokenExchangeFactory::get_recommended_config(
&TokenExchangeUseCase::EnterpriseIntegration,
);
assert!(enterprise_config.needs_audit_trail);
assert!(enterprise_config.needs_session_integration);
assert_eq!(enterprise_config.max_delegation_depth, 10);
}
}