use crate::token_cache::CachedTokenProvider;
use crate::{error::Error, jwt};
pub mod end_user;
pub mod metadata_server;
pub mod service_account;
use end_user as eu;
use metadata_server as ms;
use service_account as sa;
pub use crate::id_token::{
AccessTokenResponse, IdToken, IdTokenOrRequest, IdTokenProvider, IdTokenRequest,
IdTokenResponse,
};
pub use crate::token::{Token, TokenOrRequest, TokenProvider};
pub use {
end_user::{EndUserCredentials, EndUserCredentialsInfo},
metadata_server::MetadataServerProvider,
service_account::{ServiceAccountInfo, ServiceAccountProvider},
};
#[derive(serde::Deserialize, Debug)]
struct TokenResponse {
access_token: String,
token_type: String,
expires_in: i64,
}
pub type TokenProviderWrapper = CachedTokenProvider<TokenProviderWrapperInner>;
impl TokenProviderWrapper {
pub fn get_default_provider() -> Result<Option<Self>, Error> {
TokenProviderWrapperInner::get_default_provider()
.map(|provider| provider.map(CachedTokenProvider::wrap))
}
pub fn kind(&self) -> &'static str {
self.inner().kind()
}
pub fn is_service_account_provider(&self) -> bool {
self.inner().is_service_account_provider()
}
pub fn is_metadata_server_provider(&self) -> bool {
self.inner().is_metadata_server_provider()
}
pub fn is_end_user_credentials_provider(&self) -> bool {
self.inner().is_end_user_credentials_provider()
}
}
#[derive(Debug)]
pub enum TokenProviderWrapperInner {
EndUser(eu::EndUserCredentialsInner),
Metadata(ms::MetadataServerProviderInner),
ServiceAccount(sa::ServiceAccountProviderInner),
}
impl TokenProviderWrapperInner {
pub fn get_default_provider() -> Result<Option<Self>, Error> {
use std::{fs::read_to_string, path::PathBuf};
if let Some(cred_path) = std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") {
let key_data = match read_to_string(&cred_path) {
Ok(kd) => kd,
Err(e) => {
return Err(Error::InvalidCredentials {
file: cred_path.into(),
error: Box::new(Error::Io(e)),
});
}
};
let sa_info = match sa::ServiceAccountInfo::deserialize(key_data) {
Ok(si) => si,
Err(e) => {
return Err(Error::InvalidCredentials {
file: cred_path.into(),
error: Box::new(e),
});
}
};
return Ok(Some(TokenProviderWrapperInner::ServiceAccount(
sa::ServiceAccountProviderInner::new(sa_info).map_err(|e| {
Error::InvalidCredentials {
file: cred_path.into(),
error: Box::new(e),
}
})?,
)));
}
fn gcloud_config_file() -> Option<PathBuf> {
let cred_file = "application_default_credentials.json";
if let Some(override_dir) = std::env::var_os("CLOUDSDK_CONFIG") {
let mut pb = PathBuf::from(override_dir);
pb.push(cred_file);
return Some(pb);
}
if cfg!(windows) {
std::env::var_os("APPDATA").map(PathBuf::from)
} else {
std::env::var_os("HOME").map(|pb| {
let mut pb = PathBuf::from(pb);
pb.push(".config");
pb
})
}
.map(|mut bd| {
bd.push("gcloud");
bd.push(cred_file);
bd
})
}
if let Some(gcloud_file) = gcloud_config_file() {
match read_to_string(&gcloud_file) {
Ok(json_data) => {
let end_user_credentials = eu::EndUserCredentialsInfo::deserialize(json_data)
.map_err(|e| Error::InvalidCredentials {
file: gcloud_file,
error: Box::new(e),
})?;
return Ok(Some(TokenProviderWrapperInner::EndUser(
eu::EndUserCredentialsInner::new(end_user_credentials),
)));
}
Err(nf) if nf.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => {
return Err(Error::InvalidCredentials {
file: gcloud_file,
error: Box::new(Error::Io(err)),
});
}
}
}
if let Ok(full_name) = read_to_string("/sys/class/dmi/id/product_name") {
let trimmed = full_name.trim();
match trimmed {
"Google" | "Google Compute Engine" => {
return Ok(Some(TokenProviderWrapperInner::Metadata(
ms::MetadataServerProviderInner::new(None),
)));
}
_ => {}
}
}
Ok(None)
}
pub fn kind(&self) -> &'static str {
match self {
Self::EndUser(_) => "End User",
Self::Metadata(_) => "Metadata Server",
Self::ServiceAccount(_) => "Service Account",
}
}
pub fn is_service_account_provider(&self) -> bool {
matches!(self, TokenProviderWrapperInner::ServiceAccount(_))
}
pub fn is_metadata_server_provider(&self) -> bool {
matches!(self, TokenProviderWrapperInner::Metadata(_))
}
pub fn is_end_user_credentials_provider(&self) -> bool {
matches!(self, TokenProviderWrapperInner::EndUser(_))
}
}
impl TokenProvider for TokenProviderWrapperInner {
fn get_token_with_subject<'a, S, I, T>(
&self,
subject: Option<T>,
scopes: I,
) -> Result<TokenOrRequest, Error>
where
S: AsRef<str> + 'a,
I: IntoIterator<Item = &'a S> + Clone,
T: Into<String>,
{
match self {
Self::EndUser(token_provider) => token_provider.get_token_with_subject(subject, scopes),
Self::Metadata(token_provider) => {
token_provider.get_token_with_subject(subject, scopes)
}
Self::ServiceAccount(token_provider) => {
token_provider.get_token_with_subject(subject, scopes)
}
}
}
fn parse_token_response<S>(
&self,
hash: u64,
response: http::Response<S>,
) -> Result<Token, Error>
where
S: AsRef<[u8]>,
{
match self {
Self::EndUser(token_provider) => token_provider.parse_token_response(hash, response),
Self::Metadata(token_provider) => token_provider.parse_token_response(hash, response),
Self::ServiceAccount(token_provider) => {
token_provider.parse_token_response(hash, response)
}
}
}
}
impl IdTokenProvider for TokenProviderWrapperInner {
fn get_id_token(&self, audience: &str) -> Result<IdTokenOrRequest, Error> {
match self {
Self::EndUser(token_provider) => token_provider.get_id_token(audience),
Self::Metadata(token_provider) => token_provider.get_id_token(audience),
Self::ServiceAccount(token_provider) => token_provider.get_id_token(audience),
}
}
fn get_id_token_with_access_token<S>(
&self,
audience: &str,
response: AccessTokenResponse<S>,
) -> Result<IdTokenRequest, Error>
where
S: AsRef<[u8]>,
{
match self {
Self::EndUser(token_provider) => {
token_provider.get_id_token_with_access_token(audience, response)
}
Self::Metadata(token_provider) => {
token_provider.get_id_token_with_access_token(audience, response)
}
Self::ServiceAccount(token_provider) => {
token_provider.get_id_token_with_access_token(audience, response)
}
}
}
fn parse_id_token_response<S>(
&self,
hash: u64,
response: http::Response<S>,
) -> Result<IdToken, Error>
where
S: AsRef<[u8]>,
{
match self {
Self::EndUser(token_provider) => token_provider.parse_id_token_response(hash, response),
Self::Metadata(token_provider) => {
token_provider.parse_id_token_response(hash, response)
}
Self::ServiceAccount(token_provider) => {
token_provider.parse_id_token_response(hash, response)
}
}
}
}
impl From<TokenResponse> for Token {
fn from(tr: TokenResponse) -> Self {
Self {
access_token: tr.access_token,
token_type: tr.token_type,
refresh_token: String::new(),
expires_in: Some(tr.expires_in),
expires_in_timestamp: std::time::SystemTime::now()
.checked_add(std::time::Duration::from_secs(tr.expires_in as u64)),
}
}
}