Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/fix-oauth-quota-project-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@googleworkspace/cli": patch
---

Skip x-goog-user-project header for OAuth auth to fix 403 errors for non-project-member users
48 changes: 45 additions & 3 deletions crates/google-workspace-cli/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,12 @@ async fn build_http_request(
}
}

// Set quota project from ADC for billing/quota attribution
if let Some(quota_project) = crate::auth::get_quota_project() {
request = request.header("x-goog-user-project", quota_project);
// Only send quota project for ADC/service-account auth; OAuth users are not
// necessarily IAM members of the project, so the header causes 403 errors.
if *auth_method != AuthMethod::OAuth {
if let Some(quota_project) = crate::auth::get_quota_project() {
request = request.header("x-goog-user-project", quota_project);
}
}
Comment thread
nuthalapativarun marked this conversation as resolved.
Outdated

let mut all_query_params = input.query_params.clone();
Expand Down Expand Up @@ -2399,3 +2402,42 @@ async fn test_get_does_not_set_content_length_zero() {
"GET with no body should not have Content-Length header"
);
}

#[tokio::test]
async fn test_oauth_auth_does_not_set_quota_project_header() {
// Arrange: even if get_quota_project() would return a value, OAuth requests
// must NOT send x-goog-user-project because OAuth users are not necessarily
// IAM members of the project and the header would trigger 403 errors.
let client = reqwest::Client::new();
let method = RestMethod {
http_method: "GET".to_string(),
path: "files".to_string(),
..Default::default()
};
let input = ExecutionInput {
full_url: "https://example.com/files".to_string(),
body: None,
params: Map::new(),
query_params: Vec::new(),
is_upload: false,
};

let request = build_http_request(
&client,
&method,
&input,
Some("fake-token"),
&AuthMethod::OAuth,
None,
0,
&None,
)
.await
.unwrap();

let built = request.build().unwrap();
assert!(
built.headers().get("x-goog-user-project").is_none(),
"OAuth requests must not include x-goog-user-project header"
);
}
Loading