foxy/security/
oidc.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! OpenID-Connect bearer-token provider.
6//!
7//! Supported algs   : HS256 / 384 / 512  · RS256 / 384 / 512 · PS256 / 384 / 512
8//!                    ES256 / 384        · EdDSA (Ed25519)
9//! Bypass rules     : glob-style paths + method list, evaluated before token checks
10//! HMAC secret      : optional `shared-secret` in config (required for HS* algs)
11//! JWKS refresh     : lazy + every 30 min ± key-rotation retry
12
13use async_trait::async_trait;
14use jsonwebtoken::{decode, decode_header, jwk::JwkSet, Algorithm, DecodingKey, Validation};
15use jsonwebtoken::jwk::{AlgorithmParameters, Jwk, OctetKeyParameters};
16use reqwest::Client;
17use std::{sync::Arc, time::Duration};
18use serde::Deserialize;
19use tokio::sync::RwLock;
20use globset::{Glob, GlobSet, GlobSetBuilder};
21use crate::{core::{ProxyError, ProxyRequest}, debug_fmt, error_fmt, security::{SecurityProvider, SecurityStage}, trace_fmt, warn_fmt};
22
23pub const CLAIMS_ATTRIBUTE: &str = "oidc-claims";
24const BEARER: &str = "bearer ";
25const JWKS_REFRESH: Duration = Duration::from_secs(30 * 60);
26
27#[derive(Debug, Clone, serde::Deserialize)]
28pub struct RouteRuleConfig {
29    pub methods: Vec<String>,
30    pub path: String,
31}
32
33#[derive(Debug)]
34struct RouteRule {
35    methods: Vec<String>,
36    paths: GlobSet,
37}
38
39impl RouteRule {
40    fn matches(&self, method: &str, path: &str) -> bool {
41        let method_match = self.methods.iter().any(|m| m == "*" || m == method);
42        let path_match = self.paths.is_match(path);
43        
44        trace_fmt!("OidcProvider", "OIDC bypass rule check: method={} path={} -> method_match={} path_match={}", 
45            method, path, method_match, path_match);
46            
47        method_match && path_match
48    }
49}
50
51/// Top-level OIDC section under `"security_chain"` in config.
52#[derive(Debug, Clone, Deserialize)]
53pub struct OidcConfig {
54    #[serde(rename = "issuer-uri")]
55    pub issuer_uri: String,
56    
57    /// Expected audience claim (optional)
58    pub aud: Option<String>,
59    
60    /// Shared secret for HS* algorithms (optional)
61    #[serde(rename = "shared-secret")]
62    pub shared_secret: Option<String>,
63    
64    /// Routes to bypass authentication for
65    #[serde(default)]
66    pub bypass: Vec<RouteRuleConfig>,
67}
68
69/// OpenID Connect security provider.
70#[derive(Debug)]
71pub struct OidcProvider {
72    /// Issuer URI
73    issuer: String,
74    
75    /// Expected audience claim
76    aud: Option<String>,
77    
78    /// Shared secret for HS* algorithms
79    shared_secret: Option<String>,
80    
81    /// JWKS URI
82    jwks_uri: String,
83    
84    /// Cached JWKS
85    jwks: Arc<RwLock<Option<JwkSet>>>,
86    
87    /// Last refresh time
88    last_refresh: Arc<RwLock<tokio::time::Instant>>,
89    
90    /// HTTP client
91    http: Client,
92    
93    /// Bypass rules
94    rules: Vec<RouteRule>,
95}
96
97impl OidcProvider {
98    /// Discover OIDC configuration from the issuer URI.
99    pub async fn discover(cfg: OidcConfig) -> Result<Self, ProxyError> {
100        // --- minimal discovery ---
101        debug_fmt!("OidcProvider", "OIDC discovery from {}", cfg.issuer_uri);
102        
103        let client = Client::builder()
104            .user_agent("foxy/oidc")
105            .build()
106            .map_err(|e| {
107                let err = ProxyError::SecurityError(format!("Failed to build HTTP client: {e}"));
108                error_fmt!("OidcProvider", "{}", err);
109                err
110            })?;
111            
112        #[derive(Deserialize)]
113        struct Discovery { jwks_uri: String }
114        
115        let meta: Discovery = match client.get(&cfg.issuer_uri).send().await {
116            Ok(response) => {
117                match response.error_for_status() {
118                    Ok(response) => {
119                        match response.json().await {
120                            Ok(meta) => meta,
121                            Err(e) => {
122                                let err = ProxyError::SecurityError(
123                                    format!("Failed to parse OIDC discovery response: {e}")
124                                );
125                                error_fmt!("OidcProvider", "{}", err);
126                                return Err(err);
127                            }
128                        }
129                    },
130                    Err(e) => {
131                        let err = ProxyError::SecurityError(
132                            format!("OIDC discovery endpoint returned error: {e}")
133                        );
134                        error_fmt!("OidcProvider", "{}", err);
135                        return Err(err);
136                    }
137                }
138            },
139            Err(e) => {
140                let err = ProxyError::SecurityError(
141                    format!("Failed to connect to OIDC discovery endpoint: {e}")
142                );
143                error_fmt!("OidcProvider", "{}", err);
144                return Err(err);
145            }
146        };
147
148        debug_fmt!("OidcProvider", "OIDC discovery successful, JWKS URI: {}", meta.jwks_uri);
149
150        // --- compile bypass rules ---
151        let mut rules = Vec::with_capacity(cfg.bypass.len());
152        for raw in cfg.bypass {
153            let mut builder = GlobSetBuilder::new();
154            match Glob::new(&raw.path) {
155                Ok(glob) => {
156                    builder.add(glob);
157                    rules.push(RouteRule {
158                        methods: raw.methods.iter().map(|m| m.to_ascii_uppercase()).collect(),
159                        paths: match builder.build() {
160                            Ok(set) => set,
161                            Err(e) => {
162                                let err = ProxyError::SecurityError(
163                                    format!("Failed to build glob set for path {}: {}", raw.path, e)
164                                );
165                                error_fmt!("OidcProvider", "{}", err);
166                                return Err(err);
167                            }
168                        },
169                    });
170                    debug_fmt!("OidcProvider", "Added OIDC bypass rule: methods={:?}, path={}", raw.methods, raw.path);
171                },
172                Err(e) => {
173                    let err = ProxyError::SecurityError(
174                        format!("Invalid glob pattern in bypass rule: {e}")
175                    );
176                    error_fmt!("OidcProvider", "{}", err);
177                    return Err(err);
178                }
179            }
180        }
181
182        Ok(Self {
183            issuer: cfg
184                .issuer_uri
185                .trim_end_matches("/.well-known/openid-configuration")
186                .to_owned(),
187            aud: cfg.aud,
188            shared_secret: cfg.shared_secret,
189            jwks_uri: meta.jwks_uri,
190            jwks: Arc::new(RwLock::new(None)),
191            last_refresh: Arc::new(RwLock::new(
192                tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
193                    .unwrap_or_else(|| {
194                        // If we can't subtract, use a very old instant
195                        tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
196                            .unwrap_or_else(|| tokio::time::Instant::now())
197                    }),
198            )),
199            http: client,
200            rules,
201        })
202    }
203
204    /* ---------- helpers -------------------------------------------------- */
205
206    async fn refresh_jwks(&self) -> Result<(), ProxyError> {
207        let now = tokio::time::Instant::now();
208
209        // Check if cache is empty or expired
210        let should_refresh = {
211            let jwks_guard = self.jwks.read().await;
212            let cache_empty = jwks_guard.is_none();
213            let cache_expired = now.duration_since(*self.last_refresh.read().await) >= JWKS_REFRESH;
214            cache_empty || cache_expired
215        };
216
217        if !should_refresh {
218            trace_fmt!("OidcProvider", "JWKS cache still fresh, skipping refresh");
219            return Ok(());
220        }
221        
222        debug_fmt!("OidcProvider", "Refreshing JWKS from {}", self.jwks_uri);
223        
224        // Fetch the JWKS
225        let jwks = match self.http.get(&self.jwks_uri).send().await {
226            Ok(response) => {
227                match response.error_for_status() {
228                    Ok(response) => {
229                        match response.json::<JwkSet>().await {
230                            Ok(jwks) => jwks,
231                            Err(e) => {
232                                let err = ProxyError::SecurityError(
233                                    format!("Failed to parse JWKS response: {e}")
234                                );
235                                error_fmt!("OidcProvider", "{}", err);
236                                return Err(err);
237                            }
238                        }
239                    },
240                    Err(e) => {
241                        let err = ProxyError::SecurityError(
242                            format!("JWKS endpoint returned error: {e}")
243                        );
244                        error_fmt!("OidcProvider", "{}", err);
245                        return Err(err);
246                    }
247                }
248            },
249            Err(e) => {
250                let err = ProxyError::SecurityError(
251                    format!("Failed to connect to JWKS endpoint: {e}")
252                );
253                error_fmt!("OidcProvider", "{}", err);
254                return Err(err);
255            }
256        };
257        
258        debug_fmt!("OidcProvider", "JWKS refresh successful, found {} keys", jwks.keys.len());
259        
260        // Update the cache
261        {
262            let mut w = self.jwks.write().await;
263            *w = Some(jwks);
264        }
265        {
266            let mut w = self.last_refresh.write().await;
267            *w = now;
268        }
269        
270        Ok(())
271    }
272
273    fn jwk_to_decoding_key(&self, jwk: &Jwk) -> Result<DecodingKey, ProxyError> {
274        match &jwk.algorithm {
275            AlgorithmParameters::RSA(params) => {
276                trace_fmt!("OidcProvider", "Converting RSA JWK to decoding key");
277                DecodingKey::from_rsa_components(&params.n, &params.e)
278                    .map_err(|e| {
279                        let err = ProxyError::SecurityError(format!("Invalid RSA key: {e}"));
280                        error_fmt!("OidcProvider", "{}", err);
281                        err
282                    })
283            }
284            AlgorithmParameters::EllipticCurve(params) => {
285                trace_fmt!("OidcProvider", "Converting EC JWK to decoding key");
286                DecodingKey::from_ec_components(&params.x, &params.y)
287                    .map_err(|e| {
288                        let err = ProxyError::SecurityError(format!("Invalid EC key: {e}"));
289                        error_fmt!("OidcProvider", "{}", err);
290                        err
291                    })
292            }
293            AlgorithmParameters::OctetKey(OctetKeyParameters { value, .. }) => {
294                trace_fmt!("OidcProvider", "Converting octet JWK to decoding key");
295                Ok(DecodingKey::from_secret(value.as_bytes()))
296            }
297            AlgorithmParameters::OctetKeyPair(params) => {
298                trace_fmt!("OidcProvider", "Converting OKP JWK to decoding key");
299                DecodingKey::from_ed_components(&params.x)
300                    .map_err(|e| {
301                        let err = ProxyError::SecurityError(format!("Invalid OKP key: {e}"));
302                        error_fmt!("OidcProvider", "{}", err);
303                        err
304                    })
305            }
306        }
307    }
308
309    async fn validate_token(&self, token: &str) -> Result<serde_json::Value, ProxyError> {
310        // Parse the header to determine the key ID and algorithm
311        let header = match decode_header(token) {
312            Ok(h) => h,
313            Err(e) => {
314                let err = ProxyError::SecurityError(format!("Invalid JWT header: {e}"));
315                warn_fmt!("OidcProvider", "{}", err);
316                return Err(err);
317            }
318        };
319        
320        trace_fmt!("OidcProvider", "JWT header: alg={:?}, kid={:?}", header.alg, header.kid);
321        
322        // Check for allowed algorithms
323        let allowed_algs = [
324            Algorithm::RS256, Algorithm::RS384, Algorithm::RS512,
325            Algorithm::PS256, Algorithm::PS384, Algorithm::PS512,
326            Algorithm::ES256, Algorithm::ES384,
327            Algorithm::EdDSA,
328            Algorithm::HS256, Algorithm::HS384, Algorithm::HS512,
329        ];
330        
331        if !allowed_algs.contains(&header.alg) {
332            let err = ProxyError::SecurityError(
333                format!("Algorithm not allowed: {:?}", header.alg)
334            );
335            warn_fmt!("OidcProvider", "{}", err);
336            return Err(err);
337        }
338
339        // Get the key
340        let key = match &header.kid {
341            Some(kid) => {
342                // Ensure we have a fresh JWKS when we need to look up a key
343                self.refresh_jwks().await?;
344
345                // Find the key in the JWKS
346                let jwks = self.jwks.read().await;
347                let jwks = match &*jwks {
348                    Some(j) => j,
349                    None => {
350                        let err = ProxyError::SecurityError("No JWKS available".to_string());
351                        error_fmt!("OidcProvider", "{}", err);
352                        return Err(err);
353                    }
354                };
355
356                // Try to find the key by ID
357                match jwks.keys.iter().find(|k| k.common.key_id == Some(kid.clone())) {
358                    Some(key) => {
359                        trace_fmt!("OidcProvider", "Found key with ID {}", kid);
360                        match self.jwk_to_decoding_key(key) {
361                            Ok(key) => key,
362                            Err(e) => {
363                                error_fmt!("OidcProvider", "Failed to convert JWK to decoding key: {}", e);
364                                return Err(e);
365                            }
366                        }
367                    }
368                    None => {
369                        // If we have a shared secret, use that for HS* algorithms
370                        if let Some(ref secret) = self.shared_secret {
371                            if matches!(header.alg, Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512) {
372                                trace_fmt!("OidcProvider", "Using shared secret for HS* algorithm");
373                                DecodingKey::from_secret(secret.as_bytes())
374                            } else {
375                                let err = ProxyError::SecurityError(format!("Key ID {kid} not found in JWKS"));
376                                warn_fmt!("OidcProvider", "{}", err);
377                                return Err(err);
378                            }
379                        } else {
380                            let err = ProxyError::SecurityError(format!("Key ID {kid} not found in JWKS"));
381                            warn_fmt!("OidcProvider", "{}", err);
382                            return Err(err);
383                        }
384                    }
385                }
386            }
387            None => {
388                // No key ID, try to use shared secret if available
389                if let Some(ref secret) = self.shared_secret {
390                    trace_fmt!("OidcProvider", "No key ID in token, using shared secret");
391                    DecodingKey::from_secret(secret.as_bytes())
392                } else {
393                    let err = ProxyError::SecurityError("No key ID in token and no shared secret configured".to_string());
394                    warn_fmt!("OidcProvider", "{}", err);
395                    return Err(err);
396                }
397            }
398        };
399
400        // Set up validation
401        let mut validation = Validation::new(header.alg);
402        validation.set_audience(&[&self.aud.clone().unwrap_or_default()]);
403        validation.set_issuer(&[&self.issuer]);
404
405        // Validate the token
406        match decode::<serde_json::Value>(token, &key, &validation) {
407            Ok(token_data) => {
408                debug_fmt!("OidcProvider", "JWT validation successful");
409                Ok(token_data.claims)
410            }
411            Err(e) => {
412                let err = ProxyError::SecurityError(format!("JWT validation failed: {e}"));
413                warn_fmt!("OidcProvider", "{}", err);
414                Err(err)
415            }
416        }
417    }
418
419    #[allow(dead_code)]
420    fn validate_std_claims(&self, claims: &serde_json::Value) -> Result<(), ProxyError> {
421        // Check issuer
422        if let Some(iss) = claims["iss"].as_str() {
423            if iss != self.issuer {
424                let err = ProxyError::SecurityError(
425                    format!("Invalid issuer: expected '{}', got '{}'", self.issuer, iss)
426                );
427                warn_fmt!("OidcProvider", "{}", err);
428                return Err(err);
429            }
430        } else {
431            let err = ProxyError::SecurityError("Missing issuer claim".to_string());
432            warn_fmt!("OidcProvider", "{}", err);
433            return Err(err);
434        }
435        
436        // Check audience if configured
437        if let Some(ref expected_aud) = self.aud {
438            let valid_audience = match &claims["aud"] {
439                serde_json::Value::String(aud) => aud == expected_aud,
440                serde_json::Value::Array(auds) => auds.iter()
441                    .filter_map(|a| a.as_str())
442                    .any(|a| a == expected_aud),
443                _ => false,
444            };
445            
446            if !valid_audience {
447                let err = ProxyError::SecurityError(
448                    format!("Invalid audience: expected '{expected_aud}'")
449                );
450                warn_fmt!("OidcProvider", "{}", err);
451                return Err(err);
452            }
453        }
454        
455        // Check expiration
456        if let Some(exp) = claims["exp"].as_i64() {
457            let now = std::time::SystemTime::now()
458                .duration_since(std::time::UNIX_EPOCH)
459                .unwrap_or_default()
460                .as_secs() as i64;
461                
462            if exp <= now {
463                let err = ProxyError::SecurityError(
464                    format!("Token expired at {exp}, current time is {now}")
465                );
466                warn_fmt!("OidcProvider", "{}", err);
467                return Err(err);
468            }
469        }
470        
471        debug_fmt!("OidcProvider", "Token claims validation successful");
472        Ok(())
473    }
474
475    #[inline]
476    fn is_bypassed(&self, method: &str, path: &str) -> bool {
477        let bypassed = self.rules.iter().any(|r| r.matches(method, path));
478        if bypassed {
479            debug_fmt!("OidcProvider", "OIDC bypass for {} {}", method, path);
480        }
481        bypassed
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488    use crate::core::{ProxyRequest, HttpMethod, RequestContext};
489    use reqwest::header::HeaderMap;
490    use std::sync::Arc;
491    use tokio::sync::RwLock;
492    use wiremock::{MockServer, Mock, ResponseTemplate};
493    use wiremock::matchers::{method, path};
494    use base64::{Engine as _};
495
496    #[test]
497    fn test_route_rule_config_deserialization() {
498        let json = r#"{
499            "methods": ["GET", "POST"],
500            "path": "/api/*"
501        }"#;
502
503        let config: RouteRuleConfig = serde_json::from_str(json).unwrap();
504        assert_eq!(config.methods, vec!["GET", "POST"]);
505        assert_eq!(config.path, "/api/*");
506    }
507
508    #[test]
509    fn test_route_rule_matches() {
510        let mut builder = GlobSetBuilder::new();
511        builder.add(Glob::new("/api/*").unwrap());
512        let paths = builder.build().unwrap();
513
514        let rule = RouteRule {
515            methods: vec!["GET".to_string(), "POST".to_string()],
516            paths,
517        };
518
519        // Test method and path matching
520        assert!(rule.matches("GET", "/api/users"));
521        assert!(rule.matches("POST", "/api/users"));
522        assert!(!rule.matches("DELETE", "/api/users"));
523        assert!(!rule.matches("GET", "/health"));
524
525        // Test case sensitive method matching (methods are stored in uppercase)
526        assert!(!rule.matches("get", "/api/users"));
527        assert!(!rule.matches("post", "/api/users"));
528    }
529
530    #[test]
531    fn test_route_rule_wildcard_methods() {
532        let mut builder = GlobSetBuilder::new();
533        builder.add(Glob::new("/health").unwrap());
534        let paths = builder.build().unwrap();
535
536        let rule = RouteRule {
537            methods: vec!["*".to_string()],
538            paths,
539        };
540
541        assert!(rule.matches("GET", "/health"));
542        assert!(rule.matches("POST", "/health"));
543        assert!(rule.matches("DELETE", "/health"));
544        assert!(!rule.matches("GET", "/api"));
545    }
546
547    #[test]
548    fn test_oidc_config_deserialization() {
549        let json = r#"{
550            "issuer-uri": "https://auth.example.com/.well-known/openid-configuration",
551            "aud": "my-app",
552            "shared-secret": "secret123",
553            "bypass": [
554                {
555                    "methods": ["GET"],
556                    "path": "/health"
557                }
558            ]
559        }"#;
560
561        let config: OidcConfig = serde_json::from_str(json).unwrap();
562        assert_eq!(config.issuer_uri, "https://auth.example.com/.well-known/openid-configuration");
563        assert_eq!(config.aud, Some("my-app".to_string()));
564        assert_eq!(config.shared_secret, Some("secret123".to_string()));
565        assert_eq!(config.bypass.len(), 1);
566        assert_eq!(config.bypass[0].methods, vec!["GET"]);
567        assert_eq!(config.bypass[0].path, "/health");
568    }
569
570    #[test]
571    fn test_oidc_config_minimal() {
572        let json = r#"{
573            "issuer-uri": "https://auth.example.com"
574        }"#;
575
576        let config: OidcConfig = serde_json::from_str(json).unwrap();
577        assert_eq!(config.issuer_uri, "https://auth.example.com");
578        assert_eq!(config.aud, None);
579        assert_eq!(config.shared_secret, None);
580        assert!(config.bypass.is_empty());
581    }
582
583    #[test]
584    fn test_oidc_config_empty_bypass() {
585        let json = r#"{
586            "issuer-uri": "https://auth.example.com",
587            "bypass": []
588        }"#;
589
590        let config: OidcConfig = serde_json::from_str(json).unwrap();
591        assert!(config.bypass.is_empty());
592    }
593
594    #[tokio::test]
595    async fn test_oidc_provider_discover_success() {
596        // Setup mock server
597        let mock_server = MockServer::start().await;
598
599        // Mock the discovery endpoint
600        Mock::given(method("GET"))
601            .and(path("/"))
602            .respond_with(ResponseTemplate::new(200)
603                .set_body_json(serde_json::json!({
604                    "issuer": mock_server.uri(),
605                    "jwks_uri": format!("{}/jwks", mock_server.uri()),
606                    "authorization_endpoint": format!("{}/auth", mock_server.uri()),
607                    "token_endpoint": format!("{}/token", mock_server.uri())
608                })))
609            .mount(&mock_server)
610            .await;
611
612        let config = OidcConfig {
613            issuer_uri: mock_server.uri(),
614            aud: Some("test-audience".to_string()),
615            shared_secret: Some("test-secret".to_string()),
616            bypass: vec![
617                RouteRuleConfig {
618                    methods: vec!["GET".to_string()],
619                    path: "/health".to_string(),
620                },
621                RouteRuleConfig {
622                    methods: vec!["*".to_string()],
623                    path: "/public/*".to_string(),
624                },
625            ],
626        };
627
628        let result = OidcProvider::discover(config.clone()).await;
629        assert!(result.is_ok());
630
631        let provider = result.unwrap();
632        assert_eq!(provider.issuer, mock_server.uri());
633        assert_eq!(provider.aud, Some("test-audience".to_string()));
634        assert_eq!(provider.shared_secret, Some("test-secret".to_string()));
635        assert_eq!(provider.jwks_uri, format!("{}/jwks", mock_server.uri()));
636        assert_eq!(provider.rules.len(), 2);
637
638        // Test bypass rules were compiled correctly
639        assert!(provider.is_bypassed("GET", "/health"));
640        assert!(provider.is_bypassed("POST", "/public/api"));
641        assert!(!provider.is_bypassed("POST", "/private/api"));
642    }
643
644    #[tokio::test]
645    async fn test_oidc_provider_discover_success_minimal_config() {
646        // Setup mock server
647        let mock_server = MockServer::start().await;
648
649        // Mock the discovery endpoint
650        Mock::given(method("GET"))
651            .and(path("/"))
652            .respond_with(ResponseTemplate::new(200)
653                .set_body_json(serde_json::json!({
654                    "issuer": mock_server.uri(),
655                    "jwks_uri": format!("{}/jwks", mock_server.uri())
656                })))
657            .mount(&mock_server)
658            .await;
659
660        let config = OidcConfig {
661            issuer_uri: mock_server.uri(),
662            aud: None,
663            shared_secret: None,
664            bypass: vec![],
665        };
666
667        let result = OidcProvider::discover(config).await;
668        assert!(result.is_ok());
669
670        let provider = result.unwrap();
671        assert_eq!(provider.issuer, mock_server.uri());
672        assert_eq!(provider.aud, None);
673        assert_eq!(provider.shared_secret, None);
674        assert_eq!(provider.jwks_uri, format!("{}/jwks", mock_server.uri()));
675        assert!(provider.rules.is_empty());
676    }
677
678    #[tokio::test]
679    async fn test_oidc_provider_discover_success_with_well_known_suffix() {
680        // Setup mock server
681        let mock_server = MockServer::start().await;
682
683        // Mock the discovery endpoint
684        Mock::given(method("GET"))
685            .and(path("/.well-known/openid-configuration"))
686            .respond_with(ResponseTemplate::new(200)
687                .set_body_json(serde_json::json!({
688                    "issuer": mock_server.uri(),
689                    "jwks_uri": format!("{}/jwks", mock_server.uri())
690                })))
691            .mount(&mock_server)
692            .await;
693
694        let config = OidcConfig {
695            issuer_uri: format!("{}/.well-known/openid-configuration", mock_server.uri()),
696            aud: None,
697            shared_secret: None,
698            bypass: vec![],
699        };
700
701        let result = OidcProvider::discover(config).await;
702        assert!(result.is_ok());
703
704        let provider = result.unwrap();
705        // Should strip the .well-known suffix from issuer
706        assert_eq!(provider.issuer, mock_server.uri());
707        assert_eq!(provider.jwks_uri, format!("{}/jwks", mock_server.uri()));
708    }
709
710    #[tokio::test]
711    async fn test_oidc_provider_discover_invalid_url() {
712        let config = OidcConfig {
713            issuer_uri: "invalid-url".to_string(),
714            aud: None,
715            shared_secret: None,
716            bypass: vec![],
717        };
718
719        let result = OidcProvider::discover(config).await;
720        assert!(result.is_err());
721
722        if let Err(ProxyError::SecurityError(msg)) = result {
723            assert!(msg.contains("Failed to connect to OIDC discovery endpoint"));
724        } else {
725            panic!("Expected SecurityError");
726        }
727    }
728
729    #[tokio::test]
730    async fn test_oidc_provider_discover_http_error() {
731        // Setup mock server
732        let mock_server = MockServer::start().await;
733
734        // Mock the discovery endpoint to return 404
735        Mock::given(method("GET"))
736            .and(path("/"))
737            .respond_with(ResponseTemplate::new(404))
738            .mount(&mock_server)
739            .await;
740
741        let config = OidcConfig {
742            issuer_uri: mock_server.uri(),
743            aud: None,
744            shared_secret: None,
745            bypass: vec![],
746        };
747
748        let result = OidcProvider::discover(config).await;
749        assert!(result.is_err());
750
751        if let Err(ProxyError::SecurityError(msg)) = result {
752            assert!(msg.contains("OIDC discovery endpoint returned error"));
753        } else {
754            panic!("Expected SecurityError");
755        }
756    }
757
758    #[tokio::test]
759    async fn test_oidc_provider_discover_invalid_json() {
760        // Setup mock server
761        let mock_server = MockServer::start().await;
762
763        // Mock the discovery endpoint to return invalid JSON
764        Mock::given(method("GET"))
765            .and(path("/"))
766            .respond_with(ResponseTemplate::new(200)
767                .set_body_string("invalid json"))
768            .mount(&mock_server)
769            .await;
770
771        let config = OidcConfig {
772            issuer_uri: mock_server.uri(),
773            aud: None,
774            shared_secret: None,
775            bypass: vec![],
776        };
777
778        let result = OidcProvider::discover(config).await;
779        assert!(result.is_err());
780
781        if let Err(ProxyError::SecurityError(msg)) = result {
782            assert!(msg.contains("Failed to parse OIDC discovery response"));
783        } else {
784            panic!("Expected SecurityError");
785        }
786    }
787
788    #[tokio::test]
789    async fn test_oidc_provider_discover_missing_jwks_uri() {
790        // Setup mock server
791        let mock_server = MockServer::start().await;
792
793        // Mock the discovery endpoint to return JSON without jwks_uri
794        Mock::given(method("GET"))
795            .and(path("/"))
796            .respond_with(ResponseTemplate::new(200)
797                .set_body_json(serde_json::json!({
798                    "issuer": mock_server.uri(),
799                    "authorization_endpoint": format!("{}/auth", mock_server.uri())
800                })))
801            .mount(&mock_server)
802            .await;
803
804        let config = OidcConfig {
805            issuer_uri: mock_server.uri(),
806            aud: None,
807            shared_secret: None,
808            bypass: vec![],
809        };
810
811        let result = OidcProvider::discover(config).await;
812        assert!(result.is_err());
813
814        if let Err(ProxyError::SecurityError(msg)) = result {
815            assert!(msg.contains("Failed to parse OIDC discovery response"));
816        } else {
817            panic!("Expected SecurityError");
818        }
819    }
820
821    #[tokio::test]
822    async fn test_oidc_provider_discover_invalid_bypass_glob() {
823        // Setup mock server
824        let mock_server = MockServer::start().await;
825
826        // Mock the discovery endpoint
827        Mock::given(method("GET"))
828            .and(path("/"))
829            .respond_with(ResponseTemplate::new(200)
830                .set_body_json(serde_json::json!({
831                    "issuer": mock_server.uri(),
832                    "jwks_uri": format!("{}/jwks", mock_server.uri())
833                })))
834            .mount(&mock_server)
835            .await;
836
837        let config = OidcConfig {
838            issuer_uri: mock_server.uri(),
839            aud: None,
840            shared_secret: None,
841            bypass: vec![
842                RouteRuleConfig {
843                    methods: vec!["GET".to_string()],
844                    path: "[invalid-glob".to_string(), // Invalid glob pattern
845                },
846            ],
847        };
848
849        let result = OidcProvider::discover(config).await;
850        assert!(result.is_err());
851
852        if let Err(ProxyError::SecurityError(msg)) = result {
853            assert!(msg.contains("Invalid glob pattern in bypass rule"));
854        } else {
855            panic!("Expected SecurityError");
856        }
857    }
858
859    #[tokio::test]
860    async fn test_oidc_provider_discover_complex_bypass_rules() {
861        // Setup mock server
862        let mock_server = MockServer::start().await;
863
864        // Mock the discovery endpoint
865        Mock::given(method("GET"))
866            .and(path("/"))
867            .respond_with(ResponseTemplate::new(200)
868                .set_body_json(serde_json::json!({
869                    "issuer": mock_server.uri(),
870                    "jwks_uri": format!("{}/jwks", mock_server.uri())
871                })))
872            .mount(&mock_server)
873            .await;
874
875        let config = OidcConfig {
876            issuer_uri: mock_server.uri(),
877            aud: None,
878            shared_secret: None,
879            bypass: vec![
880                RouteRuleConfig {
881                    methods: vec!["get".to_string(), "post".to_string()], // lowercase methods
882                    path: "/api/v*/health".to_string(),
883                },
884                RouteRuleConfig {
885                    methods: vec!["*".to_string()],
886                    path: "/static/**".to_string(),
887                },
888            ],
889        };
890
891        let result = OidcProvider::discover(config).await;
892        assert!(result.is_ok());
893
894        let provider = result.unwrap();
895        assert_eq!(provider.rules.len(), 2);
896
897        // Test that methods are converted to uppercase
898        assert!(provider.is_bypassed("GET", "/api/v1/health"));
899        assert!(provider.is_bypassed("POST", "/api/v2/health"));
900        assert!(provider.is_bypassed("DELETE", "/static/css/style.css"));
901        assert!(!provider.is_bypassed("GET", "/api/v1/users"));
902    }
903
904    #[tokio::test]
905    async fn test_jwks_refresh_cache_fresh() {
906        // Setup mock server
907        let mock_server = MockServer::start().await;
908
909        // Mock the discovery endpoint
910        Mock::given(method("GET"))
911            .and(path("/"))
912            .respond_with(ResponseTemplate::new(200)
913                .set_body_json(serde_json::json!({
914                    "issuer": mock_server.uri(),
915                    "jwks_uri": format!("{}/jwks", mock_server.uri())
916                })))
917            .mount(&mock_server)
918            .await;
919
920        let config = OidcConfig {
921            issuer_uri: mock_server.uri(),
922            aud: None,
923            shared_secret: None,
924            bypass: vec![],
925        };
926
927        let provider = OidcProvider::discover(config).await.unwrap();
928
929        // Set last refresh to now (fresh cache) and populate the cache
930        {
931            let mut jwks_w = provider.jwks.write().await;
932            *jwks_w = Some(JwkSet { keys: vec![] }); // Add some dummy data to cache
933        }
934        {
935            let mut refresh_w = provider.last_refresh.write().await;
936            *refresh_w = tokio::time::Instant::now();
937        }
938
939        // Should not make HTTP request since cache is fresh and not empty
940        let result = provider.refresh_jwks().await;
941        assert!(result.is_ok());
942    }
943
944    #[tokio::test]
945    async fn test_jwks_refresh_success() {
946        // Setup mock server
947        let mock_server = MockServer::start().await;
948
949        // Mock the discovery endpoint
950        Mock::given(method("GET"))
951            .and(path("/"))
952            .respond_with(ResponseTemplate::new(200)
953                .set_body_json(serde_json::json!({
954                    "issuer": mock_server.uri(),
955                    "jwks_uri": format!("{}/jwks", mock_server.uri())
956                })))
957            .mount(&mock_server)
958            .await;
959
960        // Mock the JWKS endpoint
961        Mock::given(method("GET"))
962            .and(path("/jwks"))
963            .respond_with(ResponseTemplate::new(200)
964                .set_body_json(serde_json::json!({
965                    "keys": [
966                        {
967                            "kty": "RSA",
968                            "kid": "test-key-1",
969                            "use": "sig",
970                            "alg": "RS256",
971                            "n": "test-modulus",
972                            "e": "AQAB"
973                        }
974                    ]
975                })))
976            .mount(&mock_server)
977            .await;
978
979        let config = OidcConfig {
980            issuer_uri: mock_server.uri(),
981            aud: None,
982            shared_secret: None,
983            bypass: vec![],
984        };
985
986        let provider = OidcProvider::discover(config).await.unwrap();
987
988        // Force cache expiration by clearing the cache and setting last_refresh to an old time
989        {
990            let mut jwks_w = provider.jwks.write().await;
991            *jwks_w = None; // Clear the cache
992        }
993        {
994            let mut refresh_w = provider.last_refresh.write().await;
995            // Set to a time that's guaranteed to trigger refresh
996            *refresh_w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH + std::time::Duration::from_secs(1))
997                .unwrap_or_else(|| {
998                    // Fallback: use a very old instant by subtracting a small amount
999                    tokio::time::Instant::now().checked_sub(std::time::Duration::from_millis(1))
1000                        .unwrap_or_else(|| tokio::time::Instant::now())
1001                });
1002        }
1003
1004        let result = provider.refresh_jwks().await;
1005        assert!(result.is_ok());
1006
1007        // Verify JWKS was cached
1008        let jwks = provider.jwks.read().await;
1009        assert!(jwks.is_some());
1010        let jwks = jwks.as_ref().unwrap();
1011        assert_eq!(jwks.keys.len(), 1);
1012        assert_eq!(jwks.keys[0].common.key_id, Some("test-key-1".to_string()));
1013    }
1014
1015    #[tokio::test]
1016    async fn test_jwks_refresh_http_error() {
1017        // Setup mock server
1018        let mock_server = MockServer::start().await;
1019
1020        // Mock the discovery endpoint
1021        Mock::given(method("GET"))
1022            .and(path("/"))
1023            .respond_with(ResponseTemplate::new(200)
1024                .set_body_json(serde_json::json!({
1025                    "issuer": mock_server.uri(),
1026                    "jwks_uri": format!("{}/jwks", mock_server.uri())
1027                })))
1028            .mount(&mock_server)
1029            .await;
1030
1031        // Mock the JWKS endpoint to return 500 error
1032        Mock::given(method("GET"))
1033            .and(path("/jwks"))
1034            .respond_with(ResponseTemplate::new(500))
1035            .mount(&mock_server)
1036            .await;
1037
1038        let config = OidcConfig {
1039            issuer_uri: mock_server.uri(),
1040            aud: None,
1041            shared_secret: None,
1042            bypass: vec![],
1043        };
1044
1045        let provider = OidcProvider::discover(config).await.unwrap();
1046
1047        // Force cache expiration by setting last_refresh to a very old time
1048        {
1049            let mut w = provider.last_refresh.write().await;
1050            *w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
1051                .unwrap_or_else(|| {
1052                    tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
1053                        .unwrap_or_else(|| tokio::time::Instant::now())
1054                });
1055        }
1056
1057        let result = provider.refresh_jwks().await;
1058        assert!(result.is_err());
1059
1060        if let Err(ProxyError::SecurityError(msg)) = result {
1061            assert!(msg.contains("JWKS endpoint returned error"));
1062        } else {
1063            panic!("Expected SecurityError");
1064        }
1065    }
1066
1067    #[tokio::test]
1068    async fn test_jwks_refresh_invalid_json() {
1069        // Setup mock server
1070        let mock_server = MockServer::start().await;
1071
1072        // Mock the discovery endpoint
1073        Mock::given(method("GET"))
1074            .and(path("/"))
1075            .respond_with(ResponseTemplate::new(200)
1076                .set_body_json(serde_json::json!({
1077                    "issuer": mock_server.uri(),
1078                    "jwks_uri": format!("{}/jwks", mock_server.uri())
1079                })))
1080            .mount(&mock_server)
1081            .await;
1082
1083        // Mock the JWKS endpoint to return invalid JSON
1084        Mock::given(method("GET"))
1085            .and(path("/jwks"))
1086            .respond_with(ResponseTemplate::new(200)
1087                .set_body_string("invalid json"))
1088            .mount(&mock_server)
1089            .await;
1090
1091        let config = OidcConfig {
1092            issuer_uri: mock_server.uri(),
1093            aud: None,
1094            shared_secret: None,
1095            bypass: vec![],
1096        };
1097
1098        let provider = OidcProvider::discover(config).await.unwrap();
1099
1100        // Force cache expiration by clearing the cache and setting last_refresh to an old time
1101        {
1102            let mut jwks_w = provider.jwks.write().await;
1103            *jwks_w = None; // Clear the cache
1104        }
1105        {
1106            let mut refresh_w = provider.last_refresh.write().await;
1107            // Set to a time that's guaranteed to trigger refresh
1108            *refresh_w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH + std::time::Duration::from_secs(1))
1109                .unwrap_or_else(|| {
1110                    // Fallback: use a very old instant by subtracting a small amount
1111                    tokio::time::Instant::now().checked_sub(std::time::Duration::from_millis(1))
1112                        .unwrap_or_else(|| tokio::time::Instant::now())
1113                });
1114        }
1115
1116        let result = provider.refresh_jwks().await;
1117        assert!(result.is_err());
1118
1119        if let Err(ProxyError::SecurityError(msg)) = result {
1120            assert!(msg.contains("Failed to parse JWKS response"));
1121        } else {
1122            panic!("Expected SecurityError");
1123        }
1124    }
1125
1126    #[tokio::test]
1127    async fn test_jwks_refresh_connection_error() {
1128        let provider = OidcProvider {
1129            issuer: "https://auth.example.com".to_string(),
1130            aud: None,
1131            shared_secret: None,
1132            jwks_uri: "http://invalid-host-12345.example.com/jwks".to_string(),
1133            jwks: Arc::new(RwLock::new(None)),
1134            last_refresh: Arc::new(RwLock::new(
1135                tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
1136                    .unwrap_or_else(|| {
1137                        tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
1138                            .unwrap_or_else(|| tokio::time::Instant::now())
1139                    })
1140            )),
1141            http: reqwest::Client::new(),
1142            rules: vec![],
1143        };
1144
1145        let result = provider.refresh_jwks().await;
1146        assert!(result.is_err());
1147
1148        if let Err(ProxyError::SecurityError(msg)) = result {
1149            assert!(msg.contains("Failed to connect to JWKS endpoint"));
1150        } else {
1151            panic!("Expected SecurityError");
1152        }
1153    }
1154
1155    #[tokio::test]
1156    async fn test_validate_token_invalid_header() {
1157        let provider = OidcProvider {
1158            issuer: "https://auth.example.com".to_string(),
1159            aud: None,
1160            shared_secret: None,
1161            jwks_uri: "https://auth.example.com/jwks".to_string(),
1162            jwks: Arc::new(RwLock::new(None)),
1163            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1164            http: reqwest::Client::new(),
1165            rules: vec![],
1166        };
1167
1168        // Invalid JWT token (not base64 encoded)
1169        let result = provider.validate_token("invalid.token.here").await;
1170        assert!(result.is_err());
1171
1172        if let Err(ProxyError::SecurityError(msg)) = result {
1173            assert!(msg.contains("Invalid JWT header"));
1174        } else {
1175            panic!("Expected SecurityError");
1176        }
1177    }
1178
1179    #[tokio::test]
1180    async fn test_validate_token_unsupported_algorithm() {
1181        use jsonwebtoken::Header;
1182        use serde_json::json;
1183
1184        let provider = OidcProvider {
1185            issuer: "https://auth.example.com".to_string(),
1186            aud: None,
1187            shared_secret: None,
1188            jwks_uri: "https://auth.example.com/jwks".to_string(),
1189            jwks: Arc::new(RwLock::new(None)),
1190            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1191            http: reqwest::Client::new(),
1192            rules: vec![],
1193        };
1194
1195        // Create a JWT with unsupported algorithm (none)
1196
1197
1198        let mut _header = Header::new(jsonwebtoken::Algorithm::HS256);
1199        _header.alg = jsonwebtoken::Algorithm::HS256; // This will be overridden
1200
1201        // We need to manually create a token with "none" algorithm
1202        // Since jsonwebtoken doesn't support "none", we'll create a malformed token
1203        let header_json = json!({"alg": "none", "typ": "JWT"});
1204        let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1205            .encode(header_json.to_string().as_bytes());
1206        let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1207            .encode(json!({"sub": "test"}).to_string().as_bytes());
1208        let token = format!("{}.{}.signature", header_b64, payload_b64);
1209
1210        let result = provider.validate_token(&token).await;
1211        assert!(result.is_err());
1212
1213        if let Err(ProxyError::SecurityError(msg)) = result {
1214            assert!(msg.contains("Invalid JWT header") || msg.contains("Algorithm not allowed"));
1215        } else {
1216            panic!("Expected SecurityError");
1217        }
1218    }
1219
1220    #[tokio::test]
1221    async fn test_validate_token_no_jwks_available() {
1222        use jsonwebtoken::{Header, Algorithm};
1223        use serde_json::json;
1224
1225        let provider = OidcProvider {
1226            issuer: "https://auth.example.com".to_string(),
1227            aud: None,
1228            shared_secret: None,
1229            jwks_uri: "https://auth.example.com/jwks".to_string(),
1230            jwks: Arc::new(RwLock::new(None)), // No JWKS available
1231            last_refresh: Arc::new(RwLock::new(
1232                tokio::time::Instant::now().checked_sub(JWKS_REFRESH * 2)
1233                    .unwrap_or_else(|| {
1234                        tokio::time::Instant::now().checked_sub(std::time::Duration::from_secs(1))
1235                            .unwrap_or_else(|| tokio::time::Instant::now())
1236                    })
1237            )),
1238            http: reqwest::Client::new(),
1239            rules: vec![],
1240        };
1241
1242        // Create a valid JWT header with RS256
1243
1244        let _header = Header {
1245            alg: Algorithm::RS256,
1246            kid: Some("test-key".to_string()),
1247            ..Default::default()
1248        };
1249
1250        let header_json = json!({
1251            "alg": "RS256",
1252            "typ": "JWT",
1253            "kid": "test-key"
1254        });
1255        let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1256            .encode(header_json.to_string().as_bytes());
1257        let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1258            .encode(json!({"sub": "test"}).to_string().as_bytes());
1259        let token = format!("{}.{}.signature", header_b64, payload_b64);
1260
1261        let result = provider.validate_token(&token).await;
1262        assert!(result.is_err());
1263
1264        if let Err(ProxyError::SecurityError(msg)) = result {
1265            assert!(msg.contains("No JWKS available") || msg.contains("Failed to connect"));
1266        } else {
1267            panic!("Expected SecurityError");
1268        }
1269    }
1270
1271    #[tokio::test]
1272    async fn test_validate_token_hmac_success() {
1273        let provider = OidcProvider {
1274            issuer: "https://auth.example.com".to_string(),
1275            aud: Some("test-audience".to_string()),
1276            shared_secret: Some("test-secret-key".to_string()),
1277            jwks_uri: "https://auth.example.com/jwks".to_string(),
1278            jwks: Arc::new(RwLock::new(None)),
1279            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1280            http: reqwest::Client::new(),
1281            rules: vec![],
1282        };
1283
1284        // Create a valid HMAC JWT token
1285        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1286
1287        let header = Header::new(Algorithm::HS256);
1288
1289        #[derive(serde::Serialize)]
1290        struct HmacTestClaims {
1291            iss: String,
1292            aud: String,
1293            sub: String,
1294            exp: i64,
1295            iat: i64,
1296        }
1297
1298        let claims = HmacTestClaims {
1299            iss: "https://auth.example.com".to_string(),
1300            aud: "test-audience".to_string(),
1301            sub: "test-user".to_string(),
1302            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1303            iat: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64,
1304        };
1305
1306        let token = encode(
1307            &header,
1308            &claims,
1309            &EncodingKey::from_secret("test-secret-key".as_ref())
1310        ).unwrap();
1311
1312        let result = provider.validate_token(&token).await;
1313        assert!(result.is_ok());
1314
1315        let validated_claims = result.unwrap();
1316        assert_eq!(validated_claims["iss"], "https://auth.example.com");
1317        assert_eq!(validated_claims["aud"], "test-audience");
1318        assert_eq!(validated_claims["sub"], "test-user");
1319    }
1320
1321    #[tokio::test]
1322    async fn test_validate_token_hmac_wrong_secret() {
1323        let provider = OidcProvider {
1324            issuer: "https://auth.example.com".to_string(),
1325            aud: None,
1326            shared_secret: Some("wrong-secret".to_string()),
1327            jwks_uri: "https://auth.example.com/jwks".to_string(),
1328            jwks: Arc::new(RwLock::new(None)),
1329            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1330            http: reqwest::Client::new(),
1331            rules: vec![],
1332        };
1333
1334        // Create a JWT token with different secret
1335        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1336
1337        let header = Header::new(Algorithm::HS256);
1338
1339        #[derive(serde::Serialize)]
1340        struct WrongSecretClaims {
1341            iss: String,
1342            sub: String,
1343            exp: i64,
1344        }
1345
1346        let claims = WrongSecretClaims {
1347            iss: "https://auth.example.com".to_string(),
1348            sub: "test-user".to_string(),
1349            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1350        };
1351
1352        let token = encode(
1353            &header,
1354            &claims,
1355            &EncodingKey::from_secret("correct-secret".as_ref())
1356        ).unwrap();
1357
1358        let result = provider.validate_token(&token).await;
1359        assert!(result.is_err());
1360
1361        if let Err(ProxyError::SecurityError(msg)) = result {
1362            assert!(msg.contains("JWT validation failed: InvalidSignature"));
1363        } else {
1364            panic!("Expected SecurityError");
1365        }
1366    }
1367
1368    #[tokio::test]
1369    async fn test_validate_token_hmac_no_shared_secret() {
1370        let provider = OidcProvider {
1371            issuer: "https://auth.example.com".to_string(),
1372            aud: None,
1373            shared_secret: None, // No shared secret configured
1374            jwks_uri: "https://auth.example.com/jwks".to_string(),
1375            jwks: Arc::new(RwLock::new(None)),
1376            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1377            http: reqwest::Client::new(),
1378            rules: vec![],
1379        };
1380
1381        // Create a HMAC JWT token
1382        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1383
1384        let header = Header::new(Algorithm::HS256);
1385
1386        #[derive(serde::Serialize)]
1387        struct NoSecretClaims {
1388            iss: String,
1389            sub: String,
1390            exp: i64,
1391        }
1392
1393        let claims = NoSecretClaims {
1394            iss: "https://auth.example.com".to_string(),
1395            sub: "test-user".to_string(),
1396            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1397        };
1398
1399        let token = encode(
1400            &header,
1401            &claims,
1402            &EncodingKey::from_secret("test-secret".as_ref())
1403        ).unwrap();
1404
1405        let result = provider.validate_token(&token).await;
1406        assert!(result.is_err());
1407
1408        if let Err(ProxyError::SecurityError(msg)) = result {
1409            assert!(msg.contains("No key ID in token and no shared secret configured"));
1410        } else {
1411            panic!("Expected SecurityError");
1412        }
1413    }
1414
1415    #[tokio::test]
1416    async fn test_validate_claims_wrong_issuer() {
1417        let provider = OidcProvider {
1418            issuer: "https://auth.example.com".to_string(),
1419            aud: None,
1420            shared_secret: Some("test-secret".to_string()),
1421            jwks_uri: "https://auth.example.com/jwks".to_string(),
1422            jwks: Arc::new(RwLock::new(None)),
1423            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1424            http: reqwest::Client::new(),
1425            rules: vec![],
1426        };
1427
1428        // Create a JWT token with wrong issuer
1429        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1430
1431        let header = Header::new(Algorithm::HS256);
1432
1433        #[derive(serde::Serialize)]
1434        struct WrongIssuerClaims {
1435            iss: String,
1436            sub: String,
1437            exp: i64,
1438        }
1439
1440        let claims = WrongIssuerClaims {
1441            iss: "https://wrong-issuer.com".to_string(), // Wrong issuer
1442            sub: "test-user".to_string(),
1443            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1444        };
1445
1446        let token = encode(
1447            &header,
1448            &claims,
1449            &EncodingKey::from_secret("test-secret".as_ref())
1450        ).unwrap();
1451
1452        let result = provider.validate_token(&token).await;
1453        assert!(result.is_err());
1454
1455        if let Err(ProxyError::SecurityError(msg)) = result {
1456            assert!(msg.contains("InvalidIssuer"));
1457        } else {
1458            panic!("Expected SecurityError");
1459        }
1460    }
1461
1462    #[tokio::test]
1463    async fn test_validate_claims_wrong_audience() {
1464        let provider = OidcProvider {
1465            issuer: "https://auth.example.com".to_string(),
1466            aud: Some("expected-audience".to_string()),
1467            shared_secret: Some("test-secret".to_string()),
1468            jwks_uri: "https://auth.example.com/jwks".to_string(),
1469            jwks: Arc::new(RwLock::new(None)),
1470            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1471            http: reqwest::Client::new(),
1472            rules: vec![],
1473        };
1474
1475        // Create a JWT token with wrong audience
1476        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1477
1478        let header = Header::new(Algorithm::HS256);
1479
1480        #[derive(serde::Serialize)]
1481        struct WrongAudClaims {
1482            iss: String,
1483            aud: String,
1484            sub: String,
1485            exp: i64,
1486        }
1487
1488        let claims = WrongAudClaims {
1489            iss: "https://auth.example.com".to_string(),
1490            aud: "wrong-audience".to_string(), // Wrong audience
1491            sub: "test-user".to_string(),
1492            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
1493        };
1494
1495        let token = encode(
1496            &header,
1497            &claims,
1498            &EncodingKey::from_secret("test-secret".as_ref())
1499        ).unwrap();
1500
1501        let result = provider.validate_token(&token).await;
1502        assert!(result.is_err());
1503
1504        if let Err(ProxyError::SecurityError(msg)) = result {
1505            assert!(msg.contains("InvalidAudience"));
1506        } else {
1507            panic!("Expected SecurityError");
1508        }
1509    }
1510
1511    #[tokio::test]
1512    async fn test_validate_claims_expired_token() {
1513        let provider = OidcProvider {
1514            issuer: "https://auth.example.com".to_string(),
1515            aud: None,
1516            shared_secret: Some("test-secret".to_string()),
1517            jwks_uri: "https://auth.example.com/jwks".to_string(),
1518            jwks: Arc::new(RwLock::new(None)),
1519            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1520            http: reqwest::Client::new(),
1521            rules: vec![],
1522        };
1523
1524        // Create an expired JWT token
1525        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1526
1527        let header = Header::new(Algorithm::HS256);
1528
1529        #[derive(serde::Serialize)]
1530        struct ExpiredClaims {
1531            iss: String,
1532            sub: String,
1533            exp: i64,
1534        }
1535
1536        let claims = ExpiredClaims {
1537            iss: "https://auth.example.com".to_string(),
1538            sub: "test-user".to_string(),
1539            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() - 3600) as i64, // Expired 1 hour ago
1540        };
1541
1542        let token = encode(
1543            &header,
1544            &claims,
1545            &EncodingKey::from_secret("test-secret".as_ref())
1546        ).unwrap();
1547
1548        let result = provider.validate_token(&token).await;
1549        assert!(result.is_err());
1550
1551        if let Err(ProxyError::SecurityError(msg)) = result {
1552            assert!(msg.contains("ExpiredSignature"));
1553        } else {
1554            panic!("Expected SecurityError");
1555        }
1556    }
1557
1558    #[tokio::test]
1559    async fn test_validate_claims_long_expiration() {
1560        let provider = OidcProvider {
1561            issuer: "https://auth.example.com".to_string(),
1562            aud: None,
1563            shared_secret: Some("test-secret".to_string()),
1564            jwks_uri: "https://auth.example.com/jwks".to_string(),
1565            jwks: Arc::new(RwLock::new(None)),
1566            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1567            http: reqwest::Client::new(),
1568            rules: vec![],
1569        };
1570
1571        // Create a JWT token with very long expiration (effectively no expiration)
1572        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
1573
1574
1575        let header = Header::new(Algorithm::HS256);
1576
1577        // Create claims with very long expiration (100 years from now)
1578        #[derive(serde::Serialize)]
1579        struct TestClaimsLongExp {
1580            iss: String,
1581            sub: String,
1582            exp: i64,
1583        }
1584
1585        let claims = TestClaimsLongExp {
1586            iss: "https://auth.example.com".to_string(),
1587            sub: "test-user".to_string(),
1588            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + (100 * 365 * 24 * 3600)) as i64, // 100 years
1589        };
1590
1591        let token = encode(
1592            &header,
1593            &claims,
1594            &EncodingKey::from_secret("test-secret".as_ref())
1595        ).unwrap();
1596
1597        let result = provider.validate_token(&token).await;
1598        if result.is_err() {
1599            println!("Long expiration test error: {:?}", result);
1600        }
1601        assert!(result.is_ok()); // Should succeed with very long expiration
1602
1603        let validated_claims = result.unwrap();
1604        assert_eq!(validated_claims["iss"], "https://auth.example.com");
1605        assert_eq!(validated_claims["sub"], "test-user");
1606    }
1607
1608    #[tokio::test]
1609    async fn test_validate_token_missing_key_id() {
1610        // Setup mock server
1611        let mock_server = MockServer::start().await;
1612
1613        // Mock the discovery endpoint
1614        Mock::given(method("GET"))
1615            .and(path("/"))
1616            .respond_with(ResponseTemplate::new(200)
1617                .set_body_json(serde_json::json!({
1618                    "issuer": mock_server.uri(),
1619                    "jwks_uri": format!("{}/jwks", mock_server.uri())
1620                })))
1621            .mount(&mock_server)
1622            .await;
1623
1624        // Mock the JWKS endpoint
1625        Mock::given(method("GET"))
1626            .and(path("/jwks"))
1627            .respond_with(ResponseTemplate::new(200)
1628                .set_body_json(serde_json::json!({
1629                    "keys": [
1630                        {
1631                            "kty": "RSA",
1632                            "kid": "test-key-1",
1633                            "use": "sig",
1634                            "alg": "RS256",
1635                            "n": "test-modulus",
1636                            "e": "AQAB"
1637                        }
1638                    ]
1639                })))
1640            .mount(&mock_server)
1641            .await;
1642
1643        let config = OidcConfig {
1644            issuer_uri: mock_server.uri(),
1645            aud: None,
1646            shared_secret: None,
1647            bypass: vec![],
1648        };
1649
1650        let provider = OidcProvider::discover(config).await.unwrap();
1651
1652        // Create a JWT header without kid
1653        use serde_json::json;
1654        let header_json = json!({
1655            "alg": "RS256",
1656            "typ": "JWT"
1657            // No kid
1658        });
1659        let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1660            .encode(header_json.to_string().as_bytes());
1661        let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1662            .encode(json!({"sub": "test"}).to_string().as_bytes());
1663        let token = format!("{}.{}.signature", header_b64, payload_b64);
1664
1665        let result = provider.validate_token(&token).await;
1666        assert!(result.is_err());
1667
1668        if let Err(ProxyError::SecurityError(msg)) = result {
1669            assert!(msg.contains("No key ID in token and no shared secret configured"));
1670        } else {
1671            panic!("Expected SecurityError");
1672        }
1673    }
1674
1675    #[tokio::test]
1676    async fn test_validate_token_key_not_found() {
1677        // Setup mock server
1678        let mock_server = MockServer::start().await;
1679
1680        // Mock the discovery endpoint
1681        Mock::given(method("GET"))
1682            .and(path("/"))
1683            .respond_with(ResponseTemplate::new(200)
1684                .set_body_json(serde_json::json!({
1685                    "issuer": mock_server.uri(),
1686                    "jwks_uri": format!("{}/jwks", mock_server.uri())
1687                })))
1688            .mount(&mock_server)
1689            .await;
1690
1691        // Mock the JWKS endpoint
1692        Mock::given(method("GET"))
1693            .and(path("/jwks"))
1694            .respond_with(ResponseTemplate::new(200)
1695                .set_body_json(serde_json::json!({
1696                    "keys": [
1697                        {
1698                            "kty": "RSA",
1699                            "kid": "different-key",
1700                            "use": "sig",
1701                            "alg": "RS256",
1702                            "n": "test-modulus",
1703                            "e": "AQAB"
1704                        }
1705                    ]
1706                })))
1707            .mount(&mock_server)
1708            .await;
1709
1710        let config = OidcConfig {
1711            issuer_uri: mock_server.uri(),
1712            aud: None,
1713            shared_secret: None,
1714            bypass: vec![],
1715        };
1716
1717        let provider = OidcProvider::discover(config).await.unwrap();
1718
1719        // Create a JWT header with non-existent kid
1720        use serde_json::json;
1721        let header_json = json!({
1722            "alg": "RS256",
1723            "typ": "JWT",
1724            "kid": "missing-key"
1725        });
1726        let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1727            .encode(header_json.to_string().as_bytes());
1728        let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
1729            .encode(json!({"sub": "test"}).to_string().as_bytes());
1730        let token = format!("{}.{}.signature", header_b64, payload_b64);
1731
1732        let result = provider.validate_token(&token).await;
1733        assert!(result.is_err());
1734
1735        if let Err(ProxyError::SecurityError(msg)) = result {
1736            assert!(msg.contains("not found in JWKS"));
1737        } else {
1738            panic!("Expected SecurityError");
1739        }
1740    }
1741
1742    #[tokio::test]
1743    async fn test_integration_full_oidc_flow_with_bypass() {
1744        // Setup mock server
1745        let mock_server = MockServer::start().await;
1746
1747        // Mock the discovery endpoint
1748        Mock::given(method("GET"))
1749            .and(path("/"))
1750            .respond_with(ResponseTemplate::new(200)
1751                .set_body_json(serde_json::json!({
1752                    "issuer": mock_server.uri(),
1753                    "jwks_uri": format!("{}/jwks", mock_server.uri())
1754                })))
1755            .mount(&mock_server)
1756            .await;
1757
1758        let config = OidcConfig {
1759            issuer_uri: mock_server.uri(),
1760            aud: Some("test-app".to_string()),
1761            shared_secret: Some("integration-secret".to_string()),
1762            bypass: vec![
1763                RouteRuleConfig {
1764                    methods: vec!["GET".to_string()],
1765                    path: "/health".to_string(),
1766                },
1767                RouteRuleConfig {
1768                    methods: vec!["*".to_string()],
1769                    path: "/public/*".to_string(),
1770                },
1771            ],
1772        };
1773
1774        let provider = OidcProvider::discover(config).await.unwrap();
1775
1776        // Test 1: Bypass should work
1777        let bypass_request = ProxyRequest {
1778            method: HttpMethod::Get,
1779            path: "/health".to_string(),
1780            query: None,
1781            headers: HeaderMap::new(),
1782            body: reqwest::Body::from(Vec::new()),
1783            context: Arc::new(RwLock::new(RequestContext::default())),
1784            custom_target: Some("http://test.example.com".to_string()),
1785        };
1786
1787        let result = provider.pre(bypass_request).await;
1788        assert!(result.is_ok());
1789
1790        // Test 2: Test that non-bypassed requests require authentication
1791        let auth_request = ProxyRequest {
1792            method: HttpMethod::Post,
1793            path: "/api/users".to_string(),
1794            query: None,
1795            headers: HeaderMap::new(), // No authorization header
1796            body: reqwest::Body::from(Vec::new()),
1797            context: Arc::new(RwLock::new(RequestContext::default())),
1798            custom_target: Some("http://test.example.com".to_string()),
1799        };
1800
1801        let result = provider.pre(auth_request).await;
1802        assert!(result.is_err());
1803
1804        if let Err(ProxyError::SecurityError(msg)) = result {
1805            assert!(msg.contains("Missing authorization header"));
1806        } else {
1807            panic!("Expected SecurityError for missing auth header");
1808        }
1809    }
1810
1811    #[tokio::test]
1812    async fn test_integration_full_oidc_flow_auth_failure() {
1813        // Setup mock server
1814        let mock_server = MockServer::start().await;
1815
1816        // Mock the discovery endpoint
1817        Mock::given(method("GET"))
1818            .and(path("/"))
1819            .respond_with(ResponseTemplate::new(200)
1820                .set_body_json(serde_json::json!({
1821                    "issuer": mock_server.uri(),
1822                    "jwks_uri": format!("{}/jwks", mock_server.uri())
1823                })))
1824            .mount(&mock_server)
1825            .await;
1826
1827        let config = OidcConfig {
1828            issuer_uri: mock_server.uri(),
1829            aud: None,
1830            shared_secret: Some("test-secret".to_string()),
1831            bypass: vec![],
1832        };
1833
1834        let provider = OidcProvider::discover(config).await.unwrap();
1835
1836        // Test with missing authorization header
1837        let request = ProxyRequest {
1838            method: HttpMethod::Post,
1839            path: "/api/users".to_string(),
1840            query: None,
1841            headers: HeaderMap::new(),
1842            body: reqwest::Body::from(Vec::new()),
1843            context: Arc::new(RwLock::new(RequestContext::default())),
1844            custom_target: Some("http://test.example.com".to_string()),
1845        };
1846
1847        let result = provider.pre(request).await;
1848        assert!(result.is_err());
1849
1850        if let Err(ProxyError::SecurityError(msg)) = result {
1851            assert!(msg.contains("Missing authorization header"));
1852        } else {
1853            panic!("Expected SecurityError");
1854        }
1855    }
1856
1857    #[test]
1858    fn test_oidc_provider_is_bypassed() {
1859        let mut builder1 = GlobSetBuilder::new();
1860        builder1.add(Glob::new("/health").unwrap());
1861        let paths1 = builder1.build().unwrap();
1862
1863        let mut builder2 = GlobSetBuilder::new();
1864        builder2.add(Glob::new("/public/*").unwrap());
1865        let paths2 = builder2.build().unwrap();
1866
1867        let rules = vec![
1868            RouteRule {
1869                methods: vec!["GET".to_string()],
1870                paths: paths1,
1871            },
1872            RouteRule {
1873                methods: vec!["*".to_string()],
1874                paths: paths2,
1875            },
1876        ];
1877
1878        let provider = OidcProvider {
1879            issuer: "https://auth.example.com".to_string(),
1880            aud: None,
1881            shared_secret: None,
1882            jwks_uri: "https://auth.example.com/jwks".to_string(),
1883            jwks: Arc::new(RwLock::new(None)),
1884            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1885            http: reqwest::Client::new(),
1886            rules,
1887        };
1888
1889        // Test bypass rules
1890        assert!(provider.is_bypassed("GET", "/health"));
1891        assert!(!provider.is_bypassed("POST", "/health"));
1892        assert!(provider.is_bypassed("GET", "/public/api"));
1893        assert!(provider.is_bypassed("POST", "/public/api"));
1894        assert!(!provider.is_bypassed("GET", "/private/api"));
1895    }
1896
1897    #[test]
1898    fn test_security_provider_trait_implementation() {
1899        let provider = OidcProvider {
1900            issuer: "https://auth.example.com".to_string(),
1901            aud: None,
1902            shared_secret: None,
1903            jwks_uri: "https://auth.example.com/jwks".to_string(),
1904            jwks: Arc::new(RwLock::new(None)),
1905            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1906            http: reqwest::Client::new(),
1907            rules: vec![],
1908        };
1909
1910        assert_eq!(provider.name(), "OidcProvider");
1911        assert_eq!(provider.stage(), SecurityStage::Pre);
1912    }
1913
1914    #[tokio::test]
1915    async fn test_oidc_provider_pre_bypass() {
1916        let mut builder = GlobSetBuilder::new();
1917        builder.add(Glob::new("/health").unwrap());
1918        let paths = builder.build().unwrap();
1919
1920        let rules = vec![
1921            RouteRule {
1922                methods: vec!["GET".to_string()],
1923                paths,
1924            },
1925        ];
1926
1927        let provider = OidcProvider {
1928            issuer: "https://auth.example.com".to_string(),
1929            aud: None,
1930            shared_secret: None,
1931            jwks_uri: "https://auth.example.com/jwks".to_string(),
1932            jwks: Arc::new(RwLock::new(None)),
1933            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1934            http: reqwest::Client::new(),
1935            rules,
1936        };
1937
1938        let headers = HeaderMap::new();
1939        let request = ProxyRequest {
1940            method: HttpMethod::Get,
1941            path: "/health".to_string(),
1942            query: None,
1943            headers,
1944            body: reqwest::Body::from(""),
1945            context: Arc::new(RwLock::new(RequestContext::default())),
1946            custom_target: None,
1947        };
1948
1949        let result = provider.pre(request).await;
1950        assert!(result.is_ok());
1951    }
1952
1953    #[tokio::test]
1954    async fn test_oidc_provider_pre_missing_auth_header() {
1955        let provider = OidcProvider {
1956            issuer: "https://auth.example.com".to_string(),
1957            aud: None,
1958            shared_secret: None,
1959            jwks_uri: "https://auth.example.com/jwks".to_string(),
1960            jwks: Arc::new(RwLock::new(None)),
1961            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1962            http: reqwest::Client::new(),
1963            rules: vec![],
1964        };
1965
1966        let headers = HeaderMap::new();
1967        let request = ProxyRequest {
1968            method: HttpMethod::Get,
1969            path: "/api/users".to_string(),
1970            query: None,
1971            headers,
1972            body: reqwest::Body::from(""),
1973            context: Arc::new(RwLock::new(RequestContext::default())),
1974            custom_target: None,
1975        };
1976
1977        let result = provider.pre(request).await;
1978        assert!(result.is_err());
1979
1980        if let Err(ProxyError::SecurityError(msg)) = result {
1981            assert_eq!(msg, "Missing authorization header");
1982        } else {
1983            panic!("Expected SecurityError");
1984        }
1985    }
1986
1987    #[tokio::test]
1988    async fn test_oidc_provider_pre_invalid_auth_scheme() {
1989        let provider = OidcProvider {
1990            issuer: "https://auth.example.com".to_string(),
1991            aud: None,
1992            shared_secret: None,
1993            jwks_uri: "https://auth.example.com/jwks".to_string(),
1994            jwks: Arc::new(RwLock::new(None)),
1995            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
1996            http: reqwest::Client::new(),
1997            rules: vec![],
1998        };
1999
2000        let mut headers = HeaderMap::new();
2001        headers.insert("authorization", "Basic dXNlcjpwYXNz".parse().unwrap());
2002
2003        let request = ProxyRequest {
2004            method: HttpMethod::Get,
2005            path: "/api/users".to_string(),
2006            query: None,
2007            headers,
2008            body: reqwest::Body::from(""),
2009            context: Arc::new(RwLock::new(RequestContext::default())),
2010            custom_target: None,
2011        };
2012
2013        let result = provider.pre(request).await;
2014        assert!(result.is_err());
2015
2016        if let Err(ProxyError::SecurityError(msg)) = result {
2017            assert!(msg.contains("Invalid authorization scheme"));
2018            assert!(msg.contains("expected 'Bearer'"));
2019        } else {
2020            panic!("Expected SecurityError");
2021        }
2022    }
2023
2024    #[tokio::test]
2025    async fn test_oidc_provider_pre_empty_bearer_token() {
2026        let provider = OidcProvider {
2027            issuer: "https://auth.example.com".to_string(),
2028            aud: None,
2029            shared_secret: None,
2030            jwks_uri: "https://auth.example.com/jwks".to_string(),
2031            jwks: Arc::new(RwLock::new(None)),
2032            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2033            http: reqwest::Client::new(),
2034            rules: vec![],
2035        };
2036
2037        let mut headers = HeaderMap::new();
2038        headers.insert("authorization", "Bearer ".parse().unwrap());
2039
2040        let request = ProxyRequest {
2041            method: HttpMethod::Get,
2042            path: "/api/users".to_string(),
2043            query: None,
2044            headers,
2045            body: reqwest::Body::from(""),
2046            context: Arc::new(RwLock::new(RequestContext::default())),
2047            custom_target: None,
2048        };
2049
2050        let result = provider.pre(request).await;
2051        assert!(result.is_err());
2052
2053        if let Err(ProxyError::SecurityError(msg)) = result {
2054            assert_eq!(msg, "Empty bearer token");
2055        } else {
2056            panic!("Expected SecurityError");
2057        }
2058    }
2059
2060    #[tokio::test]
2061    async fn test_jwk_to_decoding_key_rsa_success() {
2062        // Setup mock server for discovery
2063        let mock_server = MockServer::start().await;
2064
2065        Mock::given(method("GET"))
2066            .and(path("/"))
2067            .respond_with(ResponseTemplate::new(200)
2068                .set_body_json(serde_json::json!({
2069                    "issuer": mock_server.uri(),
2070                    "jwks_uri": format!("{}/jwks", mock_server.uri())
2071                })))
2072            .mount(&mock_server)
2073            .await;
2074
2075        // Mock JWKS with RSA key
2076        Mock::given(method("GET"))
2077            .and(path("/jwks"))
2078            .respond_with(ResponseTemplate::new(200)
2079                .set_body_json(serde_json::json!({
2080                    "keys": [
2081                        {
2082                            "kty": "RSA",
2083                            "kid": "rsa-key-1",
2084                            "use": "sig",
2085                            "alg": "RS256",
2086                            "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
2087                            "e": "AQAB"
2088                        }
2089                    ]
2090                })))
2091            .mount(&mock_server)
2092            .await;
2093
2094        let config = OidcConfig {
2095            issuer_uri: mock_server.uri(),
2096            aud: None,
2097            shared_secret: None,
2098            bypass: vec![],
2099        };
2100
2101        let provider = OidcProvider::discover(config).await.unwrap();
2102
2103        // Force JWKS refresh
2104        provider.refresh_jwks().await.unwrap();
2105
2106        let jwks = provider.jwks.read().await;
2107        let jwks = jwks.as_ref().unwrap();
2108        let jwk = &jwks.keys[0];
2109
2110        let result = provider.jwk_to_decoding_key(jwk);
2111        assert!(result.is_ok());
2112    }
2113
2114    #[tokio::test]
2115    async fn test_jwk_to_decoding_key_ec_success() {
2116        // Setup mock server for discovery
2117        let mock_server = MockServer::start().await;
2118
2119        Mock::given(method("GET"))
2120            .and(path("/"))
2121            .respond_with(ResponseTemplate::new(200)
2122                .set_body_json(serde_json::json!({
2123                    "issuer": mock_server.uri(),
2124                    "jwks_uri": format!("{}/jwks", mock_server.uri())
2125                })))
2126            .mount(&mock_server)
2127            .await;
2128
2129        // Mock JWKS with EC key
2130        Mock::given(method("GET"))
2131            .and(path("/jwks"))
2132            .respond_with(ResponseTemplate::new(200)
2133                .set_body_json(serde_json::json!({
2134                    "keys": [
2135                        {
2136                            "kty": "EC",
2137                            "kid": "ec-key-1",
2138                            "use": "sig",
2139                            "alg": "ES256",
2140                            "crv": "P-256",
2141                            "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
2142                            "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
2143                        }
2144                    ]
2145                })))
2146            .mount(&mock_server)
2147            .await;
2148
2149        let config = OidcConfig {
2150            issuer_uri: mock_server.uri(),
2151            aud: None,
2152            shared_secret: None,
2153            bypass: vec![],
2154        };
2155
2156        let provider = OidcProvider::discover(config).await.unwrap();
2157
2158        // Force JWKS refresh
2159        provider.refresh_jwks().await.unwrap();
2160
2161        let jwks = provider.jwks.read().await;
2162        let jwks = jwks.as_ref().unwrap();
2163        let jwk = &jwks.keys[0];
2164
2165        let result = provider.jwk_to_decoding_key(jwk);
2166        assert!(result.is_ok());
2167    }
2168
2169    #[tokio::test]
2170    async fn test_jwk_to_decoding_key_octet_key_success() {
2171        // Setup mock server for discovery
2172        let mock_server = MockServer::start().await;
2173
2174        Mock::given(method("GET"))
2175            .and(path("/"))
2176            .respond_with(ResponseTemplate::new(200)
2177                .set_body_json(serde_json::json!({
2178                    "issuer": mock_server.uri(),
2179                    "jwks_uri": format!("{}/jwks", mock_server.uri())
2180                })))
2181            .mount(&mock_server)
2182            .await;
2183
2184        // Mock JWKS with octet key (HMAC)
2185        Mock::given(method("GET"))
2186            .and(path("/jwks"))
2187            .respond_with(ResponseTemplate::new(200)
2188                .set_body_json(serde_json::json!({
2189                    "keys": [
2190                        {
2191                            "kty": "oct",
2192                            "kid": "hmac-key-1",
2193                            "use": "sig",
2194                            "alg": "HS256",
2195                            "k": "GawgguFyGrWKav7AX4VKUg"
2196                        }
2197                    ]
2198                })))
2199            .mount(&mock_server)
2200            .await;
2201
2202        let config = OidcConfig {
2203            issuer_uri: mock_server.uri(),
2204            aud: None,
2205            shared_secret: None,
2206            bypass: vec![],
2207        };
2208
2209        let provider = OidcProvider::discover(config).await.unwrap();
2210
2211        // Force JWKS refresh
2212        provider.refresh_jwks().await.unwrap();
2213
2214        let jwks = provider.jwks.read().await;
2215        let jwks = jwks.as_ref().unwrap();
2216        let jwk = &jwks.keys[0];
2217
2218        let result = provider.jwk_to_decoding_key(jwk);
2219        assert!(result.is_ok());
2220    }
2221
2222    #[tokio::test]
2223    async fn test_jwk_to_decoding_key_okp_success() {
2224        // Setup mock server for discovery
2225        let mock_server = MockServer::start().await;
2226
2227        Mock::given(method("GET"))
2228            .and(path("/"))
2229            .respond_with(ResponseTemplate::new(200)
2230                .set_body_json(serde_json::json!({
2231                    "issuer": mock_server.uri(),
2232                    "jwks_uri": format!("{}/jwks", mock_server.uri())
2233                })))
2234            .mount(&mock_server)
2235            .await;
2236
2237        // Mock JWKS with OKP key (EdDSA)
2238        Mock::given(method("GET"))
2239            .and(path("/jwks"))
2240            .respond_with(ResponseTemplate::new(200)
2241                .set_body_json(serde_json::json!({
2242                    "keys": [
2243                        {
2244                            "kty": "OKP",
2245                            "kid": "ed25519-key-1",
2246                            "use": "sig",
2247                            "alg": "EdDSA",
2248                            "crv": "Ed25519",
2249                            "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
2250                        }
2251                    ]
2252                })))
2253            .mount(&mock_server)
2254            .await;
2255
2256        let config = OidcConfig {
2257            issuer_uri: mock_server.uri(),
2258            aud: None,
2259            shared_secret: None,
2260            bypass: vec![],
2261        };
2262
2263        let provider = OidcProvider::discover(config).await.unwrap();
2264
2265        // Force JWKS refresh
2266        provider.refresh_jwks().await.unwrap();
2267
2268        let jwks = provider.jwks.read().await;
2269        let jwks = jwks.as_ref().unwrap();
2270        let jwk = &jwks.keys[0];
2271
2272        let result = provider.jwk_to_decoding_key(jwk);
2273        assert!(result.is_ok());
2274    }
2275
2276    #[tokio::test]
2277    async fn test_validate_token_with_kid_fallback_to_shared_secret() {
2278        let provider = OidcProvider {
2279            issuer: "https://auth.example.com".to_string(),
2280            aud: None,
2281            shared_secret: Some("test-secret".to_string()),
2282            jwks_uri: "https://auth.example.com/jwks".to_string(),
2283            jwks: Arc::new(RwLock::new(Some(JwkSet { keys: vec![] }))), // Empty JWKS
2284            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2285            http: reqwest::Client::new(),
2286            rules: vec![],
2287        };
2288
2289        // Create a HMAC JWT token with kid that won't be found in JWKS
2290        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
2291
2292        let mut header = Header::new(Algorithm::HS256);
2293        header.kid = Some("missing-key".to_string());
2294
2295        #[derive(serde::Serialize)]
2296        struct TestClaims {
2297            iss: String,
2298            sub: String,
2299            exp: i64,
2300        }
2301
2302        let claims = TestClaims {
2303            iss: "https://auth.example.com".to_string(),
2304            sub: "test-user".to_string(),
2305            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
2306        };
2307
2308        let token = encode(
2309            &header,
2310            &claims,
2311            &EncodingKey::from_secret("test-secret".as_ref())
2312        ).unwrap();
2313
2314        let result = provider.validate_token(&token).await;
2315        assert!(result.is_ok());
2316
2317        let validated_claims = result.unwrap();
2318        assert_eq!(validated_claims["iss"], "https://auth.example.com");
2319        assert_eq!(validated_claims["sub"], "test-user");
2320    }
2321
2322    #[tokio::test]
2323    async fn test_validate_token_non_hmac_algorithm_with_missing_kid() {
2324        let provider = OidcProvider {
2325            issuer: "https://auth.example.com".to_string(),
2326            aud: None,
2327            shared_secret: Some("test-secret".to_string()),
2328            jwks_uri: "https://auth.example.com/jwks".to_string(),
2329            jwks: Arc::new(RwLock::new(Some(JwkSet { keys: vec![] }))), // Empty JWKS
2330            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2331            http: reqwest::Client::new(),
2332            rules: vec![],
2333        };
2334
2335        // Create a JWT header with RS256 algorithm and missing kid
2336        use serde_json::json;
2337        let header_json = json!({
2338            "alg": "RS256",
2339            "typ": "JWT",
2340            "kid": "missing-rsa-key"
2341        });
2342        let header_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
2343            .encode(header_json.to_string().as_bytes());
2344        let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
2345            .encode(json!({"sub": "test"}).to_string().as_bytes());
2346        let token = format!("{}.{}.signature", header_b64, payload_b64);
2347
2348        let result = provider.validate_token(&token).await;
2349        assert!(result.is_err());
2350
2351        if let Err(ProxyError::SecurityError(msg)) = result {
2352            assert!(msg.contains("not found in JWKS"));
2353        } else {
2354            panic!("Expected SecurityError");
2355        }
2356    }
2357
2358    #[tokio::test]
2359    async fn test_validate_token_different_algorithms() {
2360        let provider = OidcProvider {
2361            issuer: "https://auth.example.com".to_string(),
2362            aud: None,
2363            shared_secret: Some("test-secret-key-for-hs384".to_string()),
2364            jwks_uri: "https://auth.example.com/jwks".to_string(),
2365            jwks: Arc::new(RwLock::new(None)),
2366            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2367            http: reqwest::Client::new(),
2368            rules: vec![],
2369        };
2370
2371        // Test HS384 algorithm
2372        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
2373
2374        let header = Header::new(Algorithm::HS384);
2375
2376        #[derive(serde::Serialize)]
2377        struct TestClaims {
2378            iss: String,
2379            sub: String,
2380            exp: i64,
2381        }
2382
2383        let claims = TestClaims {
2384            iss: "https://auth.example.com".to_string(),
2385            sub: "test-user".to_string(),
2386            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
2387        };
2388
2389        let token = encode(
2390            &header,
2391            &claims,
2392            &EncodingKey::from_secret("test-secret-key-for-hs384".as_ref())
2393        ).unwrap();
2394
2395        let result = provider.validate_token(&token).await;
2396        assert!(result.is_ok());
2397
2398        let validated_claims = result.unwrap();
2399        assert_eq!(validated_claims["iss"], "https://auth.example.com");
2400        assert_eq!(validated_claims["sub"], "test-user");
2401    }
2402
2403    #[tokio::test]
2404    async fn test_validate_token_hs512_algorithm() {
2405        let provider = OidcProvider {
2406            issuer: "https://auth.example.com".to_string(),
2407            aud: None,
2408            shared_secret: Some("test-secret-key-for-hs512-algorithm".to_string()),
2409            jwks_uri: "https://auth.example.com/jwks".to_string(),
2410            jwks: Arc::new(RwLock::new(None)),
2411            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2412            http: reqwest::Client::new(),
2413            rules: vec![],
2414        };
2415
2416        // Test HS512 algorithm
2417        use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
2418
2419        let header = Header::new(Algorithm::HS512);
2420
2421        #[derive(serde::Serialize)]
2422        struct TestClaims {
2423            iss: String,
2424            sub: String,
2425            exp: i64,
2426        }
2427
2428        let claims = TestClaims {
2429            iss: "https://auth.example.com".to_string(),
2430            sub: "test-user".to_string(),
2431            exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64,
2432        };
2433
2434        let token = encode(
2435            &header,
2436            &claims,
2437            &EncodingKey::from_secret("test-secret-key-for-hs512-algorithm".as_ref())
2438        ).unwrap();
2439
2440        let result = provider.validate_token(&token).await;
2441        assert!(result.is_ok());
2442
2443        let validated_claims = result.unwrap();
2444        assert_eq!(validated_claims["iss"], "https://auth.example.com");
2445        assert_eq!(validated_claims["sub"], "test-user");
2446    }
2447
2448    #[test]
2449    fn test_validate_std_claims_success() {
2450        let provider = OidcProvider {
2451            issuer: "https://auth.example.com".to_string(),
2452            aud: Some("test-audience".to_string()),
2453            shared_secret: None,
2454            jwks_uri: "https://auth.example.com/jwks".to_string(),
2455            jwks: Arc::new(RwLock::new(None)),
2456            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2457            http: reqwest::Client::new(),
2458            rules: vec![],
2459        };
2460
2461        let claims = serde_json::json!({
2462            "iss": "https://auth.example.com",
2463            "aud": "test-audience",
2464            "sub": "test-user",
2465            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2466        });
2467
2468        let result = provider.validate_std_claims(&claims);
2469        assert!(result.is_ok());
2470    }
2471
2472    #[test]
2473    fn test_validate_std_claims_wrong_issuer() {
2474        let provider = OidcProvider {
2475            issuer: "https://auth.example.com".to_string(),
2476            aud: None,
2477            shared_secret: None,
2478            jwks_uri: "https://auth.example.com/jwks".to_string(),
2479            jwks: Arc::new(RwLock::new(None)),
2480            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2481            http: reqwest::Client::new(),
2482            rules: vec![],
2483        };
2484
2485        let claims = serde_json::json!({
2486            "iss": "https://wrong-issuer.com",
2487            "sub": "test-user",
2488            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2489        });
2490
2491        let result = provider.validate_std_claims(&claims);
2492        assert!(result.is_err());
2493
2494        if let Err(ProxyError::SecurityError(msg)) = result {
2495            assert!(msg.contains("Invalid issuer"));
2496            assert!(msg.contains("expected 'https://auth.example.com'"));
2497            assert!(msg.contains("got 'https://wrong-issuer.com'"));
2498        } else {
2499            panic!("Expected SecurityError");
2500        }
2501    }
2502
2503    #[test]
2504    fn test_validate_std_claims_missing_issuer() {
2505        let provider = OidcProvider {
2506            issuer: "https://auth.example.com".to_string(),
2507            aud: None,
2508            shared_secret: None,
2509            jwks_uri: "https://auth.example.com/jwks".to_string(),
2510            jwks: Arc::new(RwLock::new(None)),
2511            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2512            http: reqwest::Client::new(),
2513            rules: vec![],
2514        };
2515
2516        let claims = serde_json::json!({
2517            "sub": "test-user",
2518            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2519        });
2520
2521        let result = provider.validate_std_claims(&claims);
2522        assert!(result.is_err());
2523
2524        if let Err(ProxyError::SecurityError(msg)) = result {
2525            assert_eq!(msg, "Missing issuer claim");
2526        } else {
2527            panic!("Expected SecurityError");
2528        }
2529    }
2530
2531    #[test]
2532    fn test_validate_std_claims_audience_array_success() {
2533        let provider = OidcProvider {
2534            issuer: "https://auth.example.com".to_string(),
2535            aud: Some("test-audience".to_string()),
2536            shared_secret: None,
2537            jwks_uri: "https://auth.example.com/jwks".to_string(),
2538            jwks: Arc::new(RwLock::new(None)),
2539            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2540            http: reqwest::Client::new(),
2541            rules: vec![],
2542        };
2543
2544        let claims = serde_json::json!({
2545            "iss": "https://auth.example.com",
2546            "aud": ["other-audience", "test-audience", "another-audience"],
2547            "sub": "test-user",
2548            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2549        });
2550
2551        let result = provider.validate_std_claims(&claims);
2552        assert!(result.is_ok());
2553    }
2554
2555    #[test]
2556    fn test_validate_std_claims_audience_array_failure() {
2557        let provider = OidcProvider {
2558            issuer: "https://auth.example.com".to_string(),
2559            aud: Some("test-audience".to_string()),
2560            shared_secret: None,
2561            jwks_uri: "https://auth.example.com/jwks".to_string(),
2562            jwks: Arc::new(RwLock::new(None)),
2563            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2564            http: reqwest::Client::new(),
2565            rules: vec![],
2566        };
2567
2568        let claims = serde_json::json!({
2569            "iss": "https://auth.example.com",
2570            "aud": ["other-audience", "wrong-audience", "another-audience"],
2571            "sub": "test-user",
2572            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2573        });
2574
2575        let result = provider.validate_std_claims(&claims);
2576        assert!(result.is_err());
2577
2578        if let Err(ProxyError::SecurityError(msg)) = result {
2579            assert!(msg.contains("Invalid audience"));
2580            assert!(msg.contains("expected 'test-audience'"));
2581        } else {
2582            panic!("Expected SecurityError");
2583        }
2584    }
2585
2586    #[test]
2587    fn test_validate_std_claims_invalid_audience_type() {
2588        let provider = OidcProvider {
2589            issuer: "https://auth.example.com".to_string(),
2590            aud: Some("test-audience".to_string()),
2591            shared_secret: None,
2592            jwks_uri: "https://auth.example.com/jwks".to_string(),
2593            jwks: Arc::new(RwLock::new(None)),
2594            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2595            http: reqwest::Client::new(),
2596            rules: vec![],
2597        };
2598
2599        let claims = serde_json::json!({
2600            "iss": "https://auth.example.com",
2601            "aud": 12345, // Invalid type (number instead of string/array)
2602            "sub": "test-user",
2603            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2604        });
2605
2606        let result = provider.validate_std_claims(&claims);
2607        assert!(result.is_err());
2608
2609        if let Err(ProxyError::SecurityError(msg)) = result {
2610            assert!(msg.contains("Invalid audience"));
2611            assert!(msg.contains("expected 'test-audience'"));
2612        } else {
2613            panic!("Expected SecurityError");
2614        }
2615    }
2616
2617    #[test]
2618    fn test_validate_std_claims_expired_token() {
2619        let provider = OidcProvider {
2620            issuer: "https://auth.example.com".to_string(),
2621            aud: None,
2622            shared_secret: None,
2623            jwks_uri: "https://auth.example.com/jwks".to_string(),
2624            jwks: Arc::new(RwLock::new(None)),
2625            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2626            http: reqwest::Client::new(),
2627            rules: vec![],
2628        };
2629
2630        let claims = serde_json::json!({
2631            "iss": "https://auth.example.com",
2632            "sub": "test-user",
2633            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() - 3600) as i64 // Expired 1 hour ago
2634        });
2635
2636        let result = provider.validate_std_claims(&claims);
2637        assert!(result.is_err());
2638
2639        if let Err(ProxyError::SecurityError(msg)) = result {
2640            assert!(msg.contains("Token expired"));
2641        } else {
2642            panic!("Expected SecurityError");
2643        }
2644    }
2645
2646    #[test]
2647    fn test_validate_std_claims_no_audience_configured() {
2648        let provider = OidcProvider {
2649            issuer: "https://auth.example.com".to_string(),
2650            aud: None, // No audience configured
2651            shared_secret: None,
2652            jwks_uri: "https://auth.example.com/jwks".to_string(),
2653            jwks: Arc::new(RwLock::new(None)),
2654            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2655            http: reqwest::Client::new(),
2656            rules: vec![],
2657        };
2658
2659        let claims = serde_json::json!({
2660            "iss": "https://auth.example.com",
2661            "aud": "any-audience", // Should be ignored since no audience is configured
2662            "sub": "test-user",
2663            "exp": (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as i64
2664        });
2665
2666        let result = provider.validate_std_claims(&claims);
2667        assert!(result.is_ok());
2668    }
2669
2670
2671
2672    #[tokio::test]
2673    async fn test_jwks_refresh_empty_keys() {
2674        // Setup mock server
2675        let mock_server = MockServer::start().await;
2676
2677        // Mock the discovery endpoint
2678        Mock::given(method("GET"))
2679            .and(path("/"))
2680            .respond_with(ResponseTemplate::new(200)
2681                .set_body_json(serde_json::json!({
2682                    "issuer": mock_server.uri(),
2683                    "jwks_uri": format!("{}/jwks", mock_server.uri())
2684                })))
2685            .mount(&mock_server)
2686            .await;
2687
2688        // Mock the JWKS endpoint with empty keys array
2689        Mock::given(method("GET"))
2690            .and(path("/jwks"))
2691            .respond_with(ResponseTemplate::new(200)
2692                .set_body_json(serde_json::json!({
2693                    "keys": []
2694                })))
2695            .mount(&mock_server)
2696            .await;
2697
2698        let config = OidcConfig {
2699            issuer_uri: mock_server.uri(),
2700            aud: None,
2701            shared_secret: None,
2702            bypass: vec![],
2703        };
2704
2705        let provider = OidcProvider::discover(config).await.unwrap();
2706
2707        // Force cache expiration
2708        {
2709            let mut jwks_w = provider.jwks.write().await;
2710            *jwks_w = None;
2711        }
2712        {
2713            let mut refresh_w = provider.last_refresh.write().await;
2714            *refresh_w = tokio::time::Instant::now().checked_sub(JWKS_REFRESH + std::time::Duration::from_secs(1))
2715                .unwrap_or_else(|| {
2716                    tokio::time::Instant::now().checked_sub(std::time::Duration::from_millis(1))
2717                        .unwrap_or_else(|| tokio::time::Instant::now())
2718                });
2719        }
2720
2721        let result = provider.refresh_jwks().await;
2722        assert!(result.is_ok());
2723
2724        // Verify JWKS was cached with empty keys
2725        let jwks = provider.jwks.read().await;
2726        assert!(jwks.is_some());
2727        let jwks = jwks.as_ref().unwrap();
2728        assert_eq!(jwks.keys.len(), 0);
2729    }
2730
2731    #[tokio::test]
2732    async fn test_oidc_provider_discover_glob_set_build_failure() {
2733        // This test is challenging because GlobSetBuilder::build() rarely fails
2734        // after Glob::new() succeeds. We'll test the error path by creating
2735        // a scenario that could theoretically cause build() to fail.
2736
2737        // Setup mock server
2738        let mock_server = MockServer::start().await;
2739
2740        Mock::given(method("GET"))
2741            .and(path("/"))
2742            .respond_with(ResponseTemplate::new(200)
2743                .set_body_json(serde_json::json!({
2744                    "issuer": mock_server.uri(),
2745                    "jwks_uri": format!("{}/jwks", mock_server.uri())
2746                })))
2747            .mount(&mock_server)
2748            .await;
2749
2750        // Create a config with a very complex glob pattern that might stress the builder
2751        let config = OidcConfig {
2752            issuer_uri: mock_server.uri(),
2753            aud: None,
2754            shared_secret: None,
2755            bypass: vec![
2756                RouteRuleConfig {
2757                    methods: vec!["GET".to_string()],
2758                    path: "/api/*".to_string(), // Simple valid pattern
2759                },
2760            ],
2761        };
2762
2763        // This should succeed since we're using a valid pattern
2764        let result = OidcProvider::discover(config).await;
2765        assert!(result.is_ok());
2766    }
2767
2768    #[tokio::test]
2769    async fn test_multiple_bypass_rules_overlapping() {
2770        // Setup mock server
2771        let mock_server = MockServer::start().await;
2772
2773        Mock::given(method("GET"))
2774            .and(path("/"))
2775            .respond_with(ResponseTemplate::new(200)
2776                .set_body_json(serde_json::json!({
2777                    "issuer": mock_server.uri(),
2778                    "jwks_uri": format!("{}/jwks", mock_server.uri())
2779                })))
2780            .mount(&mock_server)
2781            .await;
2782
2783        let config = OidcConfig {
2784            issuer_uri: mock_server.uri(),
2785            aud: None,
2786            shared_secret: None,
2787            bypass: vec![
2788                RouteRuleConfig {
2789                    methods: vec!["GET".to_string()],
2790                    path: "/api/*".to_string(),
2791                },
2792                RouteRuleConfig {
2793                    methods: vec!["*".to_string()],
2794                    path: "/api/health".to_string(), // Overlaps with first rule
2795                },
2796                RouteRuleConfig {
2797                    methods: vec!["POST".to_string(), "PUT".to_string()],
2798                    path: "/api/users/*".to_string(),
2799                },
2800            ],
2801        };
2802
2803        let provider = OidcProvider::discover(config).await.unwrap();
2804        assert_eq!(provider.rules.len(), 3);
2805
2806        // Test overlapping rules - should match first applicable rule
2807        assert!(provider.is_bypassed("GET", "/api/health")); // Matches both rule 1 and 2
2808        assert!(provider.is_bypassed("DELETE", "/api/health")); // Matches rule 2 only
2809        assert!(provider.is_bypassed("POST", "/api/users/123")); // Matches rule 3
2810        assert!(provider.is_bypassed("GET", "/api/users/123")); // Matches rule 1 (GET /api/*)
2811    }
2812
2813    #[tokio::test]
2814    async fn test_authorization_header_with_extra_whitespace() {
2815        let provider = OidcProvider {
2816            issuer: "https://auth.example.com".to_string(),
2817            aud: None,
2818            shared_secret: Some("test-secret".to_string()),
2819            jwks_uri: "https://auth.example.com/jwks".to_string(),
2820            jwks: Arc::new(RwLock::new(None)),
2821            last_refresh: Arc::new(RwLock::new(tokio::time::Instant::now())),
2822            http: reqwest::Client::new(),
2823            rules: vec![],
2824        };
2825
2826        // Test with extra whitespace in authorization header - should fail
2827        // because the current implementation doesn't handle extra whitespace
2828        let mut headers = HeaderMap::new();
2829        headers.insert("authorization", "  Bearer   token123  ".parse().unwrap());
2830
2831        let request = ProxyRequest {
2832            method: HttpMethod::Get,
2833            path: "/api/users".to_string(),
2834            query: None,
2835            headers,
2836            body: reqwest::Body::from(""),
2837            context: Arc::new(RwLock::new(RequestContext::default())),
2838            custom_target: None,
2839        };
2840
2841        let result = provider.pre(request).await;
2842        assert!(result.is_err());
2843
2844        // Should fail because "  bearer   " doesn't match "bearer "
2845        if let Err(ProxyError::SecurityError(msg)) = result {
2846            assert!(msg.contains("Invalid authorization scheme"));
2847        } else {
2848            panic!("Expected SecurityError");
2849        }
2850    }
2851
2852    #[test]
2853    fn test_route_rule_matches_edge_cases() {
2854        let mut builder = GlobSetBuilder::new();
2855        builder.add(Glob::new("/**").unwrap()); // Match everything
2856        let paths = builder.build().unwrap();
2857
2858        let rule = RouteRule {
2859            methods: vec!["GET".to_string(), "POST".to_string()],
2860            paths,
2861        };
2862
2863        // Test various path formats
2864        assert!(rule.matches("GET", "/"));
2865        assert!(rule.matches("POST", "/api"));
2866        assert!(rule.matches("GET", "/api/v1/users/123"));
2867        assert!(rule.matches("POST", "/very/deep/nested/path/structure"));
2868        assert!(!rule.matches("DELETE", "/api")); // Wrong method
2869        assert!(!rule.matches("PUT", "/")); // Wrong method
2870    }
2871
2872    #[test]
2873    fn test_route_rule_matches_empty_methods() {
2874        let mut builder = GlobSetBuilder::new();
2875        builder.add(Glob::new("/health").unwrap());
2876        let paths = builder.build().unwrap();
2877
2878        let rule = RouteRule {
2879            methods: vec![], // Empty methods list
2880            paths,
2881        };
2882
2883        // Should not match anything since no methods are allowed
2884        assert!(!rule.matches("GET", "/health"));
2885        assert!(!rule.matches("POST", "/health"));
2886        assert!(!rule.matches("*", "/health"));
2887    }
2888}
2889
2890#[async_trait]
2891impl SecurityProvider for OidcProvider {
2892    fn name(&self) -> &str { "OidcProvider" }
2893
2894    fn stage(&self) -> SecurityStage { SecurityStage::Pre }
2895
2896    async fn pre(&self, req: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
2897        // 0) Bypass?
2898        if self.is_bypassed(&req.method.to_string(), &req.path) {
2899            debug_fmt!("OidcProvider", "OIDC bypass for {} {}", req.method, req.path);
2900            return Ok(req);
2901        }
2902
2903        debug_fmt!("OidcProvider", "OIDC validating request: {} {}", req.method, req.path);
2904
2905        // 1) Extract bearer token
2906        let auth_header = match req.headers.get("authorization") {
2907            Some(h) => match h.to_str() {
2908                Ok(s) => s.to_lowercase(),
2909                Err(e) => {
2910                    let err = ProxyError::SecurityError(
2911                        format!("Invalid authorization header: {e}")
2912                    );
2913                    warn_fmt!("OidcProvider", "{}", err);
2914                    return Err(err);
2915                }
2916            },
2917            None => {
2918                let err = ProxyError::SecurityError("Missing authorization header".to_string());
2919                warn_fmt!("OidcProvider", "{}", err);
2920                return Err(err);
2921            }
2922        };
2923
2924        if !auth_header.starts_with(BEARER) {
2925            let err = ProxyError::SecurityError(
2926                format!("Invalid authorization scheme: expected 'Bearer', got '{}'", 
2927                    auth_header.split_whitespace().next().unwrap_or(""))
2928            );
2929            warn_fmt!("OidcProvider", "{}", err);
2930            return Err(err);
2931        }
2932
2933        let token = &auth_header[BEARER.len()..];
2934        if token.is_empty() {
2935            let err = ProxyError::SecurityError("Empty bearer token".to_string());
2936            warn_fmt!("OidcProvider", "{}", err);
2937            return Err(err);
2938        }
2939
2940        // 2) Validate the token
2941        trace_fmt!("OidcProvider", "Validating token: {}", token);
2942        let claims = match self.validate_token(token).await {
2943            Ok(claims) => claims,
2944            Err(e) => {
2945                warn_fmt!("OidcProvider", "Token validation failed: {}", e);
2946                return Err(e);
2947            }
2948        };
2949
2950        // 3) Store claims in request context
2951        {
2952            let mut ctx = req.context.write().await;
2953            ctx.attributes.insert(CLAIMS_ATTRIBUTE.to_string(), claims);
2954        }
2955
2956        debug_fmt!("OidcProvider", "OIDC validation successful for {} {}", req.method, req.path);
2957        Ok(req)
2958    }
2959}