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