1use std::{
9 fmt,
10 sync::{Arc, Mutex},
11 time::Duration,
12};
13
14use base64::{Engine as _, engine::general_purpose::STANDARD};
15use bytes::Bytes;
16use futures_util::{
17 FutureExt,
18 future::{BoxFuture, Shared, WeakShared},
19};
20use hmac::{Hmac, Mac};
21use http::{
22 HeaderMap, HeaderValue, Method,
23 header::{AUTHORIZATION, CONTENT_TYPE},
24};
25use serde::Deserialize;
26use sha2::Sha256;
27use tokio::time::Instant;
28use zeroize::Zeroizing;
29
30use crate::{
31 Error, ErrorKind, Result, Secret,
32 config::{DestinationPolicy, DestinationPurpose, Endpoint, RequestOptions, Session},
33 transport::{Transport, TransportRequest, read_limited},
34};
35
36const MAX_TOKEN_RESPONSE: usize = 64 * 1024;
37const MAX_REFRESH_TIME: Duration = Duration::from_secs(30);
38const MAX_TOKEN_LIFETIME: Duration = Duration::from_secs(31 * 24 * 60 * 60);
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42#[non_exhaustive]
43pub enum RefreshBuffer {
44 Adaptive(Duration),
46 Fixed(Duration),
48}
49impl Default for RefreshBuffer {
50 fn default() -> Self {
51 Self::Adaptive(Duration::from_secs(30))
52 }
53}
54impl RefreshBuffer {
55 fn window(self, ttl: Duration) -> Duration {
56 match self {
57 Self::Adaptive(maximum) => maximum.min(ttl / 5),
58 Self::Fixed(buffer) => buffer,
59 }
60 }
61}
62
63#[derive(Clone, Debug)]
65#[non_exhaustive]
66pub struct OAuthProviderOptions {
67 pub refresh_buffer: RefreshBuffer,
68 pub event_capacity: usize,
70}
71impl Default for OAuthProviderOptions {
72 fn default() -> Self {
73 Self {
74 refresh_buffer: RefreshBuffer::default(),
75 event_capacity: 16,
76 }
77 }
78}
79impl OAuthProviderOptions {
80 pub fn validate(&self) -> Result<()> {
81 if !(1..=1024).contains(&self.event_capacity) {
82 return Err(Error::new(ErrorKind::Configuration));
83 }
84 Ok(())
85 }
86}
87
88#[derive(Clone)]
90pub struct AuthContext {
91 issuer: Endpoint,
92 client_id: String,
93 credential_generation: u64,
94 tsg_id: String,
95 scopes: Vec<String>,
96 audience: Option<String>,
97}
98
99impl AuthContext {
100 pub fn new(
102 issuer: Endpoint,
103 client_id: impl Into<String>,
104 credential_generation: u64,
105 tsg_id: impl Into<String>,
106 ) -> Result<Self> {
107 let client_id = client_id.into();
108 let tsg_id = tsg_id.into();
109 if client_id.is_empty()
110 || client_id.len() > 1024
111 || client_id.chars().any(char::is_control)
112 || tsg_id.is_empty()
113 || tsg_id.len() > 32
114 || !tsg_id.bytes().all(|value| value.is_ascii_digit())
115 {
116 return Err(Error::new(ErrorKind::Configuration));
117 }
118 let scopes = vec![format!("tsg_id:{tsg_id}")];
119 Ok(Self {
120 issuer,
121 client_id,
122 credential_generation,
123 tsg_id,
124 scopes,
125 audience: None,
126 })
127 }
128
129 pub fn with_scopes(mut self, scopes: Vec<String>) -> Result<Self> {
132 let tenant_scope = format!("tsg_id:{}", self.tsg_id);
133 if scopes.is_empty()
134 || scopes.len() > 64
135 || !scopes.contains(&tenant_scope)
136 || scopes.iter().enumerate().any(|(index, scope)| {
137 scope.is_empty()
138 || scope.len() > 512
139 || !scope
140 .bytes()
141 .all(|value| matches!(value, 0x21 | 0x23..=0x5b | 0x5d..=0x7e))
142 || (scope.starts_with("tsg_id:") && scope != &tenant_scope)
143 || scopes[..index].contains(scope)
144 })
145 {
146 return Err(Error::new(ErrorKind::Configuration));
147 }
148 self.scopes = scopes;
149 Ok(self)
150 }
151
152 pub fn with_audience(mut self, audience: impl Into<String>) -> Result<Self> {
154 let audience = audience.into();
155 if audience.trim().is_empty()
156 || audience.len() > 2048
157 || audience.chars().any(char::is_control)
158 {
159 return Err(Error::new(ErrorKind::Configuration));
160 }
161 self.audience = Some(audience);
162 Ok(self)
163 }
164
165 pub fn issuer(&self) -> &Endpoint {
166 &self.issuer
167 }
168 pub fn client_id(&self) -> &str {
169 &self.client_id
170 }
171 pub fn credential_generation(&self) -> u64 {
172 self.credential_generation
173 }
174 pub fn tsg_id(&self) -> &str {
175 &self.tsg_id
176 }
177 pub fn scopes(&self) -> &[String] {
178 &self.scopes
179 }
180 pub fn audience(&self) -> Option<&str> {
181 self.audience.as_deref()
182 }
183}
184
185impl fmt::Debug for AuthContext {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.debug_struct("AuthContext")
188 .field("identity", &"[REDACTED]")
189 .field("credential_generation", &self.credential_generation)
190 .field("scope_count", &self.scopes.len())
191 .field("has_audience", &self.audience.is_some())
192 .finish()
193 }
194}
195
196#[derive(Clone)]
199pub struct TokenLease {
200 token: Secret,
201 generation: u64,
202 expires_at: Instant,
203 refresh_at: Instant,
204 expires_at_wall: std::time::SystemTime,
205}
206
207impl TokenLease {
208 pub fn new(token: Secret, generation: u64, valid_for: Duration) -> Result<Self> {
210 Self::issued(token, generation, Instant::now(), valid_for)
211 }
212
213 fn issued(
214 token: Secret,
215 generation: u64,
216 issued_at: Instant,
217 valid_for: Duration,
218 ) -> Result<Self> {
219 Self::issued_with_buffer(
220 token,
221 generation,
222 issued_at,
223 valid_for,
224 RefreshBuffer::default(),
225 )
226 }
227
228 fn issued_with_buffer(
229 token: Secret,
230 generation: u64,
231 issued_at: Instant,
232 valid_for: Duration,
233 refresh_buffer: RefreshBuffer,
234 ) -> Result<Self> {
235 validate_bearer(token.expose())?;
236 if valid_for.is_zero() || valid_for > MAX_TOKEN_LIFETIME {
237 return Err(Error::new(ErrorKind::Authentication));
238 }
239 let expires_at = issued_at
240 .checked_add(valid_for)
241 .ok_or_else(|| Error::new(ErrorKind::Authentication))?;
242 if expires_at <= Instant::now() {
243 return Err(Error::new(ErrorKind::Authentication));
244 }
245 let buffer = refresh_buffer.window(valid_for);
246 let expires_at_wall = std::time::SystemTime::now()
247 .checked_add(expires_at.saturating_duration_since(Instant::now()))
248 .ok_or_else(|| Error::new(ErrorKind::Authentication))?;
249 Ok(Self {
250 token,
251 generation,
252 expires_at,
253 refresh_at: issued_at + valid_for.saturating_sub(buffer),
254 expires_at_wall,
255 })
256 }
257
258 pub fn token(&self) -> &Secret {
259 &self.token
260 }
261 pub fn secret(&self) -> &Secret {
262 &self.token
263 }
264 pub fn generation(&self) -> u64 {
265 self.generation
266 }
267 pub fn expires_at(&self) -> std::time::Instant {
268 self.expires_at.into_std()
269 }
270 pub fn remaining(&self) -> Duration {
271 self.expires_at.saturating_duration_since(Instant::now())
272 }
273 fn usable(&self) -> bool {
274 self.refresh_at > Instant::now()
275 }
276}
277
278impl fmt::Debug for TokenLease {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 f.debug_struct("TokenLease")
281 .field("token", &"[REDACTED]")
282 .field("generation", &self.generation)
283 .field("remaining", &self.remaining())
284 .finish()
285 }
286}
287
288#[derive(Clone, Copy, Debug, PartialEq, Eq)]
290#[non_exhaustive]
291pub struct TokenInfo {
292 pub cached: bool,
293 pub generation: Option<u64>,
294 pub remaining: Option<Duration>,
295 pub refresh_in_progress: bool,
296 pub is_valid: bool,
297 pub is_expired: bool,
298 pub is_expiring_soon: bool,
299 pub expires_at: Option<std::time::SystemTime>,
301}
302
303pub trait TokenProvider: Send + Sync {
305 fn token<'a>(&'a self, options: &'a RequestOptions) -> BoxFuture<'a, Result<TokenLease>>;
306 fn context(&self) -> &AuthContext;
307 fn invalidate(&self, generation: u64) -> bool;
309}
310
311type Refresh = BoxFuture<'static, Result<TokenLease>>;
312struct Pending {
313 generation: u64,
314 future: WeakShared<Refresh>,
315}
316#[derive(Default)]
317struct Cache {
318 token: Option<TokenLease>,
319 pending: Option<Pending>,
320 clear_epoch: u64,
321 next_generation: u64,
322}
323
324fn cache_info(cache: &Cache, refresh_in_progress: bool) -> TokenInfo {
325 let now = Instant::now();
326 let is_expired = cache
327 .token
328 .as_ref()
329 .is_none_or(|token| token.expires_at <= now);
330 let is_expiring_soon = cache
331 .token
332 .as_ref()
333 .is_none_or(|token| token.refresh_at <= now);
334 TokenInfo {
335 cached: cache.token.is_some(),
336 generation: cache.token.as_ref().map(TokenLease::generation),
337 remaining: cache
338 .token
339 .as_ref()
340 .map(|token| token.expires_at.saturating_duration_since(now)),
341 refresh_in_progress,
342 is_valid: cache.token.is_some() && !is_expiring_soon,
343 is_expired,
344 is_expiring_soon,
345 expires_at: cache.token.as_ref().map(|token| token.expires_at_wall),
346 }
347}
348
349struct OAuthInner {
350 context: AuthContext,
351 client_secret: Secret,
352 transport: Arc<dyn Transport>,
353 policy: Arc<dyn DestinationPolicy>,
354 session: Session,
355 cache: Mutex<Cache>,
356 options: OAuthProviderOptions,
357 refresh_events: tokio::sync::broadcast::Sender<TokenInfo>,
358}
359
360#[derive(Clone)]
362pub struct OAuthProvider {
363 inner: Arc<OAuthInner>,
364}
365
366impl OAuthProvider {
367 pub fn new(
368 context: AuthContext,
369 client_secret: Secret,
370 transport: Arc<dyn Transport>,
371 policy: Arc<dyn DestinationPolicy>,
372 session: Session,
373 ) -> Result<Self> {
374 Self::new_with_options(
375 context,
376 client_secret,
377 transport,
378 policy,
379 session,
380 OAuthProviderOptions::default(),
381 )
382 }
383
384 pub fn new_with_options(
385 context: AuthContext,
386 client_secret: Secret,
387 transport: Arc<dyn Transport>,
388 policy: Arc<dyn DestinationPolicy>,
389 session: Session,
390 options: OAuthProviderOptions,
391 ) -> Result<Self> {
392 if client_secret.expose().chars().any(char::is_control) {
393 return Err(Error::new(ErrorKind::Configuration));
394 }
395 options.validate()?;
396 let (refresh_events, _) = tokio::sync::broadcast::channel(options.event_capacity);
397 Ok(Self {
398 inner: Arc::new(OAuthInner {
399 context,
400 client_secret,
401 transport,
402 policy,
403 session,
404 cache: Mutex::new(Cache::default()),
405 options,
406 refresh_events,
407 }),
408 })
409 }
410
411 pub fn subscribe_refresh(&self) -> tokio::sync::broadcast::Receiver<TokenInfo> {
414 self.inner.refresh_events.subscribe()
415 }
416
417 pub fn clear(&self) -> Result<()> {
420 let mut cache = self
421 .inner
422 .cache
423 .lock()
424 .map_err(|_| Error::new(ErrorKind::Authentication))?;
425 cache.clear_epoch = cache
426 .clear_epoch
427 .checked_add(1)
428 .ok_or_else(|| Error::new(ErrorKind::Authentication))?;
429 cache.token = None;
430 cache.pending = None;
431 Ok(())
432 }
433
434 pub async fn get_token(&self, options: Option<&RequestOptions>) -> Result<TokenLease> {
436 let options = options
437 .cloned()
438 .map_or_else(|| RequestOptions::with_timeout(MAX_REFRESH_TIME), Ok)?;
439 self.token(&options).await
440 }
441
442 pub fn is_token_expired(&self) -> Result<bool> {
443 Ok(self.token_info()?.is_expired)
444 }
445
446 pub fn is_token_expiring_soon(&self, buffer: Option<Duration>) -> Result<bool> {
448 let cache = self
449 .inner
450 .cache
451 .lock()
452 .map_err(|_| Error::new(ErrorKind::Authentication))?;
453 Ok(cache.token.as_ref().is_none_or(|token| match buffer {
454 Some(buffer) => token.remaining() <= buffer,
455 None => !token.usable(),
456 }))
457 }
458
459 pub fn token_info(&self) -> Result<TokenInfo> {
460 let cache = self
461 .inner
462 .cache
463 .lock()
464 .map_err(|_| Error::new(ErrorKind::Authentication))?;
465 let pending = cache
466 .pending
467 .as_ref()
468 .and_then(|pending| pending.future.upgrade());
469 let snapshot = cache_info(&cache, pending.is_some());
470 drop(cache);
473 drop(pending);
474 Ok(snapshot)
475 }
476
477 fn shared_refresh(&self) -> Result<Shared<Refresh>> {
478 let mut cache = self
479 .inner
480 .cache
481 .lock()
482 .map_err(|_| Error::new(ErrorKind::Authentication))?;
483 if self.inner.session.is_cancelled() {
484 return Err(Error::new(ErrorKind::Cancelled));
485 }
486 if let Some(token) = cache.token.as_ref().filter(|token| token.usable()) {
487 return Ok(futures_util::future::ready(Ok(token.clone()))
488 .boxed()
489 .shared());
490 }
491 if let Some(pending) = cache
492 .pending
493 .as_ref()
494 .and_then(|pending| pending.future.upgrade())
495 {
496 return Ok(pending);
497 }
498 cache.next_generation = cache
499 .next_generation
500 .checked_add(1)
501 .ok_or_else(|| Error::new(ErrorKind::Authentication))?;
502 let generation = cache.next_generation;
503 let epoch = cache.clear_epoch;
504 let inner = Arc::clone(&self.inner);
505 let future: Shared<Refresh> = async move {
506 let result = inner.refresh(generation).await;
507 let mut cache = inner
508 .cache
509 .lock()
510 .map_err(|_| Error::new(ErrorKind::Authentication))?;
511 if cache.clear_epoch != epoch {
512 return Err(Error::new(ErrorKind::Authentication));
513 }
514 if cache
515 .pending
516 .as_ref()
517 .is_some_and(|pending| pending.generation == generation)
518 {
519 cache.pending = None;
520 }
521 let event = if let Ok(token) = &result {
522 cache.token = Some(token.clone());
523 Some(cache_info(&cache, false))
524 } else {
525 None
526 };
527 drop(cache);
528 if let Some(event) = event {
529 crate::observability::published();
530 let _ = inner.refresh_events.send(event);
533 }
534 result
535 }
536 .boxed()
537 .shared();
538 cache.pending = Some(Pending {
539 generation,
540 future: future
541 .downgrade()
542 .ok_or_else(|| Error::new(ErrorKind::Authentication))?,
543 });
544 Ok(future)
545 }
546}
547
548impl TokenProvider for OAuthProvider {
549 fn token<'a>(&'a self, options: &'a RequestOptions) -> BoxFuture<'a, Result<TokenLease>> {
550 async move {
551 let refresh = self.shared_refresh()?;
552 options.run(&self.inner.session, refresh).await
555 }
556 .boxed()
557 }
558 fn context(&self) -> &AuthContext {
559 &self.inner.context
560 }
561 fn invalidate(&self, generation: u64) -> bool {
562 let Ok(mut cache) = self.inner.cache.lock() else {
563 return false;
564 };
565 if cache
566 .token
567 .as_ref()
568 .is_some_and(|token| token.generation == generation)
569 {
570 cache.token = None;
571 true
573 } else {
574 false
575 }
576 }
577}
578
579impl fmt::Debug for OAuthProvider {
580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
581 f.debug_struct("OAuthProvider")
582 .field("context", &self.inner.context)
583 .field("credentials", &"[REDACTED]")
584 .finish_non_exhaustive()
585 }
586}
587
588#[derive(Deserialize)]
589struct WireToken {
590 access_token: String,
591 token_type: String,
592 expires_in: u64,
593}
594
595impl OAuthInner {
596 async fn refresh(&self, generation: u64) -> Result<TokenLease> {
597 let lifecycle =
598 crate::observability::Lifecycle::start(crate::observability::Operation::OAuthRefresh);
599 let result = self.refresh_observed(generation, &lifecycle).await;
600 lifecycle.finish(&result);
601 result
602 }
603 async fn refresh_observed(
604 &self,
605 generation: u64,
606 lifecycle: &crate::observability::Lifecycle,
607 ) -> Result<TokenLease> {
608 let options = RequestOptions::with_timeout(MAX_REFRESH_TIME)?;
609 options
610 .run(&self.session, async {
611 let destination = self.context.issuer.url().clone();
612 self.context.issuer.admit(&destination)?;
613 self.policy
614 .check(&destination, DestinationPurpose::OAuthToken)?;
615 let form_body = {
616 let mut form = url::form_urlencoded::Serializer::new(String::new());
617 form.append_pair("grant_type", "client_credentials")
618 .append_pair("scope", &self.context.scopes.join(" "));
619 if let Some(audience) = &self.context.audience {
620 form.append_pair("audience", audience);
621 }
622 form.finish()
623 };
624 let encoded_id: String =
625 url::form_urlencoded::byte_serialize(self.context.client_id.as_bytes())
626 .collect();
627 let encoded_secret = Zeroizing::new(
628 url::form_urlencoded::byte_serialize(self.client_secret.expose().as_bytes())
629 .collect::<String>(),
630 );
631 let pair = Zeroizing::new(format!("{encoded_id}:{}", encoded_secret.as_str()));
632 let basic = Zeroizing::new(format!("Basic {}", STANDARD.encode(pair.as_bytes())));
633 let mut headers = HeaderMap::new();
634 headers.insert(AUTHORIZATION, sensitive_header(basic.as_str())?);
635 headers.insert(
636 CONTENT_TYPE,
637 HeaderValue::from_static("application/x-www-form-urlencoded"),
638 );
639 let issued_at = Instant::now();
640 lifecycle.dispatched();
641 let mut response = self
642 .transport
643 .send(TransportRequest::new(
644 Method::POST,
645 destination,
646 headers,
647 Bytes::from(form_body),
648 "oauth.token",
649 ))
650 .await?;
651 lifecycle.response(
652 response.status.as_u16(),
653 crate::engine::error_metadata(&response.headers, None)
654 .0
655 .is_some(),
656 );
657 if !response.status.is_success() {
658 return Err(
659 Error::new(ErrorKind::Authentication).with_status(response.status.as_u16())
660 );
661 }
662 let body = read_limited(&mut response.body, MAX_TOKEN_RESPONSE).await?;
663 let wire: WireToken = crate::json_decode::from_slice(&body)
664 .and_then(crate::json_decode::from_value)
665 .map_err(|_| Error::new(ErrorKind::Decode))?;
666 let access_token = Zeroizing::new(wire.access_token);
667 if !wire.token_type.eq_ignore_ascii_case("bearer") {
668 return Err(Error::new(ErrorKind::Authentication));
669 }
670 TokenLease::issued_with_buffer(
671 Secret::new(access_token.as_str())
672 .map_err(|_| Error::new(ErrorKind::Authentication))?,
673 generation,
674 issued_at,
675 Duration::from_secs(wire.expires_in),
676 self.options.refresh_buffer,
677 )
678 })
679 .await
680 .map_err(|error| error.with_operation("oauth.token"))
681 }
682}
683
684#[derive(Clone)]
686#[non_exhaustive]
687pub enum Authentication {
688 None,
689 Scan {
690 api_key: Option<Secret>,
691 api_token: Option<Secret>,
692 },
693 OAuth {
694 provider: Arc<dyn TokenProvider>,
695 tsg_header: Option<String>,
696 },
697 RuntimeKey {
698 api_key: Secret,
699 },
700}
701
702impl Authentication {
703 pub fn scan(api_key: Option<Secret>, api_token: Option<Secret>) -> Result<Self> {
704 if api_key.is_none() && api_token.is_none() {
705 return Err(Error::new(ErrorKind::Configuration));
706 }
707 for secret in [&api_key, &api_token].into_iter().flatten() {
708 validate_header_secret(secret.expose())?;
709 }
710 Ok(Self::Scan { api_key, api_token })
711 }
712 pub fn oauth(provider: Arc<dyn TokenProvider>, with_tsg_header: bool) -> Self {
714 let tsg_header = with_tsg_header.then(|| provider.context().tsg_id.clone());
715 Self::OAuth {
716 provider,
717 tsg_header,
718 }
719 }
720 pub fn runtime_key(api_key: Secret) -> Result<Self> {
721 validate_header_secret(api_key.expose())?;
722 Ok(Self::RuntimeKey { api_key })
723 }
724
725 pub async fn prepare(
728 &self,
729 headers: &mut HeaderMap,
730 body: &Bytes,
731 has_body: bool,
732 options: &RequestOptions,
733 ) -> Result<Option<u64>> {
734 for name in [
736 "authorization",
737 "x-pan-token",
738 "x-payload-hash",
739 "x-portkey-api-key",
740 "x-tsg-id",
741 ] {
742 if headers.contains_key(name) {
743 return Err(Error::new(ErrorKind::Configuration));
744 }
745 }
746 match self {
747 Self::None => Ok(None),
748 Self::Scan { api_key, api_token } => {
749 if api_key.is_none() && api_token.is_none() {
750 return Err(Error::new(ErrorKind::Configuration));
751 }
752 if let Some(token) = api_token {
753 validate_header_secret(token.expose())?;
754 headers.insert(
755 AUTHORIZATION,
756 sensitive_header(&format!("Bearer {}", token.expose()))?,
757 );
758 }
759 if let Some(key) = api_key {
760 validate_header_secret(key.expose())?;
761 headers.insert("x-pan-token", sensitive_header(key.expose())?);
762 if has_body {
763 let mut hmac = Hmac::<Sha256>::new_from_slice(key.expose().as_bytes())
764 .map_err(|_| Error::new(ErrorKind::Authentication))?;
765 hmac.update(body);
766 let signature: String = hmac
767 .finalize()
768 .into_bytes()
769 .iter()
770 .map(|byte| format!("{byte:02x}"))
771 .collect();
772 headers.insert("x-payload-hash", sensitive_header(&signature)?);
773 }
774 }
775 Ok(None)
776 }
777 Self::OAuth {
778 provider,
779 tsg_header,
780 } => {
781 if let Some(tenant) = tsg_header {
782 if tenant != provider.context().tsg_id() {
783 return Err(Error::new(ErrorKind::Configuration));
784 }
785 validate_header_secret(tenant)?;
786 }
787 let lease = provider.token(options).await?;
788 validate_bearer(lease.token.expose())?;
789 if lease.remaining().is_zero() {
790 return Err(Error::new(ErrorKind::Authentication));
791 }
792 headers.insert(
793 AUTHORIZATION,
794 sensitive_header(&format!("Bearer {}", lease.token.expose()))?,
795 );
796 if let Some(tenant) = tsg_header {
797 headers.insert("x-tsg-id", sensitive_header(tenant)?);
798 }
799 Ok(Some(lease.generation))
800 }
801 Self::RuntimeKey { api_key } => {
802 validate_header_secret(api_key.expose())?;
803 headers.insert("x-portkey-api-key", sensitive_header(api_key.expose())?);
804 Ok(None)
805 }
806 }
807 }
808
809 pub fn on_unauthorized(&self, generation: u64) -> bool {
810 match self {
811 Self::OAuth { provider, .. } => provider.invalidate(generation),
812 _ => false,
813 }
814 }
815}
816
817impl fmt::Debug for Authentication {
818 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
819 match self {
820 Self::None => f.write_str("Authentication::None"),
821 Self::Scan { api_key, api_token } => f
822 .debug_struct("Authentication::Scan")
823 .field("has_api_key", &api_key.is_some())
824 .field("has_api_token", &api_token.is_some())
825 .finish(),
826 Self::OAuth { tsg_header, .. } => f
827 .debug_struct("Authentication::OAuth")
828 .field("has_tenant_header", &tsg_header.is_some())
829 .finish_non_exhaustive(),
830 Self::RuntimeKey { .. } => f.write_str("Authentication::RuntimeKey([REDACTED])"),
831 }
832 }
833}
834
835fn sensitive_header(value: &str) -> Result<HeaderValue> {
836 let mut header =
837 HeaderValue::from_str(value).map_err(|_| Error::new(ErrorKind::Authentication))?;
838 header.set_sensitive(true);
839 Ok(header)
840}
841fn validate_header_secret(value: &str) -> Result<()> {
842 if value.is_empty()
843 || value.len() > 16 * 1024
844 || !value.bytes().all(|byte| byte.is_ascii_graphic())
845 {
846 Err(Error::new(ErrorKind::Configuration))
847 } else {
848 Ok(())
849 }
850}
851fn validate_bearer(value: &str) -> Result<()> {
852 if value.is_empty() || value.len() > 16 * 1024 {
853 return Err(Error::new(ErrorKind::Authentication));
854 }
855 let stripped = value.trim_end_matches('=');
856 if stripped.is_empty()
857 || !stripped
858 .bytes()
859 .all(|byte| byte.is_ascii_alphanumeric() || b"-._~+/".contains(&byte))
860 {
861 return Err(Error::new(ErrorKind::Authentication));
862 }
863 Ok(())
864}
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869 use crate::{config::OriginPolicy, transport::TransportResponse};
870 use http::StatusCode;
871 use std::{
872 collections::VecDeque,
873 sync::atomic::{AtomicUsize, Ordering},
874 };
875 use tokio::sync::{Notify, Semaphore};
876
877 struct FakeTransport {
878 requests: Mutex<Vec<TransportRequest>>,
879 responses: Mutex<VecDeque<(StatusCode, Bytes)>>,
880 release: Semaphore,
881 started: Notify,
882 active: AtomicUsize,
883 dropped: AtomicUsize,
884 blocked: bool,
885 }
886 impl FakeTransport {
887 fn new(blocked: bool, responses: Vec<Bytes>) -> Arc<Self> {
888 Arc::new(Self {
889 requests: Mutex::new(Vec::new()),
890 responses: Mutex::new(
891 responses
892 .into_iter()
893 .map(|body| (StatusCode::OK, body))
894 .collect(),
895 ),
896 release: Semaphore::new(0),
897 started: Notify::new(),
898 active: AtomicUsize::new(0),
899 dropped: AtomicUsize::new(0),
900 blocked,
901 })
902 }
903 fn count(&self) -> usize {
904 self.requests.lock().unwrap().len()
905 }
906 }
907 struct InFlight<'a> {
908 transport: &'a FakeTransport,
909 completed: bool,
910 }
911 impl Drop for InFlight<'_> {
912 fn drop(&mut self) {
913 self.transport.active.fetch_sub(1, Ordering::SeqCst);
914 if !self.completed {
915 self.transport.dropped.fetch_add(1, Ordering::SeqCst);
916 }
917 }
918 }
919 impl Transport for FakeTransport {
920 fn send<'a>(
921 &'a self,
922 request: TransportRequest,
923 ) -> BoxFuture<'a, Result<TransportResponse>> {
924 async move {
925 self.requests.lock().unwrap().push(request);
926 self.active.fetch_add(1, Ordering::SeqCst);
927 let mut guard = InFlight {
928 transport: self,
929 completed: false,
930 };
931 let (status, body) = self
932 .responses
933 .lock()
934 .unwrap()
935 .pop_front()
936 .unwrap_or((StatusCode::OK, token("fallback", 3600)));
937 self.started.notify_one();
938 if self.blocked {
939 self.release.acquire().await.unwrap().forget();
940 }
941 guard.completed = true;
942 Ok(TransportResponse::from_bytes(
943 status,
944 HeaderMap::new(),
945 body,
946 ))
947 }
948 .boxed()
949 }
950 }
951 fn token(value: &str, ttl: u64) -> Bytes {
952 Bytes::from(
953 serde_json::json!({"access_token":value,"token_type":"Bearer","expires_in":ttl})
954 .to_string(),
955 )
956 }
957 fn context(tenant: &str) -> AuthContext {
958 AuthContext::new(
959 Endpoint::parse("https://issuer.example/private-token-path").unwrap(),
960 "client-sensitive",
961 7,
962 tenant,
963 )
964 .unwrap()
965 }
966 fn provider_with(
967 context: AuthContext,
968 transport: Arc<FakeTransport>,
969 session: Session,
970 ) -> OAuthProvider {
971 let policy = Arc::new(OriginPolicy::new([context.issuer.clone()]));
972 OAuthProvider::new(
973 context,
974 Secret::new("secret-sensitive").unwrap(),
975 transport,
976 policy,
977 session,
978 )
979 .unwrap()
980 }
981 fn provider(transport: Arc<FakeTransport>) -> OAuthProvider {
982 provider_with(context("123"), transport, Session::new())
983 }
984 fn options() -> RequestOptions {
985 RequestOptions::with_timeout(Duration::from_secs(60)).unwrap()
986 }
987
988 #[tokio::test(start_paused = true)]
989 async fn public_lifecycle_reports_expiry_buffer_and_clear_without_secrets() {
990 let transport = FakeTransport::new(false, vec![token("lifecycle-secret", 100)]);
991 let provider = provider(Arc::clone(&transport));
992 assert!(provider.is_token_expired().unwrap());
993 assert!(provider.is_token_expiring_soon(None).unwrap());
994 let absent = provider.token_info().unwrap();
995 assert!(!absent.cached && !absent.is_valid && absent.is_expired);
996 assert!(absent.expires_at.is_none());
997 let lease = provider.get_token(None).await.unwrap();
998 assert_eq!(lease.secret().expose(), "lifecycle-secret");
999 assert!(!provider.is_token_expired().unwrap());
1000 assert!(!provider.is_token_expiring_soon(None).unwrap());
1001 assert!(
1002 provider
1003 .is_token_expiring_soon(Some(Duration::from_secs(101)))
1004 .unwrap()
1005 );
1006 let current = provider.token_info().unwrap();
1007 assert!(current.cached && current.is_valid && !current.is_expired);
1008 assert!(current.expires_at.is_some());
1009 assert!(!format!("{current:?} {lease:?}").contains("lifecycle-secret"));
1010 tokio::time::advance(Duration::from_secs(81)).await;
1011 let soon = provider.token_info().unwrap();
1012 assert!(!soon.is_expired && soon.is_expiring_soon && !soon.is_valid);
1013 tokio::time::advance(Duration::from_secs(19)).await;
1014 assert!(provider.is_token_expired().unwrap());
1015 provider.clear().unwrap();
1016 assert!(!provider.token_info().unwrap().cached);
1017 assert_eq!(transport.count(), 1);
1018 }
1019
1020 #[tokio::test]
1021 async fn rfc4231_signature_uses_exact_bytes_and_protected_headers() {
1022 let auth = Authentication::scan(Some(Secret::new("Jefe").unwrap()), None).unwrap();
1023 let mut headers = HeaderMap::new();
1024 auth.prepare(
1025 &mut headers,
1026 &Bytes::from_static(b"what do ya want for nothing?"),
1027 true,
1028 &options(),
1029 )
1030 .await
1031 .unwrap();
1032 assert_eq!(
1033 headers["x-payload-hash"],
1034 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
1035 );
1036 assert!(headers["x-pan-token"].is_sensitive());
1037 assert!(headers["x-payload-hash"].is_sensitive());
1038 let mut without_body = HeaderMap::new();
1039 auth.prepare(&mut without_body, &Bytes::new(), false, &options())
1040 .await
1041 .unwrap();
1042 assert!(!without_body.contains_key("x-payload-hash"));
1043 let mut empty_body = HeaderMap::new();
1044 auth.prepare(&mut empty_body, &Bytes::new(), true, &options())
1045 .await
1046 .unwrap();
1047 assert!(empty_body.contains_key("x-payload-hash"));
1048 let mut override_headers = HeaderMap::new();
1049 override_headers.insert(AUTHORIZATION, HeaderValue::from_static("caller-token"));
1050 assert_eq!(
1051 auth.prepare(&mut override_headers, &Bytes::new(), false, &options())
1052 .await
1053 .unwrap_err()
1054 .kind(),
1055 ErrorKind::Configuration
1056 );
1057 }
1058
1059 #[tokio::test]
1060 async fn header_injection_and_wrong_auth_domains_are_rejected() {
1061 for invalid in ["key\r\nx-secret: leaked", "key with spaces", ""] {
1062 let result = Secret::new(invalid).and_then(Authentication::runtime_key);
1063 assert!(result.is_err());
1064 assert!(!format!("{:?}", result.err().unwrap()).contains("leaked"));
1065 }
1066 for invalid in ["tok en", "tok\nX", "=", "to=ken", "é"] {
1067 assert!(
1068 TokenLease::new(Secret::new(invalid).unwrap(), 1, Duration::from_secs(60)).is_err()
1069 );
1070 }
1071 assert!(Authentication::scan(None, None).is_err());
1072 let raw_empty_scan = Authentication::Scan {
1075 api_key: None,
1076 api_token: None,
1077 };
1078 assert_eq!(
1079 raw_empty_scan
1080 .prepare(&mut HeaderMap::new(), &Bytes::new(), false, &options())
1081 .await
1082 .unwrap_err()
1083 .kind(),
1084 ErrorKind::Configuration
1085 );
1086 let transport = FakeTransport::new(false, vec![token("good", 3600)]);
1087 let auth = Authentication::OAuth {
1088 provider: Arc::new(provider(transport.clone())),
1089 tsg_header: Some("456".to_owned()),
1090 };
1091 assert!(
1092 auth.prepare(&mut HeaderMap::new(), &Bytes::new(), false, &options())
1093 .await
1094 .is_err()
1095 );
1096 assert_eq!(transport.count(), 0);
1097 }
1098
1099 #[tokio::test]
1100 async fn token_exchange_uses_encoded_basic_form_and_no_runtime_credentials() {
1101 let transport = FakeTransport::new(false, vec![token("token-1", 3600)]);
1102 let context = AuthContext::new(
1103 Endpoint::parse("https://issuer.example/token").unwrap(),
1104 "client: &+",
1105 1,
1106 "123",
1107 )
1108 .unwrap()
1109 .with_scopes(vec!["tsg_id:123".into(), "read:api".into()])
1110 .unwrap()
1111 .with_audience("service & scope")
1112 .unwrap();
1113 let provider = OAuthProvider::new(
1114 context.clone(),
1115 Secret::new("sec/ +:").unwrap(),
1116 transport.clone(),
1117 Arc::new(OriginPolicy::new([context.issuer.clone()])),
1118 Session::new(),
1119 )
1120 .unwrap();
1121 let lease = provider.token(&options()).await.unwrap();
1122 assert_eq!(lease.secret().expose(), "token-1");
1123 let requests = transport.requests.lock().unwrap();
1124 let request = &requests[0];
1125 assert_eq!(request.method(), Method::POST);
1126 assert_eq!(request.url(), context.issuer.url());
1127 assert_eq!(
1128 request.headers()[CONTENT_TYPE],
1129 "application/x-www-form-urlencoded"
1130 );
1131 let basic = request.headers()[AUTHORIZATION]
1132 .to_str()
1133 .unwrap()
1134 .strip_prefix("Basic ")
1135 .unwrap();
1136 assert_eq!(
1137 STANDARD.decode(basic).unwrap(),
1138 b"client%3A+%26%2B:sec%2F+%2B%3A"
1139 );
1140 let form: std::collections::BTreeMap<_, _> = url::form_urlencoded::parse(request.body())
1141 .into_owned()
1142 .collect();
1143 assert_eq!(form["scope"], "tsg_id:123 read:api");
1144 assert_eq!(form["audience"], "service & scope");
1145 assert_eq!(form["grant_type"], "client_credentials");
1146 assert!(request.headers()[AUTHORIZATION].is_sensitive());
1147 assert!(!request.headers().contains_key("x-portkey-api-key"));
1148 assert!(!request.headers().contains_key("x-pan-token"));
1149 }
1150
1151 #[tokio::test]
1152 async fn token_destination_policy_runs_before_transport() {
1153 struct Reject;
1154 impl DestinationPolicy for Reject {
1155 fn check(&self, _: &url::Url, purpose: DestinationPurpose) -> Result<()> {
1156 assert_eq!(purpose, DestinationPurpose::OAuthToken);
1157 Err(Error::new(ErrorKind::Destination))
1158 }
1159 }
1160 let transport = FakeTransport::new(false, vec![]);
1161 let provider = OAuthProvider::new(
1162 context("123"),
1163 Secret::new("credential").unwrap(),
1164 transport.clone(),
1165 Arc::new(Reject),
1166 Session::new(),
1167 )
1168 .unwrap();
1169 assert_eq!(
1170 provider.token(&options()).await.unwrap_err().kind(),
1171 ErrorKind::Destination
1172 );
1173 assert_eq!(transport.count(), 0);
1174 }
1175
1176 #[tokio::test]
1177 async fn refresh_is_deduplicated_and_one_cancelled_waiter_preserves_the_other() {
1178 let transport = FakeTransport::new(true, vec![token("shared-token", 3600)]);
1179 let provider = provider(transport.clone());
1180 let first_options = options();
1181 let first_cancel = first_options.clone();
1182 let first_provider = provider.clone();
1183 let first = tokio::spawn(async move { first_provider.token(&first_options).await });
1184 transport.started.notified().await;
1185 let second_provider = provider.clone();
1186 let second = tokio::spawn(async move { second_provider.token(&options()).await });
1187 tokio::task::yield_now().await;
1188 assert_eq!(transport.count(), 1);
1189 first_cancel.cancel();
1190 assert_eq!(
1191 first.await.unwrap().unwrap_err().kind(),
1192 ErrorKind::Cancelled
1193 );
1194 assert_eq!(transport.active.load(Ordering::SeqCst), 1);
1195 transport.release.add_permits(1);
1196 assert_eq!(
1197 second.await.unwrap().unwrap().secret().expose(),
1198 "shared-token"
1199 );
1200 assert_eq!(transport.dropped.load(Ordering::SeqCst), 0);
1201 assert_eq!(provider.token(&options()).await.unwrap().generation(), 1);
1202 assert_eq!(transport.count(), 1);
1203 }
1204
1205 #[tokio::test]
1206 async fn last_waiter_drop_releases_http_and_next_call_can_refresh() {
1207 let transport = FakeTransport::new(
1208 true,
1209 vec![token("abandoned", 3600), token("replacement", 3600)],
1210 );
1211 let provider = provider(transport.clone());
1212 let task_provider = provider.clone();
1213 let task = tokio::spawn(async move { task_provider.token(&options()).await });
1214 transport.started.notified().await;
1215 task.abort();
1216 assert!(task.await.is_err());
1217 assert_eq!(transport.active.load(Ordering::SeqCst), 0);
1218 assert_eq!(transport.dropped.load(Ordering::SeqCst), 1);
1219 assert!(!provider.token_info().unwrap().refresh_in_progress);
1220 transport.release.add_permits(1);
1221 let lease = provider.token(&options()).await.unwrap();
1222 assert_eq!(lease.secret().expose(), "replacement");
1223 assert_eq!(transport.count(), 2);
1224 }
1225
1226 #[tokio::test]
1227 async fn explicit_session_cancellation_interrupts_shared_refresh() {
1228 let transport = FakeTransport::new(true, vec![token("pending", 3600)]);
1229 let session = Session::new();
1230 let provider = provider_with(context("123"), transport.clone(), session.clone());
1231 let task_provider = provider.clone();
1232 let task = tokio::spawn(async move { task_provider.token(&options()).await });
1233 transport.started.notified().await;
1234 session.cancel();
1235 assert_eq!(
1236 task.await.unwrap().unwrap_err().kind(),
1237 ErrorKind::Cancelled
1238 );
1239 assert_eq!(transport.active.load(Ordering::SeqCst), 0);
1240 assert_eq!(
1241 provider.token(&options()).await.unwrap_err().kind(),
1242 ErrorKind::Cancelled
1243 );
1244 assert_eq!(transport.count(), 1);
1245 }
1246
1247 #[tokio::test]
1248 async fn already_cancelled_waiter_never_starts_token_exchange() {
1249 let transport = FakeTransport::new(false, vec![token("unused", 3600)]);
1250 let provider = provider(transport.clone());
1251 let options = options();
1252 options.cancel();
1253 assert_eq!(
1254 provider.token(&options).await.unwrap_err().kind(),
1255 ErrorKind::Cancelled
1256 );
1257 assert_eq!(transport.count(), 0);
1258 assert!(!provider.token_info().unwrap().refresh_in_progress);
1259 }
1260
1261 #[tokio::test(start_paused = true)]
1262 async fn refresh_has_its_own_thirty_second_deadline() {
1263 let transport = FakeTransport::new(true, vec![token("pending", 3600)]);
1264 let provider = provider(transport.clone());
1265 let task = tokio::spawn(async move { provider.token(&options()).await });
1266 transport.started.notified().await;
1267 tokio::time::advance(Duration::from_secs(31)).await;
1268 assert_eq!(task.await.unwrap().unwrap_err().kind(), ErrorKind::Deadline);
1269 assert_eq!(transport.active.load(Ordering::SeqCst), 0);
1270 assert_eq!(transport.dropped.load(Ordering::SeqCst), 1);
1271 }
1272
1273 #[tokio::test(start_paused = true)]
1274 async fn one_waiter_deadline_does_not_expire_other_waiters() {
1275 let transport = FakeTransport::new(true, vec![token("shared-after-deadline", 3600)]);
1276 let provider = provider(transport.clone());
1277 let first_provider = provider.clone();
1278 let first = tokio::spawn(async move {
1279 first_provider
1280 .token(&RequestOptions::with_timeout(Duration::from_secs(1)).unwrap())
1281 .await
1282 });
1283 transport.started.notified().await;
1284 let second_provider = provider.clone();
1285 let second = tokio::spawn(async move { second_provider.token(&options()).await });
1286 tokio::task::yield_now().await;
1287 tokio::time::advance(Duration::from_secs(2)).await;
1288 assert_eq!(
1289 first.await.unwrap().unwrap_err().kind(),
1290 ErrorKind::Deadline
1291 );
1292 assert_eq!(transport.active.load(Ordering::SeqCst), 1);
1293 transport.release.add_permits(1);
1294 assert_eq!(
1295 second.await.unwrap().unwrap().secret().expose(),
1296 "shared-after-deadline"
1297 );
1298 assert_eq!(transport.count(), 1);
1299 assert_eq!(transport.dropped.load(Ordering::SeqCst), 0);
1300 }
1301
1302 #[tokio::test(start_paused = true)]
1303 async fn late_unauthorized_response_cannot_evict_new_generation() {
1304 let transport = FakeTransport::new(false, vec![token("old", 2), token("new", 3600)]);
1305 let provider = provider(transport.clone());
1306 let old = provider.token(&options()).await.unwrap();
1307 tokio::time::advance(Duration::from_secs(2)).await;
1308 let new = provider.token(&options()).await.unwrap();
1309 assert!(new.generation() > old.generation());
1310 assert!(!provider.invalidate(old.generation()));
1311 assert_eq!(
1312 provider.token(&options()).await.unwrap().secret().expose(),
1313 "new"
1314 );
1315 assert_eq!(transport.count(), 2);
1316 assert!(provider.invalidate(new.generation()));
1317 assert!(!provider.token_info().unwrap().cached);
1318 }
1319
1320 #[tokio::test(start_paused = true)]
1321 async fn token_lifetime_starts_before_exchange_latency() {
1322 let transport = FakeTransport::new(true, vec![token("already-expired", 1)]);
1323 let provider = provider(transport.clone());
1324 let task_provider = provider.clone();
1325 let task = tokio::spawn(async move { task_provider.token(&options()).await });
1326 transport.started.notified().await;
1327 tokio::time::advance(Duration::from_secs(2)).await;
1328 transport.release.add_permits(1);
1329 assert_eq!(
1330 task.await.unwrap().unwrap_err().kind(),
1331 ErrorKind::Authentication
1332 );
1333 assert!(!provider.token_info().unwrap().cached);
1334 }
1335
1336 #[tokio::test(start_paused = true)]
1337 async fn invalidating_old_cached_token_preserves_new_pending_refresh() {
1338 let transport = FakeTransport::new(true, vec![token("old", 2), token("new", 3600)]);
1339 let provider = provider(transport.clone());
1340 transport.release.add_permits(1);
1341 let old = provider.token(&options()).await.unwrap();
1342 tokio::time::advance(Duration::from_secs(2)).await;
1343 let next_provider = provider.clone();
1344 let next = tokio::spawn(async move { next_provider.token(&options()).await });
1345 while transport.count() < 2 {
1346 tokio::task::yield_now().await;
1347 }
1348 assert!(provider.invalidate(old.generation()));
1349 transport.release.add_permits(1);
1350 let new = next.await.unwrap().unwrap();
1351 assert_eq!(
1352 provider.token(&options()).await.unwrap().generation(),
1353 new.generation()
1354 );
1355 assert_eq!(transport.count(), 2);
1356 }
1357
1358 #[tokio::test]
1359 async fn clear_prevents_pending_old_refresh_from_restoring_cache() {
1360 let transport = FakeTransport::new(
1361 true,
1362 vec![token("old-pending", 3600), token("new-pending", 3600)],
1363 );
1364 let provider = provider(transport.clone());
1365 let old_provider = provider.clone();
1366 let old = tokio::spawn(async move { old_provider.token(&options()).await });
1367 transport.started.notified().await;
1368 provider.clear().unwrap();
1369 let next_provider = provider.clone();
1370 let next = tokio::spawn(async move { next_provider.token(&options()).await });
1371 while transport.count() < 2 {
1372 tokio::task::yield_now().await;
1373 }
1374 transport.release.add_permits(2);
1375 assert_eq!(
1376 old.await.unwrap().unwrap_err().kind(),
1377 ErrorKind::Authentication
1378 );
1379 assert_eq!(
1380 next.await.unwrap().unwrap().secret().expose(),
1381 "new-pending"
1382 );
1383 assert_eq!(
1384 provider.token(&options()).await.unwrap().secret().expose(),
1385 "new-pending"
1386 );
1387 }
1388
1389 #[tokio::test]
1390 async fn identities_caches_and_formatting_are_isolated() {
1391 let transport = FakeTransport::new(
1392 false,
1393 vec![
1394 token("tenant-one-token", 3600),
1395 token("tenant-two-token", 3600),
1396 ],
1397 );
1398 let first = provider_with(context("123"), transport.clone(), Session::new());
1399 let second = provider_with(context("456"), transport.clone(), Session::new());
1400 let first_token = first.token(&options()).await.unwrap();
1401 let second_token = second.token(&options()).await.unwrap();
1402 assert_ne!(
1403 first_token.secret().expose(),
1404 second_token.secret().expose()
1405 );
1406 assert_eq!(transport.count(), 2);
1407 assert!(first.invalidate(first_token.generation()));
1408 assert!(second.token_info().unwrap().cached);
1409 let auth = Authentication::oauth(Arc::new(second.clone()), true);
1410 let output = format!(
1411 "{first:?} {second:?} {auth:?} {first_token:?} {:?}",
1412 first.token_info().unwrap()
1413 );
1414 for secret in [
1415 "issuer.example",
1416 "private-token-path",
1417 "client-sensitive",
1418 "secret-sensitive",
1419 "tenant-one-token",
1420 "tenant-two-token",
1421 "tsg_id:123",
1422 "tsg_id:456",
1423 ] {
1424 assert!(!output.contains(secret));
1425 }
1426 let mut headers = HeaderMap::new();
1427 assert_eq!(
1428 auth.prepare(&mut headers, &Bytes::new(), false, &options())
1429 .await
1430 .unwrap(),
1431 Some(second_token.generation())
1432 );
1433 assert_eq!(headers["x-tsg-id"], "456");
1434 assert_eq!(headers[AUTHORIZATION], "Bearer tenant-two-token");
1435 assert!(headers[AUTHORIZATION].is_sensitive());
1436 assert!(headers["x-tsg-id"].is_sensitive());
1437 }
1438
1439 #[tokio::test]
1440 async fn malformed_or_unbounded_token_responses_fail_without_values() {
1441 let cases = [
1442 (
1443 Bytes::from_static(br#"{"access_token":"secret\r\nvalue","token_type":"Bearer","expires_in":3600}"#),
1444 ErrorKind::Authentication,
1445 ),
1446 (
1447 Bytes::from_static(br#"{"access_token":"secret","token_type":"MAC","expires_in":3600}"#),
1448 ErrorKind::Authentication,
1449 ),
1450 (
1451 Bytes::from_static(br#"{"access_token":"secret","token_type":"Bearer","expires_in":0}"#),
1452 ErrorKind::Authentication,
1453 ),
1454 (
1455 Bytes::from_static(br#"{"access_token":"secret","token_type":"Bearer","expires_in":999999999999}"#),
1456 ErrorKind::Authentication,
1457 ),
1458 (
1459 Bytes::from_static(br#"{"access_token":"secret","token_type":"Bearer","expires_in":"3600"}"#),
1460 ErrorKind::Decode,
1461 ),
1462 (Bytes::from_static(b"secret malformed upstream response"), ErrorKind::Decode),
1463 (Bytes::from(vec![b'x'; MAX_TOKEN_RESPONSE + 1]), ErrorKind::Limit),
1464 ];
1465 for (body, kind) in cases {
1466 let transport = FakeTransport::new(false, vec![body]);
1467 let error = provider(transport).token(&options()).await.unwrap_err();
1468 assert_eq!(error.kind(), kind);
1469 assert!(!format!("{error:?} {error}").contains("secret"));
1470 }
1471 let transport =
1472 FakeTransport::new(false, vec![Bytes::from_static(b"secret upstream error")]);
1473 transport.responses.lock().unwrap()[0].0 = StatusCode::UNAUTHORIZED;
1474 let error = provider(transport).token(&options()).await.unwrap_err();
1475 assert_eq!(error.status(), Some(401));
1476 assert!(!format!("{error:?} {error}").contains("secret"));
1477 }
1478
1479 #[test]
1480 fn immutable_context_rejects_cross_tenant_or_unsafe_scope_configuration() {
1481 assert!(
1482 context("123")
1483 .with_scopes(vec!["tsg_id:456".into()])
1484 .is_err()
1485 );
1486 assert!(
1487 context("123")
1488 .with_scopes(vec!["tsg_id:123".into(), "two scopes".into()])
1489 .is_err()
1490 );
1491 assert!(
1492 context("123")
1493 .with_scopes(vec!["tsg_id:123".into(), "tsg_id:123".into()])
1494 .is_err()
1495 );
1496 assert!(context("123").with_audience("audience\nInjected").is_err());
1497 assert!(
1498 AuthContext::new(
1499 Endpoint::parse("https://issuer.example/token").unwrap(),
1500 "client",
1501 1,
1502 "123\n"
1503 )
1504 .is_err()
1505 );
1506 }
1507 fn configured_provider(
1508 transport: Arc<FakeTransport>,
1509 buffer: RefreshBuffer,
1510 capacity: usize,
1511 ) -> OAuthProvider {
1512 let context = context("123");
1513 let policy = Arc::new(OriginPolicy::new([context.issuer.clone()]));
1514 OAuthProvider::new_with_options(
1515 context,
1516 Secret::new("secret-sensitive").unwrap(),
1517 transport,
1518 policy,
1519 Session::new(),
1520 OAuthProviderOptions {
1521 refresh_buffer: buffer,
1522 event_capacity: capacity,
1523 },
1524 )
1525 .unwrap()
1526 }
1527 #[tokio::test(start_paused = true)]
1528 async fn configurable_fixed_and_adaptive_windows_preserve_source_and_safe_defaults() {
1529 let fixed_transport =
1530 FakeTransport::new(false, vec![token("fixed", 100), token("fixed2", 100)]);
1531 let adaptive_transport = FakeTransport::new(false, vec![token("adaptive", 100)]);
1532 let fixed = configured_provider(
1533 fixed_transport.clone(),
1534 RefreshBuffer::Fixed(Duration::from_secs(30)),
1535 16,
1536 );
1537 let adaptive = configured_provider(
1538 adaptive_transport.clone(),
1539 RefreshBuffer::Adaptive(Duration::from_secs(30)),
1540 16,
1541 );
1542 fixed.get_token(None).await.unwrap();
1543 adaptive.get_token(None).await.unwrap();
1544 tokio::time::advance(Duration::from_secs(71)).await;
1545 assert!(fixed.token_info().unwrap().is_expiring_soon);
1546 assert!(!adaptive.token_info().unwrap().is_expiring_soon);
1547 assert_eq!(
1548 fixed.get_token(None).await.unwrap().secret().expose(),
1549 "fixed2"
1550 );
1551 tokio::time::advance(Duration::from_secs(10)).await;
1552 assert!(adaptive.token_info().unwrap().is_expiring_soon);
1553 let transport = FakeTransport::new(false, vec![token("zero", 10)]);
1554 let zero = configured_provider(transport.clone(), RefreshBuffer::Fixed(Duration::ZERO), 16);
1555 zero.get_token(None).await.unwrap();
1556 tokio::time::advance(Duration::from_secs(9)).await;
1557 assert!(zero.token_info().unwrap().is_valid);
1558 zero.get_token(None).await.unwrap();
1559 assert_eq!(transport.count(), 1);
1560 tokio::time::advance(Duration::from_secs(1)).await;
1561 assert!(zero.is_token_expired().unwrap());
1562 let transport = FakeTransport::new(false, vec![token("first", 10), token("second", 10)]);
1563 let eager = configured_provider(transport.clone(), RefreshBuffer::Fixed(Duration::MAX), 16);
1564 assert_eq!(
1565 eager.get_token(None).await.unwrap().secret().expose(),
1566 "first"
1567 );
1568 assert!(eager.token_info().unwrap().is_expiring_soon);
1569 assert_eq!(
1570 eager.get_token(None).await.unwrap().secret().expose(),
1571 "second"
1572 );
1573 assert_eq!(transport.count(), 2);
1574 }
1575 #[tokio::test]
1576 async fn refresh_metadata_is_once_per_published_generation_and_never_contains_credentials() {
1577 let transport = FakeTransport::new(true, vec![token("event-secret", 3600)]);
1578 let provider = configured_provider(transport.clone(), RefreshBuffer::default(), 16);
1579 let mut events = provider.subscribe_refresh();
1580 let one = tokio::spawn({
1581 let provider = provider.clone();
1582 async move { provider.get_token(None).await }
1583 });
1584 transport.started.notified().await;
1585 let two = tokio::spawn({
1586 let provider = provider.clone();
1587 async move { provider.get_token(None).await }
1588 });
1589 tokio::task::yield_now().await;
1590 transport.release.add_permits(1);
1591 let first = one.await.unwrap().unwrap();
1592 let second = two.await.unwrap().unwrap();
1593 assert_eq!(first.generation(), second.generation());
1594 assert_eq!(transport.count(), 1);
1595 let event = events.recv().await.unwrap();
1596 assert_eq!(event.generation, Some(first.generation()));
1597 assert!(event.cached && event.is_valid && !event.refresh_in_progress);
1598 assert!(!format!("{event:?}").contains("event-secret"));
1599 assert!(matches!(
1600 events.try_recv(),
1601 Err(tokio::sync::broadcast::error::TryRecvError::Empty)
1602 ));
1603 provider.get_token(None).await.unwrap();
1604 assert!(matches!(
1605 events.try_recv(),
1606 Err(tokio::sync::broadcast::error::TryRecvError::Empty)
1607 ));
1608 assert!(provider.token_info().unwrap().cached);
1610 provider.clear().unwrap();
1611 assert!(!provider.token_info().unwrap().cached);
1612 }
1613 #[tokio::test]
1614 async fn failed_and_cleared_pending_refreshes_publish_no_success_event() {
1615 let transport = FakeTransport::new(false, vec![Bytes::from_static(b"invalid-secret-json")]);
1616 let provider = configured_provider(transport, RefreshBuffer::default(), 16);
1617 let mut events = provider.subscribe_refresh();
1618 assert!(provider.get_token(None).await.is_err());
1619 assert!(matches!(
1620 events.try_recv(),
1621 Err(tokio::sync::broadcast::error::TryRecvError::Empty)
1622 ));
1623 let transport = FakeTransport::new(true, vec![token("stale-secret", 3600)]);
1624 let provider = configured_provider(transport.clone(), RefreshBuffer::default(), 16);
1625 let mut events = provider.subscribe_refresh();
1626 let pending = tokio::spawn({
1627 let provider = provider.clone();
1628 async move { provider.get_token(None).await }
1629 });
1630 transport.started.notified().await;
1631 provider.clear().unwrap();
1632 transport.release.add_permits(1);
1633 assert_eq!(
1634 pending.await.unwrap().unwrap_err().kind(),
1635 ErrorKind::Authentication
1636 );
1637 assert!(matches!(
1638 events.try_recv(),
1639 Err(tokio::sync::broadcast::error::TryRecvError::Empty)
1640 ));
1641 assert!(!provider.token_info().unwrap().cached);
1642 }
1643 #[tokio::test]
1644 async fn lagged_dropped_and_absent_subscribers_never_block_token_delivery() {
1645 let transport = FakeTransport::new(false, vec![]);
1646 let provider =
1647 configured_provider(transport.clone(), RefreshBuffer::Fixed(Duration::MAX), 1);
1648 let mut slow = provider.subscribe_refresh();
1649 for _ in 0..5 {
1650 provider.get_token(None).await.unwrap();
1651 }
1652 assert!(matches!(
1653 slow.try_recv(),
1654 Err(tokio::sync::broadcast::error::TryRecvError::Lagged(4))
1655 ));
1656 assert_eq!(slow.try_recv().unwrap().generation, Some(5));
1657 drop(slow);
1658 provider.get_token(None).await.unwrap();
1659 assert_eq!(transport.count(), 6);
1660 let mut last = provider.subscribe_refresh();
1661 drop(provider);
1662 assert!(matches!(
1663 last.recv().await,
1664 Err(tokio::sync::broadcast::error::RecvError::Closed)
1665 ));
1666 }
1667 #[test]
1668 fn refresh_event_capacity_is_validated_before_provider_creation() {
1669 for capacity in [0, 1025, usize::MAX] {
1670 let context = context("123");
1671 let policy = Arc::new(OriginPolicy::new([context.issuer.clone()]));
1672 let transport = FakeTransport::new(false, vec![]);
1673 let result = OAuthProvider::new_with_options(
1674 context,
1675 Secret::new("secret").unwrap(),
1676 transport.clone(),
1677 policy,
1678 Session::new(),
1679 OAuthProviderOptions {
1680 event_capacity: capacity,
1681 ..OAuthProviderOptions::default()
1682 },
1683 );
1684 assert_eq!(result.unwrap_err().kind(), ErrorKind::Configuration);
1685 assert_eq!(transport.count(), 0);
1686 }
1687 }
1688}