google_auth 0.2.1

get google default credentials
Documentation
use crate::token::{Token, TokenValue};
use anyhow::Result;
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{borrow::Borrow, collections::HashMap};
use tokio::{fs::OpenOptions, io::AsyncReadExt};

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(untagged)]
enum CredentialType {
    #[allow(non_camel_case_types)]
    AuthorizedUser(ApplicationDefaultCredentials),
    #[allow(non_camel_case_types)]
    ServiceAccount(ServiceAccountCredentials),
    MetadataServer,
}

#[derive(Debug, Clone)]
pub(crate) struct Credentials {
    scopes: String,
    credential_type: CredentialType,
}

impl Credentials {
    /// 1. A JSON file whose path is specified by the
    ///    GOOGLE_APPLICATION_CREDENTIALS environment variable.
    /// 2. A JSON file in a location known to the gcloud command-line tool.
    ///    On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
    ///    On other systems, $HOME/.config/gcloud/application_default_credentials.json.
    /// 3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses
    ///    the appengine.AccessToken function.
    /// 4. On Google Compute Engine, Google App Engine standard second generation runtimes
    ///    (>= Go 1.11), and Google App Engine flexible environment, it fetches
    ///    credentials from the metadata server.
    pub(crate) async fn new(scopes: &[&str]) -> Result<Self> {
        let scopes = scopes.join(" ");
        let credential_type = if let Ok(gac) = Self::try_gac().await {
            gac
        } else if let Ok(adc) = Self::try_well_known().await {
            adc
        } else {
            CredentialType::MetadataServer
        };
        Ok(Self {
            scopes,
            credential_type,
        })
    }
    async fn try_gac() -> Result<CredentialType> {
        let gac = std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?;
        let path = PathBuf::from(gac);
        Self::from_file(path).await
    }
    async fn try_well_known() -> Result<CredentialType> {
        let home = std::env::var("HOME")?;
        let config_path = ".config/gcloud/application_default_credentials.json";
        let home_path = PathBuf::from(home).join(config_path);
        Self::from_file(home_path).await
    }
    async fn from_file(path: PathBuf) -> Result<CredentialType> {
        let mut file = OpenOptions::new().read(true).open(path).await?;
        let mut buf = vec![];
        file.read_to_end(&mut buf).await?;
        let credentials: CredentialType = serde_json::from_slice(&buf)?;
        Ok(credentials)
    }
    pub(crate) async fn token(&self) -> Result<Token> {
        let token = match self.credential_type.borrow() {
            CredentialType::AuthorizedUser(adc) => adc.token().await?,
            CredentialType::ServiceAccount(sa) => sa.token(&self.scopes).await?,
            CredentialType::MetadataServer => Self::from_metadata_server().await?,
        };
        Ok(token)
    }
    /// https://cloud.google.com/compute/docs/storing-retrieving-metadata
    async fn from_metadata_server() -> Result<Token> {
        let url = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
        let client = reqwest::Client::new();
        let token_value: TokenValue = client
            .get(url)
            .header("Metadata-Flavor", "Google")
            .send()
            .await?
            .json()
            .await?;
        Ok(Token::new(token_value))
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct ApplicationDefaultCredentials {
    client_id: String,
    client_secret: String,
    refresh_token: String,
    r#type: String,
}

impl ApplicationDefaultCredentials {
    async fn token(&self) -> Result<Token> {
        let client = reqwest::Client::new();
        let mut form: HashMap<String, String> = HashMap::new();
        form.insert(String::from("client_id"), self.client_id.clone());
        form.insert(String::from("client_secret"), self.client_secret.clone());
        form.insert(String::from("refresh_token"), self.refresh_token.clone());
        form.insert(String::from("grant_type"), String::from("refresh_token"));
        let resp = client
            .post("https://oauth2.googleapis.com/token")
            .form(&form)
            .send()
            .await?;
        let resp_string = resp.text().await?;
        let token_value = serde_json::from_str(&resp_string)?;
        Ok(Token::new(token_value))
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct ServiceAccountCredentials {
    r#type: String,
    project_id: String,
    private_key_id: String,
    private_key: String,
    client_email: String,
    client_id: String,
    auth_uri: String,
    token_uri: String,
    auth_provider_x509_cert_url: String,
    client_x509_cert_url: String,
}

#[derive(Deserialize, Serialize, Debug)]
struct Claims {
    iss: String,
    scope: String,
    aud: String,
    exp: u64,
    iat: u64,
}

impl ServiceAccountCredentials {
    async fn token(&self, scope: &str) -> Result<Token> {
        let ts = std::time::SystemTime::now()
            .duration_since(std::time::SystemTime::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        let claims = Claims {
            iss: self.client_email.clone(),
            scope: scope.to_string(),
            aud: self.token_uri.clone(),
            iat: ts,
            exp: ts + 3600,
        };
        let header = Header {
            typ: Some("JWT".to_owned()),
            alg: Algorithm::RS256,
            cty: None,
            jku: None,
            kid: None,
            x5u: None,
            x5t: None,
        };
        let key = EncodingKey::from_rsa_pem(self.private_key.as_ref()).unwrap();
        let jwt = encode(&header, &claims, &key)?;
        let mut form: HashMap<&str, &str> = HashMap::new();
        form.insert("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
        form.insert("assertion", &jwt);
        let client = reqwest::Client::new();
        let resp = client.post(&self.token_uri).form(&form).send().await?;
        let token_value = resp.json::<TokenValue>().await?;
        Ok(Token::new(token_value))
    }
}