diff options
author | Chris Smith <chrisdsmith@google.com> | 2022-04-01 15:29:47 -0600 |
---|---|---|
committer | Cody Oss <codyoss@google.com> | 2022-06-08 16:14:50 +0000 |
commit | d0670ef3b1ebba3a000f754b3acf1c4be6c221b0 (patch) | |
tree | 06663cdfccf7e6c0b8d0a660fd2800e25896303f | |
parent | 622c5d57e401754bcdaf99beee1fe0c1136fe3d9 (diff) | |
download | golang-x-oauth2-d0670ef3b1ebba3a000f754b3acf1c4be6c221b0.tar.gz |
google: Wrap token sources in errWrappingTokenSource
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: https://github.com/googleapis/google-api-go-client/issues/1445
Change-Id: I27c76cb0c71b918c25a640f40d0bd515b2e488fc
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/403846
Reviewed-by: Cody Oss <codyoss@google.com>
Reviewed-by: Tyler Bui-Palsulich <tbp@google.com>
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | google/default.go | 1 | ||||
-rw-r--r-- | google/error.go | 64 | ||||
-rw-r--r-- | google/error_test.go | 111 | ||||
-rw-r--r-- | google/jwt.go | 3 |
5 files changed, 179 insertions, 2 deletions
@@ -1,6 +1,6 @@ module golang.org/x/oauth2 -go 1.11 +go 1.15 require ( cloud.google.com/go v0.65.0 diff --git a/google/default.go b/google/default.go index dd00420..024a104 100644 --- a/google/default.go +++ b/google/default.go @@ -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, diff --git a/google/error.go b/google/error.go new file mode 100644 index 0000000..d84dd00 --- /dev/null +++ b/google/error.go @@ -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 +} diff --git a/google/error_test.go b/google/error_test.go new file mode 100644 index 0000000..cd60e91 --- /dev/null +++ b/google/error_test.go @@ -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) + } +} diff --git a/google/jwt.go b/google/jwt.go index 67d97b9..e89e6ae 100644 --- a/google/jwt.go +++ b/google/jwt.go @@ -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 { |