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 _ => Err(error::Error::UnsupportedAccountType(credentials.tp.to_string())),
89 }?;
90 let token = ts.token().await?;
91 Ok(Box::new(ReuseTokenSource::new(ts, token)))
92}