summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Smith <chrisdsmith@google.com>2022-04-01 15:29:47 -0600
committerCody Oss <codyoss@google.com>2022-06-08 16:14:50 +0000
commitd0670ef3b1ebba3a000f754b3acf1c4be6c221b0 (patch)
tree06663cdfccf7e6c0b8d0a660fd2800e25896303f
parent622c5d57e401754bcdaf99beee1fe0c1136fe3d9 (diff)
downloadgolang-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.mod2
-rw-r--r--google/default.go1
-rw-r--r--google/error.go64
-rw-r--r--google/error_test.go111
-rw-r--r--google/jwt.go3
5 files changed, 179 insertions, 2 deletions
diff --git a/go.mod b/go.mod
index 468b626..c95f33f 100644
--- a/go.mod
+++ b/go.mod
@@ -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 {