Skip to content

fix(executor): skip x-goog-user-project header for OAuth auth method#863

Open
mateusmaaia wants to merge 5 commits into
googleworkspace:mainfrom
mateusmaaia:test-729-fix
Open

fix(executor): skip x-goog-user-project header for OAuth auth method#863
mateusmaaia wants to merge 5 commits into
googleworkspace:mainfrom
mateusmaaia:test-729-fix

Conversation

@mateusmaaia

Copy link
Copy Markdown

Summary

Picks up the fix from #827 (by @nuthalapativarun), which was auto-closed by the stale-bot after 72 hours of inactivity — not because it was rejected. The PR had already passed CI (fmt, clippy, tests) and been through several rounds of review addressing gemini-code-assist feedback.

Root cause (from #827/#729): the CLI unconditionally sends the x-goog-user-project header (from project_id in client_secret.json) on every request. For OAuth desktop-app credentials, this triggers a GCP IAM check (serviceusage.services.use) that fails for any user who isn't an IAM member of the project backing the shared OAuth client — a common setup when an OAuth client is distributed org-wide. The API then returns a permission error that, depending on the service, surfaces as 403 insufficientPermissions: Request had insufficient authentication scopes even though the token's actual scopes are correct.

New evidence this affects more than Drive:

Fix (unchanged from #827, plus two small follow-ups)

  • ServiceAccount: always forward the quota project (env var, config, or ADC) — service accounts need it and are project members by construction.
  • OAuth: only send x-goog-user-project when GOOGLE_WORKSPACE_PROJECT_ID is explicitly set (opt-in) — otherwise omit it entirely, since OAuth users may not be IAM members of the client's backing project.
  • None: never send it.

On top of #827's four commits, I added:

  • #[cfg(test)] on QUOTA_PROJECT_ENV_MUTEX — it's only referenced from #[tokio::test] functions, which aren't compiled outside test builds, so the plain cargo clippy --workspace (CI's lint job, no --tests/--all-targets) flagged it as dead code.
  • A cargo fmt pass on the new test file's assert_eq! line wrap.

Verification

  • cargo test -p google-workspace-cli: 703 passed, 0 failed (includes the two regression tests from fix(executor): skip x-goog-user-project header for OAuth auth method #827 covering both the default-omit and explicit-opt-in cases).
  • cargo fmt --all -- --check: clean.
  • cargo clippy --workspace -- -D warnings (CI's exact lint command): clean except for one pre-existing, unrelated collapsible_match warning in helpers/script.rs that I confirmed also fails on a fresh origin/main checkout without this branch — not introduced by this change.
  • Branch is a clean fast-forward on top of current origin/main (no rebase conflicts).
  • Built the patched binary locally and ran it against a real Google Calendar account that was previously failing 100% of the time on calendar.events.list with this exact error — it now succeeds consistently, including with the multi-week maxResults/orderBy params a real caller would use.

Fixes #729
Fixes #861
Related to the admin-reports case reported in #729 (comment) (@snacsnoc)

Co-authored-by: Varun Nuthalapati nuthalapativarun@gmail.com

…ta header

AuthMethod::OAuth covers both user OAuth and service-account credentials,
so the previous check (*auth_method != AuthMethod::OAuth) would incorrectly
suppress the x-goog-user-project header for service accounts (which do need
it) while also setting it on unauthenticated (None) requests.

Add AuthMethod::ServiceAccount and auth::CredentialKind so the call site in
main.rs can tag the request with the right variant. The quota header is now
only sent for ServiceAccount auth; user OAuth requests remain header-free to
avoid 403 errors for users who are not IAM members of the project.
Replace the separate CredentialKind enum with the existing AuthMethod
enum (moved from executor.rs to auth.rs so authentication types live
with authentication code).  get_token_with_kind now returns AuthMethod
directly, eliminating the mapping boilerplate in main.rs.

Resolves Gemini r2 comment on PR googleworkspace#827.
…icitly set

The previous fix completely omitted x-goog-user-project for OAuth, which
broke the documented GOOGLE_WORKSPACE_PROJECT_ID env var behaviour. Users
who explicitly set that variable expect the header to be forwarded.

New behaviour:
- ServiceAccount: always send the header (env var, config, or ADC)
- OAuth: only send when GOOGLE_WORKSPACE_PROJECT_ID is explicitly set
- None: never send

Also serialise the two env-var-mutating tests with a static Mutex to
avoid races when the test binary runs threads in parallel.
@changeset-bot

changeset-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 943f8ad

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@googleworkspace-bot googleworkspace-bot added area: auth area: http area: core Core CLI parsing, commands, error handling, utilities labels Jul 3, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a long-standing issue where the CLI would trigger 403 permission errors by unconditionally sending the 'x-goog-user-project' header during OAuth-authenticated requests. By defaulting to omitting this header for user-based OAuth and providing an opt-in mechanism for power users, the change resolves authentication failures across multiple Google Workspace services while preserving necessary functionality for service accounts.

Highlights

  • OAuth Authentication Fix: Modified the CLI to stop sending the 'x-goog-user-project' header by default for OAuth authentication, preventing 403 'insufficientPermissions' errors for users who are not IAM members of the project backing the OAuth client.
  • Opt-in Quota Project Header: Added support for an explicit opt-in via the 'GOOGLE_WORKSPACE_PROJECT_ID' environment variable, allowing users to still send the quota header when required.
  • Service Account Consistency: Ensured that service account authentication continues to forward the quota project header as expected, maintaining existing functionality for automated service accounts.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@google-cla

google-cla Bot commented Jul 3, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request modifies the CLI to skip the x-goog-user-project header during OAuth authentication unless the GOOGLE_WORKSPACE_PROJECT_ID environment variable is explicitly set, preventing 403 errors for non-project-member users. The feedback highlights a potential race condition in the newly added tests; using a local mutex instead of the #[serial_test::serial] attribute can lead to flaky test failures when running concurrently with other environment-mutating tests in the workspace.

Comment on lines +2406 to +2413
/// Mutex to serialise tests that mutate GOOGLE_WORKSPACE_PROJECT_ID so they
/// don't race with each other when the test binary runs its threads in parallel.
#[cfg(test)]
static QUOTA_PROJECT_ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());

#[tokio::test]
async fn test_oauth_auth_does_not_set_quota_project_header_by_default() {
let _guard = QUOTA_PROJECT_ENV_MUTEX.lock().unwrap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The tests in auth.rs (which also mutate environment variables like GOOGLE_WORKSPACE_PROJECT_ID) are annotated with #[serial_test::serial]. Since serial_test uses its own global static lock to serialize tests, any test not annotated with #[serial] can run concurrently with them. By using a local QUOTA_PROJECT_ENV_MUTEX instead of #[serial_test::serial], these new tests in executor.rs can run in parallel with the auth.rs tests, leading to environment variable races and flaky test failures in CI. Using #[serial_test::serial] ensures all environment-mutating tests across the workspace are properly serialized.

#[tokio::test]
#[serial_test::serial]
async fn test_oauth_auth_does_not_set_quota_project_header_by_default() {

Comment on lines +2451 to +2453
#[tokio::test]
async fn test_oauth_auth_sends_quota_project_when_env_var_explicitly_set() {
let _guard = QUOTA_PROJECT_ENV_MUTEX.lock().unwrap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Replace the local mutex guard with #[serial_test::serial] to ensure this test is properly serialized with other environment-mutating tests in the workspace (such as those in auth.rs), preventing flaky test runs.

Suggested change
#[tokio::test]
async fn test_oauth_auth_sends_quota_project_when_env_var_explicitly_set() {
let _guard = QUOTA_PROJECT_ENV_MUTEX.lock().unwrap();
#[tokio::test]
#[serial_test::serial]
async fn test_oauth_auth_sends_quota_project_when_env_var_explicitly_set() {

- QUOTA_PROJECT_ENV_MUTEX is only referenced from #[tokio::test]
  functions, which aren't compiled outside test builds, so plain
  `cargo clippy --workspace` (CI's lint job) flagged it as dead code.
- cargo fmt line-wrap on the new test's assert_eq! chain.
@googleworkspace-bot

Copy link
Copy Markdown
Collaborator

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request modifies the Google Workspace CLI to conditionally omit the x-goog-user-project header during OAuth authentication unless the GOOGLE_WORKSPACE_PROJECT_ID environment variable is explicitly set, resolving 403 errors for non-project-member users. It introduces an AuthMethod enum and a get_token_with_kind function to track the authentication type. The feedback recommends replacing the local QUOTA_PROJECT_ENV_MUTEX in the new tests with the #[serial_test::serial] attribute, as other test files also mutate the same environment variable and could cause flaky test runs due to parallel execution.

Comment on lines +2406 to +2409
/// Mutex to serialise tests that mutate GOOGLE_WORKSPACE_PROJECT_ID so they
/// don't race with each other when the test binary runs its threads in parallel.
#[cfg(test)]
static QUOTA_PROJECT_ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The local QUOTA_PROJECT_ENV_MUTEX only serializes tests within executor.rs. However, tests in auth.rs (such as test_get_quota_project_priority_env_var) also mutate the shared GOOGLE_WORKSPACE_PROJECT_ID environment variable. Because they do not share this mutex, they can still run in parallel and race, leading to flaky test failures.

Since the serial_test crate is already a dependency and used in auth.rs, we should use #[serial_test::serial] on these tests instead and remove this local mutex entirely.

Comment on lines +2411 to +2413
#[tokio::test]
async fn test_oauth_auth_does_not_set_quota_project_header_by_default() {
let _guard = QUOTA_PROJECT_ENV_MUTEX.lock().unwrap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Use #[serial_test::serial] to serialize this test with other tests in the crate that mutate environment variables, and remove the local mutex guard.

Suggested change
#[tokio::test]
async fn test_oauth_auth_does_not_set_quota_project_header_by_default() {
let _guard = QUOTA_PROJECT_ENV_MUTEX.lock().unwrap();
#[tokio::test]
#[serial_test::serial]
async fn test_oauth_auth_does_not_set_quota_project_header_by_default() {

Comment on lines +2451 to +2453
#[tokio::test]
async fn test_oauth_auth_sends_quota_project_when_env_var_explicitly_set() {
let _guard = QUOTA_PROJECT_ENV_MUTEX.lock().unwrap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Use #[serial_test::serial] to serialize this test with other tests in the crate that mutate environment variables, and remove the local mutex guard.

Suggested change
#[tokio::test]
async fn test_oauth_auth_sends_quota_project_when_env_var_explicitly_set() {
let _guard = QUOTA_PROJECT_ENV_MUTEX.lock().unwrap();
#[tokio::test]
#[serial_test::serial]
async fn test_oauth_auth_sends_quota_project_when_env_var_explicitly_set() {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: auth area: core Core CLI parsing, commands, error handling, utilities area: http

Projects

None yet

3 participants