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        //skips all checks, so audience has to be manually checked if necessary
71        let token = jsonwebtoken::dangerous::insecure_decode::<ExpClaim>(self.id_token.as_bytes())?;
72        Ok(token.claims.exp)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use crate::credentials::CredentialsFile;
79    use crate::error::Error;
80    use crate::token_source::service_account_token_source::{
81        OAuth2ServiceAccountTokenSource, ServiceAccountTokenSource,
82    };
83    use crate::token_source::TokenSource;
84
85    #[tokio::test]
86    async fn test_jwt_token_source() -> Result<(), Error> {
87        let credentials = CredentialsFile::new().await?;
88        let audience = "https://spanner.googleapis.com/";
89        let ts = ServiceAccountTokenSource::new(&credentials, audience)?;
90        let token = ts.token().await?;
91        assert_eq!("Bearer", token.token_type);
92        assert!(token.expiry.unwrap().unix_timestamp() > 0);
93        Ok(())
94    }
95
96    #[tokio::test]
97    async fn test_oauth2_token_source() -> Result<(), Error> {
98        let credentials = CredentialsFile::new().await?;
99        let scope = "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/spanner.data";
100        let sub = None;
101        let ts = OAuth2ServiceAccountTokenSource::new(&credentials, scope, sub)?;
102        let token = ts.token().await?;
103        assert_eq!("Bearer", token.token_type);
104        assert!(token.expiry.unwrap().unix_timestamp() > 0);
105        Ok(())
106    }
107}