Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting service account key format OR user credential formats #76

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions src/authentication_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use async_trait::async_trait;
use tokio::sync::Mutex;

use crate::custom_service_account::CustomServiceAccount;
use crate::default_authorized_user::ConfigDefaultCredentials;
use crate::default_service_account::MetadataServiceAccount;
use crate::error::Error;
use crate::flexible_credential_source::FlexibleCredentialSource;
use crate::gcloud_authorized_user::GCloudAuthorizedUser;
use crate::types::{self, HyperClient, Token};

Expand Down Expand Up @@ -43,15 +43,25 @@ impl AuthenticationManager {
#[tracing::instrument]
pub async fn new() -> Result<Self, Error> {
tracing::debug!("Initializing gcp_auth");
if let Some(service_account) = CustomServiceAccount::from_env()? {
return Ok(service_account.into());
let client = types::client();
msdrigg marked this conversation as resolved.
Show resolved Hide resolved
if let Some(service_account) = FlexibleCredentialSource::from_env().await? {
tracing::debug!("Using GOOGLE_APPLICATION_CREDENTIALS env");

return Ok(Self {
service_account: service_account.try_into_service_account(&client).await?,
client,
refresh_mutex: Mutex::new(()),
});
}

let client = types::client();
let default_user_error = match ConfigDefaultCredentials::new(&client).await {
let default_user_error = match FlexibleCredentialSource::from_default_credentials().await {
Ok(service_account) => {
tracing::debug!("Using ConfigDefaultCredentials");
return Ok(Self::build(client, service_account));
return Ok(Self {
service_account: service_account.try_into_service_account(&client).await?,
client,
refresh_mutex: Mutex::new(()),
});
}
Err(e) => e,
};
Expand Down
3 changes: 1 addition & 2 deletions src/custom_service_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl CustomServiceAccount {
}
}

fn new(credentials: ApplicationCredentials) -> Result<Self, Error> {
pub(crate) fn new(credentials: ApplicationCredentials) -> Result<Self, Error> {
Ok(Self {
signer: Signer::new(&credentials.private_key)?,
credentials,
Expand Down Expand Up @@ -137,7 +137,6 @@ impl ServiceAccount for CustomServiceAccount {

#[derive(Serialize, Deserialize, Clone)]
pub(crate) struct ApplicationCredentials {
pub(crate) r#type: Option<String>,
/// project_id
pub(crate) project_id: Option<String>,
/// private_key_id
Expand Down
20 changes: 5 additions & 15 deletions src/default_authorized_user.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::fs;
use std::sync::RwLock;

use async_trait::async_trait;
Expand All @@ -19,18 +18,11 @@ pub(crate) struct ConfigDefaultCredentials {

impl ConfigDefaultCredentials {
const DEFAULT_TOKEN_GCP_URI: &'static str = "https://accounts.google.com/o/oauth2/token";
const USER_CREDENTIALS_PATH: &'static str =
".config/gcloud/application_default_credentials.json";

pub(crate) async fn new(client: &HyperClient) -> Result<Self, Error> {
tracing::debug!("Loading user credentials file");
let mut home = dirs_next::home_dir().ok_or(Error::NoHomeDir)?;
home.push(Self::USER_CREDENTIALS_PATH);

let file = fs::File::open(home).map_err(Error::UserProfilePath)?;
let credentials = serde_json::from_reader::<_, UserCredentials>(file)
.map_err(Error::UserProfileFormat)?;

pub(crate) async fn from_user_credentials(
credentials: UserCredentials,
client: &HyperClient,
) -> Result<Self, Error> {
Ok(Self {
token: RwLock::new(Self::get_token(&credentials, client).await?),
credentials,
Expand Down Expand Up @@ -105,7 +97,7 @@ struct RefreshRequest<'a> {
}

#[derive(Serialize, Deserialize, Debug, Clone)]
struct UserCredentials {
pub(crate) struct UserCredentials {
/// Client id
pub(crate) client_id: String,
/// Client secret
Expand All @@ -114,8 +106,6 @@ struct UserCredentials {
pub(crate) quota_project_id: Option<String>,
/// Refresh Token
pub(crate) refresh_token: String,
/// Type
pub(crate) r#type: String,
}
djc marked this conversation as resolved.
Show resolved Hide resolved

/// How many times to attempt to fetch a token from the GCP token endpoint.
Expand Down
195 changes: 195 additions & 0 deletions src/flexible_credential_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};
use tokio::fs;

use crate::{
authentication_manager::ServiceAccount,
custom_service_account::ApplicationCredentials,
default_authorized_user::{ConfigDefaultCredentials, UserCredentials},
types::HyperClient,
CustomServiceAccount, Error,
};

// Implementation referenced from
// https://github.com/golang/oauth2/blob/a835fc4358f6852f50c4c5c33fddcd1adade5b0a/google/google.go#L158
// Currently not implementing external account credentials
// Currently not implementing impersonating service accounts (coming soon !)
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub(crate) enum FlexibleCredentialSource {
msdrigg marked this conversation as resolved.
Show resolved Hide resolved
// This credential parses the `key.json` file created when running
// `gcloud iam service-accounts keys create key.json --iam-account=SA_NAME@PROJECT_ID.iam.gserviceaccount.com`
ServiceAccount(ApplicationCredentials),
// This credential parses the `~/.config/gcloud/application_default_credentials.json` file
// created when running `gcloud auth application-default login`
AuthorizedUser(UserCredentials),
}

impl FlexibleCredentialSource {
const USER_CREDENTIALS_PATH: &'static str =
".config/gcloud/application_default_credentials.json";

pub(crate) async fn from_env() -> Result<Option<Self>, Error> {
let creds_path = std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS");
if let Some(path) = creds_path {
tracing::debug!("Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var");
let creds = Self::from_file(PathBuf::from(path)).await?;
msdrigg marked this conversation as resolved.
Show resolved Hide resolved
Ok(Some(creds))
} else {
Ok(None)
}
}

pub(crate) async fn from_default_credentials() -> Result<Self, Error> {
tracing::debug!("Loading user credentials file");
let mut home = dirs_next::home_dir().ok_or(Error::NoHomeDir)?;
home.push(Self::USER_CREDENTIALS_PATH);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it an actually expected use case that we find ApplicationCredentials in the USER_CREDENTIALS_PATH? That doesn't seem like something that makes sense.

Copy link
Author

@msdrigg msdrigg Aug 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this isn't a typical pattern, but Google's documentation refers to both the GOOGLE_APPLICATION_CREDENTIALS and the ~/.config/gcloud/application_default_credentials.json file as types of application default credentials, so it feels reasonable that we accept both behaviors.

https://cloud.google.com/docs/authentication/application-default-credentials#order

Additionally in the go source I have been referencing, all these credential types are contained in one struct and there isn't any runtime checks to restrict usage between certain types.

Copy link
Author

@msdrigg msdrigg Aug 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing: gcloud accepts this behavior. I can run

gcloud iam service-accounts keys create ~/.config/gcloud/application_default_credentials.json --iam-account=<service act>
gcloud auth application-default print-access-token

and the service account's token will get printed.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, in that case I suggest that we do this at the top level:

  • Check for the GOOGLE_APPLICATION_CREDENTIALS environment variable
  • If that doesn't exist, check the USER_CREDENTIALS_PATH
  • For both of these, deserialize and pass the results to the appropriate trait impl
  • .. other options

Copy link
Author

@msdrigg msdrigg Aug 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying to return an error (don't try other options) if USER_CREDENTIALS_PATH exists but fails to parse as a valid ServiceAccount for some reason. This is the current behavior for GOOGLE_APPLICATION_CREDENTIALS but not USER_CREDENTIALS_PATH

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't really thought about it that way, but it seems kinda reasonable to me? Or do you think the USER_CREDENTIALS_PATH could contain other things? What does the Go code do in this case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. The gcloud isn't open source, and the referenced go code doesn't cover that situation. I think keeping the current behavior is fine. I don't see a reason to change since we already have been using the current behavior -- I just wanted to clarify if you were expecting that.

Self::from_file(home).await
}

pub(crate) async fn try_into_service_account(
msdrigg marked this conversation as resolved.
Show resolved Hide resolved
self,
client: &HyperClient,
) -> Result<Box<dyn ServiceAccount>, Error> {
match self {
FlexibleCredentialSource::ServiceAccount(creds) => {
let service_account = CustomServiceAccount::new(creds)?;
Ok(Box::new(service_account))
}
FlexibleCredentialSource::AuthorizedUser(creds) => {
let service_account =
ConfigDefaultCredentials::from_user_credentials(creds, client).await?;
Ok(Box::new(service_account))
}
}
}

/// Read service account credentials from the given JSON file
async fn from_file<T: AsRef<Path>>(path: T) -> Result<Self, Error> {
let creds_string = fs::read_to_string(&path)
.await
.map_err(Error::UserProfilePath)?;

serde_json::from_str::<FlexibleCredentialSource>(&creds_string)
msdrigg marked this conversation as resolved.
Show resolved Hide resolved
.map_err(Error::CustomServiceAccountCredentials)
}
}

#[cfg(test)]
mod tests {
use crate::{flexible_credential_source::FlexibleCredentialSource, types};

#[tokio::test]
async fn test_parse_application_default_credentials() {
let test_creds = r#"{
"client_id": "***id***.apps.googleusercontent.com",
"client_secret": "***secret***",
"quota_project_id": "test_project",
"refresh_token": "***refresh***",
"type": "authorized_user"
}"#;

let cred_source: FlexibleCredentialSource =
serde_json::from_str(test_creds).expect("Valid creds to parse");

assert!(matches!(
cred_source,
FlexibleCredentialSource::AuthorizedUser(_)
));

// Can't test converting this into a service account because it requires actually getting a key
}

#[tokio::test]
async fn test_parse_service_account_key() {
// Don't worry, even though the key is a real private_key, it's not used for anything
let test_creds = r#" {
"type": "service_account",
"project_id": "test_project",
"private_key_id": "***key_id***",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5M5y3WwsRk8NX\npF9fKaZukNspot9Ecmk1PAkupcHLKVhalwPxU4sMNWXgM9H2LTWSvvyOT//rDQpn\n3SGYri/lMhzb4lI8h10E7k6zyFQUPujxkXFBkMOzhIDUgtiiht0WvIw6M8nbaPqI\nxn/aYmPsFhvJfKCthYAt2UUz+D3enI9QjCuhic8iSMnvKT8m0QkOG2eALYGUaLF1\ngRkbV4BiBUGZfXfNEBdux3Wf4kNUau32LA0XotomlvNvf1oH77v5Hc1R/KMMIk5F\nJWVBuAr4jwkN9hwtOozpJ/52wSpddxsZuj+0nP1a3f0UyvrmMnuwszardPK39BoH\nJ+5+HZM3AgMBAAECggEADrHZrXK73hkrVrjkGFjlq8Ayo4sYzAWH84Ff+SONzODq\n8cUpuuw2DDHwc2mpLy9HIO2mfGQ8mhneyX7yO3sWscjYIVpDzCmxZ8LA2+L5SOH0\n+bXglqM14/iPgE0hg0PQJw2u0q9pRM9/kXquilVkOEdIzSPmW95L3Vdv9j+sKQ2A\nOL23l4dsaG4+i1lWRBKiGsLh1kB9FRnm4BzcOxd3WGooy7L1/jo9BoYRss1YABls\nmmyZ9f7r28zjclhpOBkE3OXX0zNbp4yIu1O1Bt9X2p87EOuYqlFA5eEvDbiTPZbk\n6wKEX3BPUkeIo8OaGvsGhHCWx0lv/sDPw/UofycOgQKBgQD4BD059aXEV13Byc5D\nh8LQSejjeM/Vx+YeCFI66biaIOvUs+unyxkH+qxXTuW6AgOgcvrJo93xkyAZ9SeR\nc6Vj9g5mZ5vqSJz5Hg8h8iZBAYtf40qWq0pHcmUIm2Z9LvrG5ZFHU5EEcCtLyBVS\nAv+pLLLf3OsAkJuuqTAgygBbOwKBgQC/KcBa9sUg2u9qIpq020UOW/n4KFWhSJ8h\ngXqqmjOnPqmDc5AnYg1ZdYdqSSgdiK8lJpRL/S2UjYUQp3H+56z0eK/b1iKM51n+\n6D80nIxWeKJ+n7VKI7cBXwc/KokaXgkz0It2UEZSlhPUMImnYcOvGIZ7cMr3Q6mf\n6FwD15UQNQKBgQDyAsDz454DvvS/+noJL1qMAPL9tI+pncwQljIXRqVZ0LIO9hoH\nu4kLXjH5aAWGwhxj3o6VYA9cgSIb8jrQFbbXmexnRMbBkGWMOSavCykE2cr0oEfS\nSgbLPPcVtP4HPWZ72tsubH7fg8zbv7v+MOrkW7eX9mxiOrmPb4yFElfSrQKBgA7y\nMLvr91WuSHG/6uChFDEfN9gTLz7A8tAn03NrQwace5xveKHbpLeN3NyOg7hra2Y4\nMfgO/3VR60l2Dg+kBX3HwdgqUeE6ZWrstaRjaQWJwQqtafs196T/zQ0/QiDxoT6P\n25eQhy8F1N8OPHT9y9Lw0/LqyrOycpyyCh+yx1DRAoGAJ/6dlhyQnwSfMAe3mfRC\noiBQG6FkyoeXHHYcoQ/0cSzwp0BwBlar1Z28P7KTGcUNqV+YfK9nF47eoLaTLCmG\nG5du0Ds6m2Eg0sOBBqXHnw6R1PC878tgT/XokNxIsVlF5qRz88q7Rn0J1lzB7+Tl\n2HSAcyIUcmr0gxlhRmC2Jq4=\n-----END PRIVATE KEY-----\n",
"client_email": "test_account@test.iam.gserviceaccount.com",
"client_id": "***id***",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test_account%40test.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}"#;

let cred_source: FlexibleCredentialSource =
serde_json::from_str(test_creds).expect("Valid creds to parse");

assert!(matches!(
cred_source,
FlexibleCredentialSource::ServiceAccount(_)
));

let client = types::client();
let creds = cred_source
.try_into_service_account(&client)
.await
.expect("Valid creds to parse");

assert_eq!(
creds
.project_id(&client)
.await
.expect("Project ID to be present"),
"test_project".to_string(),
"Project ID should be parsed"
);
}

#[tokio::test]
async fn test_additional_service_account_keys() {
// Using test cases from https://github.com/golang/oauth2/blob/a835fc4358f6852f50c4c5c33fddcd1adade5b0a/google/google_test.go#L40
// We have to use a real private key because we validate private keys on parsing as well.
let k1 = r#"{
"private_key_id": "268f54e43a1af97cfc71731688434f45aca15c8b",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5M5y3WwsRk8NX\npF9fKaZukNspot9Ecmk1PAkupcHLKVhalwPxU4sMNWXgM9H2LTWSvvyOT//rDQpn\n3SGYri/lMhzb4lI8h10E7k6zyFQUPujxkXFBkMOzhIDUgtiiht0WvIw6M8nbaPqI\nxn/aYmPsFhvJfKCthYAt2UUz+D3enI9QjCuhic8iSMnvKT8m0QkOG2eALYGUaLF1\ngRkbV4BiBUGZfXfNEBdux3Wf4kNUau32LA0XotomlvNvf1oH77v5Hc1R/KMMIk5F\nJWVBuAr4jwkN9hwtOozpJ/52wSpddxsZuj+0nP1a3f0UyvrmMnuwszardPK39BoH\nJ+5+HZM3AgMBAAECggEADrHZrXK73hkrVrjkGFjlq8Ayo4sYzAWH84Ff+SONzODq\n8cUpuuw2DDHwc2mpLy9HIO2mfGQ8mhneyX7yO3sWscjYIVpDzCmxZ8LA2+L5SOH0\n+bXglqM14/iPgE0hg0PQJw2u0q9pRM9/kXquilVkOEdIzSPmW95L3Vdv9j+sKQ2A\nOL23l4dsaG4+i1lWRBKiGsLh1kB9FRnm4BzcOxd3WGooy7L1/jo9BoYRss1YABls\nmmyZ9f7r28zjclhpOBkE3OXX0zNbp4yIu1O1Bt9X2p87EOuYqlFA5eEvDbiTPZbk\n6wKEX3BPUkeIo8OaGvsGhHCWx0lv/sDPw/UofycOgQKBgQD4BD059aXEV13Byc5D\nh8LQSejjeM/Vx+YeCFI66biaIOvUs+unyxkH+qxXTuW6AgOgcvrJo93xkyAZ9SeR\nc6Vj9g5mZ5vqSJz5Hg8h8iZBAYtf40qWq0pHcmUIm2Z9LvrG5ZFHU5EEcCtLyBVS\nAv+pLLLf3OsAkJuuqTAgygBbOwKBgQC/KcBa9sUg2u9qIpq020UOW/n4KFWhSJ8h\ngXqqmjOnPqmDc5AnYg1ZdYdqSSgdiK8lJpRL/S2UjYUQp3H+56z0eK/b1iKM51n+\n6D80nIxWeKJ+n7VKI7cBXwc/KokaXgkz0It2UEZSlhPUMImnYcOvGIZ7cMr3Q6mf\n6FwD15UQNQKBgQDyAsDz454DvvS/+noJL1qMAPL9tI+pncwQljIXRqVZ0LIO9hoH\nu4kLXjH5aAWGwhxj3o6VYA9cgSIb8jrQFbbXmexnRMbBkGWMOSavCykE2cr0oEfS\nSgbLPPcVtP4HPWZ72tsubH7fg8zbv7v+MOrkW7eX9mxiOrmPb4yFElfSrQKBgA7y\nMLvr91WuSHG/6uChFDEfN9gTLz7A8tAn03NrQwace5xveKHbpLeN3NyOg7hra2Y4\nMfgO/3VR60l2Dg+kBX3HwdgqUeE6ZWrstaRjaQWJwQqtafs196T/zQ0/QiDxoT6P\n25eQhy8F1N8OPHT9y9Lw0/LqyrOycpyyCh+yx1DRAoGAJ/6dlhyQnwSfMAe3mfRC\noiBQG6FkyoeXHHYcoQ/0cSzwp0BwBlar1Z28P7KTGcUNqV+YfK9nF47eoLaTLCmG\nG5du0Ds6m2Eg0sOBBqXHnw6R1PC878tgT/XokNxIsVlF5qRz88q7Rn0J1lzB7+Tl\n2HSAcyIUcmr0gxlhRmC2Jq4=\n-----END PRIVATE KEY-----\n",
"client_email": "gopher@developer.gserviceaccount.com",
"client_id": "gopher.apps.googleusercontent.com",
"token_uri": "https://accounts.google.com/o/gophers/token",
"type": "service_account",
"audience": "https://testservice.googleapis.com/"
}"#;

let k3 = r#"{
"private_key_id": "268f54e43a1af97cfc71731688434f45aca15c8b",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5M5y3WwsRk8NX\npF9fKaZukNspot9Ecmk1PAkupcHLKVhalwPxU4sMNWXgM9H2LTWSvvyOT//rDQpn\n3SGYri/lMhzb4lI8h10E7k6zyFQUPujxkXFBkMOzhIDUgtiiht0WvIw6M8nbaPqI\nxn/aYmPsFhvJfKCthYAt2UUz+D3enI9QjCuhic8iSMnvKT8m0QkOG2eALYGUaLF1\ngRkbV4BiBUGZfXfNEBdux3Wf4kNUau32LA0XotomlvNvf1oH77v5Hc1R/KMMIk5F\nJWVBuAr4jwkN9hwtOozpJ/52wSpddxsZuj+0nP1a3f0UyvrmMnuwszardPK39BoH\nJ+5+HZM3AgMBAAECggEADrHZrXK73hkrVrjkGFjlq8Ayo4sYzAWH84Ff+SONzODq\n8cUpuuw2DDHwc2mpLy9HIO2mfGQ8mhneyX7yO3sWscjYIVpDzCmxZ8LA2+L5SOH0\n+bXglqM14/iPgE0hg0PQJw2u0q9pRM9/kXquilVkOEdIzSPmW95L3Vdv9j+sKQ2A\nOL23l4dsaG4+i1lWRBKiGsLh1kB9FRnm4BzcOxd3WGooy7L1/jo9BoYRss1YABls\nmmyZ9f7r28zjclhpOBkE3OXX0zNbp4yIu1O1Bt9X2p87EOuYqlFA5eEvDbiTPZbk\n6wKEX3BPUkeIo8OaGvsGhHCWx0lv/sDPw/UofycOgQKBgQD4BD059aXEV13Byc5D\nh8LQSejjeM/Vx+YeCFI66biaIOvUs+unyxkH+qxXTuW6AgOgcvrJo93xkyAZ9SeR\nc6Vj9g5mZ5vqSJz5Hg8h8iZBAYtf40qWq0pHcmUIm2Z9LvrG5ZFHU5EEcCtLyBVS\nAv+pLLLf3OsAkJuuqTAgygBbOwKBgQC/KcBa9sUg2u9qIpq020UOW/n4KFWhSJ8h\ngXqqmjOnPqmDc5AnYg1ZdYdqSSgdiK8lJpRL/S2UjYUQp3H+56z0eK/b1iKM51n+\n6D80nIxWeKJ+n7VKI7cBXwc/KokaXgkz0It2UEZSlhPUMImnYcOvGIZ7cMr3Q6mf\n6FwD15UQNQKBgQDyAsDz454DvvS/+noJL1qMAPL9tI+pncwQljIXRqVZ0LIO9hoH\nu4kLXjH5aAWGwhxj3o6VYA9cgSIb8jrQFbbXmexnRMbBkGWMOSavCykE2cr0oEfS\nSgbLPPcVtP4HPWZ72tsubH7fg8zbv7v+MOrkW7eX9mxiOrmPb4yFElfSrQKBgA7y\nMLvr91WuSHG/6uChFDEfN9gTLz7A8tAn03NrQwace5xveKHbpLeN3NyOg7hra2Y4\nMfgO/3VR60l2Dg+kBX3HwdgqUeE6ZWrstaRjaQWJwQqtafs196T/zQ0/QiDxoT6P\n25eQhy8F1N8OPHT9y9Lw0/LqyrOycpyyCh+yx1DRAoGAJ/6dlhyQnwSfMAe3mfRC\noiBQG6FkyoeXHHYcoQ/0cSzwp0BwBlar1Z28P7KTGcUNqV+YfK9nF47eoLaTLCmG\nG5du0Ds6m2Eg0sOBBqXHnw6R1PC878tgT/XokNxIsVlF5qRz88q7Rn0J1lzB7+Tl\n2HSAcyIUcmr0gxlhRmC2Jq4=\n-----END PRIVATE KEY-----\n",
"client_email": "gopher@developer.gserviceaccount.com",
"client_id": "gopher.apps.googleusercontent.com",
"token_uri": "https://accounts.google.com/o/gophers/token",
"type": "service_account"
}"#;

let client = types::client();
for key in [k1, k3] {
let cred_source: FlexibleCredentialSource =
serde_json::from_str(key).expect("Valid creds to parse");

assert!(matches!(
cred_source,
FlexibleCredentialSource::ServiceAccount(_)
));

let creds = cred_source
.try_into_service_account(&client)
.await
.expect("Valid creds to parse");

assert!(
matches!(
creds
.project_id(&client)
.await
.expect_err("Project ID to not be present"),
crate::Error::ProjectIdNotFound,
),
"Project id should not be found here",
);
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ mod custom_service_account;
mod default_authorized_user;
mod default_service_account;
mod error;
mod flexible_credential_source;
mod gcloud_authorized_user;
mod jwt;
mod types;
Expand Down
Loading