Skip to content

Commit d0670ef

Browse files
quartzmocodyoss
authored andcommitted
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: 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]>
1 parent 622c5d5 commit d0670ef

File tree

5 files changed

+179
-2
lines changed

5 files changed

+179
-2
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module golang.org/x/oauth2
22

3-
go 1.11
3+
go 1.15
44

55
require (
66
cloud.google.com/go v0.65.0

google/default.go

+1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params
190190
if err != nil {
191191
return nil, err
192192
}
193+
ts = newErrWrappingTokenSource(ts)
193194
return &DefaultCredentials{
194195
ProjectID: f.ProjectID,
195196
TokenSource: ts,

google/error.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package google
6+
7+
import (
8+
"errors"
9+
10+
"golang.org/x/oauth2"
11+
)
12+
13+
// AuthenticationError indicates there was an error in the authentication flow.
14+
//
15+
// Use (*AuthenticationError).Temporary to check if the error can be retried.
16+
type AuthenticationError struct {
17+
err *oauth2.RetrieveError
18+
}
19+
20+
func newAuthenticationError(err error) error {
21+
re := &oauth2.RetrieveError{}
22+
if !errors.As(err, &re) {
23+
return err
24+
}
25+
return &AuthenticationError{
26+
err: re,
27+
}
28+
}
29+
30+
// Temporary indicates that the network error has one of the following status codes and may be retried: 500, 503, 408, or 429.
31+
func (e *AuthenticationError) Temporary() bool {
32+
if e.err.Response == nil {
33+
return false
34+
}
35+
sc := e.err.Response.StatusCode
36+
return sc == 500 || sc == 503 || sc == 408 || sc == 429
37+
}
38+
39+
func (e *AuthenticationError) Error() string {
40+
return e.err.Error()
41+
}
42+
43+
func (e *AuthenticationError) Unwrap() error {
44+
return e.err
45+
}
46+
47+
type errWrappingTokenSource struct {
48+
src oauth2.TokenSource
49+
}
50+
51+
func newErrWrappingTokenSource(ts oauth2.TokenSource) oauth2.TokenSource {
52+
return &errWrappingTokenSource{src: ts}
53+
}
54+
55+
// Token returns the current token if it's still valid, else will
56+
// refresh the current token (using r.Context for HTTP client
57+
// information) and return the new one.
58+
func (s *errWrappingTokenSource) Token() (*oauth2.Token, error) {
59+
t, err := s.src.Token()
60+
if err != nil {
61+
return nil, newAuthenticationError(err)
62+
}
63+
return t, nil
64+
}

google/error_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package google
6+
7+
import (
8+
"net/http"
9+
"testing"
10+
11+
"golang.org/x/oauth2"
12+
)
13+
14+
func TestAuthenticationError_Temporary(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
code int
18+
want bool
19+
}{
20+
{
21+
name: "temporary with 500",
22+
code: 500,
23+
want: true,
24+
},
25+
{
26+
name: "temporary with 503",
27+
code: 503,
28+
want: true,
29+
},
30+
{
31+
name: "temporary with 408",
32+
code: 408,
33+
want: true,
34+
},
35+
{
36+
name: "temporary with 429",
37+
code: 429,
38+
want: true,
39+
},
40+
{
41+
name: "temporary with 418",
42+
code: 418,
43+
want: false,
44+
},
45+
}
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
ae := &AuthenticationError{
49+
err: &oauth2.RetrieveError{
50+
Response: &http.Response{
51+
StatusCode: tt.code,
52+
},
53+
},
54+
}
55+
if got := ae.Temporary(); got != tt.want {
56+
t.Errorf("Temporary() = %v; want %v", got, tt.want)
57+
}
58+
})
59+
}
60+
}
61+
62+
func TestErrWrappingTokenSource_Token(t *testing.T) {
63+
tok := oauth2.Token{AccessToken: "MyAccessToken"}
64+
ts := errWrappingTokenSource{
65+
src: oauth2.StaticTokenSource(&tok),
66+
}
67+
got, err := ts.Token()
68+
if *got != tok {
69+
t.Errorf("Token() = %v; want %v", got, tok)
70+
}
71+
if err != nil {
72+
t.Error(err)
73+
}
74+
}
75+
76+
type errTokenSource struct {
77+
err error
78+
}
79+
80+
func (s *errTokenSource) Token() (*oauth2.Token, error) {
81+
return nil, s.err
82+
}
83+
84+
func TestErrWrappingTokenSource_TokenError(t *testing.T) {
85+
re := &oauth2.RetrieveError{
86+
Response: &http.Response{
87+
StatusCode: 500,
88+
},
89+
}
90+
ts := errWrappingTokenSource{
91+
src: &errTokenSource{
92+
err: re,
93+
},
94+
}
95+
_, err := ts.Token()
96+
if err == nil {
97+
t.Fatalf("errWrappingTokenSource.Token() err = nil, want *AuthenticationError")
98+
}
99+
ae, ok := err.(*AuthenticationError)
100+
if !ok {
101+
t.Fatalf("errWrappingTokenSource.Token() err = %T, want *AuthenticationError", err)
102+
}
103+
wrappedErr := ae.Unwrap()
104+
if wrappedErr == nil {
105+
t.Fatalf("AuthenticationError.Unwrap() err = nil, want *oauth2.RetrieveError")
106+
}
107+
_, ok = wrappedErr.(*oauth2.RetrieveError)
108+
if !ok {
109+
t.Errorf("AuthenticationError.Unwrap() err = %T, want *oauth2.RetrieveError", err)
110+
}
111+
}

google/jwt.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ func newJWTSource(jsonKey []byte, audience string, scopes []string) (oauth2.Toke
6666
if err != nil {
6767
return nil, err
6868
}
69-
return oauth2.ReuseTokenSource(tok, ts), nil
69+
rts := newErrWrappingTokenSource(oauth2.ReuseTokenSource(tok, ts))
70+
return rts, nil
7071
}
7172

7273
type jwtAccessTokenSource struct {

0 commit comments

Comments
 (0)