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 <adg@golang.org>
diff --git a/oauth2.go b/oauth2.go
index 4004642..4350a67 100644
--- a/oauth2.go
+++ b/oauth2.go
@@ -374,11 +374,11 @@
// 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 @@
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 @@
}
}
+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")