all: modernize with doc links and any

Change-Id: If3fc4542b92da802a31dcabc3405f7b1ab06a18d
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/666396
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Sean Liao <sean@liao.dev>
Reviewed-by: Matt Hickford <matt.hickford@gmail.com>
This commit is contained in:
Sean Liao 2025-04-18 18:55:03 +01:00 committed by Gopher Robot
parent 471209bbe2
commit 696f7b3128
20 changed files with 84 additions and 98 deletions

View File

@ -34,7 +34,7 @@ type PKCEParams struct {
// and returns an auth code and state upon approval. // and returns an auth code and state upon approval.
type AuthorizationHandler func(authCodeURL string) (code string, state string, err error) type AuthorizationHandler func(authCodeURL string) (code string, state string, err error)
// TokenSourceWithPKCE is an enhanced version of TokenSource with PKCE support. // TokenSourceWithPKCE is an enhanced version of [oauth2.TokenSource] with PKCE support.
// //
// The pkce parameter supports PKCE flow, which uses code challenge and code verifier // The pkce parameter supports PKCE flow, which uses code challenge and code verifier
// to prevent CSRF attacks. A unique code challenge and code verifier should be generated // to prevent CSRF attacks. A unique code challenge and code verifier should be generated
@ -43,12 +43,12 @@ func TokenSourceWithPKCE(ctx context.Context, config *oauth2.Config, state strin
return oauth2.ReuseTokenSource(nil, authHandlerSource{config: config, ctx: ctx, authHandler: authHandler, state: state, pkce: pkce}) return oauth2.ReuseTokenSource(nil, authHandlerSource{config: config, ctx: ctx, authHandler: authHandler, state: state, pkce: pkce})
} }
// TokenSource returns an oauth2.TokenSource that fetches access tokens // TokenSource returns an [oauth2.TokenSource] that fetches access tokens
// using 3-legged-OAuth flow. // using 3-legged-OAuth flow.
// //
// The provided context.Context is used for oauth2 Exchange operation. // The provided [context.Context] is used for oauth2 Exchange operation.
// //
// The provided oauth2.Config should be a full configuration containing AuthURL, // The provided [oauth2.Config] should be a full configuration containing AuthURL,
// TokenURL, and Scope. // TokenURL, and Scope.
// //
// An environment-specific AuthorizationHandler is used to obtain user consent. // An environment-specific AuthorizationHandler is used to obtain user consent.

View File

@ -55,7 +55,7 @@ type Config struct {
// Token uses client credentials to retrieve a token. // Token uses client credentials to retrieve a token.
// //
// The provided context optionally controls which HTTP client is used. See the oauth2.HTTPClient variable. // The provided context optionally controls which HTTP client is used. See the [oauth2.HTTPClient] variable.
func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) { func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) {
return c.TokenSource(ctx).Token() return c.TokenSource(ctx).Token()
} }
@ -64,18 +64,18 @@ func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) {
// The token will auto-refresh as necessary. // The token will auto-refresh as necessary.
// //
// The provided context optionally controls which HTTP client // The provided context optionally controls which HTTP client
// is returned. See the oauth2.HTTPClient variable. // is returned. See the [oauth2.HTTPClient] variable.
// //
// The returned Client and its Transport should not be modified. // The returned [http.Client] and its Transport should not be modified.
func (c *Config) Client(ctx context.Context) *http.Client { func (c *Config) Client(ctx context.Context) *http.Client {
return oauth2.NewClient(ctx, c.TokenSource(ctx)) return oauth2.NewClient(ctx, c.TokenSource(ctx))
} }
// TokenSource returns a TokenSource that returns t until t expires, // TokenSource returns a [oauth2.TokenSource] that returns t until t expires,
// automatically refreshing it as necessary using the provided context and the // automatically refreshing it as necessary using the provided context and the
// client ID and client secret. // client ID and client secret.
// //
// Most users will use Config.Client instead. // Most users will use [Config.Client] instead.
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
source := &tokenSource{ source := &tokenSource{
ctx: ctx, ctx: ctx,

View File

@ -486,11 +486,11 @@ func (ts tokenSource) Token() (*oauth2.Token, error) {
ClientID: conf.ClientID, ClientID: conf.ClientID,
ClientSecret: conf.ClientSecret, ClientSecret: conf.ClientSecret,
} }
var options map[string]interface{} var options map[string]any
// Do not pass workforce_pool_user_project when client authentication is used. // Do not pass workforce_pool_user_project when client authentication is used.
// The client ID is sufficient for determining the user project. // The client ID is sufficient for determining the user project.
if conf.WorkforcePoolUserProject != "" && conf.ClientID == "" { if conf.WorkforcePoolUserProject != "" && conf.ClientID == "" {
options = map[string]interface{}{ options = map[string]any{
"userProject": conf.WorkforcePoolUserProject, "userProject": conf.WorkforcePoolUserProject,
} }
} }

View File

@ -35,7 +35,7 @@ func (cs fileCredentialSource) subjectToken() (string, error) {
tokenBytes = bytes.TrimSpace(tokenBytes) tokenBytes = bytes.TrimSpace(tokenBytes)
switch cs.Format.Type { switch cs.Format.Type {
case "json": case "json":
jsonData := make(map[string]interface{}) jsonData := make(map[string]any)
err = json.Unmarshal(tokenBytes, &jsonData) err = json.Unmarshal(tokenBytes, &jsonData)
if err != nil { if err != nil {
return "", fmt.Errorf("oauth2/google/externalaccount: failed to unmarshal subject token file: %v", err) return "", fmt.Errorf("oauth2/google/externalaccount: failed to unmarshal subject token file: %v", err)

View File

@ -53,7 +53,7 @@ func (cs urlCredentialSource) subjectToken() (string, error) {
switch cs.Format.Type { switch cs.Format.Type {
case "json": case "json":
jsonData := make(map[string]interface{}) jsonData := make(map[string]any)
err = json.Unmarshal(respBody, &jsonData) err = json.Unmarshal(respBody, &jsonData)
if err != nil { if err != nil {
return "", fmt.Errorf("oauth2/google/externalaccount: failed to unmarshal subject token file: %v", err) return "", fmt.Errorf("oauth2/google/externalaccount: failed to unmarshal subject token file: %v", err)

View File

@ -301,7 +301,7 @@ func (cs computeSource) Token() (*oauth2.Token, error) {
// NOTE(cbro): add hidden metadata about where the token is from. // NOTE(cbro): add hidden metadata about where the token is from.
// This is needed for detection by client libraries to know that credentials come from the metadata server. // This is needed for detection by client libraries to know that credentials come from the metadata server.
// This may be removed in a future version of this library. // This may be removed in a future version of this library.
return tok.WithExtra(map[string]interface{}{ return tok.WithExtra(map[string]any{
"oauth2.google.tokenSource": "compute-metadata", "oauth2.google.tokenSource": "compute-metadata",
"oauth2.google.serviceAccount": acct, "oauth2.google.serviceAccount": acct,
}), nil }), nil

View File

@ -27,7 +27,7 @@ func defaultHeader() http.Header {
// The first 4 fields are all mandatory. headers can be used to pass additional // The first 4 fields are all mandatory. headers can be used to pass additional
// headers beyond the bare minimum required by the token exchange. options can // headers beyond the bare minimum required by the token exchange. options can
// be used to pass additional JSON-structured options to the remote server. // be used to pass additional JSON-structured options to the remote server.
func ExchangeToken(ctx context.Context, endpoint string, request *TokenExchangeRequest, authentication ClientAuthentication, headers http.Header, options map[string]interface{}) (*Response, error) { func ExchangeToken(ctx context.Context, endpoint string, request *TokenExchangeRequest, authentication ClientAuthentication, headers http.Header, options map[string]any) (*Response, error) {
data := url.Values{} data := url.Values{}
data.Set("audience", request.Audience) data.Set("audience", request.Audience)
data.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") data.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")

View File

@ -146,7 +146,7 @@ func TestExchangeToken_Opts(t *testing.T) {
} else if len(strOpts) < 1 { } else if len(strOpts) < 1 {
t.Errorf("\"options\" field has length 0.") t.Errorf("\"options\" field has length 0.")
} }
var opts map[string]interface{} var opts map[string]any
err = json.Unmarshal([]byte(strOpts[0]), &opts) err = json.Unmarshal([]byte(strOpts[0]), &opts)
if err != nil { if err != nil {
t.Fatalf("Couldn't parse received \"options\" field.") t.Fatalf("Couldn't parse received \"options\" field.")
@ -159,7 +159,7 @@ func TestExchangeToken_Opts(t *testing.T) {
if !ok { if !ok {
t.Errorf("Couldn't find first option parameter.") t.Errorf("Couldn't find first option parameter.")
} else { } else {
tOpts1, ok := val.(map[string]interface{}) tOpts1, ok := val.(map[string]any)
if !ok { if !ok {
t.Errorf("Failed to assert the first option parameter as type testOpts.") t.Errorf("Failed to assert the first option parameter as type testOpts.")
} else { } else {
@ -176,7 +176,7 @@ func TestExchangeToken_Opts(t *testing.T) {
if !ok { if !ok {
t.Errorf("Couldn't find second option parameter.") t.Errorf("Couldn't find second option parameter.")
} else { } else {
tOpts2, ok := val2.(map[string]interface{}) tOpts2, ok := val2.(map[string]any)
if !ok { if !ok {
t.Errorf("Failed to assert the second option parameter as type testOpts.") t.Errorf("Failed to assert the second option parameter as type testOpts.")
} else { } else {
@ -200,7 +200,7 @@ func TestExchangeToken_Opts(t *testing.T) {
firstOption := testOpts{optsValues[0][0], optsValues[0][1]} firstOption := testOpts{optsValues[0][0], optsValues[0][1]}
secondOption := testOpts{optsValues[1][0], optsValues[1][1]} secondOption := testOpts{optsValues[1][0], optsValues[1][1]}
inputOpts := make(map[string]interface{}) inputOpts := make(map[string]any)
inputOpts["one"] = firstOption inputOpts["one"] = firstOption
inputOpts["two"] = secondOption inputOpts["two"] = secondOption
ExchangeToken(context.Background(), ts.URL, &exchangeTokenRequest, auth, headers, inputOpts) ExchangeToken(context.Background(), ts.URL, &exchangeTokenRequest, auth, headers, inputOpts)

View File

@ -2,5 +2,5 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package internal contains support packages for oauth2 package. // Package internal contains support packages for [golang.org/x/oauth2].
package internal package internal

View File

@ -13,7 +13,7 @@ import (
) )
// ParseKey converts the binary contents of a private key file // ParseKey converts the binary contents of a private key file
// to an *rsa.PrivateKey. It detects whether the private key is in a // to an [*rsa.PrivateKey]. It detects whether the private key is in a
// PEM container or not. If so, it extracts the private key // PEM container or not. If so, it extracts the private key
// from PEM container before conversion. It only supports PEM // from PEM container before conversion. It only supports PEM
// containers with no passphrase. // containers with no passphrase.

View File

@ -25,9 +25,9 @@ import (
// the requests to access protected resources on the OAuth 2.0 // the requests to access protected resources on the OAuth 2.0
// provider's backend. // provider's backend.
// //
// This type is a mirror of oauth2.Token and exists to break // This type is a mirror of [golang.org/x/oauth2.Token] and exists to break
// an otherwise-circular dependency. Other internal packages // an otherwise-circular dependency. Other internal packages
// should convert this Token into an oauth2.Token before use. // should convert this Token into an [golang.org/x/oauth2.Token] before use.
type Token struct { type Token struct {
// AccessToken is the token that authorizes and authenticates // AccessToken is the token that authorizes and authenticates
// the requests. // the requests.
@ -58,7 +58,7 @@ type Token struct {
// Raw optionally contains extra metadata from the server // Raw optionally contains extra metadata from the server
// when updating a token. // when updating a token.
Raw interface{} Raw any
} }
// tokenJSON is the struct representing the HTTP response from OAuth2 // tokenJSON is the struct representing the HTTP response from OAuth2
@ -319,7 +319,7 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
RefreshToken: tj.RefreshToken, RefreshToken: tj.RefreshToken,
Expiry: tj.expiry(), Expiry: tj.expiry(),
ExpiresIn: int64(tj.ExpiresIn), ExpiresIn: int64(tj.ExpiresIn),
Raw: make(map[string]interface{}), Raw: make(map[string]any),
} }
json.Unmarshal(body, &token.Raw) // no error checks for optional fields json.Unmarshal(body, &token.Raw) // no error checks for optional fields
} }

View File

@ -9,8 +9,8 @@ import (
"net/http" "net/http"
) )
// HTTPClient is the context key to use with [context.WithValue]'s // HTTPClient is the context key to use with [context.WithValue]
// function to associate an *http.Client value with a context. // to associate an [*http.Client] value with a context.
var HTTPClient ContextKey var HTTPClient ContextKey
// ContextKey is just an empty struct. It exists so HTTPClient can be // ContextKey is just an empty struct. It exists so HTTPClient can be

View File

@ -4,7 +4,7 @@
// Package jws provides a partial implementation // Package jws provides a partial implementation
// of JSON Web Signature encoding and decoding. // of JSON Web Signature encoding and decoding.
// It exists to support the golang.org/x/oauth2 package. // It exists to support the [golang.org/x/oauth2] package.
// //
// See RFC 7515. // See RFC 7515.
// //
@ -48,7 +48,7 @@ type ClaimSet struct {
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3 // See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
// This array is marshalled using custom code (see (c *ClaimSet) encode()). // This array is marshalled using custom code (see (c *ClaimSet) encode()).
PrivateClaims map[string]interface{} `json:"-"` PrivateClaims map[string]any `json:"-"`
} }
func (c *ClaimSet) encode() (string, error) { func (c *ClaimSet) encode() (string, error) {
@ -152,7 +152,7 @@ func EncodeWithSigner(header *Header, c *ClaimSet, sg Signer) (string, error) {
} }
// Encode encodes a signed JWS with provided header and claim set. // Encode encodes a signed JWS with provided header and claim set.
// This invokes EncodeWithSigner using crypto/rsa.SignPKCS1v15 with the given RSA private key. // This invokes [EncodeWithSigner] using [crypto/rsa.SignPKCS1v15] with the given RSA private key.
func Encode(header *Header, c *ClaimSet, key *rsa.PrivateKey) (string, error) { func Encode(header *Header, c *ClaimSet, key *rsa.PrivateKey) (string, error) {
sg := func(data []byte) (sig []byte, err error) { sg := func(data []byte) (sig []byte, err error) {
h := sha256.New() h := sha256.New()

View File

@ -68,7 +68,7 @@ type Config struct {
// PrivateClaims optionally specifies custom private claims in the JWT. // PrivateClaims optionally specifies custom private claims in the JWT.
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3 // See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
PrivateClaims map[string]interface{} PrivateClaims map[string]any
// UseIDToken optionally specifies whether ID token should be used instead // UseIDToken optionally specifies whether ID token should be used instead
// of access token when the server returns both. // of access token when the server returns both.
@ -157,7 +157,7 @@ func (js jwtSource) Token() (*oauth2.Token, error) {
AccessToken: tokenRes.AccessToken, AccessToken: tokenRes.AccessToken,
TokenType: tokenRes.TokenType, TokenType: tokenRes.TokenType,
} }
raw := make(map[string]interface{}) raw := make(map[string]any)
json.Unmarshal(body, &raw) // no error checks for optional fields json.Unmarshal(body, &raw) // no error checks for optional fields
token = token.WithExtra(raw) token = token.WithExtra(raw)

View File

@ -227,7 +227,7 @@ func TestJWTFetch_AssertionPayload(t *testing.T) {
PrivateKey: dummyPrivateKey, PrivateKey: dummyPrivateKey,
PrivateKeyID: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", PrivateKeyID: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
TokenURL: ts.URL, TokenURL: ts.URL,
PrivateClaims: map[string]interface{}{ PrivateClaims: map[string]any{
"private0": "claim0", "private0": "claim0",
"private1": "claim1", "private1": "claim1",
}, },
@ -273,11 +273,11 @@ func TestJWTFetch_AssertionPayload(t *testing.T) {
t.Errorf("payload prn = %q; want %q", got, want) t.Errorf("payload prn = %q; want %q", got, want)
} }
if len(conf.PrivateClaims) > 0 { if len(conf.PrivateClaims) > 0 {
var got interface{} var got any
if err := json.Unmarshal(gotjson, &got); err != nil { if err := json.Unmarshal(gotjson, &got); err != nil {
t.Errorf("failed to parse payload; err = %q", err) t.Errorf("failed to parse payload; err = %q", err)
} }
m := got.(map[string]interface{}) m := got.(map[string]any)
for v, k := range conf.PrivateClaims { for v, k := range conf.PrivateClaims {
if !reflect.DeepEqual(m[v], k) { if !reflect.DeepEqual(m[v], k) {
t.Errorf("payload private claims key = %q: got %#v; want %#v", v, m[v], k) t.Errorf("payload private claims key = %q: got %#v; want %#v", v, m[v], k)

View File

@ -24,7 +24,7 @@ import (
// NoContext is the default context you should supply if not using // NoContext is the default context you should supply if not using
// your own [context.Context]. // your own [context.Context].
// //
// Deprecated: Use context.Background() or context.TODO() instead. // Deprecated: Use [context.Background] or [context.TODO] instead.
var NoContext = context.TODO() var NoContext = context.TODO()
// RegisterBrokenAuthHeaderProvider previously did something. It is now a no-op. // RegisterBrokenAuthHeaderProvider previously did something. It is now a no-op.
@ -37,8 +37,8 @@ func RegisterBrokenAuthHeaderProvider(tokenURL string) {}
// Config describes a typical 3-legged OAuth2 flow, with both the // Config describes a typical 3-legged OAuth2 flow, with both the
// client application information and the server's endpoint URLs. // client application information and the server's endpoint URLs.
// For the client credentials 2-legged OAuth2 flow, see the clientcredentials // For the client credentials 2-legged OAuth2 flow, see the
// package (https://golang.org/x/oauth2/clientcredentials). // [golang.org/x/oauth2/clientcredentials] package.
type Config struct { type Config struct {
// ClientID is the application's ID. // ClientID is the application's ID.
ClientID string ClientID string
@ -46,7 +46,7 @@ type Config struct {
// ClientSecret is the application's secret. // ClientSecret is the application's secret.
ClientSecret string ClientSecret string
// Endpoint contains the resource server's token endpoint // Endpoint contains the authorization server's token endpoint
// URLs. These are constants specific to each server and are // URLs. These are constants specific to each server and are
// often available via site-specific packages, such as // often available via site-specific packages, such as
// google.Endpoint or github.Endpoint. // google.Endpoint or github.Endpoint.
@ -135,7 +135,7 @@ type setParam struct{ k, v string }
func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) } func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
// SetAuthURLParam builds an AuthCodeOption which passes key/value parameters // SetAuthURLParam builds an [AuthCodeOption] which passes key/value parameters
// to a provider's authorization endpoint. // to a provider's authorization endpoint.
func SetAuthURLParam(key, value string) AuthCodeOption { func SetAuthURLParam(key, value string) AuthCodeOption {
return setParam{key, value} return setParam{key, value}
@ -148,8 +148,8 @@ func SetAuthURLParam(key, value string) AuthCodeOption {
// request and callback. The authorization server includes this value when // request and callback. The authorization server includes this value when
// redirecting the user agent back to the client. // redirecting the user agent back to the client.
// //
// Opts may include AccessTypeOnline or AccessTypeOffline, as well // Opts may include [AccessTypeOnline] or [AccessTypeOffline], as well
// as ApprovalForce. // as [ApprovalForce].
// //
// To protect against CSRF attacks, opts should include a PKCE challenge // To protect against CSRF attacks, opts should include a PKCE challenge
// (S256ChallengeOption). Not all servers support PKCE. An alternative is to // (S256ChallengeOption). Not all servers support PKCE. An alternative is to
@ -194,7 +194,7 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
// and when other authorization grant types are not available." // and when other authorization grant types are not available."
// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. // See https://tools.ietf.org/html/rfc6749#section-4.3 for more info.
// //
// The provided context optionally controls which HTTP client is used. See the HTTPClient variable. // The provided context optionally controls which HTTP client is used. See the [HTTPClient] variable.
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
v := url.Values{ v := url.Values{
"grant_type": {"password"}, "grant_type": {"password"},
@ -212,10 +212,10 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor
// It is used after a resource provider redirects the user back // It is used after a resource provider redirects the user back
// to the Redirect URI (the URL obtained from AuthCodeURL). // to the Redirect URI (the URL obtained from AuthCodeURL).
// //
// The provided context optionally controls which HTTP client is used. See the HTTPClient variable. // The provided context optionally controls which HTTP client is used. See the [HTTPClient] variable.
// //
// The code will be in the *http.Request.FormValue("code"). Before // The code will be in the [http.Request.FormValue]("code"). Before
// calling Exchange, be sure to validate FormValue("state") if you are // calling Exchange, be sure to validate [http.Request.FormValue]("state") if you are
// using it to protect against CSRF attacks. // using it to protect against CSRF attacks.
// //
// If using PKCE to protect against CSRF attacks, opts should include a // If using PKCE to protect against CSRF attacks, opts should include a
@ -242,10 +242,10 @@ func (c *Config) Client(ctx context.Context, t *Token) *http.Client {
return NewClient(ctx, c.TokenSource(ctx, t)) return NewClient(ctx, c.TokenSource(ctx, t))
} }
// TokenSource returns a TokenSource that returns t until t expires, // TokenSource returns a [TokenSource] that returns t until t expires,
// automatically refreshing it as necessary using the provided context. // automatically refreshing it as necessary using the provided context.
// //
// Most users will use Config.Client instead. // Most users will use [Config.Client] instead.
func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource { func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource {
tkr := &tokenRefresher{ tkr := &tokenRefresher{
ctx: ctx, ctx: ctx,
@ -260,7 +260,7 @@ func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource {
} }
} }
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token" // tokenRefresher is a TokenSource that makes "grant_type=refresh_token"
// HTTP requests to renew a token using a RefreshToken. // HTTP requests to renew a token using a RefreshToken.
type tokenRefresher struct { type tokenRefresher struct {
ctx context.Context // used to get HTTP requests ctx context.Context // used to get HTTP requests
@ -305,8 +305,7 @@ type reuseTokenSource struct {
} }
// Token returns the current token if it's still valid, else will // Token returns the current token if it's still valid, else will
// refresh the current token (using r.Context for HTTP client // refresh the current token and return the new one.
// information) and return the new one.
func (s *reuseTokenSource) Token() (*Token, error) { func (s *reuseTokenSource) Token() (*Token, error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -322,7 +321,7 @@ func (s *reuseTokenSource) Token() (*Token, error) {
return t, nil return t, nil
} }
// StaticTokenSource returns a TokenSource that always returns the same token. // StaticTokenSource returns a [TokenSource] that always returns the same token.
// Because the provided token t is never refreshed, StaticTokenSource is only // Because the provided token t is never refreshed, StaticTokenSource is only
// useful for tokens that never expire. // useful for tokens that never expire.
func StaticTokenSource(t *Token) TokenSource { func StaticTokenSource(t *Token) TokenSource {
@ -338,16 +337,16 @@ func (s staticTokenSource) Token() (*Token, error) {
return s.t, nil return s.t, nil
} }
// HTTPClient is the context key to use with [context.WithValue]'s // HTTPClient is the context key to use with [context.WithValue]
// function to associate an *http.Client value with a context. // to associate a [*http.Client] value with a context.
var HTTPClient internal.ContextKey var HTTPClient internal.ContextKey
// NewClient creates an *http.Client from a Context and TokenSource. // NewClient creates an [*http.Client] from a [context.Context] and [TokenSource].
// The returned client is not valid beyond the lifetime of the context. // The returned client is not valid beyond the lifetime of the context.
// //
// Note that if a custom *http.Client is provided via the Context it // Note that if a custom [*http.Client] is provided via the [context.Context] it
// is used only for token acquisition and is not used to configure the // is used only for token acquisition and is not used to configure the
// *http.Client returned from NewClient. // [*http.Client] returned from NewClient.
// //
// As a special case, if src is nil, a non-OAuth2 client is returned // As a special case, if src is nil, a non-OAuth2 client is returned
// using the provided context. This exists to support related OAuth2 // using the provided context. This exists to support related OAuth2
@ -368,7 +367,7 @@ func NewClient(ctx context.Context, src TokenSource) *http.Client {
} }
} }
// ReuseTokenSource returns a TokenSource which repeatedly returns the // ReuseTokenSource returns a [TokenSource] which repeatedly returns the
// same token as long as it's valid, starting with t. // same token as long as it's valid, starting with t.
// When its cached token is invalid, a new token is obtained from src. // When its cached token is invalid, a new token is obtained from src.
// //
@ -376,10 +375,10 @@ func NewClient(ctx context.Context, src TokenSource) *http.Client {
// (such as a file on disk) between runs of a program, rather than // (such as a file on disk) between runs of a program, rather than
// obtaining new tokens unnecessarily. // obtaining new tokens unnecessarily.
// //
// The initial token t may be nil, in which case the TokenSource is // The initial token t may be nil, in which case the [TokenSource] is
// wrapped in a caching version if it isn't one already. This also // wrapped in a caching version if it isn't one already. This also
// means it's always safe to wrap ReuseTokenSource around any other // means it's always safe to wrap ReuseTokenSource around any other
// TokenSource without adverse effects. // [TokenSource] without adverse effects.
func ReuseTokenSource(t *Token, src TokenSource) TokenSource { func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
// Don't wrap a reuseTokenSource in itself. That would work, // Don't wrap a reuseTokenSource in itself. That would work,
// but cause an unnecessary number of mutex operations. // but cause an unnecessary number of mutex operations.
@ -397,8 +396,8 @@ func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
} }
} }
// ReuseTokenSourceWithExpiry returns a TokenSource that acts in the same manner as the // ReuseTokenSourceWithExpiry returns a [TokenSource] that acts in the same manner as the
// TokenSource returned by ReuseTokenSource, except the expiry buffer is // [TokenSource] returned by [ReuseTokenSource], except the expiry buffer is
// configurable. The expiration time of a token is calculated as // configurable. The expiration time of a token is calculated as
// t.Expiry.Add(-earlyExpiry). // t.Expiry.Add(-earlyExpiry).
func ReuseTokenSourceWithExpiry(t *Token, src TokenSource, earlyExpiry time.Duration) TokenSource { func ReuseTokenSourceWithExpiry(t *Token, src TokenSource, earlyExpiry time.Duration) TokenSource {

15
pkce.go
View File

@ -1,6 +1,7 @@
// Copyright 2023 The Go Authors. All rights reserved. // Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package oauth2 package oauth2
import ( import (
@ -20,9 +21,9 @@ const (
// This follows recommendations in RFC 7636. // This follows recommendations in RFC 7636.
// //
// A fresh verifier should be generated for each authorization. // A fresh verifier should be generated for each authorization.
// S256ChallengeOption(verifier) should then be passed to Config.AuthCodeURL // The resulting verifier should be passed to [Config.AuthCodeURL] or [Config.DeviceAuth]
// (or Config.DeviceAuth) and VerifierOption(verifier) to Config.Exchange // with [S256ChallengeOption], and to [Config.Exchange] or [Config.DeviceAccessToken]
// (or Config.DeviceAccessToken). // with [VerifierOption].
func GenerateVerifier() string { func GenerateVerifier() string {
// "RECOMMENDED that the output of a suitable random number generator be // "RECOMMENDED that the output of a suitable random number generator be
// used to create a 32-octet sequence. The octet sequence is then // used to create a 32-octet sequence. The octet sequence is then
@ -36,22 +37,22 @@ func GenerateVerifier() string {
return base64.RawURLEncoding.EncodeToString(data) return base64.RawURLEncoding.EncodeToString(data)
} }
// VerifierOption returns a PKCE code verifier AuthCodeOption. It should be // VerifierOption returns a PKCE code verifier [AuthCodeOption]. It should only be
// passed to Config.Exchange or Config.DeviceAccessToken only. // passed to [Config.Exchange] or [Config.DeviceAccessToken].
func VerifierOption(verifier string) AuthCodeOption { func VerifierOption(verifier string) AuthCodeOption {
return setParam{k: codeVerifierKey, v: verifier} return setParam{k: codeVerifierKey, v: verifier}
} }
// S256ChallengeFromVerifier returns a PKCE code challenge derived from verifier with method S256. // S256ChallengeFromVerifier returns a PKCE code challenge derived from verifier with method S256.
// //
// Prefer to use S256ChallengeOption where possible. // Prefer to use [S256ChallengeOption] where possible.
func S256ChallengeFromVerifier(verifier string) string { func S256ChallengeFromVerifier(verifier string) string {
sha := sha256.Sum256([]byte(verifier)) sha := sha256.Sum256([]byte(verifier))
return base64.RawURLEncoding.EncodeToString(sha[:]) return base64.RawURLEncoding.EncodeToString(sha[:])
} }
// S256ChallengeOption derives a PKCE code challenge derived from verifier with // S256ChallengeOption derives a PKCE code challenge derived from verifier with
// method S256. It should be passed to Config.AuthCodeURL or Config.DeviceAuth // method S256. It should be passed to [Config.AuthCodeURL] or [Config.DeviceAuth]
// only. // only.
func S256ChallengeOption(verifier string) AuthCodeOption { func S256ChallengeOption(verifier string) AuthCodeOption {
return challengeOption{ return challengeOption{

View File

@ -44,7 +44,7 @@ type Token struct {
// Expiry is the optional expiration time of the access token. // Expiry is the optional expiration time of the access token.
// //
// If zero, TokenSource implementations will reuse the same // If zero, [TokenSource] implementations will reuse the same
// token forever and RefreshToken or equivalent // token forever and RefreshToken or equivalent
// mechanisms for that TokenSource will not be used. // mechanisms for that TokenSource will not be used.
Expiry time.Time `json:"expiry,omitempty"` Expiry time.Time `json:"expiry,omitempty"`
@ -58,7 +58,7 @@ type Token struct {
// raw optionally contains extra metadata from the server // raw optionally contains extra metadata from the server
// when updating a token. // when updating a token.
raw interface{} raw any
// expiryDelta is used to calculate when a token is considered // expiryDelta is used to calculate when a token is considered
// expired, by subtracting from Expiry. If zero, defaultExpiryDelta // expired, by subtracting from Expiry. If zero, defaultExpiryDelta
@ -86,16 +86,16 @@ func (t *Token) Type() string {
// SetAuthHeader sets the Authorization header to r using the access // SetAuthHeader sets the Authorization header to r using the access
// token in t. // token in t.
// //
// This method is unnecessary when using Transport or an HTTP Client // This method is unnecessary when using [Transport] or an HTTP Client
// returned by this package. // returned by this package.
func (t *Token) SetAuthHeader(r *http.Request) { func (t *Token) SetAuthHeader(r *http.Request) {
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken) r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
} }
// WithExtra returns a new Token that's a clone of t, but using the // WithExtra returns a new [Token] that's a clone of t, but using the
// provided raw extra map. This is only intended for use by packages // provided raw extra map. This is only intended for use by packages
// implementing derivative OAuth2 flows. // implementing derivative OAuth2 flows.
func (t *Token) WithExtra(extra interface{}) *Token { func (t *Token) WithExtra(extra any) *Token {
t2 := new(Token) t2 := new(Token)
*t2 = *t *t2 = *t
t2.raw = extra t2.raw = extra
@ -105,8 +105,8 @@ func (t *Token) WithExtra(extra interface{}) *Token {
// Extra returns an extra field. // Extra returns an extra field.
// Extra fields are key-value pairs returned by the server as a // Extra fields are key-value pairs returned by the server as a
// part of the token retrieval response. // part of the token retrieval response.
func (t *Token) Extra(key string) interface{} { func (t *Token) Extra(key string) any {
if raw, ok := t.raw.(map[string]interface{}); ok { if raw, ok := t.raw.(map[string]any); ok {
return raw[key] return raw[key]
} }

View File

@ -12,8 +12,8 @@ import (
func TestTokenExtra(t *testing.T) { func TestTokenExtra(t *testing.T) {
type testCase struct { type testCase struct {
key string key string
val interface{} val any
want interface{} want any
} }
const key = "extra-key" const key = "extra-key"
cases := []testCase{ cases := []testCase{
@ -23,7 +23,7 @@ func TestTokenExtra(t *testing.T) {
{key: "other-key", val: "def", want: nil}, {key: "other-key", val: "def", want: nil},
} }
for _, tc := range cases { for _, tc := range cases {
extra := make(map[string]interface{}) extra := make(map[string]any)
extra[tc.key] = tc.val extra[tc.key] = tc.val
tok := &Token{raw: extra} tok := &Token{raw: extra}
if got, want := tok.Extra(key), tc.want; got != want { if got, want := tok.Extra(key), tc.want; got != want {

View File

@ -11,12 +11,12 @@ import (
"sync" "sync"
) )
// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests, // Transport is an [http.RoundTripper] that makes OAuth 2.0 HTTP requests,
// wrapping a base RoundTripper and adding an Authorization header // wrapping a base [http.RoundTripper] and adding an Authorization header
// with a token from the supplied Sources. // with a token from the supplied [TokenSource].
// //
// Transport is a low-level mechanism. Most code will use the // Transport is a low-level mechanism. Most code will use the
// higher-level Config.Client method instead. // higher-level [Config.Client] method instead.
type Transport struct { type Transport struct {
// Source supplies the token to add to outgoing requests' // Source supplies the token to add to outgoing requests'
// Authorization headers. // Authorization headers.
@ -47,7 +47,7 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
return nil, err return nil, err
} }
req2 := cloneRequest(req) // per RoundTripper contract req2 := req.Clone(req.Context())
token.SetAuthHeader(req2) token.SetAuthHeader(req2)
// req.Body is assumed to be closed by the base RoundTripper. // req.Body is assumed to be closed by the base RoundTripper.
@ -73,17 +73,3 @@ func (t *Transport) base() http.RoundTripper {
} }
return http.DefaultTransport return http.DefaultTransport
} }
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header, len(r.Header))
for k, s := range r.Header {
r2.Header[k] = append([]string(nil), s...)
}
return r2
}