use std::path::PathBuf;
use serde_json::Value;
use tracing::*;
use crate::error::ErrorKind;
pub(crate) struct AdcFile(Value);
pub(crate) enum AdcKind<'a> {
AuthorizedUser,
ExternalAccount(&'a Value),
ExternalAccountAuthorizedUser,
GdchServiceAccount,
Other,
}
impl AdcFile {
pub(crate) fn load() -> Option<Self> {
let path = Self::path()?;
let contents = match std::fs::read_to_string(&path) {
Ok(contents) => contents,
Err(error) => {
debug!(?path, %error, "No readable Application Default Credentials file");
return None;
}
};
match serde_json::from_str(&contents) {
Ok(json) => Some(Self(json)),
Err(error) => {
debug!(?path, %error, "The Application Default Credentials file is not JSON");
None
}
}
}
fn path() -> Option<PathBuf> {
std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS")
.map(PathBuf::from)
.or_else(Self::well_known_path)
}
#[cfg(target_os = "windows")]
fn well_known_path() -> Option<PathBuf> {
std::env::var_os("APPDATA")
.map(|root| PathBuf::from(root).join("gcloud/application_default_credentials.json"))
}
#[cfg(not(target_os = "windows"))]
fn well_known_path() -> Option<PathBuf> {
std::env::var_os("HOME").map(|home| {
PathBuf::from(home).join(".config/gcloud/application_default_credentials.json")
})
}
pub(crate) fn kind(&self) -> AdcKind<'_> {
match self.0.get("type").and_then(Value::as_str) {
Some("authorized_user") => AdcKind::AuthorizedUser,
Some("external_account") => AdcKind::ExternalAccount(&self.0),
Some("external_account_authorized_user") => AdcKind::ExternalAccountAuthorizedUser,
Some("gdch_service_account") => AdcKind::GdchServiceAccount,
_ => AdcKind::Other,
}
}
pub(crate) fn ensure_signing_provider(&self) -> crate::error::Result<()> {
if self.signs_locally() {
ensure_crypto_provider()
} else {
Ok(())
}
}
fn signs_locally(&self) -> bool {
let is_key = |credentials: &Value| {
credentials.get("type").and_then(Value::as_str) == Some("service_account")
};
is_key(&self.0) || self.0.get("source_credentials").is_some_and(is_key)
}
pub(crate) fn project_id(&self) -> Option<&str> {
project_of(&self.0).or_else(|| project_of(self.0.get("source_credentials")?))
}
}
pub(crate) fn ensure_crypto_provider() -> crate::error::Result<()> {
if cfg!(feature = "auth-default-crypto")
|| rustls::crypto::CryptoProvider::get_default().is_some()
{
Ok(())
} else {
Err(ErrorKind::CryptoProviderMissing.into())
}
}
fn project_of(credentials: &Value) -> Option<&str> {
credentials
.get("project_id")
.and_then(Value::as_str)
.or_else(|| credentials.get("quota_project_id")?.as_str())
}
#[cfg(test)]
impl From<Value> for AdcFile {
fn from(json: Value) -> Self {
Self(json)
}
}