Skip to content

Commit

Permalink
oauth2: add error type for unsuccessful token endpoint status
Browse files Browse the repository at this point in the history
Allows the HTTP response and body to be extracted without parsing
the error string, but keeps backwards compatibility for users who
are currently doing so.

Fixes #173

Change-Id: Id7709da827a155299b047f0bcb74aa8f91b01e96
Reviewed-on: https://go-review.googlesource.com/84156
Reviewed-by: Brad Fitzpatrick <[email protected]>
  • Loading branch information
Tim Cooper authored and bradfitz committed Dec 19, 2017
1 parent 4623166 commit 0448841
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 1 deletion.
3 changes: 3 additions & 0 deletions clientcredentials/clientcredentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ func (c *tokenSource) Token() (*oauth2.Token, error) {
}
tk, err := internal.RetrieveToken(c.ctx, c.conf.ClientID, c.conf.ClientSecret, c.conf.TokenURL, v)
if err != nil {
if rErr, ok := err.(*internal.RetrieveError); ok {
return nil, (*oauth2.RetrieveError)(rErr)
}
return nil, err
}
t := &oauth2.Token{
Expand Down
14 changes: 13 additions & 1 deletion internal/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,10 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
}
if code := r.StatusCode; code < 200 || code > 299 {
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
return nil, &RetrieveError{
Response: r,
Body: body,
}
}

var token *Token
Expand Down Expand Up @@ -249,3 +252,12 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
}
return token, nil
}

type RetrieveError struct {
Response *http.Response
Body []byte
}

func (r *RetrieveError) Error() string {
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
}
26 changes: 26 additions & 0 deletions oauth2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,32 @@ func TestFetchWithNoRefreshToken(t *testing.T) {
}
}

func TestTokenRetrieveError(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/token" {
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
}
w.Header().Set("Content-type", "application/json")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error": "invalid_grant"}`))
}))
defer ts.Close()
conf := newConf(ts.URL)
_, err := conf.Exchange(context.Background(), "exchange-code")
if err == nil {
t.Fatalf("got no error, expected one")
}
_, ok := err.(*RetrieveError)
if !ok {
t.Fatalf("got %T error, expected *RetrieveError", err)
}
// Test error string for backwards compatibility
expected := fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", "400 Bad Request", `{"error": "invalid_grant"}`)
if errStr := err.Error(); errStr != expected {
t.Fatalf("got %#v, expected %#v", errStr, expected)
}
}

func TestRefreshToken_RefreshTokenReplacement(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
Expand Down
17 changes: 17 additions & 0 deletions token.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package oauth2

import (
"fmt"
"net/http"
"net/url"
"strconv"
Expand Down Expand Up @@ -152,7 +153,23 @@ func tokenFromInternal(t *internal.Token) *Token {
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
if err != nil {
if rErr, ok := err.(*internal.RetrieveError); ok {
return nil, (*RetrieveError)(rErr)
}
return nil, err
}
return tokenFromInternal(tk), nil
}

// RetrieveError is the error returned when the token endpoint returns a
// non-2XX HTTP status code.
type RetrieveError struct {
Response *http.Response
// Body is the body that was consumed by reading Response.Body.
// It may be truncated.
Body []byte
}

func (r *RetrieveError) Error() string {
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
}

0 comments on commit 0448841

Please sign in to comment.