Skip to content

Commit cc2494a

Browse files
jfcote87adg
authored andcommitted
oauth2: fixes tokenRefresher.Token() ignores new refresh_token
Fixes bug documented in Issue #84 (#84 (comment)). During a refresh request, a new refresh token MAY be returned by the authorization server. When this occurs, tokenRefesher.Token() fails to capture the new refresh token leaving it with an invalid refresh token for future calls. Change-Id: I33b18fdbb750549174865f75eddf85b9725cf281 Reviewed-on: https://go-review.googlesource.com/4151 Reviewed-by: Andrew Gerrand <[email protected]>
1 parent 6f28996 commit cc2494a

File tree

2 files changed

+51
-17
lines changed

2 files changed

+51
-17
lines changed

Diff for: oauth2.go

+28-17
Original file line numberDiff line numberDiff line change
@@ -229,35 +229,46 @@ func (c *Config) Client(ctx Context, t *Token) *http.Client {
229229
//
230230
// Most users will use Config.Client instead.
231231
func (c *Config) TokenSource(ctx Context, t *Token) TokenSource {
232-
nwn := &reuseTokenSource{t: t}
233-
nwn.new = tokenRefresher{
234-
ctx: ctx,
235-
conf: c,
236-
oldToken: &nwn.t,
232+
tkr := &tokenRefresher{
233+
ctx: ctx,
234+
conf: c,
235+
}
236+
if t != nil {
237+
tkr.refreshToken = t.RefreshToken
238+
}
239+
return &reuseTokenSource{
240+
t: t,
241+
new: tkr,
237242
}
238-
return nwn
239243
}
240244

241245
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token"
242246
// HTTP requests to renew a token using a RefreshToken.
243247
type tokenRefresher struct {
244-
ctx Context // used to get HTTP requests
245-
conf *Config
246-
oldToken **Token // pointer to old *Token w/ RefreshToken
248+
ctx Context // used to get HTTP requests
249+
conf *Config
250+
refreshToken string
247251
}
248252

249-
func (tf tokenRefresher) Token() (*Token, error) {
250-
t := *tf.oldToken
251-
if t == nil {
252-
return nil, errors.New("oauth2: attempted use of nil Token")
253-
}
254-
if t.RefreshToken == "" {
253+
func (tf *tokenRefresher) Token() (*Token, error) {
254+
if tf.refreshToken == "" {
255255
return nil, errors.New("oauth2: token expired and refresh token is not set")
256256
}
257-
return retrieveToken(tf.ctx, tf.conf, url.Values{
257+
258+
tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{
258259
"grant_type": {"refresh_token"},
259-
"refresh_token": {t.RefreshToken},
260+
"refresh_token": {tf.refreshToken},
260261
})
262+
263+
if err != nil {
264+
return nil, err
265+
}
266+
if tk.RefreshToken != tf.refreshToken {
267+
// possible race condition avoided because tokenRefresher
268+
// should be protected by reuseTokenSource.mu
269+
tf.refreshToken = tk.RefreshToken
270+
}
271+
return tk, err
261272
}
262273

263274
// reuseTokenSource is a TokenSource that holds a single token in memory

Diff for: oauth2_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,26 @@ func TestFetchWithNoRefreshToken(t *testing.T) {
305305
t.Errorf("Fetch should return an error if no refresh token is set")
306306
}
307307
}
308+
309+
func TestRefreshToken_RefreshTokenReplacement(t *testing.T) {
310+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
311+
w.Header().Set("Content-Type", "application/json")
312+
w.Write([]byte(`{"access_token":"ACCESS TOKEN", "scope": "user", "token_type": "bearer", "refresh_token": "NEW REFRESH TOKEN"}`))
313+
return
314+
}))
315+
defer ts.Close()
316+
conf := newConf(ts.URL)
317+
tkr := tokenRefresher{
318+
conf: conf,
319+
ctx: NoContext,
320+
refreshToken: "OLD REFRESH TOKEN",
321+
}
322+
tk, err := tkr.Token()
323+
if err != nil {
324+
t.Errorf("Unexpected refreshToken error returned: %v", err)
325+
return
326+
}
327+
if tk.RefreshToken != tkr.refreshToken {
328+
t.Errorf("tokenRefresher.refresh_token = %s; want %s", tkr.refreshToken, tk.RefreshToken)
329+
}
330+
}

0 commit comments

Comments
 (0)