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
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultTokenType = "Bearer"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Token represents the crendentials used to authorize
|
|
|
|
// the requests to access protected resources on the OAuth 2.0
|
|
|
|
// provider's backend.
|
|
|
|
type Token struct {
|
|
|
|
// A token that authorizes and authenticates the requests.
|
|
|
|
AccessToken string `json:"access_token"`
|
|
|
|
|
|
|
|
// Identifies the type of token returned.
|
|
|
|
TokenType string `json:"token_type,omitempty"`
|
|
|
|
|
|
|
|
// A token that may be used to obtain a new access token.
|
|
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
|
|
|
|
|
|
// The remaining lifetime of the access token.
|
|
|
|
Expiry time.Time `json:"expiry,omitempty"`
|
|
|
|
|
2014-08-12 22:50:34 -04:00
|
|
|
// Extra optionally contains extra metadata from the server
|
|
|
|
// when updating a token. The only current key that may be
|
|
|
|
// populated is "id_token". It may be nil and will be
|
|
|
|
// initialized as needed.
|
2014-05-05 17:54:23 -04:00
|
|
|
Extra map[string]string `json:"extra,omitempty"`
|
|
|
|
|
2014-09-05 01:43:23 -04:00
|
|
|
// Subject is the user to impersonate.
|
2014-05-05 17:54:23 -04:00
|
|
|
Subject string `json:"subject,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
return t.Expiry.Before(time.Now())
|
|
|
|
}
|
|
|
|
|
2014-08-14 01:22:35 -04:00
|
|
|
// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests.
|
|
|
|
type Transport struct {
|
2014-07-11 13:57:28 -04:00
|
|
|
fetcher TokenFetcher
|
|
|
|
origTransport http.RoundTripper
|
2014-05-05 17:54:23 -04:00
|
|
|
|
2014-08-14 01:22:35 -04:00
|
|
|
mu sync.RWMutex
|
|
|
|
token *Token
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
2014-08-14 01:22:35 -04:00
|
|
|
// NewTransport creates a new Transport that uses the provided
|
|
|
|
// token fetcher as token retrieving strategy. It authenticates
|
|
|
|
// the requests and delegates origTransport to make the actual requests.
|
|
|
|
func NewTransport(origTransport http.RoundTripper, fetcher TokenFetcher, token *Token) *Transport {
|
|
|
|
return &Transport{origTransport: origTransport, fetcher: fetcher, token: token}
|
2014-05-10 03:41:39 -04:00
|
|
|
}
|
|
|
|
|
2014-08-14 01:22:35 -04:00
|
|
|
// RoundTrip authorizes and authenticates the request with an
|
|
|
|
// access token. If no token exists or token is expired,
|
|
|
|
// tries to refresh/fetch a new token.
|
|
|
|
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
2014-05-05 17:54:23 -04:00
|
|
|
token := t.Token()
|
2014-05-18 18:20:42 -04:00
|
|
|
|
2014-05-05 17:54:23 -04:00
|
|
|
if token == nil || token.Expired() {
|
|
|
|
// Check if the token is refreshable.
|
|
|
|
// If token is refreshable, don't return an error,
|
|
|
|
// rather refresh.
|
|
|
|
|
|
|
|
if err := t.RefreshToken(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
token = t.Token()
|
|
|
|
}
|
|
|
|
|
|
|
|
// To set the Authorization header, we must make a copy of the Request
|
|
|
|
// so that we don't modify the Request we were given.
|
|
|
|
// This is required by the specification of http.RoundTripper.
|
|
|
|
req = cloneRequest(req)
|
|
|
|
typ := token.TokenType
|
|
|
|
if typ == "" {
|
|
|
|
typ = defaultTokenType
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Authorization", typ+" "+token.AccessToken)
|
|
|
|
|
|
|
|
// Make the HTTP request.
|
2014-07-11 13:57:28 -04:00
|
|
|
return t.origTransport.RoundTrip(req)
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
2014-08-14 01:22:35 -04:00
|
|
|
// Token returns the token that authorizes and
|
|
|
|
// authenticates the transport.
|
|
|
|
func (t *Transport) Token() *Token {
|
2014-05-05 17:54:23 -04:00
|
|
|
t.mu.RLock()
|
|
|
|
defer t.mu.RUnlock()
|
|
|
|
if t.token == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2014-08-14 01:22:35 -04:00
|
|
|
return &Token{
|
2014-05-05 17:54:23 -04:00
|
|
|
AccessToken: t.token.AccessToken,
|
|
|
|
TokenType: t.token.TokenType,
|
|
|
|
RefreshToken: t.token.RefreshToken,
|
|
|
|
Expiry: t.token.Expiry,
|
|
|
|
Extra: t.token.Extra,
|
|
|
|
Subject: t.token.Subject,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetToken sets a token to the transport in a thread-safe way.
|
2014-08-14 01:22:35 -04:00
|
|
|
func (t *Transport) SetToken(v *Token) {
|
2014-05-05 17:54:23 -04:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
2014-08-14 01:22:35 -04:00
|
|
|
t.token = v
|
2014-05-05 17:54:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// RefreshToken retrieves a new token, if a refreshing/fetching
|
|
|
|
// method is known and required credentials are presented
|
|
|
|
// (such as a refresh token).
|
2014-08-14 01:22:35 -04:00
|
|
|
func (t *Transport) RefreshToken() error {
|
2014-05-05 17:54:23 -04:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
2014-05-10 03:41:39 -04:00
|
|
|
token, err := t.fetcher.FetchToken(t.token)
|
2014-05-05 17:54:23 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.token = token
|
2014-05-18 18:20:42 -04:00
|
|
|
|
2014-05-05 17:54:23 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// cloneRequest returns a clone of the provided *http.Request.
|
|
|
|
// The clone is a shallow copy of the struct and its Header map.
|
|
|
|
func cloneRequest(r *http.Request) *http.Request {
|
|
|
|
// shallow copy of the struct
|
|
|
|
r2 := new(http.Request)
|
|
|
|
*r2 = *r
|
|
|
|
// deep copy of the Header
|
|
|
|
r2.Header = make(http.Header)
|
|
|
|
for k, s := range r.Header {
|
|
|
|
r2.Header[k] = s
|
|
|
|
}
|
|
|
|
return r2
|
|
|
|
}
|