2014-05-17 11:26:57 -04:00
|
|
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
2014-05-13 14:06:46 -04:00
|
|
|
|
2014-05-05 17:54:23 -04:00
|
|
|
// Package oauth2 provides support for making
|
|
|
|
// OAuth2 authorized and authenticated HTTP requests.
|
|
|
|
// It can additionally grant authorization with Bearer JWT.
|
2014-11-26 14:44:45 -05:00
|
|
|
package oauth2 // import "golang.org/x/oauth2"
|
2014-05-05 17:54:23 -04:00
|
|
|
|
|
|
|
import (
|
2014-12-09 18:17:33 -05:00
|
|
|
"bytes"
|
2014-05-05 17:54:23 -04:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2014-09-05 01:43:23 -04:00
|
|
|
"fmt"
|
2014-12-09 18:17:33 -05:00
|
|
|
"io"
|
2014-05-05 17:54:23 -04:00
|
|
|
"io/ioutil"
|
|
|
|
"mime"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2014-08-12 22:50:34 -04:00
|
|
|
"strconv"
|
2014-05-05 17:54:23 -04:00
|
|
|
"strings"
|
2014-12-09 18:17:33 -05:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/net/context"
|
2014-05-05 17:54:23 -04:00
|
|
|
)
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Context can be an golang.org/x/net.Context, or an App Engine Context.
|
|
|
|
// In the future these will be unified.
|
2014-12-11 02:30:13 -05:00
|
|
|
// If you don't care and aren't running on App Engine, you may use NoContext.
|
2014-12-09 18:17:33 -05:00
|
|
|
type Context interface{}
|
|
|
|
|
2014-12-11 02:30:13 -05:00
|
|
|
// NoContext is the default context. If you're not running this code
|
|
|
|
// on App Engine or not using golang.org/x/net.Context to provide a custom
|
|
|
|
// HTTP client, you should use NoContext.
|
|
|
|
var NoContext Context = nil
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Config describes a typical 3-legged OAuth2 flow, with both the
|
|
|
|
// client application information and the server's URLs.
|
|
|
|
type Config struct {
|
|
|
|
// ClientID is the application's ID.
|
|
|
|
ClientID string
|
|
|
|
|
|
|
|
// ClientSecret is the application's secret.
|
|
|
|
ClientSecret string
|
|
|
|
|
|
|
|
// Endpoint contains the resource server's token endpoint
|
|
|
|
// URLs. These are supplied by the server and are often
|
|
|
|
// available via site-specific packages (for example,
|
|
|
|
// google.Endpoint or github.Endpoint)
|
|
|
|
Endpoint Endpoint
|
|
|
|
|
|
|
|
// RedirectURL is the URL to redirect users going through
|
|
|
|
// the OAuth flow, after the resource owner's URLs.
|
|
|
|
RedirectURL string
|
|
|
|
|
|
|
|
// Scope specifies optional requested permissions.
|
|
|
|
Scopes []string
|
2014-11-12 23:41:14 -05:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// A TokenSource is anything that can return a token.
|
|
|
|
type TokenSource interface {
|
|
|
|
// Token returns a token or an error.
|
|
|
|
Token() (*Token, error)
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
2014-05-05 17:54:23 -04:00
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Endpoint contains the OAuth 2.0 provider's authorization and token
|
|
|
|
// endpoint URLs.
|
|
|
|
type Endpoint struct {
|
|
|
|
AuthURL string
|
|
|
|
TokenURL string
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Token represents the crendentials used to authorize
|
|
|
|
// the requests to access protected resources on the OAuth 2.0
|
|
|
|
// provider's backend.
|
|
|
|
//
|
|
|
|
// Most users of this package should not access fields of Token
|
|
|
|
// directly. They're exported mostly for use by related packages
|
|
|
|
// implementing derivate OAuth2 flows.
|
|
|
|
type Token struct {
|
|
|
|
// AccessToken is the token that authorizes and authenticates
|
|
|
|
// the requests.
|
|
|
|
AccessToken string `json:"access_token"`
|
|
|
|
|
|
|
|
// TokenType is the type of token.
|
|
|
|
// The Type method returns either this or "Bearer", the default.
|
|
|
|
TokenType string `json:"token_type,omitempty"`
|
|
|
|
|
|
|
|
// RefreshToken is a token that's used by the application
|
|
|
|
// (as opposed to the user) to refresh the access token
|
|
|
|
// if it expires.
|
|
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
|
|
|
|
|
|
// Expiry is the optional expiration time of the access token.
|
|
|
|
//
|
|
|
|
// If zero, TokenSource implementations will reuse the same
|
|
|
|
// token forever and RefreshToken or equivalent
|
|
|
|
// mechanisms for that TokenSource will not be used.
|
|
|
|
Expiry time.Time `json:"expiry,omitempty"`
|
|
|
|
|
|
|
|
// raw optionally contains extra metadata from the server
|
|
|
|
// when updating a token.
|
|
|
|
raw interface{}
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Type returns t.TokenType if non-empty, else "Bearer".
|
|
|
|
func (t *Token) Type() string {
|
|
|
|
if t.TokenType != "" {
|
|
|
|
return t.TokenType
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
return "Bearer"
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// SetAuthHeader sets the Authorization header to r using the access
|
|
|
|
// token in t.
|
|
|
|
//
|
|
|
|
// This method is unnecessary when using Transport or an HTTP Client
|
|
|
|
// returned by this package.
|
|
|
|
func (t *Token) SetAuthHeader(r *http.Request) {
|
|
|
|
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Extra returns an extra field returned from the server during token
|
|
|
|
// retrieval.
|
|
|
|
func (t *Token) Extra(key string) string {
|
|
|
|
if vals, ok := t.raw.(url.Values); ok {
|
|
|
|
return vals.Get(key)
|
|
|
|
}
|
|
|
|
if raw, ok := t.raw.(map[string]interface{}); ok {
|
|
|
|
if val, ok := raw[key].(string); ok {
|
|
|
|
return val
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expired returns true if there is no access token or the
|
|
|
|
// access token is expired.
|
|
|
|
func (t *Token) Expired() bool {
|
|
|
|
if t.AccessToken == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if t.Expiry.IsZero() {
|
|
|
|
return false
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
return t.Expiry.Before(time.Now())
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// AccessTypeOnline and AccessTypeOffline are options passed
|
|
|
|
// to the Options.AuthCodeURL method. They modify the
|
|
|
|
// "access_type" field that gets sent in the URL returned by
|
|
|
|
// AuthCodeURL.
|
|
|
|
//
|
|
|
|
// Online (the default if neither is specified) is the default.
|
|
|
|
// If your application needs to refresh access tokens when the
|
|
|
|
// user is not present at the browser, then use offline. This
|
|
|
|
// will result in your application obtaining a refresh token
|
|
|
|
// the first time your application exchanges an authorization
|
|
|
|
// code for a user.
|
|
|
|
AccessTypeOnline AuthCodeOption = setParam{"access_type", "online"}
|
|
|
|
AccessTypeOffline AuthCodeOption = setParam{"access_type", "offline"}
|
|
|
|
|
|
|
|
// ApprovalForce forces the users to view the consent dialog
|
|
|
|
// and confirm the permissions request at the URL returned
|
|
|
|
// from AuthCodeURL, even if they've already done so.
|
|
|
|
ApprovalForce AuthCodeOption = setParam{"approval_prompt", "force"}
|
|
|
|
)
|
|
|
|
|
|
|
|
type setParam struct{ k, v string }
|
|
|
|
|
|
|
|
func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
|
|
|
|
|
|
|
|
// An AuthCodeOption is passed to Config.AuthCodeURL.
|
|
|
|
type AuthCodeOption interface {
|
|
|
|
setValue(url.Values)
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
|
|
|
|
// that asks for permissions for the required scopes explicitly.
|
2014-09-05 01:43:23 -04:00
|
|
|
//
|
|
|
|
// State is a token to protect the user from CSRF attacks. You must
|
|
|
|
// always provide a non-zero string and validate that it matches the
|
|
|
|
// the state query parameter on your redirect callback.
|
|
|
|
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
|
|
|
|
//
|
2014-12-09 18:17:33 -05:00
|
|
|
// Opts may include AccessTypeOnline or AccessTypeOffline, as well
|
|
|
|
// as ApprovalForce.
|
|
|
|
func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
buf.WriteString(c.Endpoint.AuthURL)
|
2014-09-04 16:28:18 -04:00
|
|
|
v := url.Values{
|
2014-12-09 18:17:33 -05:00
|
|
|
"response_type": {"code"},
|
|
|
|
"client_id": {c.ClientID},
|
|
|
|
"redirect_uri": condVal(c.RedirectURL),
|
|
|
|
"scope": condVal(strings.Join(c.Scopes, " ")),
|
|
|
|
"state": condVal(state),
|
|
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt.setValue(v)
|
2014-07-29 15:39:33 -04:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
if strings.Contains(c.Endpoint.AuthURL, "?") {
|
|
|
|
buf.WriteByte('&')
|
2014-05-05 17:54:23 -04:00
|
|
|
} else {
|
2014-12-09 18:17:33 -05:00
|
|
|
buf.WriteByte('?')
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
buf.WriteString(v.Encode())
|
|
|
|
return buf.String()
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Exchange converts an authorization code into a token.
|
|
|
|
//
|
|
|
|
// It is used after a resource provider redirects the user back
|
|
|
|
// to the Redirect URI (the URL obtained from AuthCodeURL).
|
|
|
|
//
|
|
|
|
// The HTTP client to use is derived from the context. If nil,
|
|
|
|
// http.DefaultClient is used. See the Context type's documentation.
|
|
|
|
//
|
|
|
|
// The code will be in the *http.Request.FormValue("code"). Before
|
|
|
|
// calling Exchange, be sure to validate FormValue("state").
|
|
|
|
func (c *Config) Exchange(ctx Context, code string) (*Token, error) {
|
|
|
|
return retrieveToken(ctx, c, url.Values{
|
2014-11-06 19:36:41 -05:00
|
|
|
"grant_type": {"authorization_code"},
|
|
|
|
"code": {code},
|
2014-12-09 18:17:33 -05:00
|
|
|
"redirect_uri": condVal(c.RedirectURL),
|
|
|
|
"scope": condVal(strings.Join(c.Scopes, " ")),
|
2014-11-06 19:36:41 -05:00
|
|
|
})
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// contextClientFunc is a func which tries to return an *http.Client
|
|
|
|
// given a Context value. If it returns an error, the search stops
|
|
|
|
// with that error. If it returns (nil, nil), the search continues
|
|
|
|
// down the list of registered funcs.
|
|
|
|
type contextClientFunc func(Context) (*http.Client, error)
|
|
|
|
|
|
|
|
var contextClientFuncs []contextClientFunc
|
|
|
|
|
|
|
|
func registerContextClietnFunc(fn contextClientFunc) {
|
|
|
|
contextClientFuncs = append(contextClientFuncs, fn)
|
|
|
|
}
|
|
|
|
|
|
|
|
func contextClient(ctx Context) (*http.Client, error) {
|
|
|
|
for _, fn := range contextClientFuncs {
|
|
|
|
c, err := fn(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if c != nil {
|
|
|
|
return c, nil
|
|
|
|
}
|
2014-11-12 23:41:14 -05:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
if xc, ok := ctx.(context.Context); ok {
|
|
|
|
if hc, ok := xc.Value(HTTPClient).(*http.Client); ok {
|
|
|
|
return hc, nil
|
|
|
|
}
|
2014-11-12 23:41:14 -05:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
return http.DefaultClient, nil
|
2014-11-12 23:41:14 -05:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
func contextTransport(ctx Context) http.RoundTripper {
|
|
|
|
hc, err := contextClient(ctx)
|
2014-05-05 17:54:23 -04:00
|
|
|
if err != nil {
|
2014-12-09 18:17:33 -05:00
|
|
|
// This is a rare error case (somebody using nil on App Engine),
|
|
|
|
// so I'd rather not everybody do an error check on this Client
|
|
|
|
// method. They can get the error that they're doing it wrong
|
|
|
|
// later, at client.Get/PostForm time.
|
|
|
|
return errorTransport{err}
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
return hc.Transport
|
2014-11-12 23:41:14 -05:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Client returns an HTTP client using the provided token.
|
|
|
|
// The token will auto-refresh as necessary. The underlying
|
|
|
|
// HTTP transport will be obtained using the provided context.
|
|
|
|
// The returned client and its Transport should not be modified.
|
|
|
|
func (c *Config) Client(ctx Context, t *Token) *http.Client {
|
|
|
|
return &http.Client{
|
|
|
|
Transport: &Transport{
|
|
|
|
Base: contextTransport(ctx),
|
2014-12-11 02:30:13 -05:00
|
|
|
Source: c.TokenSource(ctx, t),
|
2014-12-09 18:17:33 -05:00
|
|
|
},
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-06-26 20:27:53 -04:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// TokenSource returns a TokenSource that returns t until t expires,
|
|
|
|
// automatically refreshing it as necessary using the provided context.
|
|
|
|
// See the the Context documentation.
|
|
|
|
//
|
|
|
|
// Most users will use Config.Client instead.
|
|
|
|
func (c *Config) TokenSource(ctx Context, t *Token) TokenSource {
|
|
|
|
nwn := &newWhenNeededSource{t: t}
|
|
|
|
nwn.new = tokenRefresher{
|
|
|
|
ctx: ctx,
|
|
|
|
conf: c,
|
|
|
|
oldToken: &nwn.t,
|
2014-09-04 16:28:18 -04:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
return nwn
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token"
|
|
|
|
// HTTP requests to renew a token using a RefreshToken.
|
|
|
|
type tokenRefresher struct {
|
|
|
|
ctx Context // used to get HTTP requests
|
|
|
|
conf *Config
|
|
|
|
oldToken **Token // pointer to old *Token w/ RefreshToken
|
|
|
|
}
|
2014-11-06 19:36:41 -05:00
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
func (tf tokenRefresher) Token() (*Token, error) {
|
|
|
|
t := *tf.oldToken
|
|
|
|
if t == nil {
|
|
|
|
return nil, errors.New("oauth2: attempted use of nil Token")
|
|
|
|
}
|
|
|
|
if t.RefreshToken == "" {
|
|
|
|
return nil, errors.New("oauth2: token expired and refresh token is not set")
|
|
|
|
}
|
|
|
|
return retrieveToken(tf.ctx, tf.conf, url.Values{
|
|
|
|
"grant_type": {"refresh_token"},
|
|
|
|
"refresh_token": {t.RefreshToken},
|
|
|
|
})
|
|
|
|
}
|
2014-11-06 19:36:41 -05:00
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// newWhenNeededSource is a TokenSource that holds a single token in memory
|
|
|
|
// and validates its expiry before each call to retrieve it with
|
|
|
|
// Token. If it's expired, it will be auto-refreshed using the
|
|
|
|
// new TokenSource.
|
|
|
|
//
|
|
|
|
// The first call to TokenRefresher must be SetToken.
|
|
|
|
type newWhenNeededSource struct {
|
|
|
|
new TokenSource // called when t is expired.
|
2014-11-12 23:41:14 -05:00
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
mu sync.Mutex // guards t
|
|
|
|
t *Token
|
|
|
|
}
|
2014-11-06 19:36:41 -05:00
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
// Token returns the current token if it's still valid, else will
|
|
|
|
// refresh the current token (using r.Context for HTTP client
|
|
|
|
// information) and return the new one.
|
|
|
|
func (s *newWhenNeededSource) Token() (*Token, error) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
if s.t != nil && !s.t.Expired() {
|
|
|
|
return s.t, nil
|
|
|
|
}
|
|
|
|
t, err := s.new.Token()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
s.t = t
|
|
|
|
return t, nil
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
|
|
|
|
2014-12-09 18:17:33 -05:00
|
|
|
func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) {
|
|
|
|
hc, err := contextClient(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
v.Set("client_id", c.ClientID)
|
|
|
|
bustedAuth := !providerAuthHeaderWorks(c.Endpoint.TokenURL)
|
|
|
|
if bustedAuth && c.ClientSecret != "" {
|
|
|
|
v.Set("client_secret", c.ClientSecret)
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
req, err := http.NewRequest("POST", c.Endpoint.TokenURL, strings.NewReader(v.Encode()))
|
2014-09-04 16:28:18 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
2014-12-09 18:17:33 -05:00
|
|
|
if !bustedAuth && c.ClientSecret != "" {
|
|
|
|
req.SetBasicAuth(c.ClientID, c.ClientSecret)
|
2014-11-06 19:36:41 -05:00
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
r, err := hc.Do(req)
|
2014-05-05 17:54:23 -04:00
|
|
|
if err != nil {
|
2014-08-13 19:47:57 -04:00
|
|
|
return nil, err
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
defer r.Body.Close()
|
2014-12-09 18:17:33 -05:00
|
|
|
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
2014-09-05 01:43:23 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
|
|
|
}
|
2014-11-06 19:36:41 -05:00
|
|
|
if code := r.StatusCode; code < 200 || code > 299 {
|
2014-09-05 01:43:23 -04:00
|
|
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
2014-10-27 20:35:35 -04:00
|
|
|
token := &Token{}
|
2014-12-09 18:17:33 -05:00
|
|
|
expires := 0
|
2014-05-05 17:54:23 -04:00
|
|
|
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
|
|
switch content {
|
|
|
|
case "application/x-www-form-urlencoded", "text/plain":
|
|
|
|
vals, err := url.ParseQuery(string(body))
|
|
|
|
if err != nil {
|
2014-08-13 19:47:57 -04:00
|
|
|
return nil, err
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-10-27 20:35:35 -04:00
|
|
|
token.AccessToken = vals.Get("access_token")
|
|
|
|
token.TokenType = vals.Get("token_type")
|
|
|
|
token.RefreshToken = vals.Get("refresh_token")
|
|
|
|
token.raw = vals
|
|
|
|
e := vals.Get("expires_in")
|
|
|
|
if e == "" {
|
|
|
|
// TODO(jbd): Facebook's OAuth2 implementation is broken and
|
|
|
|
// returns expires_in field in expires. Remove the fallback to expires,
|
|
|
|
// when Facebook fixes their implementation.
|
|
|
|
e = vals.Get("expires")
|
|
|
|
}
|
|
|
|
expires, _ = strconv.Atoi(e)
|
2014-05-05 17:54:23 -04:00
|
|
|
default:
|
2014-12-09 18:17:33 -05:00
|
|
|
b := make(map[string]interface{}) // TODO: don't use a map[string]interface{}; make a type
|
2014-10-27 20:35:35 -04:00
|
|
|
if err = json.Unmarshal(body, &b); err != nil {
|
2014-08-13 19:47:57 -04:00
|
|
|
return nil, err
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-10-27 20:35:35 -04:00
|
|
|
token.AccessToken, _ = b["access_token"].(string)
|
|
|
|
token.TokenType, _ = b["token_type"].(string)
|
|
|
|
token.RefreshToken, _ = b["refresh_token"].(string)
|
|
|
|
token.raw = b
|
2014-11-10 14:59:36 -05:00
|
|
|
e, ok := b["expires_in"].(float64)
|
2014-10-27 20:35:35 -04:00
|
|
|
if !ok {
|
|
|
|
// TODO(jbd): Facebook's OAuth2 implementation is broken and
|
|
|
|
// returns expires_in field in expires. Remove the fallback to expires,
|
|
|
|
// when Facebook fixes their implementation.
|
2014-11-10 14:59:36 -05:00
|
|
|
e, _ = b["expires"].(float64)
|
2014-10-27 20:35:35 -04:00
|
|
|
}
|
2014-11-10 14:59:36 -05:00
|
|
|
expires = int(e)
|
2014-08-13 19:47:57 -04:00
|
|
|
}
|
2014-05-05 17:54:23 -04:00
|
|
|
// Don't overwrite `RefreshToken` with an empty value
|
2014-08-13 19:47:57 -04:00
|
|
|
// if this was a token refreshing request.
|
2014-10-27 20:35:35 -04:00
|
|
|
if token.RefreshToken == "" {
|
2014-08-13 19:47:57 -04:00
|
|
|
token.RefreshToken = v.Get("refresh_token")
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-10-27 20:35:35 -04:00
|
|
|
if expires == 0 {
|
2014-08-13 19:47:57 -04:00
|
|
|
token.Expiry = time.Time{}
|
2014-10-27 20:35:35 -04:00
|
|
|
} else {
|
|
|
|
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-08-13 19:47:57 -04:00
|
|
|
return token, nil
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
2014-09-02 17:06:51 -04:00
|
|
|
|
2014-09-04 16:28:18 -04:00
|
|
|
func condVal(v string) []string {
|
|
|
|
if v == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []string{v}
|
|
|
|
}
|
|
|
|
|
|
|
|
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
|
|
|
|
// implements the OAuth2 spec correctly
|
|
|
|
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
|
|
|
// In summary:
|
|
|
|
// - Reddit only accepts client secret in the Authorization header
|
|
|
|
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
|
|
|
// - Google only accepts URL param (not spec compliant?), not Auth header
|
|
|
|
func providerAuthHeaderWorks(tokenURL string) bool {
|
|
|
|
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
|
|
|
|
strings.HasPrefix(tokenURL, "https://github.com/") ||
|
|
|
|
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
|
2014-09-07 13:05:03 -04:00
|
|
|
strings.HasPrefix(tokenURL, "https://www.douban.com/") ||
|
2014-09-10 22:49:49 -04:00
|
|
|
strings.HasPrefix(tokenURL, "https://api.dropbox.com/") ||
|
2014-10-04 02:16:01 -04:00
|
|
|
strings.HasPrefix(tokenURL, "https://api.soundcloud.com/") ||
|
2014-09-10 22:49:49 -04:00
|
|
|
strings.HasPrefix(tokenURL, "https://www.linkedin.com/") {
|
2014-09-04 16:28:18 -04:00
|
|
|
// Some sites fail to implement the OAuth2 spec fully.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assume the provider implements the spec properly
|
|
|
|
// otherwise. We can add more exceptions as they're
|
|
|
|
// discovered. We will _not_ be adding configurable hooks
|
|
|
|
// to this package to let users select server bugs.
|
|
|
|
return true
|
|
|
|
}
|
2014-12-09 18:17:33 -05:00
|
|
|
|
|
|
|
// HTTPClient is the context key to use with golang.org/x/net/context's
|
|
|
|
// WithValue function to associate an *http.Client value with a context.
|
|
|
|
var HTTPClient contextKey
|
|
|
|
|
|
|
|
// contextKey is just an empty struct. It exists so HTTPClient can be
|
|
|
|
// an immutable public variable with a unique type. It's immutable
|
|
|
|
// because nobody else can create a contextKey, being unexported.
|
|
|
|
type contextKey struct{}
|