fix(auth): prioritize local project configuration over global ADC for quota attribution (#295)
* fix(auth): prioritize local project configuration over global ADC for quota attribution * chore: add changeset for project ID priority fix
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@googleworkspace/cli": patch
|
||||
---
|
||||
|
||||
Prioritize local project configuration and `GOOGLE_WORKSPACE_PROJECT_ID` over global Application Default Credentials (ADC) for quota attribution. This fixes 403 errors when the Drive API is disabled in a global gcloud project but enabled in the project configured for gws.
|
||||
@@ -199,6 +199,6 @@ Use these labels to categorize pull requests and issues:
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `GOOGLE_WORKSPACE_PROJECT_ID` | GCP project ID fallback for `gmail watch` and `events subscribe` helpers (overridden by `--project` flag) |
|
||||
| `GOOGLE_WORKSPACE_PROJECT_ID` | GCP project ID override for quota/billing and fallback for helper commands (overridden by `--project` flag) |
|
||||
|
||||
All variables can also live in a `.env` file (loaded via `dotenvy`).
|
||||
|
||||
@@ -316,7 +316,7 @@ All variables are optional. See [`.env.example`](.env.example) for a copy-paste
|
||||
| `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` | Override config directory (default: `~/.config/gws`) |
|
||||
| `GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE` | Default Model Armor template |
|
||||
| `GOOGLE_WORKSPACE_CLI_SANITIZE_MODE` | `warn` (default) or `block` |
|
||||
| `GOOGLE_WORKSPACE_PROJECT_ID` | GCP project ID fallback for helper commands |
|
||||
| `GOOGLE_WORKSPACE_PROJECT_ID` | GCP project ID override for quota/billing and fallback for helper commands |
|
||||
|
||||
Environment variables can also be set in a `.env` file (loaded via [dotenvy](https://crates.io/crates/dotenvy)).
|
||||
|
||||
|
||||
+72
-2
@@ -24,9 +24,28 @@ use anyhow::Context;
|
||||
|
||||
use crate::credential_store;
|
||||
|
||||
/// Returns the `quota_project_id` from Application Default Credentials, if present.
|
||||
/// This is used to set the `x-goog-user-project` header on API requests.
|
||||
/// Returns the project ID to be used for quota and billing (sets the `x-goog-user-project` header).
|
||||
///
|
||||
/// Priority:
|
||||
/// 1. `GOOGLE_WORKSPACE_PROJECT_ID` environment variable.
|
||||
/// 2. `project_id` from the OAuth client configuration (`client_secret.json`).
|
||||
/// 3. `quota_project_id` from Application Default Credentials (ADC).
|
||||
pub fn get_quota_project() -> Option<String> {
|
||||
// 1. Explicit environment variable (highest priority)
|
||||
if let Ok(project_id) = std::env::var("GOOGLE_WORKSPACE_PROJECT_ID") {
|
||||
if !project_id.is_empty() {
|
||||
return Some(project_id);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Project ID from the OAuth client configuration (set via `gws auth setup`)
|
||||
if let Ok(config) = crate::oauth_config::load_client_config() {
|
||||
if !config.project_id.is_empty() {
|
||||
return Some(config.project_id);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback to Application Default Credentials (ADC)
|
||||
let path = std::env::var("GOOGLE_APPLICATION_CREDENTIALS")
|
||||
.ok()
|
||||
.map(PathBuf::from)
|
||||
@@ -622,6 +641,53 @@ mod tests {
|
||||
.contains("No credentials found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_get_quota_project_priority_env_var() {
|
||||
let _env_guard = EnvVarGuard::set("GOOGLE_WORKSPACE_PROJECT_ID", "priority-env");
|
||||
let _adc_guard = EnvVarGuard::remove("GOOGLE_APPLICATION_CREDENTIALS");
|
||||
let _config_guard = EnvVarGuard::remove("GOOGLE_WORKSPACE_CLI_CONFIG_DIR");
|
||||
let _home_guard = EnvVarGuard::set("HOME", "/missing/home");
|
||||
|
||||
assert_eq!(get_quota_project(), Some("priority-env".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_get_quota_project_priority_config() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let _config_guard =
|
||||
EnvVarGuard::set("GOOGLE_WORKSPACE_CLI_CONFIG_DIR", tmp.path().to_str().unwrap());
|
||||
let _env_guard = EnvVarGuard::remove("GOOGLE_WORKSPACE_PROJECT_ID");
|
||||
let _adc_guard = EnvVarGuard::remove("GOOGLE_APPLICATION_CREDENTIALS");
|
||||
let _home_guard = EnvVarGuard::set("HOME", "/missing/home");
|
||||
|
||||
// Save a client config with a project ID
|
||||
crate::oauth_config::save_client_config("id", "secret", "config-project").unwrap();
|
||||
|
||||
assert_eq!(get_quota_project(), Some("config-project".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_get_quota_project_priority_adc_fallback() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let adc_dir = tmp.path().join(".config").join("gcloud");
|
||||
std::fs::create_dir_all(&adc_dir).unwrap();
|
||||
std::fs::write(
|
||||
adc_dir.join("application_default_credentials.json"),
|
||||
r#"{"quota_project_id": "adc-project"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _home_guard = EnvVarGuard::set("HOME", tmp.path());
|
||||
let _env_guard = EnvVarGuard::remove("GOOGLE_WORKSPACE_PROJECT_ID");
|
||||
let _config_guard = EnvVarGuard::remove("GOOGLE_WORKSPACE_CLI_CONFIG_DIR");
|
||||
let _adc_guard = EnvVarGuard::remove("GOOGLE_APPLICATION_CREDENTIALS");
|
||||
|
||||
assert_eq!(get_quota_project(), Some("adc-project".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_get_quota_project_reads_adc() {
|
||||
@@ -636,6 +702,10 @@ mod tests {
|
||||
|
||||
let _home_guard = EnvVarGuard::set("HOME", tmp.path());
|
||||
let _adc_guard = EnvVarGuard::remove("GOOGLE_APPLICATION_CREDENTIALS");
|
||||
// Isolate from local environment
|
||||
let _env_guard = EnvVarGuard::remove("GOOGLE_WORKSPACE_PROJECT_ID");
|
||||
let _config_guard = EnvVarGuard::remove("GOOGLE_WORKSPACE_CLI_CONFIG_DIR");
|
||||
|
||||
assert_eq!(get_quota_project(), Some("my-project-123".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -454,7 +454,7 @@ fn print_usage() {
|
||||
" GOOGLE_WORKSPACE_CLI_SANITIZE_MODE Sanitization mode: warn (default) or block"
|
||||
);
|
||||
println!(
|
||||
" GOOGLE_WORKSPACE_PROJECT_ID GCP project ID fallback for helper commands"
|
||||
" GOOGLE_WORKSPACE_PROJECT_ID Override the GCP project ID for quota and billing"
|
||||
);
|
||||
println!();
|
||||
println!("COMMUNITY:");
|
||||
|
||||
Reference in New Issue
Block a user