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

Allowing constructing CredentialsFile from string #201

Merged
merged 1 commit into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion foundation/auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ hex = { version = "0.4", optional = true }
tokio = { version = "1.32", features = ["test-util", "rt-multi-thread", "macros"]}
tracing-subscriber = {version="0.3", features=["env-filter","std"]}
ctor = "0.1"
serial_test = "0.9"
tempfile = "3.8.0"
temp-env = {version="0.3.6", features = ["async_closure",]}

[features]
default = ["default-tls"]
Expand Down
164 changes: 159 additions & 5 deletions foundation/auth/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,33 @@ use crate::error::Error;
const CREDENTIALS_FILE: &str = "application_default_credentials.json";

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
#[derive(Deserialize, Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub struct ServiceAccountImpersonationInfo {
pub(crate) token_lifetime_seconds: i32,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
#[derive(Deserialize, Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub struct ExecutableConfig {
pub(crate) command: String,
pub(crate) timeout_millis: Option<i32>,
pub(crate) output_file: String,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
#[derive(Deserialize, Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub struct Format {
#[serde(rename(deserialize = "type"))]
pub(crate) tp: String,
pub(crate) subject_token_field_name: String,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
#[derive(Deserialize, Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub struct CredentialSource {
pub(crate) file: Option<String>,

Expand All @@ -47,7 +51,8 @@ pub struct CredentialSource {
}

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
#[derive(Deserialize, Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub struct CredentialsFile {
#[serde(rename(deserialize = "type"))]
pub tp: String,
Expand Down Expand Up @@ -98,6 +103,10 @@ impl CredentialsFile {
Ok(serde_json::from_slice(credentials_json.as_slice())?)
}

pub async fn new_from_str(str: &str) -> Result<Self, Error> {
Ok(serde_json::from_str(str)?)
}

async fn json_from_env() -> Result<Vec<u8>, ()> {
let credentials = std::env::var("GOOGLE_APPLICATION_CREDENTIALS_JSON")
.map_err(|_| ())
Expand Down Expand Up @@ -141,3 +150,148 @@ impl CredentialsFile {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;

const CREDENTIALS_FILE_CONTENT: &str = r#"{
"type": "service_account",
"project_id": "fake_project_id",
"private_key_id": "fake_private_key_id",
"private_key": "-----BEGIN PRIVATE KEY-----\nfake_private_key\n-----END PRIVATE KEY-----\n",
"client_email": "fake@fake_project_id.iam.gserviceaccount.com",
"client_id": "123456789010111213141516171819",
"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/fake%40fake_project_id.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}"#;

#[tokio::test]
async fn test_credentials_file_new_from_file() {
// setup:
let temp_credentials_dir = tempdir().expect("Cannot create temporary directory");
let temp_credentials_path = temp_credentials_dir.path().join(CREDENTIALS_FILE);
let mut credentials_file = File::create(&temp_credentials_path).expect("Cannot create temporary file");
credentials_file
.write_all(CREDENTIALS_FILE_CONTENT.as_bytes())
.expect("Cannot write content to file");

// execute:
let credentials_file_result =
CredentialsFile::new_from_file(temp_credentials_path.to_string_lossy().to_string()).await;

// verify:
let expected_credentials_file: CredentialsFile =
serde_json::from_str(CREDENTIALS_FILE_CONTENT).expect("Credentials file JSON deserialization not working");
match credentials_file_result {
Err(_) => panic!(),
Ok(cf) => assert_eq!(expected_credentials_file, cf),
}
}

#[tokio::test]
async fn test_credentials_file_new_from_str() {
// execute:
let credentials_file_result = CredentialsFile::new_from_str(CREDENTIALS_FILE_CONTENT).await;

// verify:
let expected_credentials_file: CredentialsFile =
serde_json::from_str(CREDENTIALS_FILE_CONTENT).expect("Credentials file JSON deserialization not working");
match credentials_file_result {
Err(_) => panic!(),
Ok(cf) => assert_eq!(expected_credentials_file, cf),
}
}

#[tokio::test]
async fn test_credentials_file_new_from_env_var_json() {
// setup:
temp_env::async_with_vars(
[
("GOOGLE_APPLICATION_CREDENTIALS_JSON", Some(CREDENTIALS_FILE_CONTENT)),
("GOOGLE_APPLICATION_CREDENTIALS", None), // make sure file env is not interfering
],
async {
// execute:
let credentials_file_result = CredentialsFile::new().await;

// verify:
let expected_credentials_file: CredentialsFile = serde_json::from_str(CREDENTIALS_FILE_CONTENT)
.expect("Credentials file JSON deserialization not working");
match credentials_file_result {
Err(_) => panic!(),
Ok(cf) => assert_eq!(expected_credentials_file, cf),
}
},
)
.await;
}

#[tokio::test]
async fn test_credentials_file_new_from_env_var_json_base_64_encoded() {
// setup:
temp_env::async_with_vars(
[
(
"GOOGLE_APPLICATION_CREDENTIALS_JSON",
Some(base64::engine::general_purpose::STANDARD.encode(CREDENTIALS_FILE_CONTENT)),
),
("GOOGLE_APPLICATION_CREDENTIALS", None), // make sure file env is not interfering
],
async {
// execute:
let credentials_file_result = CredentialsFile::new().await;

// verify:
let expected_credentials_file: CredentialsFile = serde_json::from_str(CREDENTIALS_FILE_CONTENT)
.expect("Credentials file JSON deserialization not working");
match credentials_file_result {
Err(_) => panic!(),
Ok(cf) => assert_eq!(expected_credentials_file, cf),
}
},
)
.await
}

#[tokio::test]
async fn test_credentials_file_new_env_var_file() {
// setup:
let temp_credentials_dir = tempdir().expect("Cannot create temporary directory");
let temp_credentials_path = temp_credentials_dir.path().join(CREDENTIALS_FILE);
let mut credentials_file = File::create(&temp_credentials_path).expect("Cannot create temporary file");

temp_env::async_with_vars(
[
(
"GOOGLE_APPLICATION_CREDENTIALS",
Some(temp_credentials_path.to_string_lossy().to_string()),
),
("GOOGLE_APPLICATION_CREDENTIALS_JSON", None), // make sure file env is not interfering
],
async {
credentials_file
.write_all(CREDENTIALS_FILE_CONTENT.as_bytes())
.expect("Cannot write content to file");

// execute:
let credentials_file_result = CredentialsFile::new().await;

// verify:
let expected_credentials_file: CredentialsFile = serde_json::from_str(CREDENTIALS_FILE_CONTENT)
.expect("Credentials file JSON deserialization not working");
match credentials_file_result {
Err(_) => panic!(),
Ok(cf) => assert_eq!(expected_credentials_file, cf),
}
},
)
.await
}
}