Skip to content

Commit 306f6b4

Browse files
committed
fix: the logic of explicit 0 value in expires_in field was incorrect although it never triggers
1 parent 6e9ec93 commit 306f6b4

File tree

2 files changed

+111
-25
lines changed

2 files changed

+111
-25
lines changed

google/internal/externalaccount/basecredentials.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"golang.org/x/oauth2/google/internal/stsexchange"
1717
)
1818

19+
var maxUnixTime = time.Unix(1<<63-1, 999999999)
20+
1921
// now aliases time.Now for testing
2022
var now = func() time.Time {
2123
return time.Now().UTC()
@@ -241,10 +243,16 @@ func (ts tokenSource) Token() (*oauth2.Token, error) {
241243
AccessToken: stsResp.AccessToken,
242244
TokenType: stsResp.TokenType,
243245
}
246+
247+
// The RFC8693 doesn't define the explicit 0 of "expires_in" field behavior.
248+
// In practice a lot of sts request is using 0 means "never expire" for an sts token.
249+
// So the logic here shall use a max unix time value.
244250
if stsResp.ExpiresIn < 0 {
245251
return nil, fmt.Errorf("oauth2/google: got invalid expiry from security token service")
246-
} else if stsResp.ExpiresIn >= 0 {
252+
} else if stsResp.ExpiresIn > 0 {
247253
accessToken.Expiry = now().Add(time.Duration(stsResp.ExpiresIn) * time.Second)
254+
} else {
255+
accessToken.Expiry = maxUnixTime
248256
}
249257

250258
if stsResp.RefreshToken != "" {

google/internal/externalaccount/basecredentials_test.go

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package externalaccount
66

77
import (
88
"context"
9+
"encoding/json"
910
"fmt"
1011
"io/ioutil"
1112
"net/http"
@@ -99,15 +100,18 @@ func run(t *testing.T, config *Config, tets *testExchangeTokenServer) (*oauth2.T
99100
return ts.Token()
100101
}
101102

102-
func validateToken(t *testing.T, tok *oauth2.Token) {
103-
if got, want := tok.AccessToken, correctAT; got != want {
103+
func validateToken(t *testing.T, tok *oauth2.Token, expectToken *oauth2.Token) {
104+
if expectToken == nil {
105+
return
106+
}
107+
if got, want := tok.AccessToken, expectToken.AccessToken; got != want {
104108
t.Errorf("Unexpected access token: got %v, but wanted %v", got, want)
105109
}
106-
if got, want := tok.TokenType, "Bearer"; got != want {
110+
if got, want := tok.TokenType, expectToken.TokenType; got != want {
107111
t.Errorf("Unexpected TokenType: got %v, but wanted %v", got, want)
108112
}
109113

110-
if got, want := tok.Expiry, testNow().Add(time.Duration(3600)*time.Second); got != want {
114+
if got, want := tok.Expiry, expectToken.Expiry; got != want {
111115
t.Errorf("Unexpected Expiry: got %v, but wanted %v", got, want)
112116
}
113117
}
@@ -117,30 +121,94 @@ func getExpectedMetricsHeader(source string, saImpersonation bool, configLifetim
117121
}
118122

119123
func TestToken(t *testing.T) {
120-
config := Config{
121-
Audience: "32555940559.apps.googleusercontent.com",
122-
SubjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
123-
ClientSecret: "notsosecret",
124-
ClientID: "rbrgnognrhongo3bi4gb9ghg9g",
125-
CredentialSource: testBaseCredSource,
126-
Scopes: []string{"https://www.googleapis.com/auth/devstorage.full_control"},
124+
type MockSTSResponse struct {
125+
AccessToken string `json:"access_token"`
126+
IssuedTokenType string `json:"issued_token_type"`
127+
TokenType string `json:"token_type"`
128+
ExpiresIn int32 `json:"expires_in,omitempty"`
129+
Scope string `json:"scopre,omitenpty"`
127130
}
128131

129-
server := testExchangeTokenServer{
130-
url: "/",
131-
authorization: "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ=",
132-
contentType: "application/x-www-form-urlencoded",
133-
metricsHeader: getExpectedMetricsHeader("file", false, false),
134-
body: baseCredsRequestBody,
135-
response: baseCredsResponseBody,
132+
testCases := []struct {
133+
name string
134+
responseBody MockSTSResponse
135+
expectToken *oauth2.Token
136+
expectErrorMsg string
137+
}{
138+
{
139+
name: "happy case",
140+
responseBody: MockSTSResponse{
141+
AccessToken: correctAT,
142+
IssuedTokenType: "urn:ietf:params:oauth:token-type:access_token",
143+
TokenType: "Bearer",
144+
ExpiresIn: 3600,
145+
Scope: "https://www.googleapis.com/auth/cloud-platform",
146+
},
147+
expectToken: &oauth2.Token{
148+
AccessToken: correctAT,
149+
TokenType: "Bearer",
150+
Expiry: testNow().Add(time.Duration(3600) * time.Second),
151+
},
152+
},
153+
{
154+
name: "happy case, non expire token",
155+
responseBody: MockSTSResponse{
156+
AccessToken: correctAT,
157+
IssuedTokenType: "urn:ietf:params:oauth:token-type:access_token",
158+
TokenType: "Bearer",
159+
ExpiresIn: 0,
160+
Scope: "https://www.googleapis.com/auth/cloud-platform",
161+
},
162+
expectToken: &oauth2.Token{
163+
AccessToken: correctAT,
164+
TokenType: "Bearer",
165+
Expiry: maxUnixTime,
166+
},
167+
},
168+
{
169+
name: "negative expiry time",
170+
responseBody: MockSTSResponse{
171+
AccessToken: correctAT,
172+
IssuedTokenType: "urn:ietf:params:oauth:token-type:access_token",
173+
TokenType: "Bearer",
174+
ExpiresIn: -1,
175+
Scope: "https://www.googleapis.com/auth/cloud-platform",
176+
},
177+
expectToken: nil,
178+
expectErrorMsg: "oauth2/google: got invalid expiry from security token service",
179+
},
136180
}
137181

138-
tok, err := run(t, &config, &server)
182+
for _, testCase := range testCases {
183+
config := Config{
184+
Audience: "32555940559.apps.googleusercontent.com",
185+
SubjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
186+
ClientSecret: "notsosecret",
187+
ClientID: "rbrgnognrhongo3bi4gb9ghg9g",
188+
CredentialSource: testBaseCredSource,
189+
Scopes: []string{"https://www.googleapis.com/auth/devstorage.full_control"},
190+
}
139191

140-
if err != nil {
141-
t.Fatalf("Unexpected error: %e", err)
192+
responseBody, _ := json.Marshal(testCase.responseBody)
193+
194+
server := testExchangeTokenServer{
195+
url: "/",
196+
authorization: "Basic cmJyZ25vZ25yaG9uZ28zYmk0Z2I5Z2hnOWc6bm90c29zZWNyZXQ=",
197+
contentType: "application/x-www-form-urlencoded",
198+
metricsHeader: getExpectedMetricsHeader("file", false, false),
199+
body: baseCredsRequestBody,
200+
response: string(responseBody),
201+
}
202+
203+
tok, err := run(t, &config, &server)
204+
205+
if err != nil {
206+
if err.Error() != testCase.expectErrorMsg {
207+
t.Errorf("Error actual = %v, and Expect = %v", err, testCase.expectErrorMsg)
208+
}
209+
}
210+
validateToken(t, tok, testCase.expectToken)
142211
}
143-
validateToken(t, tok)
144212
}
145213

146214
func TestWorkforcePoolTokenWithClientID(t *testing.T) {
@@ -168,7 +236,12 @@ func TestWorkforcePoolTokenWithClientID(t *testing.T) {
168236
if err != nil {
169237
t.Fatalf("Unexpected error: %e", err)
170238
}
171-
validateToken(t, tok)
239+
expectToken := oauth2.Token{
240+
AccessToken: correctAT,
241+
TokenType: "Bearer",
242+
Expiry: testNow().Add(time.Duration(3600) * time.Second),
243+
}
244+
validateToken(t, tok, &expectToken)
172245
}
173246

174247
func TestWorkforcePoolTokenWithoutClientID(t *testing.T) {
@@ -195,7 +268,12 @@ func TestWorkforcePoolTokenWithoutClientID(t *testing.T) {
195268
if err != nil {
196269
t.Fatalf("Unexpected error: %e", err)
197270
}
198-
validateToken(t, tok)
271+
expectToken := oauth2.Token{
272+
AccessToken: correctAT,
273+
TokenType: "Bearer",
274+
Expiry: testNow().Add(time.Duration(3600) * time.Second),
275+
}
276+
validateToken(t, tok, &expectToken)
199277
}
200278

201279
func TestNonworkforceWithWorkforcePoolUserProject(t *testing.T) {

0 commit comments

Comments
 (0)