Skip to content

feat(util): migrate fetch.go to cloud.google.com/go/auth and bridge legacy functions via oauth2adapt - #198

Open
agrawalradhika-cell wants to merge 5 commits into
google:masterfrom
agrawalradhika-cell:feat/migrate-fetch-to-cloud-google-auth
Open

agrawalradhika-cell wants to merge 5 commits into
google:masterfrom
agrawalradhika-cell:feat/migrate-fetch-to-cloud-google-auth

Conversation

@agrawalradhika-cell

@agrawalradhika-cell agrawalradhika-cell commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Phase 4 of migrating oauth2l from golang.org/x/oauth2 to cloud.google.com/go/auth. This PR moves the core auth logic in util/fetch.go onto credentials.DetectDefault. It adds cloud.google.com/go/auth-typed versions of the fetch functions. The existing golang.org/x/oauth2-typed functions stay as thin // Deprecated: wrappers built on oauth2adapt.

What Changed

  1. New cloud.google.com/go/auth functions in util/fetch.go

    • FindAuthCredentials(ctx, settings) (*auth.Credentials, error) calls credentials.DetectDefault through a new internal helper, newDetectOptions. The helper handles three gaps between the two libraries:
      • Scopes vs. Audience: DetectOptions.validate() rejects options that set both. main.go sets both for positional --jwt args and for --sts --audience --scope. So Audience is only used for self-signed JWTs, where it wins over Scope. OAuth flows use only Scope.
      • token_uri in authorized_user JSON: cloud.google.com/go/auth ignores this field and reads DetectOptions.TokenURL instead. The helper copies token_uri into TokenURL, so --refresh (which uses BuildRefreshTokenJSON) still works against custom or mock token endpoints.
      • 3LO AuthHandlerOptions: wired from settings.resolveAuthHandler(), settings.State (defaults to "state"), and GeneratePKCEOptions().
      • HTTP client: an *http.Client stored in ctx under oauth2.HTTPClient is passed as DetectOptions.Client, so callers who injected a client into the context the golang.org/x/oauth2 way keep it. credentials.DetectDefault takes no context, so ctx is used only for this lookup.
      • Scopes are split with strings.Fields, so repeated spaces don't produce empty scope entries. An empty Scope gives no scopes, which sends the same empty scope claim as the old strings.Split("", " ") (covered by a service-account test).
    • OAuthJSONTokenProvider(ctx, settings) (auth.TokenProvider, error).
    • JWTTokenProvider(ctx, settings) (auth.TokenProvider, error) sets UseSelfSignedJWT: true and requires an audience or a scope. cloud.google.com/go/auth quietly ignores UseSelfSignedJWT for credentials that aren't service accounts, so this function rejects them explicitly, as google.JWTAccessTokenSourceFromJSON did before. When ADC finds credentials with no JSON (for example, GCE metadata), the error says that a service account key file is required. JWTTokenSource and JWTTokenProvider share one validation helper; each reports its own name in the error.
    • FetchAuthToken(ctx, settings) (*auth.Token, error):
      • Returns (nil, nil) for API keys.
      • Uses ADC with DefaultScope when settings == nil.
      • When an authorized_user refresh response leaves out refresh_token, it copies the refresh token from the credentials into token.Metadata["refresh_token"]. This matches golang.org/x/oauth2, which keeps the refresh token on refreshed tokens.
  2. Legacy functions now wrap the new ones and are marked // Deprecated:

    • FindJSONCredentials calls FindAuthCredentials and converts the result with oauth2adapt.Oauth2CredentialsFromAuthCredentials.
    • OAuthJSONTokenSource, JWTTokenSource, and FetchToken call the new providers.
    • The returned token sources are bound to the caller's ctx, as google.CredentialsFromJSONWithParams(ctx, ...) token sources were, so deadlines and cancellation still apply.
    • Tokens are converted by a small local adapter, not by oauth2adapt.TokenSourceFromTokenProvider directly. The reason: auth.Token has no RefreshToken field, and the oauth2adapt version doesn't fill in oauth2.Token.RefreshToken. The adapter:
      • copies refresh_token from the metadata into oauth2.Token.RefreshToken
      • keeps the raw response metadata in Extra(), so id_token and scope still reach MarshalWithExtras
      • adds *oauth2.RetrieveError to the error chain (via oauth2adapt.AddRetrieveErrorToAuthError), so callers using errors.As keep working
    • Removes all golang.org/x/oauth2/google calls from fetch.go: FindDefaultCredentialsWithParams, CredentialsFromJSONWithParams, DefaultTokenSource, JWTAccessTokenSourceFromJSON, and JWTAccessTokenSourceWithScope. The temporary authhandler import is removed too.
  3. util/settings.go

    • Removes the temporary blank import of cloud.google.com/go/auth/credentials, which fetch.go now imports directly. No go.mod or vendor/ changes are needed.
  4. Small cleanups: fixes the existing Unsupported authentcation method typo and the SHA256 encryption wording in the GeneratePKCEOptions comment (now "SHA-256 hashing"). TestGet3LOAuthHandlerLoopbackServerNotListening now calls silenceStdout(t) like the other handler tests.

  5. New util/fetch_test.go

    • newDetectOptions: scope/audience precedence, token_uri forwarding (only for authorized_user), Subject, and AuthHandlerOptions (handler precedence, default or custom state, PKCE S256).
    • FetchAuthToken and FetchToken, using an httptest token server:
      • API keys
      • unsupported auth type
      • authorized_user refresh, both with a kept and a rotated refresh token
      • Extra("id_token") and Extra("scope")
      • a MarshalWithExtras round trip
      • *auth.Error and *oauth2.RetrieveError error chains
    • JWTTokenProvider and JWTTokenSource: audience wins over scope; scope-only works. Each JWT's aud, scope, and iss claims are decoded and checked. Also covers the error cases (no audience or scope, non-service-account credentials).
    • FindAuthCredentials and FindJSONCredentials keep JSON and ProjectID.
    • Context handling: requests go through an oauth2.HTTPClient stored in the context, and a canceled context makes OAuthJSONTokenSource().Token() fail with context.Canceled.
    • token_uri round trip: BuildAuthRefreshTokenJSON output is sent to the client secret's token_uri with grant_type=refresh_token.
    • A service account with an empty Scope gets a token, and its JWT assertion has an empty scope.
    • withRefreshToken: nil token, refresh token already present, credentials that aren't authorized_user, empty JSON, and a check that the cached token's metadata is not mutated.
    • JWT through ADC with no JSON credentials.

Backwards Compatibility

  • No module changes. cloud.google.com/go/auth, auth/credentials, and oauth2adapt were already added to go.mod and vendor/ in build(deps): add cloud.google.com/go/auth and cloud.google.com/go/auth/oauth2adapt #195. go mod verify passes, and go mod tidy && go mod vendor produce no diff.
  • No exported signature changes. Every existing exported function in util/fetch.go keeps its exact golang.org/x/oauth2 input and output types. The CLI (main.go, util/tasks.go) still calls the deprecated wrappers, so all CLI commands now run on the cloud.google.com/go/auth backend through the bridge. Phase 5 switches the CLI to the new functions directly.
  • Small behavior differences:
    • Token endpoint error messages now come from cloud.google.com/go/auth (auth: cannot fetch token: ...) instead of oauth2: cannot fetch token: .... *oauth2.RetrieveError is still in the error chain.
    • FetchToken(ctx, nil) used to panic. It now uses ADC with DefaultScope, which is what the old newTokenSource intended.
    • Settings.State is now passed as the 3LO state (default "state"). Before, "state" was hardcoded, so any other value caused a state mismatch. The CLI already passes "state".

Testing

  • go vet ./util/
  • go test -race ./util/...
  • make test: all integration suites in integration/cli_test.go pass against integration/golden/ (2LO, 3LO, 3LO loopback, cached and refresh flows, JWT, SSO, STS, impersonation).
  • make build

- Bind legacy token sources to the caller's ctx and honor an *http.Client
  stored under oauth2.HTTPClient via DetectOptions.Client.
- Use strings.Fields for scopes; empty Scope yields no scopes.
- Share JWT audience/scope validation; clearer error when ADC has no JSON.
- Keep the API key short-circuit in one place (newCredentials).
- Fix 'authentcation' typo and 'SHA256 encryption' comment.
- Add tests for ctx client/cancellation, refresh JSON token_uri round trip,
  service account with empty scope, withRefreshToken and JSON-less ADC.
@agrawalradhika-cell
agrawalradhika-cell marked this pull request as ready for review October 30, 2026 21:13
Remove call to silenceStdout in TestGet3LOAuthHandlerLoopback.
Added tests for OAuth handler loopback scenarios.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant