oauth2: fixing abstractions to be able to provide external token fetchers

This commit is contained in:
Burcu Dogan 2014-05-10 09:41:39 +02:00
parent c32debaa6f
commit 2af52e700e
4 changed files with 43 additions and 28 deletions

View File

@ -75,21 +75,35 @@ const (
uriGoogleToken = "https://accounts.google.com/o/oauth2/token" uriGoogleToken = "https://accounts.google.com/o/oauth2/token"
) )
// ComputeEngineConfig represents a OAuth 2.0 consumer client
// running on Google Compute Engine.
type ComputeEngineConfig struct{}
// NewConfig creates a new OAuth2 config that uses Google // NewConfig creates a new OAuth2 config that uses Google
// endpoints. // endpoints.
func NewConfig(opts *oauth2.Options) (oauth2.Config, error) { func NewConfig(opts *oauth2.Options) (oauth2.Config, error) {
return oauth2.NewConfig(opts, uriGoogleAuth, uriGoogleToken) return oauth2.NewConfig(opts, uriGoogleAuth, uriGoogleToken)
} }
// NewComputeEngineConfig creates a new config that can fetch tokens
// from Google Compute Engine instance's metaserver.
func NewComputeEngineConfig() (oauth2.Config, error) {
// Should fetch an access token from the meta server.
panic("not yet implemented")
}
// NewServiceAccountConfig creates a new JWT config that can // NewServiceAccountConfig creates a new JWT config that can
// fetch Bearer JWT tokens from Google endpoints. // fetch Bearer JWT tokens from Google endpoints.
func NewServiceAccountConfig(opts *oauth2.JWTOptions) (oauth2.JWTConfig, error) { func NewServiceAccountConfig(opts *oauth2.JWTOptions) (oauth2.JWTConfig, error) {
return oauth2.NewJWTConfig(opts, uriGoogleToken) return oauth2.NewJWTConfig(opts, uriGoogleToken)
} }
// NewComputeEngineConfig creates a new config that can fetch tokens
// from Google Compute Engine instance's metaserver.
func NewComputeEngineConfig() (*ComputeEngineConfig, error) {
// Should fetch an access token from the meta server.
return &ComputeEngineConfig{}, nil
}
// NewTransport creates an authorized transport.
func (c *ComputeEngineConfig) NewTransport() (oauth2.Transport, error) {
return oauth2.NewAuthorizedTransport(c, nil), nil
}
// FetchToken retrieves a new access token via metadata server.
func (c *ComputeEngineConfig) FetchToken(existing *oauth2.Token) (*oauth2.Token, error) {
panic("not yet implemented")
}

6
jwt.go
View File

@ -56,18 +56,18 @@ 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, error) { func (c *jwtConfig) NewTransport() (Transport, error) {
return &authorizedTransport{fetcher: c, token: &Token{}}, nil return NewAuthorizedTransport(c, &Token{}), nil
} }
// 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, error) { func (c *jwtConfig) NewTransportWithUser(user string) (Transport, error) {
return &authorizedTransport{fetcher: c, token: &Token{Subject: user}}, nil return NewAuthorizedTransport(c, &Token{Subject: user}), nil
} }
// fetchToken retrieves a new access token and updates the existing token // fetchToken retrieves a new access token and updates the existing token
// with the newly fetched credentials. // with the newly fetched credentials.
func (c *jwtConfig) fetchToken(existing *Token) (token *Token, err error) { func (c *jwtConfig) FetchToken(existing *Token) (token *Token, err error) {
if existing == nil { if existing == nil {
existing = &Token{} existing = &Token{}

View File

@ -52,14 +52,14 @@ type tokenRespBody struct {
IdToken string `json:"id_token"` IdToken string `json:"id_token"`
} }
// tokenFetcher refreshes or fetches a new access token from the // TokenFetcher refreshes or fetches a new access token from the
// provider. It should return an error if it's not capable of // provider. It should return an error if it's not capable of
// retrieving a token. // retrieving a token.
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. // it returns an error.
fetchToken(existing *Token) (*Token, error) FetchToken(existing *Token) (*Token, error)
} }
// Options represents options to provide OAuth 2.0 client credentials // Options represents options to provide OAuth 2.0 client credentials
@ -120,9 +120,6 @@ type Config interface {
// Exchange ecxhanges the code with the provider to retrieve // Exchange ecxhanges the code with the provider to retrieve
// a new access token. // a new access token.
Exchange(exchangeCode string) (*Token, error) Exchange(exchangeCode string) (*Token, error)
// TODO(jbd): Token fetcher strategy should be settable
// from external packages.
} }
// Config represents an OAuth 2.0 provider and client options to // Config represents an OAuth 2.0 provider and client options to
@ -136,9 +133,6 @@ type JWTConfig interface {
// to be authorized with OAuth 2.0 JWT Bearer flow and // to be authorized with OAuth 2.0 JWT Bearer flow and
// impersonates the provided user. // impersonates the provided user.
NewTransportWithUser(user string) (Transport, error) NewTransportWithUser(user string) (Transport, error)
// TODO(jbd): Token fetcher strategy should be settable
// from external packages.
} }
// NewConfig creates a generic OAuth 2.0 configuration that talks // NewConfig creates a generic OAuth 2.0 configuration that talks
@ -155,7 +149,7 @@ func NewConfig(opts *Options, authURL, tokenURL string) (Config, error) {
return conf, nil return conf, nil
} }
// config represent the configuration of an OAuth 2.0 consumer client. // config represents the configuration of an OAuth 2.0 consumer client.
type config struct { type config struct {
opts *Options opts *Options
// AuthURL is the URL the user will be directed to // AuthURL is the URL the user will be directed to
@ -199,7 +193,7 @@ func (c *config) AuthCodeURL(state string) (authURL string, err error) {
// t.SetToken(validToken) // t.SetToken(validToken)
// //
func (c *config) NewTransport() (Transport, error) { func (c *config) NewTransport() (Transport, error) {
return &authorizedTransport{fetcher: c}, nil return NewAuthorizedTransport(c, nil), nil
} }
// NewTransportWithCode exchanges the OAuth 2.0 exchange code with // NewTransportWithCode exchanges the OAuth 2.0 exchange code with
@ -211,7 +205,7 @@ func (c *config) NewTransportWithCode(exchangeCode string) (Transport, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &authorizedTransport{fetcher: c, token: token}, nil return NewAuthorizedTransport(c, token), nil
} }
// Exchange exchanges the exchange code with the OAuth 2.0 provider // Exchange exchanges the exchange code with the OAuth 2.0 provider
@ -230,10 +224,10 @@ func (c *config) Exchange(exchangeCode string) (*Token, error) {
return token, nil return token, nil
} }
// fetchToken retrieves a new access token and updates the existing token // FetchToken retrieves a new access token and updates the existing token
// with the newly fetched credentials. If existing token doesn't // with the newly fetched credentials. If existing token doesn't
// contain a refresh token, it returns an error. // contain a refresh token, it returns an error.
func (c *config) fetchToken(existing *Token) (*Token, error) { func (c *config) FetchToken(existing *Token) (*Token, error) {
if existing == nil || existing.RefreshToken == "" { if existing == nil || existing.RefreshToken == "" {
return nil, errors.New("cannot fetch access token without refresh token.") return nil, errors.New("cannot fetch access token without refresh token.")
} }
@ -309,7 +303,7 @@ func (c *config) updateToken(tok *Token, v url.Values) error {
tok.AccessToken = resp.AccessToken tok.AccessToken = resp.AccessToken
tok.TokenType = resp.TokenType tok.TokenType = resp.TokenType
// Don't overwrite `RefreshToken` with an empty value // Don't overwrite `RefreshToken` with an empty value
if resp.RefreshToken == "" { if resp.RefreshToken != "" {
tok.RefreshToken = resp.RefreshToken tok.RefreshToken = resp.RefreshToken
} }
if resp.ExpiresIn == 0 { if resp.ExpiresIn == 0 {

View File

@ -72,13 +72,20 @@ type Transport interface {
} }
type authorizedTransport struct { type authorizedTransport struct {
fetcher tokenFetcher fetcher TokenFetcher
token *Token token *Token
// Mutex to protect token during auto refreshments. // Mutex to protect token during auto refreshments.
mu sync.RWMutex mu sync.RWMutex
} }
// NewAuthorizedTransport creates a tranport that uses the provided
// token fetcher to retrieve new tokens if there is no access token
// provided or it is expired.
func NewAuthorizedTransport(fetcher TokenFetcher, token *Token) Transport {
return &authorizedTransport{fetcher: fetcher, token: token}
}
// RoundTrip authorizes the request with the existing token. // RoundTrip authorizes the request with the existing token.
// If token is expired, tries to refresh/fetch a new token. // If token is expired, tries to refresh/fetch a new token.
func (t *authorizedTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { func (t *authorizedTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
@ -142,7 +149,7 @@ func (t *authorizedTransport) RefreshToken() error {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
token, err := t.fetcher.fetchToken(t.token) token, err := t.fetcher.FetchToken(t.token)
if err != nil { if err != nil {
return err return err
} }