Skip to content

Commit

Permalink
google: Wrap token sources in errWrappingTokenSource
Browse files Browse the repository at this point in the history
Introduce new AuthenticationError type returned by
errWrappingTokenSource.Token. The new error wrapper
exposes a boolean method Temporary, identifying the
underlying network error as retryable based on the
following status codes: 500, 503, 408, or 429.

Bump go.mod version to 1.15

refs: googleapis/google-api-go-client#1445

Change-Id: I27c76cb0c71b918c25a640f40d0bd515b2e488fc
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/403846
Reviewed-by: Cody Oss <[email protected]>
Reviewed-by: Tyler Bui-Palsulich <[email protected]>
  • Loading branch information
quartzmo authored and codyoss committed Jun 8, 2022
1 parent 622c5d5 commit d0670ef
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 2 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module golang.org/x/oauth2

go 1.11
go 1.15

require (
cloud.google.com/go v0.65.0
Expand Down
1 change: 1 addition & 0 deletions google/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params
if err != nil {
return nil, err
}
ts = newErrWrappingTokenSource(ts)
return &DefaultCredentials{
ProjectID: f.ProjectID,
TokenSource: ts,
Expand Down
64 changes: 64 additions & 0 deletions google/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package google

import (
"errors"

"golang.org/x/oauth2"
)

// AuthenticationError indicates there was an error in the authentication flow.
//
// Use (*AuthenticationError).Temporary to check if the error can be retried.
type AuthenticationError struct {
err *oauth2.RetrieveError
}

func newAuthenticationError(err error) error {
re := &oauth2.RetrieveError{}
if !errors.As(err, &re) {
return err
}
return &AuthenticationError{
err: re,
}
}

// Temporary indicates that the network error has one of the following status codes and may be retried: 500, 503, 408, or 429.
func (e *AuthenticationError) Temporary() bool {
if e.err.Response == nil {
return false
}
sc := e.err.Response.StatusCode
return sc == 500 || sc == 503 || sc == 408 || sc == 429
}

func (e *AuthenticationError) Error() string {
return e.err.Error()
}

func (e *AuthenticationError) Unwrap() error {
return e.err
}

type errWrappingTokenSource struct {
src oauth2.TokenSource
}

func newErrWrappingTokenSource(ts oauth2.TokenSource) oauth2.TokenSource {
return &errWrappingTokenSource{src: ts}
}

// Token returns the current token if it's still valid, else will
// refresh the current token (using r.Context for HTTP client
// information) and return the new one.
func (s *errWrappingTokenSource) Token() (*oauth2.Token, error) {
t, err := s.src.Token()
if err != nil {
return nil, newAuthenticationError(err)
}
return t, nil
}
111 changes: 111 additions & 0 deletions google/error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package google

import (
"net/http"
"testing"

"golang.org/x/oauth2"
)

func TestAuthenticationError_Temporary(t *testing.T) {
tests := []struct {
name string
code int
want bool
}{
{
name: "temporary with 500",
code: 500,
want: true,
},
{
name: "temporary with 503",
code: 503,
want: true,
},
{
name: "temporary with 408",
code: 408,
want: true,
},
{
name: "temporary with 429",
code: 429,
want: true,
},
{
name: "temporary with 418",
code: 418,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ae := &AuthenticationError{
err: &oauth2.RetrieveError{
Response: &http.Response{
StatusCode: tt.code,
},
},
}
if got := ae.Temporary(); got != tt.want {
t.Errorf("Temporary() = %v; want %v", got, tt.want)
}
})
}
}

func TestErrWrappingTokenSource_Token(t *testing.T) {
tok := oauth2.Token{AccessToken: "MyAccessToken"}
ts := errWrappingTokenSource{
src: oauth2.StaticTokenSource(&tok),
}
got, err := ts.Token()
if *got != tok {
t.Errorf("Token() = %v; want %v", got, tok)
}
if err != nil {
t.Error(err)
}
}

type errTokenSource struct {
err error
}

func (s *errTokenSource) Token() (*oauth2.Token, error) {
return nil, s.err
}

func TestErrWrappingTokenSource_TokenError(t *testing.T) {
re := &oauth2.RetrieveError{
Response: &http.Response{
StatusCode: 500,
},
}
ts := errWrappingTokenSource{
src: &errTokenSource{
err: re,
},
}
_, err := ts.Token()
if err == nil {
t.Fatalf("errWrappingTokenSource.Token() err = nil, want *AuthenticationError")
}
ae, ok := err.(*AuthenticationError)
if !ok {
t.Fatalf("errWrappingTokenSource.Token() err = %T, want *AuthenticationError", err)
}
wrappedErr := ae.Unwrap()
if wrappedErr == nil {
t.Fatalf("AuthenticationError.Unwrap() err = nil, want *oauth2.RetrieveError")
}
_, ok = wrappedErr.(*oauth2.RetrieveError)
if !ok {
t.Errorf("AuthenticationError.Unwrap() err = %T, want *oauth2.RetrieveError", err)
}
}
3 changes: 2 additions & 1 deletion google/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ func newJWTSource(jsonKey []byte, audience string, scopes []string) (oauth2.Toke
if err != nil {
return nil, err
}
return oauth2.ReuseTokenSource(tok, ts), nil
rts := newErrWrappingTokenSource(oauth2.ReuseTokenSource(tok, ts))
return rts, nil
}

type jwtAccessTokenSource struct {
Expand Down

0 comments on commit d0670ef

Please sign in to comment.