gcloud_auth/
idtoken.rs

1use std::collections::HashMap;
2
3use crate::{
4    credentials::CredentialsFile,
5    error,
6    project::{project, Project, SERVICE_ACCOUNT_KEY},
7    token_source::{
8        compute_identity_source::ComputeIdentitySource, reuse_token_source::ReuseTokenSource,
9        service_account_token_source::OAuth2ServiceAccountTokenSource, TokenSource,
10    },
11};
12
13#[derive(Clone, Default)]
14pub struct IdTokenSourceConfig {
15    credentials: Option<CredentialsFile>,
16    custom_claims: HashMap<String, serde_json::Value>,
17}
18
19impl std::fmt::Debug for IdTokenSourceConfig {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("IdTokenConfig")
22            .field("custom_claims", &self.custom_claims)
23            .finish_non_exhaustive()
24    }
25}
26
27impl IdTokenSourceConfig {
28    pub fn new() -> IdTokenSourceConfig {
29        IdTokenSourceConfig::default()
30    }
31
32    pub fn with_credentials(mut self, creds: CredentialsFile) -> Self {
33        self.credentials = creds.into();
34        self
35    }
36
37    pub fn with_custom_claims(mut self, custom_claims: HashMap<String, serde_json::Value>) -> Self {
38        self.custom_claims = custom_claims;
39        self
40    }
41
42    pub async fn build(self, audience: &str) -> Result<Box<dyn TokenSource>, error::Error> {
43        create_id_token_source(self, audience).await
44    }
45}
46
47pub async fn create_id_token_source(
48    config: IdTokenSourceConfig,
49    audience: &str,
50) -> Result<Box<dyn TokenSource>, error::Error> {
51    if audience.is_empty() {
52        return Err(error::Error::ScopeOrAudienceRequired);
53    }
54
55    if let Some(credentials) = &config.credentials {
56        return id_token_source_from_credentials(&config.custom_claims, credentials, audience).await;
57    }
58
59    match project().await? {
60        Project::FromFile(credentials) => {
61            id_token_source_from_credentials(&config.custom_claims, &credentials, audience).await
62        }
63        Project::FromMetadataServer(_) => {
64            let ts = ComputeIdentitySource::new(audience)?;
65            let token = ts.token().await?;
66            Ok(Box::new(ReuseTokenSource::new(Box::new(ts), token)))
67        }
68    }
69}
70
71pub(crate) async fn id_token_source_from_credentials(
72    custom_claims: &HashMap<String, serde_json::Value>,
73    credentials: &CredentialsFile,
74    audience: &str,
75) -> Result<Box<dyn TokenSource>, error::Error> {
76    let ts = match credentials.tp.as_str() {
77        SERVICE_ACCOUNT_KEY => {
78            let mut claims = custom_claims.clone();
79            claims.insert("target_audience".into(), audience.into());
80
81            let source = OAuth2ServiceAccountTokenSource::new(credentials, "", None)?
82                .with_use_id_token()
83                .with_private_claims(claims);
84
85            Ok(Box::new(source))
86        }
87        // TODO: support impersonation and external account
88        _ => Err(error::Error::UnsupportedAccountType(credentials.tp.to_string())),
89    }?;
90    let token = ts.token().await?;
91    Ok(Box::new(ReuseTokenSource::new(ts, token)))
92}