gcloud_auth/token_source/
mod.rs1use std::fmt::Debug;
2use std::time::Duration;
3
4use async_trait::async_trait;
5use jsonwebtoken;
6use serde::Deserialize;
7
8use crate::error::Error;
9use crate::token::Token;
10
11pub mod authorized_user_token_source;
12pub mod compute_identity_source;
13pub mod compute_token_source;
14pub mod impersonate_token_source;
15pub mod reuse_token_source;
16pub mod service_account_token_source;
17
18#[cfg(feature = "external-account")]
19pub mod external_account_source;
20
21#[async_trait]
22pub trait TokenSource: Send + Sync + Debug {
23 async fn token(&self) -> Result<Token, Error>;
24}
25
26pub(crate) fn default_http_client() -> reqwest::Client {
27 reqwest::Client::builder()
28 .timeout(Duration::from_secs(3))
29 .build()
30 .unwrap()
31}
32
33#[derive(Clone, Deserialize)]
34struct InternalToken {
35 pub access_token: String,
36 pub token_type: String,
37 pub expires_in: Option<i64>,
38}
39
40impl InternalToken {
41 fn to_token(&self, now: time::OffsetDateTime) -> Token {
42 Token {
43 access_token: self.access_token.clone(),
44 token_type: self.token_type.clone(),
45 expiry: self.expires_in.map(|s| now + time::Duration::seconds(s)),
46 }
47 }
48}
49
50#[derive(Clone, Deserialize)]
51struct InternalIdToken {
52 pub id_token: String,
53}
54
55#[derive(Deserialize)]
56struct ExpClaim {
57 exp: i64,
58}
59
60impl InternalIdToken {
61 fn to_token(&self, audience: &str) -> Result<Token, Error> {
62 Ok(Token {
63 access_token: self.id_token.clone(),
64 token_type: "Bearer".into(),
65 expiry: time::OffsetDateTime::from_unix_timestamp(self.get_exp(audience)?).ok(),
66 })
67 }
68
69 fn get_exp(&self, audience: &str) -> Result<i64, Error> {
70 let mut validation = jsonwebtoken::Validation::default();
71 validation.insecure_disable_signature_validation();
72 validation.set_audience(&[audience]);
73 let decoding_key = jsonwebtoken::DecodingKey::from_secret(b"");
74 Ok(
75 jsonwebtoken::decode::<ExpClaim>(self.id_token.as_str(), &decoding_key, &validation)?
76 .claims
77 .exp,
78 )
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use crate::credentials::CredentialsFile;
85 use crate::error::Error;
86 use crate::token_source::service_account_token_source::{
87 OAuth2ServiceAccountTokenSource, ServiceAccountTokenSource,
88 };
89 use crate::token_source::TokenSource;
90
91 #[tokio::test]
92 async fn test_jwt_token_source() -> Result<(), Error> {
93 let credentials = CredentialsFile::new().await?;
94 let audience = "https://spanner.googleapis.com/";
95 let ts = ServiceAccountTokenSource::new(&credentials, audience)?;
96 let token = ts.token().await?;
97 assert_eq!("Bearer", token.token_type);
98 assert!(token.expiry.unwrap().unix_timestamp() > 0);
99 Ok(())
100 }
101
102 #[tokio::test]
103 async fn test_oauth2_token_source() -> Result<(), Error> {
104 let credentials = CredentialsFile::new().await?;
105 let scope = "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/spanner.data";
106 let sub = None;
107 let ts = OAuth2ServiceAccountTokenSource::new(&credentials, scope, sub)?;
108 let token = ts.token().await?;
109 assert_eq!("Bearer", token.token_type);
110 assert!(token.expiry.unwrap().unix_timestamp() > 0);
111 Ok(())
112 }
113}