Don't provide a Transport interface but provide a http.RoundTripper implementation.

This commit is contained in:
Burcu Dogan 2014-08-13 22:22:35 -07:00
parent b314823c0b
commit ee77246177
7 changed files with 38 additions and 64 deletions

View File

@ -27,16 +27,16 @@ func NewAppEngineConfig(context appengine.Context, scopes []string) *AppEngineCo
// NewTransport returns a transport that authorizes // NewTransport returns a transport that authorizes
// the requests with the application's service account. // the requests with the application's service account.
func (c *AppEngineConfig) NewTransport() oauth2.Transport { func (c *AppEngineConfig) NewTransport() *oauth2.Transport {
if c.Transport != nil { if c.Transport != nil {
return oauth2.NewAuthorizedTransport(c.Transport, c, nil) return oauth2.NewTransport(c.Transport, c, nil)
} }
transport := &urlfetch.Transport{ transport := &urlfetch.Transport{
Context: c.context, Context: c.context,
Deadline: 0, Deadline: 0,
AllowInvalidServerCertificate: false, AllowInvalidServerCertificate: false,
} }
return oauth2.NewAuthorizedTransport(transport, c, nil) return oauth2.NewTransport(transport, c, nil)
} }
// FetchToken fetches a new access token for the provided scopes. // FetchToken fetches a new access token for the provided scopes.

View File

@ -24,8 +24,8 @@ func NewAppEngineConfig(context appengine.Context, scopes []string) *AppEngineCo
// NewTransport returns a transport that authorizes // NewTransport returns a transport that authorizes
// the requests with the application's service account. // the requests with the application's service account.
func (c *AppEngineConfig) NewTransport() oauth2.Transport { func (c *AppEngineConfig) NewTransport() *oauth2.Transport {
return oauth2.NewAuthorizedTransport(http.DefaultTransport, c, nil) return oauth2.NewTransport(http.DefaultTransport, c, nil)
} }
// FetchToken fetches a new access token for the provided scopes. // FetchToken fetches a new access token for the provided scopes.

View File

@ -60,8 +60,8 @@ func NewComputeEngineConfig(account string) *ComputeEngineConfig {
} }
// NewTransport creates an authorized transport. // NewTransport creates an authorized transport.
func (c *ComputeEngineConfig) NewTransport() oauth2.Transport { func (c *ComputeEngineConfig) NewTransport() *oauth2.Transport {
return oauth2.NewAuthorizedTransport(http.DefaultTransport, c, nil) return oauth2.NewTransport(http.DefaultTransport, c, nil)
} }
// FetchToken retrieves a new access token via metadata server. // FetchToken retrieves a new access token via metadata server.

8
jwt.go
View File

@ -78,14 +78,14 @@ type JWTConfig struct {
// NewTransport creates a transport that is authorize with the // NewTransport creates a transport that is authorize with the
// parent JWT configuration. // parent JWT configuration.
func (c *JWTConfig) NewTransport() Transport { func (c *JWTConfig) NewTransport() *Transport {
return NewAuthorizedTransport(http.DefaultTransport, c, &Token{}) return NewTransport(http.DefaultTransport, c, &Token{})
} }
// NewTransportWithUser creates a transport that is authorized by // NewTransportWithUser creates a transport that is authorized by
// the client and impersonates the specified user. // the client and impersonates the specified user.
func (c *JWTConfig) NewTransportWithUser(user string) Transport { func (c *JWTConfig) NewTransportWithUser(user string) *Transport {
return NewAuthorizedTransport(http.DefaultTransport, c, &Token{Subject: user}) return NewTransport(http.DefaultTransport, c, &Token{Subject: user})
} }
// fetchToken retrieves a new access token and updates the existing token // fetchToken retrieves a new access token and updates the existing token

View File

@ -33,7 +33,7 @@ type tokenRespBody struct {
type TokenFetcher interface { type TokenFetcher interface {
// FetchToken retrieves a new access token for the provider. // FetchToken retrieves a new access token for the provider.
// If the implementation doesn't know how to retrieve a new token, // If the implementation doesn't know how to retrieve a new token,
// it returns an error. Existing token could be nil. // it returns an error. The existing token may be nil.
FetchToken(existing *Token) (*Token, error) FetchToken(existing *Token) (*Token, error)
} }
@ -150,20 +150,20 @@ func (c *Config) AuthCodeURL(state string) (authURL string) {
// t, _ := c.NewTransport() // t, _ := c.NewTransport()
// t.SetToken(validToken) // t.SetToken(validToken)
// //
func (c *Config) NewTransport() Transport { func (c *Config) NewTransport() *Transport {
return NewAuthorizedTransport(http.DefaultTransport, c, nil) return NewTransport(http.DefaultTransport, c, nil)
} }
// NewTransportWithCode exchanges the OAuth 2.0 exchange code with // NewTransportWithCode exchanges the OAuth 2.0 exchange code with
// the provider to fetch a new access token (and refresh token). Once // the provider to fetch a new access token (and refresh token). Once
// it succesffully retrieves a new token, creates a new transport // it succesffully retrieves a new token, creates a new transport
// authorized with it. // authorized with it.
func (c *Config) NewTransportWithCode(exchangeCode string) (Transport, error) { func (c *Config) NewTransportWithCode(exchangeCode string) (*Transport, error) {
token, err := c.Exchange(exchangeCode) token, err := c.Exchange(exchangeCode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewAuthorizedTransport(http.DefaultTransport, c, token), nil return NewTransport(http.DefaultTransport, c, token), nil
} }
// FetchToken retrieves a new access token and updates the existing token // FetchToken retrieves a new access token and updates the existing token

View File

@ -52,52 +52,26 @@ func (t *Token) Expired() bool {
return t.Expiry.Before(time.Now()) return t.Expiry.Before(time.Now())
} }
// Transport represents an authorized transport. // Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests.
// Provides currently in-use user token and allows to set a token to type Transport struct {
// be used. If token expires, it tries to fetch a new token,
// if possible. Token fetching is thread-safe. If two or more
// concurrent requests are being made with the same expired token,
// one of the requests will wait for the other to refresh
// the existing token.
type Transport interface {
// Authenticates the request with the existing token. If token is
// expired, tries to refresh/fetch a new token.
// Makes the request by delegating it to the default transport.
RoundTrip(*http.Request) (*http.Response, error)
// Returns the token authenticates the transport.
// This operation is thread-safe.
Token() *Token
// Sets a new token to authenticate the transport.
// This operation is thread-safe.
SetToken(token *Token)
// Refreshes the token if refresh is possible (such as in the
// presense of a refresh token). Returns an error if refresh is
// not possible. Refresh is thread-safe.
RefreshToken() error
}
type authorizedTransport struct {
fetcher TokenFetcher fetcher TokenFetcher
token *Token
origTransport http.RoundTripper origTransport http.RoundTripper
// Mutex to protect token during auto refreshments. mu sync.RWMutex
mu sync.RWMutex token *Token
} }
// NewAuthorizedTransport creates a transport that uses the provided // NewTransport creates a new Transport that uses the provided
// token fetcher to retrieve new tokens if there is no access token // token fetcher as token retrieving strategy. It authenticates
// provided or it is expired. // the requests and delegates origTransport to make the actual requests.
func NewAuthorizedTransport(origTransport http.RoundTripper, fetcher TokenFetcher, token *Token) Transport { func NewTransport(origTransport http.RoundTripper, fetcher TokenFetcher, token *Token) *Transport {
return &authorizedTransport{origTransport: origTransport, fetcher: fetcher, token: token} return &Transport{origTransport: origTransport, fetcher: fetcher, token: token}
} }
// RoundTrip authorizes the request with the existing token. // RoundTrip authorizes and authenticates the request with an
// If token is expired, tries to refresh/fetch a new token. // access token. If no token exists or token is expired,
func (t *authorizedTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { // tries to refresh/fetch a new token.
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
token := t.Token() token := t.Token()
if token == nil || token.Expired() { if token == nil || token.Expired() {
@ -126,14 +100,15 @@ func (t *authorizedTransport) RoundTrip(req *http.Request) (resp *http.Response,
return t.origTransport.RoundTrip(req) return t.origTransport.RoundTrip(req)
} }
// Token returns the existing token that authorizes the Transport. // Token returns the token that authorizes and
func (t *authorizedTransport) Token() *Token { // authenticates the transport.
func (t *Transport) Token() *Token {
t.mu.RLock() t.mu.RLock()
defer t.mu.RUnlock() defer t.mu.RUnlock()
if t.token == nil { if t.token == nil {
return nil return nil
} }
token := &Token{ return &Token{
AccessToken: t.token.AccessToken, AccessToken: t.token.AccessToken,
TokenType: t.token.TokenType, TokenType: t.token.TokenType,
RefreshToken: t.token.RefreshToken, RefreshToken: t.token.RefreshToken,
@ -141,21 +116,20 @@ func (t *authorizedTransport) Token() *Token {
Extra: t.token.Extra, Extra: t.token.Extra,
Subject: t.token.Subject, Subject: t.token.Subject,
} }
return token
} }
// SetToken sets a token to the transport in a thread-safe way. // SetToken sets a token to the transport in a thread-safe way.
func (t *authorizedTransport) SetToken(token *Token) { func (t *Transport) SetToken(v *Token) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
t.token = token t.token = v
} }
// RefreshToken retrieves a new token, if a refreshing/fetching // RefreshToken retrieves a new token, if a refreshing/fetching
// method is known and required credentials are presented // method is known and required credentials are presented
// (such as a refresh token). // (such as a refresh token).
func (t *authorizedTransport) RefreshToken() error { func (t *Transport) RefreshToken() error {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()

View File

@ -14,7 +14,7 @@ func (f *mockTokenFetcher) FetchToken(existing *Token) (*Token, error) {
} }
func TestInitialTokenRead(t *testing.T) { func TestInitialTokenRead(t *testing.T) {
tr := NewAuthorizedTransport(http.DefaultTransport, nil, &Token{AccessToken: "abc"}) tr := NewTransport(http.DefaultTransport, nil, &Token{AccessToken: "abc"})
server := newMockServer(func(w http.ResponseWriter, r *http.Request) { server := newMockServer(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer abc" { if r.Header.Get("Authorization") != "Bearer abc" {
t.Errorf("Transport doesn't set the Authorization header from the initial token") t.Errorf("Transport doesn't set the Authorization header from the initial token")
@ -31,7 +31,7 @@ func TestTokenFetch(t *testing.T) {
AccessToken: "abc", AccessToken: "abc",
}, },
} }
tr := NewAuthorizedTransport(http.DefaultTransport, fetcher, nil) tr := NewTransport(http.DefaultTransport, fetcher, nil)
server := newMockServer(func(w http.ResponseWriter, r *http.Request) { server := newMockServer(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer abc" { if r.Header.Get("Authorization") != "Bearer abc" {
t.Errorf("Transport doesn't set the Authorization header from the fetched token") t.Errorf("Transport doesn't set the Authorization header from the fetched token")