From 6d4eed4495a291c9f2447c7dac779c9b08a2e2ab Mon Sep 17 00:00:00 2001 From: Nikolay Turpitko Date: Fri, 6 Mar 2015 14:54:39 +0600 Subject: [PATCH] oauth2: fix expires_in for PayPal PayPal returns "expires_in" token field as string, not integer. So, current implementation cannot unmarshal json of tokenJSON due type mismatch. This patch fixes the issue declaring field as interface{} in tokenJSON and performing type switch in "func (e *tokenJSON) expiry()". Related to issue #41. Change-Id: I69301e08c8a56fca049ca47906e32528cd22aef9 Reviewed-on: https://go-review.googlesource.com/6924 Reviewed-by: Andrew Gerrand --- oauth2.go | 26 +++++++++++++++++++----- oauth2_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/oauth2.go b/oauth2.go index 4004642..4350a67 100644 --- a/oauth2.go +++ b/oauth2.go @@ -374,11 +374,11 @@ func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) // tokenJSON is the struct representing the HTTP response from OAuth2 // providers returning a token in JSON form. type tokenJSON struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - RefreshToken string `json:"refresh_token"` - ExpiresIn int32 `json:"expires_in"` - Expires int32 `json:"expires"` // broken Facebook spelling of expires_in + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number + Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in } func (e *tokenJSON) expiry() (t time.Time) { @@ -391,6 +391,22 @@ func (e *tokenJSON) expiry() (t time.Time) { return } +type expirationTime int32 + +func (e *expirationTime) UnmarshalJSON(b []byte) error { + var n json.Number + err := json.Unmarshal(b, &n) + if err != nil { + return err + } + i, err := n.Int64() + if err != nil { + return err + } + *e = expirationTime(i) + return nil +} + func condVal(v string) []string { if v == "" { return nil diff --git a/oauth2_test.go b/oauth2_test.go index 2ec482b..908a190 100644 --- a/oauth2_test.go +++ b/oauth2_test.go @@ -5,12 +5,16 @@ package oauth2 import ( + "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "net/http/httptest" + "reflect" + "strconv" "testing" + "time" "golang.org/x/net/context" ) @@ -159,6 +163,56 @@ func TestExchangeRequest_JSONResponse(t *testing.T) { } } +const day = 24 * time.Hour + +func TestExchangeRequest_JSONResponse_Expiry(t *testing.T) { + seconds := int32(day.Seconds()) + jsonNumberType := reflect.TypeOf(json.Number("0")) + for _, c := range []struct { + expires string + expect error + }{ + {fmt.Sprintf(`"expires_in": %d`, seconds), nil}, + {fmt.Sprintf(`"expires_in": "%d"`, seconds), nil}, // PayPal case + {fmt.Sprintf(`"expires": %d`, seconds), nil}, // Facebook case + {`"expires": false`, &json.UnmarshalTypeError{"bool", jsonNumberType}}, // wrong type + {`"expires": {}`, &json.UnmarshalTypeError{"object", jsonNumberType}}, // wrong type + {`"expires": "zzz"`, &strconv.NumError{"ParseInt", "zzz", strconv.ErrSyntax}}, // wrong value + } { + testExchangeRequest_JSONResponse_expiry(t, c.expires, c.expect) + } +} + +func testExchangeRequest_JSONResponse_expiry(t *testing.T, exp string, expect error) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(fmt.Sprintf(`{"access_token": "90d", "scope": "user", "token_type": "bearer", %s}`, exp))) + })) + defer ts.Close() + conf := newConf(ts.URL) + t1 := time.Now().Add(day) + tok, err := conf.Exchange(NoContext, "exchange-code") + t2 := time.Now().Add(day) + if err == nil && expect != nil { + t.Errorf("Incorrect state, conf.Exchange() should return an error: %v", expect) + } else if err != nil { + if reflect.DeepEqual(err, expect) { + t.Logf("Expected error: %v", err) + return + } else { + t.Error(err) + } + + } + if !tok.Valid() { + t.Fatalf("Token invalid. Got: %#v", tok) + } + expiry := tok.Expiry + if expiry.Before(t1) || expiry.After(t2) { + t.Errorf("Unexpected value for Expiry: %v (shold be between %v and %v)", expiry, t1, t2) + } +} + func TestExchangeRequest_BadResponse(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")