auth_framework/server/token_exchange/
token_exchange_common.rs1use crate::errors::{AuthError, Result};
14use async_trait::async_trait;
15use chrono::{DateTime, Utc};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18
19#[async_trait]
21pub trait TokenExchangeService: Send + Sync {
22 type Request: Send + Sync;
24
25 type Response: Send + Sync;
27
28 type Config: Send + Sync;
30
31 async fn exchange_token(&self, request: Self::Request) -> Result<Self::Response>;
33
34 async fn validate_token(&self, token: &str, token_type: &str) -> Result<TokenValidationResult>;
36
37 fn supported_subject_token_types(&self) -> Vec<String>;
39
40 fn supported_requested_token_types(&self) -> Vec<String>;
42 fn capabilities(&self) -> TokenExchangeCapabilities;
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct TokenValidationResult {
49 pub is_valid: bool,
51
52 pub subject: Option<String>,
54
55 pub issuer: Option<String>,
57
58 pub audience: Vec<String>,
60
61 pub scopes: Vec<String>,
63
64 pub expires_at: Option<DateTime<Utc>>,
66
67 pub metadata: HashMap<String, serde_json::Value>,
69
70 pub validation_messages: Vec<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct TokenExchangeCapabilities {
77 pub basic_exchange: bool,
79
80 pub multi_party_chains: bool,
82
83 pub context_preservation: bool,
85
86 pub audit_trail: bool,
88
89 pub session_integration: bool,
91
92 pub jwt_operations: bool,
94
95 pub policy_control: bool,
97
98 pub cross_domain_exchange: bool,
100
101 pub max_delegation_depth: usize,
103
104 pub complexity_level: ServiceComplexityLevel,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110pub enum ServiceComplexityLevel {
111 Basic,
113 Advanced,
115}
116
117pub struct ValidationUtils;
119
120impl ValidationUtils {
121 pub fn validate_grant_type(grant_type: &str) -> Result<()> {
123 if grant_type != "urn:ietf:params:oauth:grant-type:token-exchange" {
124 return Err(AuthError::InvalidRequest(
125 "Invalid grant type for token exchange".to_string(),
126 ));
127 }
128 Ok(())
129 }
130
131 pub fn validate_token_type(token_type: &str, supported_types: &[String]) -> Result<()> {
133 if !supported_types.contains(&token_type.to_string()) {
134 return Err(AuthError::InvalidRequest(format!(
135 "Unsupported token type: {}",
136 token_type
137 )));
138 }
139 Ok(())
140 }
141
142 pub fn extract_subject(metadata: &HashMap<String, serde_json::Value>) -> Option<String> {
144 metadata
145 .get("sub")
146 .and_then(|v| v.as_str())
147 .map(|s| s.to_string())
148 }
149
150 pub fn extract_scopes(
152 metadata: &HashMap<String, serde_json::Value>,
153 scope_string: Option<&str>,
154 ) -> Vec<String> {
155 if let Some(scopes) = metadata.get("scope").or_else(|| metadata.get("scopes")) {
157 if let Some(scope_str) = scopes.as_str() {
158 return scope_str
159 .split_whitespace()
160 .map(|s| s.to_string())
161 .collect();
162 } else if let Some(scope_array) = scopes.as_array() {
163 return scope_array
164 .iter()
165 .filter_map(|v| v.as_str())
166 .map(|s| s.to_string())
167 .collect();
168 }
169 }
170
171 scope_string
173 .map(|s| {
174 s.split_whitespace()
175 .map(|scope| scope.to_string())
176 .collect()
177 })
178 .unwrap_or_default()
179 }
180
181 pub fn validate_delegation_depth(current_depth: usize, max_depth: usize) -> Result<()> {
183 if current_depth > max_depth {
184 return Err(AuthError::InvalidRequest(
185 "Maximum delegation depth exceeded".to_string(),
186 ));
187 }
188 Ok(())
189 }
190
191 pub fn normalize_token_type(token_type: &str) -> String {
193 match token_type {
194 "jwt" => "urn:ietf:params:oauth:token-type:jwt".to_string(),
195 "access_token" => "urn:ietf:params:oauth:token-type:access_token".to_string(),
196 "refresh_token" => "urn:ietf:params:oauth:token-type:refresh_token".to_string(),
197 "id_token" => "urn:ietf:params:oauth:token-type:id_token".to_string(),
198 "saml2" => "urn:ietf:params:oauth:token-type:saml2".to_string(),
199 _ => token_type.to_string(),
200 }
201 }
202
203 pub fn is_jwt_token_type(token_type: &str) -> bool {
205 matches!(
206 token_type,
207 "urn:ietf:params:oauth:token-type:jwt"
208 | "urn:ietf:params:oauth:token-type:access_token"
209 | "urn:ietf:params:oauth:token-type:id_token"
210 )
211 }
212
213 pub fn validate_scope_requirements(
215 requested_scopes: &[String],
216 available_scopes: &[String],
217 require_all: bool,
218 ) -> Result<()> {
219 if require_all {
220 for scope in requested_scopes {
221 if !available_scopes.contains(scope) {
222 return Err(AuthError::InvalidRequest(format!(
223 "Required scope not available: {}",
224 scope
225 )));
226 }
227 }
228 } else {
229 let has_any = requested_scopes
230 .iter()
231 .any(|scope| available_scopes.contains(scope));
232 if !has_any && !requested_scopes.is_empty() {
233 return Err(AuthError::InvalidRequest(
234 "None of the requested scopes are available".to_string(),
235 ));
236 }
237 }
238 Ok(())
239 }
240}
241
242pub struct TokenExchangeFactory;
244
245impl TokenExchangeFactory {
246 pub async fn create_manager(
248 requirements: &ExchangeRequirements,
249 ) -> Result<Box<dyn TokenExchangeService<Request = (), Response = (), Config = ()>>> {
250 let complexity = Self::determine_manager_type(requirements);
252
253 match complexity {
256 ServiceComplexityLevel::Advanced => Err(AuthError::InvalidRequest(
257 "Use TokenExchangeFactory::create_advanced_manager() for advanced requirements"
258 .to_string(),
259 )),
260 ServiceComplexityLevel::Basic => Err(AuthError::InvalidRequest(
261 "Use TokenExchangeFactory::create_basic_manager() for basic requirements"
262 .to_string(),
263 )),
264 }
265 }
266
267 pub fn determine_manager_type(requirements: &ExchangeRequirements) -> ServiceComplexityLevel {
269 if requirements.needs_audit_trail
271 || requirements.needs_session_integration
272 || requirements.needs_context_preservation
273 || requirements.needs_multi_party_chains
274 || requirements.needs_jwt_operations
275 || requirements.needs_policy_control
276 || requirements.needs_cross_domain
277 || requirements.max_delegation_depth > 3
278 {
279 ServiceComplexityLevel::Advanced
280 } else {
281 ServiceComplexityLevel::Basic
282 }
283 }
284
285 pub fn get_recommended_config(use_case: &TokenExchangeUseCase) -> ExchangeRequirements {
287 match use_case {
288 TokenExchangeUseCase::SimpleServiceToService => ExchangeRequirements {
289 needs_audit_trail: false,
290 needs_session_integration: false,
291 needs_context_preservation: false,
292 needs_multi_party_chains: false,
293 needs_jwt_operations: false,
294 needs_policy_control: false,
295 needs_cross_domain: false,
296 max_delegation_depth: 1,
297 },
298 TokenExchangeUseCase::MicroserviceChain => ExchangeRequirements {
299 needs_audit_trail: true,
300 needs_session_integration: false,
301 needs_context_preservation: true,
302 needs_multi_party_chains: true,
303 needs_jwt_operations: false,
304 needs_policy_control: true,
305 needs_cross_domain: false,
306 max_delegation_depth: 5,
307 },
308 TokenExchangeUseCase::EnterpriseIntegration => ExchangeRequirements {
309 needs_audit_trail: true,
310 needs_session_integration: true,
311 needs_context_preservation: true,
312 needs_multi_party_chains: true,
313 needs_jwt_operations: true,
314 needs_policy_control: true,
315 needs_cross_domain: true,
316 max_delegation_depth: 10,
317 },
318 TokenExchangeUseCase::CrossDomainFederation => ExchangeRequirements {
319 needs_audit_trail: true,
320 needs_session_integration: false,
321 needs_context_preservation: true,
322 needs_multi_party_chains: false,
323 needs_jwt_operations: true,
324 needs_policy_control: true,
325 needs_cross_domain: true,
326 max_delegation_depth: 3,
327 },
328 }
329 }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct ExchangeRequirements {
335 pub needs_audit_trail: bool,
337
338 pub needs_session_integration: bool,
340
341 pub needs_context_preservation: bool,
343
344 pub needs_multi_party_chains: bool,
346
347 pub needs_jwt_operations: bool,
349
350 pub needs_policy_control: bool,
352
353 pub needs_cross_domain: bool,
355
356 pub max_delegation_depth: usize,
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
362pub enum TokenExchangeUseCase {
363 SimpleServiceToService,
365
366 MicroserviceChain,
368
369 EnterpriseIntegration,
371
372 CrossDomainFederation,
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn test_validation_utils_grant_type() {
382 assert!(
384 ValidationUtils::validate_grant_type("urn:ietf:params:oauth:grant-type:token-exchange")
385 .is_ok()
386 );
387
388 assert!(ValidationUtils::validate_grant_type("authorization_code").is_err());
390 }
391
392 #[test]
393 fn test_validation_utils_token_type() {
394 let supported = vec!["urn:ietf:params:oauth:token-type:jwt".to_string()];
395
396 assert!(
397 ValidationUtils::validate_token_type(
398 "urn:ietf:params:oauth:token-type:jwt",
399 &supported
400 )
401 .is_ok()
402 );
403
404 assert!(ValidationUtils::validate_token_type("unsupported", &supported).is_err());
405 }
406
407 #[test]
408 fn test_extract_scopes() {
409 let mut metadata = HashMap::new();
410 metadata.insert(
411 "scope".to_string(),
412 serde_json::Value::String("read write".to_string()),
413 );
414
415 let scopes = ValidationUtils::extract_scopes(&metadata, None);
416 assert_eq!(scopes, vec!["read", "write"]);
417
418 let scopes = ValidationUtils::extract_scopes(&HashMap::new(), Some("admin user"));
420 assert_eq!(scopes, vec!["admin", "user"]);
421 }
422
423 #[test]
424 fn test_normalize_token_type() {
425 assert_eq!(
426 ValidationUtils::normalize_token_type("jwt"),
427 "urn:ietf:params:oauth:token-type:jwt"
428 );
429
430 assert_eq!(
431 ValidationUtils::normalize_token_type("urn:ietf:params:oauth:token-type:jwt"),
432 "urn:ietf:params:oauth:token-type:jwt"
433 );
434 }
435
436 #[test]
437 fn test_factory_manager_type_determination() {
438 let simple_req = ExchangeRequirements {
440 needs_audit_trail: false,
441 needs_session_integration: false,
442 needs_context_preservation: false,
443 needs_multi_party_chains: false,
444 needs_jwt_operations: false,
445 needs_policy_control: false,
446 needs_cross_domain: false,
447 max_delegation_depth: 1,
448 };
449
450 assert_eq!(
451 TokenExchangeFactory::determine_manager_type(&simple_req),
452 ServiceComplexityLevel::Basic
453 );
454
455 let complex_req = ExchangeRequirements {
457 needs_audit_trail: true,
458 needs_session_integration: true,
459 needs_context_preservation: true,
460 needs_multi_party_chains: true,
461 needs_jwt_operations: true,
462 needs_policy_control: true,
463 needs_cross_domain: true,
464 max_delegation_depth: 10,
465 };
466
467 assert_eq!(
468 TokenExchangeFactory::determine_manager_type(&complex_req),
469 ServiceComplexityLevel::Advanced
470 );
471 }
472
473 #[test]
474 fn test_use_case_recommendations() {
475 let simple_config = TokenExchangeFactory::get_recommended_config(
476 &TokenExchangeUseCase::SimpleServiceToService,
477 );
478 assert!(!simple_config.needs_audit_trail);
479 assert_eq!(simple_config.max_delegation_depth, 1);
480
481 let enterprise_config = TokenExchangeFactory::get_recommended_config(
482 &TokenExchangeUseCase::EnterpriseIntegration,
483 );
484 assert!(enterprise_config.needs_audit_trail);
485 assert!(enterprise_config.needs_session_integration);
486 assert_eq!(enterprise_config.max_delegation_depth, 10);
487 }
488}
489
490