Skip to main content

google_cloud_auth/
credentials.rs

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types and functions to work with Google Cloud authentication [Credentials].
16//!
17//! [Credentials]: https://cloud.google.com/docs/authentication#credentials
18
19use crate::build_errors::Error as BuilderError;
20use crate::constants::GOOGLE_CLOUD_QUOTA_PROJECT_VAR;
21use crate::errors::{self, CredentialsError};
22use crate::token::Token;
23use crate::{BuildResult, Result};
24use http::{Extensions, HeaderMap};
25use serde_json::Value;
26use std::future::Future;
27use std::sync::Arc;
28use std::sync::atomic::{AtomicU64, Ordering};
29pub mod anonymous;
30pub mod api_key_credentials;
31pub(crate) mod crypto_provider;
32pub mod external_account;
33pub(crate) mod external_account_sources;
34#[cfg(feature = "__gdch")]
35pub(crate) mod gdch;
36#[cfg(feature = "idtoken")]
37pub mod idtoken;
38pub mod impersonated;
39pub(crate) mod internal;
40pub mod mds;
41pub mod service_account;
42pub mod subject_token;
43pub mod user_account;
44pub(crate) const QUOTA_PROJECT_KEY: &str = "x-goog-user-project";
45
46/// Represents an Entity Tag for a [CacheableResource].
47///
48/// An `EntityTag` is an opaque token that can be used to determine if a
49/// cached resource has changed. The specific format of this tag is an
50/// implementation detail.
51///
52/// As the name indicates, these are inspired by the ETags prevalent in HTTP
53/// caching protocols. Their implementation is very different, and are only
54/// intended for use within a single program.
55#[derive(Clone, Debug, PartialEq, Default)]
56pub struct EntityTag(u64);
57
58static ENTITY_TAG_GENERATOR: AtomicU64 = AtomicU64::new(0);
59impl EntityTag {
60    /// Creates a new, unique [EntityTag].
61    pub fn new() -> Self {
62        let value = ENTITY_TAG_GENERATOR.fetch_add(1, Ordering::SeqCst);
63        Self(value)
64    }
65}
66
67/// Represents a resource that can be cached, along with its [EntityTag].
68///
69/// This enum is used to provide cacheable data to the consumers of the credential provider.
70/// It allows a data provider to return either new data (with an [EntityTag]) or
71/// indicate that the caller's cached version (identified by a previously provided [EntityTag])
72/// is still valid.
73#[derive(Clone, PartialEq, Debug)]
74pub enum CacheableResource<T> {
75    /// Indicates that the resource has not been modified and the cached version is still valid.
76    NotModified,
77    /// Contains the new resource data and its associated [EntityTag].
78    New {
79        /// The entity tag for the new resource.
80        entity_tag: EntityTag,
81        /// The new resource data.
82        data: T,
83    },
84}
85
86/// An implementation of [crate::credentials::CredentialsProvider].
87///
88/// Represents a [Credentials] used to obtain the auth request headers.
89///
90/// In general, [Credentials][credentials-link] are "digital object that provide
91/// proof of identity", the archetype may be a username and password
92/// combination, but a private RSA key may be a better example.
93///
94/// Modern authentication protocols do not send the credentials to authenticate
95/// with a service. Even when sent over encrypted transports, the credentials
96/// may be accidentally exposed via logging or may be captured if there are
97/// errors in the transport encryption. Because the credentials are often
98/// long-lived, that risk of exposure is also long-lived.
99///
100/// Instead, modern authentication protocols exchange the credentials for a
101/// time-limited [Token][token-link], a digital object that shows the caller was
102/// in possession of the credentials. Because tokens are time limited, risk of
103/// misuse is also time limited. Tokens may be further restricted to only a
104/// certain subset of the RPCs in the service, or even to specific resources, or
105/// only when used from a given machine (virtual or not). Further limiting the
106/// risks associated with any leaks of these tokens.
107///
108/// This struct also abstracts token sources that are not backed by a specific
109/// digital object. The canonical example is the [Metadata Service]. This
110/// service is available in many Google Cloud environments, including
111/// [Google Compute Engine], and [Google Kubernetes Engine].
112///
113/// [credentials-link]: https://cloud.google.com/docs/authentication#credentials
114/// [token-link]: https://cloud.google.com/docs/authentication#token
115/// [Metadata Service]: https://cloud.google.com/compute/docs/metadata/overview
116/// [Google Compute Engine]: https://cloud.google.com/products/compute
117/// [Google Kubernetes Engine]: https://cloud.google.com/kubernetes-engine
118#[derive(Clone, Debug)]
119pub struct Credentials {
120    // We use an `Arc` to hold the inner implementation.
121    //
122    // Credentials may be shared across threads (`Send + Sync`), so an `Rc`
123    // will not do.
124    //
125    // They also need to derive `Clone`, as the
126    // `google_cloud_gax::http_client::ReqwestClient`s which hold them derive `Clone`. So a
127    // `Box` will not do.
128    inner: Arc<dyn dynamic::CredentialsProvider>,
129}
130
131impl<T> std::convert::From<T> for Credentials
132where
133    T: crate::credentials::CredentialsProvider + Send + Sync + 'static,
134{
135    fn from(value: T) -> Self {
136        Self {
137            inner: Arc::new(value),
138        }
139    }
140}
141
142impl Credentials {
143    /// Asynchronously constructs the auth headers.
144    ///
145    /// Different auth tokens are sent via different headers. The
146    /// [Credentials] constructs the headers (and header values) that should be
147    /// sent with a request. If the authentication provider requires it, headers
148    /// are cached, and a background task periodically refreshes any expired
149    /// tokens.
150    ///
151    /// # Parameters
152    /// * `extensions` - An `http::Extensions` map that can be used to pass additional
153    ///   context to the credential provider. If the caller does not need to compute derived values
154    ///   from the headers then do not provide an `EntityTag`. The credentials will either return
155    ///   `Err(...)` or `Ok(CacheableResource::New {})` in this case. Since the credentials
156    ///   already cache the headers, then it can use the results directly. Some applications need
157    ///   to compute values derived from the result, and want to avoid that computation if the
158    ///   headers have not changed. In that case, provide the `EntityTag` returned from a previous
159    ///   call. If the underlying authentication data has not changed, this method returns
160    ///   `Ok(CacheableResource::NotModified)` and you can use the same derived data. If the
161    ///   caller provides an `EntityTag` and the underlying authentication data has changed, this
162    ///   function returns `Ok(CacheableResource::New { ... })`. That result invalidates the
163    ///   tag, and provides new values for the headers.
164    ///
165    /// # Returns
166    /// A `Result` containing:
167    /// * `Ok(CacheableResource::New { entity_tag, data })`: If new or updated headers
168    ///   are available.
169    /// * `Ok(CacheableResource::NotModified)`: If the headers have not changed since
170    ///   the ETag provided via `extensions` was issued.
171    /// * `Err(CredentialsError)`: If an error occurs while trying to fetch or
172    ///   generating the headers.
173    pub async fn headers(&self, extensions: Extensions) -> Result<CacheableResource<HeaderMap>> {
174        self.inner.headers(extensions).await
175    }
176
177    /// Retrieves the universe domain associated with the credentials, if any.
178    ///
179    /// A "universe" is an isolated Google Cloud environment, such as the public
180    /// cloud or a sovereign/air-gapped deployment. The universe domain is used to
181    /// construct base URLs for API endpoints within that environment.
182    ///
183    /// By default, this returns `None`, which means the default universe domain of
184    /// `googleapis.com`. You should only override this if your application is operating
185    /// within a custom Cloud universe and needs to direct authentication and service
186    /// requests to a different base endpoint.
187    pub async fn universe_domain(&self) -> Option<String> {
188        self.inner.universe_domain().await
189    }
190}
191
192/// An implementation of [crate::credentials::CredentialsProvider] that can also
193/// provide direct access to the underlying access token.
194///
195/// This struct is returned by the `build_access_token_credentials()` method on
196/// the various credential builders. It can be used to obtain an access token
197/// directly via the `access_token()` method, or it can be converted into a `Credentials`
198/// object to be used with the Google Cloud client libraries.
199#[derive(Clone, Debug)]
200pub struct AccessTokenCredentials {
201    // We use an `Arc` to hold the inner implementation.
202    //
203    // AccessTokenCredentials may be shared across threads (`Send + Sync`), so an `Rc`
204    // will not do.
205    //
206    // They also need to derive `Clone`, as the
207    // `google_cloud_gax::http_client::ReqwestClient`s which hold them derive `Clone`. So a
208    // `Box` will not do.
209    inner: Arc<dyn dynamic::AccessTokenCredentialsProvider>,
210}
211
212impl<T> std::convert::From<T> for AccessTokenCredentials
213where
214    T: crate::credentials::AccessTokenCredentialsProvider + Send + Sync + 'static,
215{
216    fn from(value: T) -> Self {
217        Self {
218            inner: Arc::new(value),
219        }
220    }
221}
222
223impl AccessTokenCredentials {
224    /// Asynchronously retrieves an access token.
225    pub async fn access_token(&self) -> Result<AccessToken> {
226        self.inner.access_token().await
227    }
228}
229
230/// Makes [AccessTokenCredentials] compatible with clients that expect
231/// a [Credentials] and/or a [CredentialsProvider].
232impl CredentialsProvider for AccessTokenCredentials {
233    async fn headers(&self, extensions: Extensions) -> Result<CacheableResource<HeaderMap>> {
234        self.inner.headers(extensions).await
235    }
236
237    async fn universe_domain(&self) -> Option<String> {
238        self.inner.universe_domain().await
239    }
240}
241
242/// Represents an OAuth 2.0 access token.
243#[derive(Clone)]
244pub struct AccessToken {
245    /// The access token string.
246    pub token: String,
247}
248
249impl std::fmt::Debug for AccessToken {
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        f.debug_struct("AccessToken")
252            .field("token", &"[censored]")
253            .finish()
254    }
255}
256
257impl std::convert::From<CacheableResource<Token>> for Result<AccessToken> {
258    fn from(token: CacheableResource<Token>) -> Self {
259        match token {
260            CacheableResource::New { data, .. } => Ok(data.into()),
261            CacheableResource::NotModified => Err(errors::CredentialsError::from_msg(
262                false,
263                "Expecting token to be present",
264            )),
265        }
266    }
267}
268
269impl std::convert::From<Token> for AccessToken {
270    fn from(token: Token) -> Self {
271        Self { token: token.token }
272    }
273}
274
275/// A trait for credential types that can provide direct access to an access token.
276///
277/// This trait is primarily intended for interoperability with other libraries that
278/// require a raw access token, or for calling Google Cloud APIs that are not yet
279/// supported by the SDK.
280pub trait AccessTokenCredentialsProvider: CredentialsProvider + std::fmt::Debug {
281    /// Asynchronously retrieves an access token.
282    fn access_token(&self) -> impl Future<Output = Result<AccessToken>> + Send;
283}
284
285/// Represents a [Credentials] used to obtain auth request headers.
286///
287/// In general, [Credentials][credentials-link] are "digital object that
288/// provide proof of identity", the archetype may be a username and password
289/// combination, but a private RSA key may be a better example.
290///
291/// Modern authentication protocols do not send the credentials to
292/// authenticate with a service. Even when sent over encrypted transports,
293/// the credentials may be accidentally exposed via logging or may be
294/// captured if there are errors in the transport encryption. Because the
295/// credentials are often long-lived, that risk of exposure is also
296/// long-lived.
297///
298/// Instead, modern authentication protocols exchange the credentials for a
299/// time-limited [Token][token-link], a digital object that shows the caller
300/// was in possession of the credentials. Because tokens are time limited,
301/// risk of misuse is also time limited. Tokens may be further restricted to
302/// only a certain subset of the RPCs in the service, or even to specific
303/// resources, or only when used from a given machine (virtual or not).
304/// Further limiting the risks associated with any leaks of these tokens.
305///
306/// This struct also abstracts token sources that are not backed by a
307/// specific digital object. The canonical example is the
308/// [Metadata Service]. This service is available in many Google Cloud
309/// environments, including [Google Compute Engine], and
310/// [Google Kubernetes Engine].
311///
312/// # Notes
313///
314/// Application developers who directly use the Auth SDK can use this trait,
315/// along with [crate::credentials::Credentials::from()] to mock the credentials.
316/// Application developers who use the Google Cloud Rust SDK directly should not
317/// need this functionality.
318///
319/// [credentials-link]: https://cloud.google.com/docs/authentication#credentials
320/// [token-link]: https://cloud.google.com/docs/authentication#token
321/// [Metadata Service]: https://cloud.google.com/compute/docs/metadata/overview
322/// [Google Compute Engine]: https://cloud.google.com/products/compute
323/// [Google Kubernetes Engine]: https://cloud.google.com/kubernetes-engine
324pub trait CredentialsProvider: std::fmt::Debug {
325    /// Asynchronously constructs the auth headers.
326    ///
327    /// Different auth tokens are sent via different headers. The
328    /// [Credentials] constructs the headers (and header values) that should be
329    /// sent with a request.
330    ///
331    /// # Parameters
332    /// * `extensions` - An `http::Extensions` map that can be used to pass additional
333    ///   context to the credential provider. For caching purposes, this can include
334    ///   an [EntityTag] from a previously returned [`CacheableResource<HeaderMap>`].
335    ///   If a valid `EntityTag` is provided and the underlying authentication data
336    ///   has not changed, this method returns `Ok(CacheableResource::NotModified)`.
337    ///
338    /// # Returns
339    /// A `Future` that resolves to a `Result` containing:
340    /// * `Ok(CacheableResource::New { entity_tag, data })`: If new or updated headers
341    ///   are available.
342    /// * `Ok(CacheableResource::NotModified)`: If the headers have not changed since
343    ///   the ETag provided via `extensions` was issued.
344    /// * `Err(CredentialsError)`: If an error occurs while trying to fetch or
345    ///   generating the headers.
346    fn headers(
347        &self,
348        extensions: Extensions,
349    ) -> impl Future<Output = Result<CacheableResource<HeaderMap>>> + Send;
350
351    /// Retrieves the universe domain associated with the credentials, if any.
352    fn universe_domain(&self) -> impl Future<Output = Option<String>> + Send;
353}
354
355pub(crate) mod dynamic {
356    use super::Result;
357    use super::{CacheableResource, Extensions, HeaderMap};
358
359    /// A dyn-compatible, crate-private version of `CredentialsProvider`.
360    #[async_trait::async_trait]
361    pub trait CredentialsProvider: Send + Sync + std::fmt::Debug {
362        /// Asynchronously constructs the auth headers.
363        ///
364        /// Different auth tokens are sent via different headers. The
365        /// [Credentials] constructs the headers (and header values) that should be
366        /// sent with a request.
367        ///
368        /// # Parameters
369        /// * `extensions` - An `http::Extensions` map that can be used to pass additional
370        ///   context to the credential provider. For caching purposes, this can include
371        ///   an [EntityTag] from a previously returned [CacheableResource<HeaderMap>].
372        ///   If a valid `EntityTag` is provided and the underlying authentication data
373        ///   has not changed, this method returns `Ok(CacheableResource::NotModified)`.
374        ///
375        /// # Returns
376        /// A `Future` that resolves to a `Result` containing:
377        /// * `Ok(CacheableResource::New { entity_tag, data })`: If new or updated headers
378        ///   are available.
379        /// * `Ok(CacheableResource::NotModified)`: If the headers have not changed since
380        ///   the ETag provided via `extensions` was issued.
381        /// * `Err(CredentialsError)`: If an error occurs while trying to fetch or
382        ///   generating the headers.
383        async fn headers(&self, extensions: Extensions) -> Result<CacheableResource<HeaderMap>>;
384
385        /// Retrieves the universe domain associated with the credentials, if any.
386        async fn universe_domain(&self) -> Option<String> {
387            Some("googleapis.com".to_string())
388        }
389    }
390
391    /// The public CredentialsProvider implements the dyn-compatible CredentialsProvider.
392    #[async_trait::async_trait]
393    impl<T> CredentialsProvider for T
394    where
395        T: super::CredentialsProvider + Send + Sync,
396    {
397        async fn headers(&self, extensions: Extensions) -> Result<CacheableResource<HeaderMap>> {
398            T::headers(self, extensions).await
399        }
400        async fn universe_domain(&self) -> Option<String> {
401            T::universe_domain(self).await
402        }
403    }
404
405    /// A dyn-compatible, crate-private version of `AccessTokenCredentialsProvider`.
406    #[async_trait::async_trait]
407    pub trait AccessTokenCredentialsProvider:
408        CredentialsProvider + Send + Sync + std::fmt::Debug
409    {
410        async fn access_token(&self) -> Result<super::AccessToken>;
411    }
412
413    #[async_trait::async_trait]
414    impl<T> AccessTokenCredentialsProvider for T
415    where
416        T: super::AccessTokenCredentialsProvider + Send + Sync,
417    {
418        async fn access_token(&self) -> Result<super::AccessToken> {
419            T::access_token(self).await
420        }
421    }
422}
423
424/// A builder for constructing [`Credentials`] instances.
425///
426/// This builder loads credentials according to the standard
427/// [Application Default Credentials (ADC)][ADC-link] strategy.
428/// ADC is the recommended approach for most applications and conforms to
429/// [AIP-4110]. If you need to load credentials from a non-standard location
430/// or source, you can use Builders on the specific credential types.
431///
432/// Common use cases where using ADC would is useful include:
433/// - Your application is deployed to a Google Cloud environment such as
434///   [Google Compute Engine (GCE)][gce-link],
435///   [Google Kubernetes Engine (GKE)][gke-link], or [Cloud Run]. Each of these
436///   deployment environments provides a default service account to the
437///   application, and offers mechanisms to change this default service account
438///   without any code changes to your application.
439/// - You are testing or developing the application on a workstation (physical
440///   or virtual). These credentials will use your preferences as set with
441///   [gcloud auth application-default]. These preferences can be your own
442///   Google Cloud user credentials, or some service account.
443/// - Regardless of where your application is running, you can use the
444///   `GOOGLE_APPLICATION_CREDENTIALS` environment variable to override the
445///   defaults. This environment variable should point to a file containing a
446///   service account key file, or a JSON object describing your user
447///   credentials.
448///
449/// The headers returned by these credentials should be used in the
450/// Authorization HTTP header.
451///
452/// The Google Cloud client libraries for Rust will typically find and use these
453/// credentials automatically if a credentials file exists in the standard ADC
454/// search paths. You might instantiate these credentials if you need to:
455/// - Override the OAuth 2.0 **scopes** being requested for the access token.
456/// - Override the **quota project ID** for billing and quota management.
457///
458/// # Example: fetching headers using ADC
459/// ```
460/// # use google_cloud_auth::credentials::Builder;
461/// # use http::Extensions;
462/// # async fn sample() -> anyhow::Result<()> {
463/// let credentials = Builder::default()
464///     .with_quota_project_id("my-project")
465///     .build()?;
466/// let headers = credentials.headers(Extensions::new()).await?;
467/// println!("Headers: {headers:?}");
468/// # Ok(()) }
469/// ```
470///
471/// [ADC-link]: https://cloud.google.com/docs/authentication/application-default-credentials
472/// [AIP-4110]: https://google.aip.dev/auth/4110
473/// [Cloud Run]: https://cloud.google.com/run
474/// [gce-link]: https://cloud.google.com/products/compute
475/// [gcloud auth application-default]: https://cloud.google.com/sdk/gcloud/reference/auth/application-default
476/// [gke-link]: https://cloud.google.com/kubernetes-engine
477#[derive(Debug)]
478pub struct Builder {
479    quota_project_id: Option<String>,
480    scopes: Option<Vec<String>>,
481    universe_domain: Option<String>,
482}
483
484impl Default for Builder {
485    /// Creates a new builder where credentials will be obtained via [application-default login].
486    ///
487    /// # Example
488    /// ```
489    /// # use google_cloud_auth::credentials::Builder;
490    /// # fn sample() -> anyhow::Result<()> {
491    /// let credentials = Builder::default().build()?;
492    /// # Ok(()) }
493    /// ```
494    ///
495    /// [application-default login]: https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login
496    fn default() -> Self {
497        Self {
498            quota_project_id: None,
499            scopes: None,
500            universe_domain: None,
501        }
502    }
503}
504
505impl Builder {
506    /// Sets the [quota project] for these credentials.
507    ///
508    /// In some services, you can use an account in one project for authentication
509    /// and authorization, and charge the usage to a different project. This requires
510    /// that the user has `serviceusage.services.use` permissions on the quota project.
511    ///
512    /// ## Important: Precedence
513    /// If the `GOOGLE_CLOUD_QUOTA_PROJECT` environment variable is set,
514    /// its value will be used **instead of** the value provided to this method.
515    ///
516    /// # Example
517    /// ```
518    /// # use google_cloud_auth::credentials::Builder;
519    /// # fn sample() -> anyhow::Result<()> {
520    /// let credentials = Builder::default()
521    ///     .with_quota_project_id("my-project")
522    ///     .build()?;
523    /// # Ok(()) }
524    /// ```
525    ///
526    /// [quota project]: https://cloud.google.com/docs/quotas/quota-project
527    pub fn with_quota_project_id<S: Into<String>>(mut self, quota_project_id: S) -> Self {
528        self.quota_project_id = Some(quota_project_id.into());
529        self
530    }
531
532    /// Sets the [scopes] for these credentials.
533    ///
534    /// `scopes` act as an additional restriction in addition to the IAM permissions
535    /// granted to the principal (user or service account) that creates the token.
536    ///
537    /// `scopes` define the *permissions being requested* for this specific access token
538    /// when interacting with a service. For example,
539    /// `https://www.googleapis.com/auth/devstorage.read_write`.
540    ///
541    /// IAM permissions, on the other hand, define the *underlying capabilities*
542    /// the principal possesses within a system. For example, `storage.buckets.delete`.
543    ///
544    /// The credentials certify that a particular token was created by a certain principal.
545    ///
546    /// When a token generated with specific scopes is used, the request must be permitted
547    /// by both the the principals's underlying IAM permissions and the scopes requested
548    /// for the token.
549    ///
550    /// [scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
551    pub fn with_scopes<I, S>(mut self, scopes: I) -> Self
552    where
553        I: IntoIterator<Item = S>,
554        S: Into<String>,
555    {
556        self.scopes = Some(scopes.into_iter().map(|s| s.into()).collect());
557        self
558    }
559
560    /// Sets the Google Cloud universe domain for these credentials.
561    ///
562    /// The universe domain is the default service domain for a given Cloud universe.
563    /// If not set, the default will be used.
564    ///
565    /// # Example
566    /// ```
567    /// # use google_cloud_auth::credentials::Builder;
568    /// # fn sample() -> anyhow::Result<()> {
569    /// let credentials = Builder::default()
570    ///     .with_universe_domain("googleapis.com")
571    ///     .build()?;
572    /// # Ok(()) }
573    /// ```
574    pub fn with_universe_domain<S: Into<String>>(mut self, universe_domain: S) -> Self {
575        self.universe_domain = Some(universe_domain.into());
576        self
577    }
578
579    /// Returns a [Credentials] instance with the configured settings.
580    ///
581    /// # Errors
582    ///
583    /// Returns a [CredentialsError] if an unsupported credential type is provided
584    /// or if the JSON value is either malformed or missing required fields.
585    ///
586    /// For more information, on how to generate the JSON for a credential,
587    /// consult the relevant section in the [application-default credentials] guide.
588    ///
589    /// [application-default credentials]: https://cloud.google.com/docs/authentication/application-default-credentials
590    pub fn build(self) -> BuildResult<Credentials> {
591        Ok(self.build_access_token_credentials()?.into())
592    }
593
594    /// Returns an [AccessTokenCredentials] instance with the configured settings.
595    ///
596    /// # Example
597    ///
598    /// ```
599    /// # use google_cloud_auth::credentials::{Builder, AccessTokenCredentials, AccessTokenCredentialsProvider};
600    /// # async fn sample() -> anyhow::Result<()> {
601    /// // This will search for Application Default Credentials and build AccessTokenCredentials.
602    /// let credentials: AccessTokenCredentials = Builder::default()
603    ///     .build_access_token_credentials()?;
604    /// let access_token = credentials.access_token().await?;
605    /// println!("Token: {}", access_token.token);
606    /// # Ok(()) }
607    /// ```
608    ///
609    /// # Errors
610    ///
611    /// Returns a [CredentialsError] if an unsupported credential type is provided
612    /// or if the JSON value is either malformed or missing required fields.
613    ///
614    /// For more information, on how to generate the JSON for a credential,
615    /// consult the relevant section in the [application-default credentials] guide.
616    ///
617    /// [application-default credentials]: https://cloud.google.com/docs/authentication/application-default-credentials
618    pub fn build_access_token_credentials(self) -> BuildResult<AccessTokenCredentials> {
619        let json_data = match load_adc()? {
620            AdcContents::Contents(contents) => {
621                Some(serde_json::from_str(&contents).map_err(BuilderError::parsing)?)
622            }
623            AdcContents::FallbackToMds => None,
624        };
625        let quota_project_id = std::env::var(GOOGLE_CLOUD_QUOTA_PROJECT_VAR)
626            .ok()
627            .or(self.quota_project_id);
628        build_credentials(
629            json_data,
630            quota_project_id,
631            self.scopes,
632            self.universe_domain,
633        )
634    }
635
636    /// Returns a [crate::signer::Signer] instance with the configured settings.
637    ///
638    /// This method automatically loads Application Default Credentials (ADC)
639    /// from the environment and uses them to create a signer.
640    ///
641    /// The returned [crate::signer::Signer] might perform signing locally (e.g. if a service
642    /// account key is found) or via a remote API (e.g. if running on GCE).
643    ///
644    /// # Example
645    ///
646    /// ```
647    /// # use google_cloud_auth::credentials::Builder;
648    /// # use google_cloud_auth::signer::Signer;
649    /// # fn sample() -> anyhow::Result<()> {
650    /// let signer: Signer = Builder::default().build_signer()?;
651    /// # Ok(()) }
652    /// ```
653    pub fn build_signer(self) -> BuildResult<crate::signer::Signer> {
654        let json_data = match load_adc()? {
655            AdcContents::Contents(contents) => {
656                Some(serde_json::from_str(&contents).map_err(BuilderError::parsing)?)
657            }
658            AdcContents::FallbackToMds => None,
659        };
660        let quota_project_id = std::env::var(GOOGLE_CLOUD_QUOTA_PROJECT_VAR)
661            .ok()
662            .or(self.quota_project_id);
663        build_signer(
664            json_data,
665            quota_project_id,
666            self.scopes,
667            self.universe_domain,
668        )
669    }
670}
671
672#[derive(Debug, PartialEq)]
673enum AdcPath {
674    FromEnv(std::path::PathBuf),
675    WellKnown(std::path::PathBuf),
676}
677
678#[derive(Debug, PartialEq)]
679enum AdcContents {
680    Contents(String),
681    FallbackToMds,
682}
683
684fn extract_credential_type(json: &Value) -> BuildResult<&str> {
685    json.get("type")
686        .ok_or_else(|| BuilderError::parsing("no `type` field found."))?
687        .as_str()
688        .ok_or_else(|| BuilderError::parsing("`type` field is not a string."))
689}
690
691/// Applies common optional configurations (quota project ID, scopes) to a
692/// specific credential builder instance and then builds it.
693///
694/// This macro centralizes the logic for optionally calling `.with_quota_project_id()`
695/// and `.with_scopes()` on different underlying credential builders (like
696/// `mds::Builder`, `service_account::Builder`, etc.) before calling `.build()`.
697/// It helps avoid repetitive code in the `build_credentials` function.
698macro_rules! config_builder {
699    ($builder_instance:expr, $quota_project_id_option:expr, $scopes_option:expr, $universe_domain_option:expr, $apply_scopes_closure:expr) => {{
700        let builder = config_common_builder!(
701            $builder_instance,
702            $quota_project_id_option,
703            $scopes_option,
704            $universe_domain_option,
705            $apply_scopes_closure
706        );
707        builder.build_access_token_credentials()
708    }};
709}
710
711/// Applies common optional configurations (quota project ID, scopes) to a
712/// specific credential builder instance and then return a signer for it.
713macro_rules! config_signer {
714    ($builder_instance:expr, $quota_project_id_option:expr, $scopes_option:expr, $universe_domain_option:expr, $apply_scopes_closure:expr) => {{
715        let builder = config_common_builder!(
716            $builder_instance,
717            $quota_project_id_option,
718            $scopes_option,
719            $universe_domain_option,
720            $apply_scopes_closure
721        );
722        builder.build_signer()
723    }};
724}
725
726macro_rules! config_common_builder {
727    ($builder_instance:expr, $quota_project_id_option:expr, $scopes_option:expr, $universe_domain_option:expr, $apply_scopes_closure:expr) => {{
728        let builder = $builder_instance;
729        let builder = $quota_project_id_option
730            .into_iter()
731            .fold(builder, |b, qp| b.with_quota_project_id(qp));
732
733        let builder = $universe_domain_option
734            .into_iter()
735            .fold(builder, |b, ud| b.with_universe_domain(ud));
736
737        let builder = $scopes_option
738            .into_iter()
739            .fold(builder, |b, s| $apply_scopes_closure(b, s));
740
741        builder
742    }};
743}
744
745fn build_credentials(
746    json: Option<Value>,
747    quota_project_id: Option<String>,
748    scopes: Option<Vec<String>>,
749    universe_domain: Option<String>,
750) -> BuildResult<AccessTokenCredentials> {
751    match json {
752        None => config_builder!(
753            mds::Builder::from_adc(),
754            quota_project_id,
755            scopes,
756            universe_domain.clone(),
757            |b: mds::Builder, s: Vec<String>| b.with_scopes(s)
758        ),
759        Some(json) => {
760            let cred_type = extract_credential_type(&json)?;
761            match cred_type {
762                "authorized_user" => {
763                    config_builder!(
764                        user_account::Builder::new(json),
765                        quota_project_id,
766                        scopes,
767                        universe_domain.clone(),
768                        |b: user_account::Builder, s: Vec<String>| b.with_scopes(s)
769                    )
770                }
771                "service_account" => config_builder!(
772                    service_account::Builder::new(json),
773                    quota_project_id,
774                    scopes,
775                    universe_domain.clone(),
776                    |b: service_account::Builder, s: Vec<String>| b
777                        .with_access_specifier(service_account::AccessSpecifier::from_scopes(s))
778                ),
779                "impersonated_service_account" => {
780                    config_builder!(
781                        impersonated::Builder::new(json),
782                        quota_project_id,
783                        scopes,
784                        universe_domain.clone(),
785                        |b: impersonated::Builder, s: Vec<String>| b.with_scopes(s)
786                    )
787                }
788                "external_account" => config_builder!(
789                    external_account::Builder::new(json),
790                    quota_project_id,
791                    scopes,
792                    universe_domain.clone(),
793                    |b: external_account::Builder, s: Vec<String>| b.with_scopes(s)
794                ),
795                _ => Err(BuilderError::unknown_type(cred_type)),
796            }
797        }
798    }
799}
800
801fn build_signer(
802    json: Option<Value>,
803    quota_project_id: Option<String>,
804    scopes: Option<Vec<String>>,
805    universe_domain: Option<String>,
806) -> BuildResult<crate::signer::Signer> {
807    match json {
808        None => config_signer!(
809            mds::Builder::from_adc(),
810            quota_project_id,
811            scopes,
812            universe_domain.clone(),
813            |b: mds::Builder, s: Vec<String>| b.with_scopes(s)
814        ),
815        Some(json) => {
816            let cred_type = extract_credential_type(&json)?;
817            match cred_type {
818                "authorized_user" => Err(BuilderError::not_supported(
819                    "authorized_user signer is not supported",
820                )),
821                "service_account" => config_signer!(
822                    service_account::Builder::new(json),
823                    quota_project_id,
824                    scopes,
825                    universe_domain.clone(),
826                    |b: service_account::Builder, s: Vec<String>| b
827                        .with_access_specifier(service_account::AccessSpecifier::from_scopes(s))
828                ),
829                "impersonated_service_account" => {
830                    config_signer!(
831                        impersonated::Builder::new(json),
832                        quota_project_id,
833                        scopes,
834                        universe_domain.clone(),
835                        |b: impersonated::Builder, s: Vec<String>| b.with_scopes(s)
836                    )
837                }
838                "external_account" => Err(BuilderError::not_supported(
839                    "external_account signer is not supported",
840                )),
841                _ => Err(BuilderError::unknown_type(cred_type)),
842            }
843        }
844    }
845}
846
847fn path_not_found(path: std::path::PathBuf) -> BuilderError {
848    BuilderError::loading(format!(
849        "{}. {}",
850        path.display(),
851        concat!(
852            "This file name was found in the `GOOGLE_APPLICATION_CREDENTIALS` ",
853            "environment variable. Verify this environment variable points to ",
854            "a valid file."
855        )
856    ))
857}
858
859fn load_adc() -> BuildResult<AdcContents> {
860    match adc_path() {
861        None => Ok(AdcContents::FallbackToMds),
862        Some(AdcPath::FromEnv(path)) => match std::fs::read_to_string(&path) {
863            Ok(contents) => Ok(AdcContents::Contents(contents)),
864            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(path_not_found(path)),
865            Err(e) => Err(BuilderError::loading(e)),
866        },
867        Some(AdcPath::WellKnown(path)) => match std::fs::read_to_string(&path) {
868            Ok(contents) => Ok(AdcContents::Contents(contents)),
869            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(AdcContents::FallbackToMds),
870            Err(e) => Err(BuilderError::loading(e)),
871        },
872    }
873}
874
875/// The path to Application Default Credentials (ADC), as specified in [AIP-4110].
876///
877/// [AIP-4110]: https://google.aip.dev/auth/4110
878fn adc_path() -> Option<AdcPath> {
879    if let Some(path) = std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") {
880        return Some(AdcPath::FromEnv(std::path::PathBuf::from(path)));
881    }
882    Some(AdcPath::WellKnown(adc_well_known_path()?))
883}
884
885/// The well-known path to ADC on Windows, as specified in [AIP-4113].
886///
887/// [AIP-4113]: https://google.aip.dev/auth/4113
888#[cfg(target_os = "windows")]
889fn adc_well_known_path() -> Option<std::path::PathBuf> {
890    std::env::var_os("APPDATA").map(|root| {
891        std::path::PathBuf::from(root).join("gcloud/application_default_credentials.json")
892    })
893}
894
895/// The well-known path to ADC on Linux and Mac, as specified in [AIP-4113].
896///
897/// [AIP-4113]: https://google.aip.dev/auth/4113
898#[cfg(not(target_os = "windows"))]
899fn adc_well_known_path() -> Option<std::path::PathBuf> {
900    std::env::var_os("HOME").map(|root| {
901        std::path::PathBuf::from(root).join(".config/gcloud/application_default_credentials.json")
902    })
903}
904
905/// A module providing invalid credentials where authentication does not matter.
906///
907/// These credentials are a convenient way to avoid errors from loading
908/// Application Default Credentials in tests.
909///
910/// This module is mainly relevant to other `google-cloud-*` crates, but some
911/// external developers (i.e. consumers, not developers of `google-cloud-rust`)
912/// may find it useful.
913// Skipping mutation testing for this module. As it exclusively provides
914// hardcoded credential stubs for testing purposes.
915#[cfg_attr(test, mutants::skip)]
916#[doc(hidden)]
917pub mod testing {
918    use super::CacheableResource;
919    use crate::Result;
920    use crate::credentials::Credentials;
921    use crate::credentials::dynamic::CredentialsProvider;
922    use http::{Extensions, HeaderMap};
923    use std::sync::Arc;
924
925    /// A simple credentials implementation to use in tests.
926    ///
927    /// Always return an error in `headers()`.
928    pub fn error_credentials(retryable: bool) -> Credentials {
929        Credentials {
930            inner: Arc::from(ErrorCredentials(retryable)),
931        }
932    }
933
934    #[derive(Debug, Default)]
935    struct ErrorCredentials(bool);
936
937    #[async_trait::async_trait]
938    impl CredentialsProvider for ErrorCredentials {
939        async fn headers(&self, _extensions: Extensions) -> Result<CacheableResource<HeaderMap>> {
940            Err(super::CredentialsError::from_msg(self.0, "test-only"))
941        }
942
943        async fn universe_domain(&self) -> Option<String> {
944            None
945        }
946    }
947}
948
949#[cfg(test)]
950pub(crate) mod tests {
951    use super::*;
952    use crate::constants::TRUST_BOUNDARY_HEADER;
953    use crate::errors::is_gax_error_retryable;
954    use base64::Engine;
955    use google_cloud_gax::backoff_policy::BackoffPolicy;
956    use google_cloud_gax::retry_policy::RetryPolicy;
957    use google_cloud_gax::retry_result::RetryResult;
958    use google_cloud_gax::retry_state::RetryState;
959    use google_cloud_gax::retry_throttler::RetryThrottler;
960    use mockall::mock;
961    use reqwest::header::AUTHORIZATION;
962    use rsa::BigUint;
963    use rsa::RsaPrivateKey;
964    use rsa::pkcs8::{EncodePrivateKey, LineEnding};
965    use scoped_env::ScopedEnv;
966    use std::error::Error;
967    use std::sync::LazyLock;
968    use test_case::test_case;
969    use tokio::time::Duration;
970    use tokio::time::Instant;
971
972    // find the last/root error in the chain that matches the given type
973    pub(crate) fn find_source_error<'a, T: Error + 'static>(
974        error: &'a (dyn Error + 'static),
975    ) -> Option<&'a T> {
976        let mut last_err = None;
977        let mut source = error.source();
978        while let Some(err) = source {
979            if let Some(target_err) = err.downcast_ref::<T>() {
980                last_err = Some(target_err);
981            }
982            source = err.source();
983        }
984        last_err
985    }
986
987    mock! {
988        #[derive(Debug)]
989        pub RetryPolicy {}
990        impl RetryPolicy for RetryPolicy {
991            fn on_error(
992                &self,
993                state: &RetryState,
994                error: google_cloud_gax::error::Error,
995            ) -> RetryResult;
996        }
997    }
998
999    mock! {
1000        #[derive(Debug)]
1001        pub BackoffPolicy {}
1002        impl BackoffPolicy for BackoffPolicy {
1003            fn on_failure(&self, state: &RetryState) -> std::time::Duration;
1004        }
1005    }
1006
1007    mockall::mock! {
1008        #[derive(Debug)]
1009        pub RetryThrottler {}
1010        impl RetryThrottler for RetryThrottler {
1011            fn throttle_retry_attempt(&self) -> bool;
1012            fn on_retry_failure(&mut self, error: &RetryResult);
1013            fn on_success(&mut self);
1014        }
1015    }
1016
1017    // Used by tests in other modules.
1018    mockall::mock! {
1019        #[derive(Debug)]
1020        pub Credentials {}
1021
1022        impl crate::credentials::CredentialsProvider for Credentials {
1023            async fn headers(&self, extensions: http::Extensions) -> std::result::Result<crate::credentials::CacheableResource<http::HeaderMap>, crate::errors::CredentialsError>;
1024            async fn universe_domain(&self) -> Option<String>;
1025        }
1026
1027        impl crate::credentials::AccessTokenCredentialsProvider for Credentials {
1028            async fn access_token(&self) -> std::result::Result<crate::credentials::AccessToken, crate::errors::CredentialsError>;
1029        }
1030    }
1031
1032    type TestResult = std::result::Result<(), Box<dyn std::error::Error>>;
1033
1034    pub(crate) fn get_mock_auth_retry_policy(attempts: usize) -> MockRetryPolicy {
1035        let mut retry_policy = MockRetryPolicy::new();
1036        retry_policy
1037            .expect_on_error()
1038            .returning(move |state, error| {
1039                if state.attempt_count >= attempts as u32 {
1040                    return RetryResult::Exhausted(error);
1041                }
1042                let is_retryable = is_gax_error_retryable(&error);
1043                if is_retryable {
1044                    RetryResult::Continue(error)
1045                } else {
1046                    RetryResult::Permanent(error)
1047                }
1048            });
1049        retry_policy
1050    }
1051
1052    pub(crate) fn get_mock_backoff_policy() -> MockBackoffPolicy {
1053        let mut backoff_policy = MockBackoffPolicy::new();
1054        backoff_policy
1055            .expect_on_failure()
1056            .return_const(Duration::from_secs(0));
1057        backoff_policy
1058    }
1059
1060    pub(crate) fn get_mock_retry_throttler() -> MockRetryThrottler {
1061        let mut throttler = MockRetryThrottler::new();
1062        throttler.expect_on_retry_failure().return_const(());
1063        throttler
1064            .expect_throttle_retry_attempt()
1065            .return_const(false);
1066        throttler.expect_on_success().return_const(());
1067        throttler
1068    }
1069
1070    pub(crate) fn get_headers_from_cache(
1071        headers: CacheableResource<HeaderMap>,
1072    ) -> Result<HeaderMap> {
1073        match headers {
1074            CacheableResource::New { data, .. } => Ok(data),
1075            CacheableResource::NotModified => Err(CredentialsError::from_msg(
1076                false,
1077                "Expecting headers to be present",
1078            )),
1079        }
1080    }
1081
1082    pub(crate) fn get_token_from_headers(headers: CacheableResource<HeaderMap>) -> Option<String> {
1083        match headers {
1084            CacheableResource::New { data, .. } => data
1085                .get(AUTHORIZATION)
1086                .and_then(|token_value| token_value.to_str().ok())
1087                .and_then(|s| s.split_whitespace().nth(1))
1088                .map(|s| s.to_string()),
1089            CacheableResource::NotModified => None,
1090        }
1091    }
1092
1093    pub(crate) fn get_access_boundary_from_headers(
1094        headers: CacheableResource<HeaderMap>,
1095    ) -> Option<String> {
1096        match headers {
1097            CacheableResource::New { data, .. } => data
1098                .get(TRUST_BOUNDARY_HEADER)
1099                .and_then(|token_value| token_value.to_str().ok())
1100                .map(|s| s.to_string()),
1101            CacheableResource::NotModified => None,
1102        }
1103    }
1104
1105    pub(crate) fn get_token_type_from_headers(
1106        headers: CacheableResource<HeaderMap>,
1107    ) -> Option<String> {
1108        match headers {
1109            CacheableResource::New { data, .. } => data
1110                .get(AUTHORIZATION)
1111                .and_then(|token_value| token_value.to_str().ok())
1112                .and_then(|s| s.split_whitespace().next())
1113                .map(|s| s.to_string()),
1114            CacheableResource::NotModified => None,
1115        }
1116    }
1117
1118    pub static RSA_PRIVATE_KEY: LazyLock<RsaPrivateKey> = LazyLock::new(|| {
1119        let p_str: &str = "141367881524527794394893355677826002829869068195396267579403819572502936761383874443619453704612633353803671595972343528718438130450055151198231345212263093247511629886734453413988207866331439612464122904648042654465604881130663408340669956544709445155137282157402427763452856646879397237752891502149781819597";
1120        let q_str: &str = "179395413952110013801471600075409598322058038890563483332288896635704255883613060744402506322679437982046475766067250097809676406576067239936945362857700460740092421061356861438909617220234758121022105150630083703531219941303688818533566528599328339894969707615478438750812672509434761181735933851075292740309";
1121        let e_str: &str = "65537";
1122
1123        let p = BigUint::parse_bytes(p_str.as_bytes(), 10).expect("Failed to parse prime P");
1124        let q = BigUint::parse_bytes(q_str.as_bytes(), 10).expect("Failed to parse prime Q");
1125        let public_exponent =
1126            BigUint::parse_bytes(e_str.as_bytes(), 10).expect("Failed to parse public exponent");
1127
1128        RsaPrivateKey::from_primes(vec![p, q], public_exponent)
1129            .expect("Failed to create RsaPrivateKey from primes")
1130    });
1131
1132    #[cfg(any(feature = "idtoken", feature = "__gdch"))]
1133    pub static ES256_PRIVATE_KEY: LazyLock<p256::SecretKey> = LazyLock::new(|| {
1134        let secret_key_bytes = [
1135            0x4c, 0x0c, 0x11, 0x6e, 0x6e, 0xb0, 0x07, 0xbd, 0x48, 0x0c, 0xc0, 0x48, 0xc0, 0x1f,
1136            0xac, 0x3d, 0x82, 0x82, 0x0e, 0x6c, 0x3d, 0x76, 0x61, 0x4d, 0x06, 0x4e, 0xdb, 0x05,
1137            0x26, 0x6c, 0x75, 0xdf,
1138        ];
1139        p256::SecretKey::from_bytes((&secret_key_bytes).into()).unwrap()
1140    });
1141
1142    #[cfg(feature = "__gdch")]
1143    pub static ES256_PEM: LazyLock<String> = LazyLock::new(|| {
1144        (*ES256_PRIVATE_KEY)
1145            .to_sec1_pem(LineEnding::LF)
1146            .expect("Failed to encode EC key to PEM")
1147            .to_string()
1148    });
1149
1150    pub static PKCS8_PK: LazyLock<String> = LazyLock::new(|| {
1151        RSA_PRIVATE_KEY
1152            .to_pkcs8_pem(LineEnding::LF)
1153            .expect("Failed to encode key to PKCS#8 PEM")
1154            .to_string()
1155    });
1156
1157    pub fn b64_decode_to_json(s: String) -> serde_json::Value {
1158        let decoded = String::from_utf8(
1159            base64::engine::general_purpose::URL_SAFE_NO_PAD
1160                .decode(s)
1161                .unwrap(),
1162        )
1163        .unwrap();
1164        serde_json::from_str(&decoded).unwrap()
1165    }
1166
1167    #[cfg(target_os = "windows")]
1168    #[test]
1169    #[serial_test::serial]
1170    fn adc_well_known_path_windows() {
1171        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1172        let _appdata = ScopedEnv::set("APPDATA", "C:/Users/foo");
1173        assert_eq!(
1174            adc_well_known_path(),
1175            Some(std::path::PathBuf::from(
1176                "C:/Users/foo/gcloud/application_default_credentials.json"
1177            ))
1178        );
1179        assert_eq!(
1180            adc_path(),
1181            Some(AdcPath::WellKnown(std::path::PathBuf::from(
1182                "C:/Users/foo/gcloud/application_default_credentials.json"
1183            )))
1184        );
1185    }
1186
1187    #[cfg(target_os = "windows")]
1188    #[test]
1189    #[serial_test::serial]
1190    fn adc_well_known_path_windows_no_appdata() {
1191        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1192        let _appdata = ScopedEnv::remove("APPDATA");
1193        assert_eq!(adc_well_known_path(), None);
1194        assert_eq!(adc_path(), None);
1195    }
1196
1197    #[cfg(not(target_os = "windows"))]
1198    #[test]
1199    #[serial_test::serial]
1200    fn adc_well_known_path_posix() {
1201        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1202        let _home = ScopedEnv::set("HOME", "/home/foo");
1203        assert_eq!(
1204            adc_well_known_path(),
1205            Some(std::path::PathBuf::from(
1206                "/home/foo/.config/gcloud/application_default_credentials.json"
1207            ))
1208        );
1209        assert_eq!(
1210            adc_path(),
1211            Some(AdcPath::WellKnown(std::path::PathBuf::from(
1212                "/home/foo/.config/gcloud/application_default_credentials.json"
1213            )))
1214        );
1215    }
1216
1217    #[cfg(not(target_os = "windows"))]
1218    #[test]
1219    #[serial_test::serial]
1220    fn adc_well_known_path_posix_no_home() {
1221        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1222        let _appdata = ScopedEnv::remove("HOME");
1223        assert_eq!(adc_well_known_path(), None);
1224        assert_eq!(adc_path(), None);
1225    }
1226
1227    #[test]
1228    #[serial_test::serial]
1229    fn adc_path_from_env() {
1230        let _creds = ScopedEnv::set(
1231            "GOOGLE_APPLICATION_CREDENTIALS",
1232            "/usr/bar/application_default_credentials.json",
1233        );
1234        assert_eq!(
1235            adc_path(),
1236            Some(AdcPath::FromEnv(std::path::PathBuf::from(
1237                "/usr/bar/application_default_credentials.json"
1238            )))
1239        );
1240    }
1241
1242    #[cfg(unix)]
1243    #[test]
1244    #[serial_test::serial]
1245    fn adc_path_from_env_non_utf8() {
1246        use std::os::unix::ffi::OsStringExt;
1247        let non_utf8_bytes = vec![
1248            b'/', b'u', b's', b'r', b'/', b'b', b'a', b'r', b'/', 0xff, b'.', b'j', b's', b'o',
1249            b'n',
1250        ];
1251        let non_utf8_os_str = std::ffi::OsString::from_vec(non_utf8_bytes);
1252
1253        let _creds = ScopedEnv::set(
1254            std::ffi::OsStr::new("GOOGLE_APPLICATION_CREDENTIALS"),
1255            non_utf8_os_str.as_os_str(),
1256        );
1257
1258        assert_eq!(
1259            adc_path(),
1260            Some(AdcPath::FromEnv(std::path::PathBuf::from(
1261                non_utf8_os_str.clone()
1262            )))
1263        );
1264    }
1265
1266    #[cfg(unix)]
1267    #[test]
1268    #[serial_test::serial]
1269    fn load_adc_no_file_at_env_is_error_non_utf8() {
1270        use std::os::unix::ffi::OsStringExt;
1271        let non_utf8_bytes = vec![
1272            b'f', b'i', b'l', b'e', b'-', 0xff, b'.', b'j', b's', b'o', b'n',
1273        ];
1274        let non_utf8_os_str = std::ffi::OsString::from_vec(non_utf8_bytes);
1275
1276        let _creds = ScopedEnv::set(
1277            std::ffi::OsStr::new("GOOGLE_APPLICATION_CREDENTIALS"),
1278            non_utf8_os_str.as_os_str(),
1279        );
1280
1281        let err = load_adc().unwrap_err();
1282        assert!(err.is_loading(), "{err:?}");
1283        let msg = format!("{err:?}");
1284        assert!(msg.contains("file-"), "{err:?}");
1285        assert!(msg.contains("GOOGLE_APPLICATION_CREDENTIALS"), "{err:?}");
1286    }
1287
1288    #[cfg(target_os = "windows")]
1289    #[test]
1290    #[serial_test::serial]
1291    fn adc_path_from_env_non_utf16() {
1292        use std::os::windows::ffi::OsStringExt;
1293        let non_utf16_wide = vec![
1294            b'C' as u16,
1295            b':' as u16,
1296            b'/' as u16,
1297            0xD800,
1298            b'.' as u16,
1299            b'j' as u16,
1300            b's' as u16,
1301            b'o' as u16,
1302            b'n' as u16,
1303        ];
1304        let non_utf16_os_str = std::ffi::OsString::from_wide(&non_utf16_wide);
1305
1306        let _creds = ScopedEnv::set(
1307            std::ffi::OsStr::new("GOOGLE_APPLICATION_CREDENTIALS"),
1308            non_utf16_os_str.as_os_str(),
1309        );
1310
1311        assert_eq!(
1312            adc_path(),
1313            Some(AdcPath::FromEnv(std::path::PathBuf::from(
1314                non_utf16_os_str.clone()
1315            )))
1316        );
1317    }
1318
1319    #[cfg(target_os = "windows")]
1320    #[test]
1321    #[serial_test::serial]
1322    fn load_adc_no_file_at_env_is_error_non_utf16() {
1323        use std::os::windows::ffi::OsStringExt;
1324        let non_utf16_wide = vec![
1325            b'f' as u16,
1326            b'i' as u16,
1327            b'l' as u16,
1328            b'e' as u16,
1329            b'-' as u16,
1330            0xD800,
1331            b'.' as u16,
1332            b'j' as u16,
1333            b's' as u16,
1334            b'o' as u16,
1335            b'n' as u16,
1336        ];
1337        let non_utf16_os_str = std::ffi::OsString::from_wide(&non_utf16_wide);
1338
1339        let _creds = ScopedEnv::set(
1340            std::ffi::OsStr::new("GOOGLE_APPLICATION_CREDENTIALS"),
1341            non_utf16_os_str.as_os_str(),
1342        );
1343
1344        let err = load_adc().unwrap_err();
1345        assert!(err.is_loading(), "{err:?}");
1346        let msg = format!("{err:?}");
1347        assert!(msg.contains("file-"), "{err:?}");
1348        assert!(msg.contains("GOOGLE_APPLICATION_CREDENTIALS"), "{err:?}");
1349    }
1350
1351    #[test]
1352    #[serial_test::serial]
1353    fn load_adc_no_well_known_path_fallback_to_mds() {
1354        let _e1 = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1355        let _e2 = ScopedEnv::remove("HOME"); // For posix
1356        let _e3 = ScopedEnv::remove("APPDATA"); // For windows
1357        assert_eq!(load_adc().unwrap(), AdcContents::FallbackToMds);
1358    }
1359
1360    #[test]
1361    #[serial_test::serial]
1362    fn load_adc_no_file_at_well_known_path_fallback_to_mds() {
1363        // Create a new temp directory. There is not an ADC file in here.
1364        let dir = tempfile::TempDir::new().unwrap();
1365        let path = dir.path().to_str().unwrap();
1366        let _e1 = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1367        let _e2 = ScopedEnv::set("HOME", path); // For posix
1368        let _e3 = ScopedEnv::set("APPDATA", path); // For windows
1369        assert_eq!(load_adc().unwrap(), AdcContents::FallbackToMds);
1370    }
1371
1372    #[test]
1373    #[serial_test::serial]
1374    fn load_adc_no_file_at_env_is_error() {
1375        let _e = ScopedEnv::set("GOOGLE_APPLICATION_CREDENTIALS", "file-does-not-exist.json");
1376        let err = load_adc().unwrap_err();
1377        assert!(err.is_loading(), "{err:?}");
1378        let msg = format!("{err:?}");
1379        assert!(msg.contains("file-does-not-exist.json"), "{err:?}");
1380        assert!(msg.contains("GOOGLE_APPLICATION_CREDENTIALS"), "{err:?}");
1381    }
1382
1383    #[test]
1384    #[serial_test::serial]
1385    fn load_adc_success() {
1386        let file = tempfile::NamedTempFile::new().unwrap();
1387        let path = file.into_temp_path();
1388        std::fs::write(&path, "contents").expect("Unable to write to temporary file.");
1389        let _e = ScopedEnv::set("GOOGLE_APPLICATION_CREDENTIALS", path.to_str().unwrap());
1390
1391        assert_eq!(
1392            load_adc().unwrap(),
1393            AdcContents::Contents("contents".to_string())
1394        );
1395    }
1396
1397    #[test_case(true; "retryable")]
1398    #[test_case(false; "non-retryable")]
1399    #[tokio::test]
1400    async fn error_credentials(retryable: bool) {
1401        let credentials = super::testing::error_credentials(retryable);
1402        assert!(
1403            credentials.universe_domain().await.is_none(),
1404            "{credentials:?}"
1405        );
1406        let err = credentials.headers(Extensions::new()).await.err().unwrap();
1407        assert_eq!(err.is_transient(), retryable, "{err:?}");
1408        let err = credentials.headers(Extensions::new()).await.err().unwrap();
1409        assert_eq!(err.is_transient(), retryable, "{err:?}");
1410    }
1411
1412    #[tokio::test]
1413    #[serial_test::serial]
1414    async fn create_access_token_credentials_fallback_to_mds_with_quota_project_override() {
1415        let _e1 = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1416        let _e2 = ScopedEnv::remove("HOME"); // For posix
1417        let _e3 = ScopedEnv::remove("APPDATA"); // For windows
1418        let _e4 = ScopedEnv::set(GOOGLE_CLOUD_QUOTA_PROJECT_VAR, "env-quota-project");
1419
1420        let mds = Builder::default()
1421            .with_quota_project_id("test-quota-project")
1422            .build()
1423            .unwrap();
1424        let fmt = format!("{mds:?}");
1425        assert!(fmt.contains("MDSCredentials"));
1426        assert!(
1427            fmt.contains("env-quota-project"),
1428            "Expected 'env-quota-project', got: {fmt}"
1429        );
1430    }
1431
1432    #[tokio::test]
1433    #[serial_test::serial]
1434    async fn create_access_token_credentials_with_quota_project_from_builder() {
1435        let _e1 = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1436        let _e2 = ScopedEnv::remove("HOME"); // For posix
1437        let _e3 = ScopedEnv::remove("APPDATA"); // For windows
1438        let _e4 = ScopedEnv::remove(GOOGLE_CLOUD_QUOTA_PROJECT_VAR);
1439
1440        let creds = Builder::default()
1441            .with_quota_project_id("test-quota-project")
1442            .build()
1443            .unwrap();
1444        let fmt = format!("{creds:?}");
1445        assert!(
1446            fmt.contains("test-quota-project"),
1447            "Expected 'test-quota-project', got: {fmt}"
1448        );
1449    }
1450
1451    #[tokio::test]
1452    #[serial_test::serial]
1453    async fn create_access_token_credentials_with_universe_domain_from_builder() {
1454        let _e1 = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
1455        let _e2 = ScopedEnv::remove("HOME"); // For posix
1456        let _e3 = ScopedEnv::remove("APPDATA"); // For windows
1457        let _e4 = ScopedEnv::remove(GOOGLE_CLOUD_QUOTA_PROJECT_VAR);
1458
1459        let creds = Builder::default()
1460            .with_universe_domain("my-custom-universe.com")
1461            .build()
1462            .unwrap();
1463
1464        let universe_domain = creds.universe_domain().await;
1465        assert_eq!(universe_domain, Some("my-custom-universe.com".to_string()));
1466    }
1467
1468    #[tokio::test]
1469    #[serial_test::serial]
1470    async fn create_access_token_service_account_credentials_with_scopes() -> TestResult {
1471        let _e1 = ScopedEnv::remove(GOOGLE_CLOUD_QUOTA_PROJECT_VAR);
1472        let mut service_account_key = serde_json::json!({
1473            "type": "service_account",
1474            "project_id": "test-project-id",
1475            "private_key_id": "test-private-key-id",
1476            "private_key": "-----BEGIN PRIVATE KEY-----\nBLAHBLAHBLAH\n-----END PRIVATE KEY-----\n",
1477            "client_email": "test-client-email",
1478            "universe_domain": "test-universe-domain"
1479        });
1480
1481        let scopes =
1482            ["https://www.googleapis.com/auth/pubsub, https://www.googleapis.com/auth/translate"];
1483
1484        service_account_key["private_key"] = Value::from(PKCS8_PK.clone());
1485
1486        let file = tempfile::NamedTempFile::new().unwrap();
1487        let path = file.into_temp_path();
1488        std::fs::write(&path, service_account_key.to_string())
1489            .expect("Unable to write to temporary file.");
1490        let _e = ScopedEnv::set("GOOGLE_APPLICATION_CREDENTIALS", path.to_str().unwrap());
1491
1492        let sac = Builder::default()
1493            .with_quota_project_id("test-quota-project")
1494            .with_scopes(scopes)
1495            .build()
1496            .unwrap();
1497
1498        let headers = sac.headers(Extensions::new()).await?;
1499        let token = get_token_from_headers(headers).unwrap();
1500        let parts: Vec<_> = token.split('.').collect();
1501        assert_eq!(parts.len(), 3);
1502        let claims = b64_decode_to_json(parts.get(1).unwrap().to_string());
1503
1504        let fmt = format!("{sac:?}");
1505        assert!(fmt.contains("ServiceAccountCredentials"));
1506        assert!(fmt.contains("test-quota-project"));
1507        assert_eq!(claims["scope"], scopes.join(" "));
1508
1509        Ok(())
1510    }
1511
1512    #[test]
1513    fn debug_access_token() {
1514        let expires_at = Instant::now() + Duration::from_secs(3600);
1515        let token = Token {
1516            token: "token-test-only".into(),
1517            token_type: "Bearer".into(),
1518            expires_at: Some(expires_at),
1519            metadata: None,
1520        };
1521        let access_token: AccessToken = token.into();
1522        let got = format!("{access_token:?}");
1523        assert!(!got.contains("token-test-only"), "{got}");
1524        assert!(got.contains("token: \"[censored]\""), "{got}");
1525    }
1526}