gcloud_auth/token_source/
mod.rs

1use 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}