зеркало из https://github.com/golang/oauth2.git
oauth2: Removing the inconsistent and duplicate features, better naming
- Removed Flow, flow is a nothing but options. - Renamed Cacher to Storer. - Removed the setter from the Transport. Store should do the initial set. Getter is not removed, because extra fields are available through Transport.Token.Extra(). It's not pleasant to implement a custom Storer implementation to read such values. oauth2: Remove VMs from the AppEngine example title
This commit is contained in:
Родитель
c048af9da2
Коммит
b846388564
|
@ -18,7 +18,7 @@ import (
|
||||||
func TestA(t *testing.T) {}
|
func TestA(t *testing.T) {}
|
||||||
|
|
||||||
func Example_regular() {
|
func Example_regular() {
|
||||||
f, err := oauth2.New(
|
opts, err := oauth2.New(
|
||||||
oauth2.Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
|
oauth2.Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
|
||||||
oauth2.RedirectURL("YOUR_REDIRECT_URL"),
|
oauth2.RedirectURL("YOUR_REDIRECT_URL"),
|
||||||
oauth2.Scope("SCOPE1", "SCOPE2"),
|
oauth2.Scope("SCOPE1", "SCOPE2"),
|
||||||
|
@ -33,7 +33,7 @@ func Example_regular() {
|
||||||
|
|
||||||
// Redirect user to consent page to ask for permission
|
// Redirect user to consent page to ask for permission
|
||||||
// for the scopes specified above.
|
// for the scopes specified above.
|
||||||
url := f.AuthCodeURL("state", "online", "auto")
|
url := opts.AuthCodeURL("state", "online", "auto")
|
||||||
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||||
|
|
||||||
// Use the authorization code that is pushed to the redirect URL.
|
// Use the authorization code that is pushed to the redirect URL.
|
||||||
|
@ -44,7 +44,7 @@ func Example_regular() {
|
||||||
if _, err = fmt.Scan(&code); err != nil {
|
if _, err = fmt.Scan(&code); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
t, err := f.NewTransportFromCode(code)
|
t, err := opts.NewTransportFromCode(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func Example_regular() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Example_jWT() {
|
func Example_jWT() {
|
||||||
f, err := oauth2.New(
|
opts, err := oauth2.New(
|
||||||
// The contents of your RSA private key or your PEM file
|
// The contents of your RSA private key or your PEM file
|
||||||
// that contains a private key.
|
// that contains a private key.
|
||||||
// If you have a p12 file instead, you
|
// If you have a p12 file instead, you
|
||||||
|
@ -82,6 +82,6 @@ func Example_jWT() {
|
||||||
|
|
||||||
// Initiate an http.Client, the following GET request will be
|
// Initiate an http.Client, the following GET request will be
|
||||||
// authorized and authenticated on the behalf of user@example.com.
|
// authorized and authenticated on the behalf of user@example.com.
|
||||||
client := http.Client{Transport: f.NewTransport()}
|
client := http.Client{Transport: opts.NewTransport()}
|
||||||
client.Get("...")
|
client.Get("...")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package google
|
package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -44,7 +45,9 @@ func init() {
|
||||||
func AppEngineContext(ctx appengine.Context) oauth2.Option {
|
func AppEngineContext(ctx appengine.Context) oauth2.Option {
|
||||||
return func(opts *oauth2.Options) error {
|
return func(opts *oauth2.Options) error {
|
||||||
opts.TokenFetcherFunc = makeAppEngineTokenFetcher(ctx, opts)
|
opts.TokenFetcherFunc = makeAppEngineTokenFetcher(ctx, opts)
|
||||||
opts.Transport = &urlfetch.Transport{Context: ctx}
|
opts.Client = &http.Client{
|
||||||
|
Transport: &urlfetch.Transport{Context: ctx},
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ func TestA(t *testing.T) {}
|
||||||
func Example_webServer() {
|
func Example_webServer() {
|
||||||
// Your credentials should be obtained from the Google
|
// Your credentials should be obtained from the Google
|
||||||
// Developer Console (https://console.developers.google.com).
|
// Developer Console (https://console.developers.google.com).
|
||||||
f, err := oauth2.New(
|
opts, err := oauth2.New(
|
||||||
oauth2.Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
|
oauth2.Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
|
||||||
oauth2.RedirectURL("YOUR_REDIRECT_URL"),
|
oauth2.RedirectURL("YOUR_REDIRECT_URL"),
|
||||||
oauth2.Scope(
|
oauth2.Scope(
|
||||||
|
@ -38,11 +38,11 @@ func Example_webServer() {
|
||||||
}
|
}
|
||||||
// Redirect user to Google's consent page to ask for permission
|
// Redirect user to Google's consent page to ask for permission
|
||||||
// for the scopes specified above.
|
// for the scopes specified above.
|
||||||
url := f.AuthCodeURL("state", "online", "auto")
|
url := opts.AuthCodeURL("state", "online", "auto")
|
||||||
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||||
|
|
||||||
// Handle the exchange code to initiate a transport
|
// Handle the exchange code to initiate a transport
|
||||||
t, err := f.NewTransportFromCode("exchange-code")
|
t, err := opts.NewTransportFromCode("exchange-code")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func Example_serviceAccountsJSON() {
|
||||||
// To create a service account client, click "Create new Client ID",
|
// To create a service account client, click "Create new Client ID",
|
||||||
// select "Service Account", and click "Create Client ID". A JSON
|
// select "Service Account", and click "Create Client ID". A JSON
|
||||||
// key file will then be downloaded to your computer.
|
// key file will then be downloaded to your computer.
|
||||||
f, err := oauth2.New(
|
opts, err := oauth2.New(
|
||||||
google.ServiceAccountJSONKey("/path/to/your-project-key.json"),
|
google.ServiceAccountJSONKey("/path/to/your-project-key.json"),
|
||||||
oauth2.Scope(
|
oauth2.Scope(
|
||||||
"https://www.googleapis.com/auth/bigquery",
|
"https://www.googleapis.com/auth/bigquery",
|
||||||
|
@ -71,14 +71,14 @@ func Example_serviceAccountsJSON() {
|
||||||
// Initiate an http.Client. The following GET request will be
|
// Initiate an http.Client. The following GET request will be
|
||||||
// authorized and authenticated on the behalf of
|
// authorized and authenticated on the behalf of
|
||||||
// your service account.
|
// your service account.
|
||||||
client := http.Client{Transport: f.NewTransport()}
|
client := http.Client{Transport: opts.NewTransport()}
|
||||||
client.Get("...")
|
client.Get("...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Example_serviceAccounts() {
|
func Example_serviceAccounts() {
|
||||||
// Your credentials should be obtained from the Google
|
// Your credentials should be obtained from the Google
|
||||||
// Developer Console (https://console.developers.google.com).
|
// Developer Console (https://console.developers.google.com).
|
||||||
f, err := oauth2.New(
|
opts, err := oauth2.New(
|
||||||
// The contents of your RSA private key or your PEM file
|
// The contents of your RSA private key or your PEM file
|
||||||
// that contains a private key.
|
// that contains a private key.
|
||||||
// If you have a p12 file instead, you
|
// If you have a p12 file instead, you
|
||||||
|
@ -107,13 +107,13 @@ func Example_serviceAccounts() {
|
||||||
|
|
||||||
// Initiate an http.Client, the following GET request will be
|
// Initiate an http.Client, the following GET request will be
|
||||||
// authorized and authenticated on the behalf of user@example.com.
|
// authorized and authenticated on the behalf of user@example.com.
|
||||||
client := http.Client{Transport: f.NewTransport()}
|
client := http.Client{Transport: opts.NewTransport()}
|
||||||
client.Get("...")
|
client.Get("...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Example_appEngineVMs() {
|
func Example_appEngine() {
|
||||||
ctx := appengine.NewContext(nil)
|
ctx := appengine.NewContext(nil)
|
||||||
f, err := oauth2.New(
|
opts, err := oauth2.New(
|
||||||
google.AppEngineContext(ctx),
|
google.AppEngineContext(ctx),
|
||||||
oauth2.Scope(
|
oauth2.Scope(
|
||||||
"https://www.googleapis.com/auth/bigquery",
|
"https://www.googleapis.com/auth/bigquery",
|
||||||
|
@ -125,12 +125,12 @@ func Example_appEngineVMs() {
|
||||||
}
|
}
|
||||||
// The following client will be authorized by the App Engine
|
// The following client will be authorized by the App Engine
|
||||||
// app's service account for the provided scopes.
|
// app's service account for the provided scopes.
|
||||||
client := http.Client{Transport: f.NewTransport()}
|
client := http.Client{Transport: opts.NewTransport()}
|
||||||
client.Get("...")
|
client.Get("...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Example_computeEngine() {
|
func Example_computeEngine() {
|
||||||
f, err := oauth2.New(
|
opts, err := oauth2.New(
|
||||||
// Query Google Compute Engine's metadata server to retrieve
|
// Query Google Compute Engine's metadata server to retrieve
|
||||||
// an access token for the provided account.
|
// an access token for the provided account.
|
||||||
// If no account is specified, "default" is used.
|
// If no account is specified, "default" is used.
|
||||||
|
@ -139,6 +139,6 @@ func Example_computeEngine() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
client := http.Client{Transport: f.NewTransport()}
|
client := http.Client{Transport: opts.NewTransport()}
|
||||||
client.Get("...")
|
client.Get("...")
|
||||||
}
|
}
|
||||||
|
|
126
oauth2.go
126
oauth2.go
|
@ -22,16 +22,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cacher implementations read and write OAuth 2.0 tokens from a cache.
|
// TokenStore implementations read and write OAuth 2.0 tokens from a persistence layer.
|
||||||
type Cacher interface {
|
type TokenStore interface {
|
||||||
// Read reads the token from the cache.
|
// ReadToken reads the token from the store.
|
||||||
// If the read is successful, it should return the token and a nil error.
|
// If the read is successful, it should return the token and a nil error.
|
||||||
// The returned tokens may be expired tokens.
|
// The returned tokens may be expired tokens.
|
||||||
// If there is no token in the cache, it should return a nil token and a nil error.
|
// If there is no token in the store, it should return a nil token and a nil error.
|
||||||
// It should return a non-nil error when an unrecoverable failure occurs.
|
// It should return a non-nil error when an unrecoverable failure occurs.
|
||||||
Read() (*Token, error)
|
ReadToken() (*Token, error)
|
||||||
// Write writes the token to the cache.
|
// WriteToken writes the token to the cache.
|
||||||
Write(*Token)
|
WriteToken(*Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option represents a function that applies some state to
|
// Option represents a function that applies some state to
|
||||||
|
@ -93,51 +93,27 @@ func HTTPClient(c *http.Client) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTripper allows you to provide a custom http.RoundTripper
|
// New builds a new options object and determines the type of the OAuth 2.0
|
||||||
// to be used to construct new oauth2.Transport instances.
|
|
||||||
// If none is provided a default RoundTripper will be used.
|
|
||||||
func RoundTripper(tr http.RoundTripper) Option {
|
|
||||||
return func(o *Options) error {
|
|
||||||
o.Transport = tr
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache requires a Cacher implementation. It will initially read
|
|
||||||
// the token if the transport is initialized with NewTransportFromCache
|
|
||||||
// and will write the refreshed tokens back to the cache.
|
|
||||||
func Cache(c Cacher) Option {
|
|
||||||
return func(o *Options) error {
|
|
||||||
o.Cache = c
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Flow struct {
|
|
||||||
opts Options
|
|
||||||
}
|
|
||||||
|
|
||||||
// New initiates a new flow. It determines the type of the OAuth 2.0
|
|
||||||
// (2-legged, 3-legged or custom) by looking at the provided options.
|
// (2-legged, 3-legged or custom) by looking at the provided options.
|
||||||
// If the flow type cannot determined automatically, an error is returned.
|
// If the flow type cannot determined automatically, an error is returned.
|
||||||
func New(options ...Option) (*Flow, error) {
|
func New(option ...Option) (*Options, error) {
|
||||||
f := &Flow{}
|
opts := &Options{}
|
||||||
for _, opt := range options {
|
for _, fn := range option {
|
||||||
if err := opt(&f.opts); err != nil {
|
if err := fn(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case f.opts.TokenFetcherFunc != nil:
|
case opts.TokenFetcherFunc != nil:
|
||||||
return f, nil
|
return opts, nil
|
||||||
case f.opts.AUD != nil:
|
case opts.AUD != nil:
|
||||||
// TODO(jbd): Assert the required JWT params.
|
// TODO(jbd): Assert the required JWT params.
|
||||||
f.opts.TokenFetcherFunc = makeTwoLeggedFetcher(&f.opts)
|
opts.TokenFetcherFunc = makeTwoLeggedFetcher(opts)
|
||||||
return f, nil
|
return opts, nil
|
||||||
case f.opts.AuthURL != nil && f.opts.TokenURL != nil:
|
case opts.AuthURL != nil && opts.TokenURL != nil:
|
||||||
// TODO(jbd): Assert the required OAuth2 params.
|
// TODO(jbd): Assert the required OAuth2 params.
|
||||||
f.opts.TokenFetcherFunc = makeThreeLeggedFetcher(&f.opts)
|
opts.TokenFetcherFunc = makeThreeLeggedFetcher(opts)
|
||||||
return f, nil
|
return opts, nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("oauth2: missing endpoints, can't determine how to fetch tokens")
|
return nil, errors.New("oauth2: missing endpoints, can't determine how to fetch tokens")
|
||||||
}
|
}
|
||||||
|
@ -166,13 +142,13 @@ func New(options ...Option) (*Flow, error) {
|
||||||
// granted consent and the code can only be exchanged for an
|
// granted consent and the code can only be exchanged for an
|
||||||
// access token. If set to "force" the user will always be prompted,
|
// access token. If set to "force" the user will always be prompted,
|
||||||
// and the code can be exchanged for a refresh token.
|
// and the code can be exchanged for a refresh token.
|
||||||
func (f *Flow) AuthCodeURL(state, accessType, prompt string) string {
|
func (o *Options) AuthCodeURL(state, accessType, prompt string) string {
|
||||||
u := *f.opts.AuthURL
|
u := *o.AuthURL
|
||||||
v := url.Values{
|
v := url.Values{
|
||||||
"response_type": {"code"},
|
"response_type": {"code"},
|
||||||
"client_id": {f.opts.ClientID},
|
"client_id": {o.ClientID},
|
||||||
"redirect_uri": condVal(f.opts.RedirectURL),
|
"redirect_uri": condVal(o.RedirectURL),
|
||||||
"scope": condVal(strings.Join(f.opts.Scopes, " ")),
|
"scope": condVal(strings.Join(o.Scopes, " ")),
|
||||||
"state": condVal(state),
|
"state": condVal(state),
|
||||||
"access_type": condVal(accessType),
|
"access_type": condVal(accessType),
|
||||||
"approval_prompt": condVal(prompt),
|
"approval_prompt": condVal(prompt),
|
||||||
|
@ -188,55 +164,57 @@ func (f *Flow) AuthCodeURL(state, accessType, prompt string) string {
|
||||||
|
|
||||||
// exchange exchanges the authorization code with the OAuth 2.0 provider
|
// exchange exchanges the authorization code with the OAuth 2.0 provider
|
||||||
// to retrieve a new access token.
|
// to retrieve a new access token.
|
||||||
func (f *Flow) exchange(code string) (*Token, error) {
|
func (o *Options) exchange(code string) (*Token, error) {
|
||||||
return retrieveToken(&f.opts, url.Values{
|
return retrieveToken(o, url.Values{
|
||||||
"grant_type": {"authorization_code"},
|
"grant_type": {"authorization_code"},
|
||||||
"code": {code},
|
"code": {code},
|
||||||
"redirect_uri": condVal(f.opts.RedirectURL),
|
"redirect_uri": condVal(o.RedirectURL),
|
||||||
"scope": condVal(strings.Join(f.opts.Scopes, " ")),
|
"scope": condVal(strings.Join(o.Scopes, " ")),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTransportFromCache reads the token from the cache and returns
|
// NewTransportFromTokenStore reads the token from the store and returns
|
||||||
// a Transport that is authorized and the authenticated
|
// a Transport that is authorized and the authenticated
|
||||||
// by the returned token.
|
// by the returned token.
|
||||||
func (f *Flow) NewTransportFromCache() (*Transport, error) {
|
func (o *Options) NewTransportFromTokenStore(store TokenStore) (*Transport, error) {
|
||||||
if f.opts.Cache == nil {
|
tok, err := store.ReadToken()
|
||||||
return nil, errors.New("oauth2: no cache is set")
|
|
||||||
}
|
|
||||||
tok, err := f.opts.Cache.Read()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
o.TokenStore = store
|
||||||
if tok == nil {
|
if tok == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return f.newTransportFromToken(tok), nil
|
return o.newTransportFromToken(tok), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTransportFromCode exchanges the code to retrieve a new access token
|
// NewTransportFromCode exchanges the code to retrieve a new access token
|
||||||
// and returns an authorized and authenticated Transport.
|
// and returns an authorized and authenticated Transport.
|
||||||
func (f *Flow) NewTransportFromCode(code string) (*Transport, error) {
|
func (o *Options) NewTransportFromCode(code string) (*Transport, error) {
|
||||||
token, err := f.exchange(code)
|
token, err := o.exchange(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return f.newTransportFromToken(token), nil
|
return o.newTransportFromToken(token), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTransport returns a Transport.
|
// NewTransport returns a Transport.
|
||||||
func (f *Flow) NewTransport() *Transport {
|
func (o *Options) NewTransport() *Transport {
|
||||||
return f.newTransportFromToken(nil)
|
return o.newTransportFromToken(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTransportFromToken returns a new Transport that is authorized
|
// newTransportFromToken returns a new Transport that is authorized
|
||||||
// and authenticated with the provided token.
|
// and authenticated with the provided token.
|
||||||
func (f *Flow) newTransportFromToken(t *Token) *Transport {
|
func (o *Options) newTransportFromToken(t *Token) *Transport {
|
||||||
tr := f.opts.Transport
|
// TODO(jbd): App Engine options initiate an http.Client that
|
||||||
if tr == nil {
|
// depends on the urlfetcher, but it breaks the promise we made
|
||||||
tr = http.DefaultTransport
|
// that the options object should be working finely with nil-values
|
||||||
|
// for the http.Client.
|
||||||
|
tr := http.DefaultTransport
|
||||||
|
if o.Client != nil && o.Client.Transport != nil {
|
||||||
|
tr = o.Client.Transport
|
||||||
}
|
}
|
||||||
return newTransport(tr, &f.opts, t)
|
return newTransport(tr, o, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeThreeLeggedFetcher(o *Options) func(t *Token) (*Token, error) {
|
func makeThreeLeggedFetcher(o *Options) func(t *Token) (*Token, error) {
|
||||||
|
@ -294,12 +272,14 @@ type Options struct {
|
||||||
// AUD represents the token endpoint required to complete the 2-legged JWT flow.
|
// AUD represents the token endpoint required to complete the 2-legged JWT flow.
|
||||||
AUD *url.URL
|
AUD *url.URL
|
||||||
|
|
||||||
Cache Cacher
|
// TokenStore reads a token from the store and writes it back to the store
|
||||||
|
// if a token refresh occurs.
|
||||||
|
// Optional.
|
||||||
|
TokenStore TokenStore
|
||||||
|
|
||||||
TokenFetcherFunc func(t *Token) (*Token, error)
|
TokenFetcherFunc func(t *Token) (*Token, error)
|
||||||
|
|
||||||
Transport http.RoundTripper
|
Client *http.Client
|
||||||
Client *http.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrieveToken(o *Options, v url.Values) (*Token, error) {
|
func retrieveToken(o *Options, v url.Values) (*Token, error) {
|
||||||
|
|
|
@ -25,38 +25,38 @@ type mockCache struct {
|
||||||
readErr error
|
readErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockCache) Read() (*Token, error) {
|
func (c *mockCache) ReadToken() (*Token, error) {
|
||||||
return c.token, c.readErr
|
return c.token, c.readErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockCache) Write(*Token) {
|
func (c *mockCache) WriteToken(*Token) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestFlow(url string) *Flow {
|
func newOpts(url string) *Options {
|
||||||
f, _ := New(
|
opts, _ := New(
|
||||||
Client("CLIENT_ID", "CLIENT_SECRET"),
|
Client("CLIENT_ID", "CLIENT_SECRET"),
|
||||||
RedirectURL("REDIRECT_URL"),
|
RedirectURL("REDIRECT_URL"),
|
||||||
Scope("scope1", "scope2"),
|
Scope("scope1", "scope2"),
|
||||||
Endpoint(url+"/auth", url+"/token"),
|
Endpoint(url+"/auth", url+"/token"),
|
||||||
)
|
)
|
||||||
return f
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthCodeURL(t *testing.T) {
|
func TestAuthCodeURL(t *testing.T) {
|
||||||
f := newTestFlow("server")
|
opts := newOpts("server")
|
||||||
url := f.AuthCodeURL("foo", "offline", "force")
|
url := opts.AuthCodeURL("foo", "offline", "force")
|
||||||
if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" {
|
if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" {
|
||||||
t.Errorf("Auth code URL doesn't match the expected, found: %v", url)
|
t.Errorf("Auth code URL doesn't match the expected, found: %v", url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthCodeURL_Optional(t *testing.T) {
|
func TestAuthCodeURL_Optional(t *testing.T) {
|
||||||
f, _ := New(
|
opts, _ := New(
|
||||||
Client("CLIENT_ID", ""),
|
Client("CLIENT_ID", ""),
|
||||||
Endpoint("auth-url", "token-token"),
|
Endpoint("auth-url", "token-token"),
|
||||||
)
|
)
|
||||||
url := f.AuthCodeURL("", "", "")
|
url := opts.AuthCodeURL("", "", "")
|
||||||
if url != "auth-url?client_id=CLIENT_ID&response_type=code" {
|
if url != "auth-url?client_id=CLIENT_ID&response_type=code" {
|
||||||
t.Fatalf("Auth code URL doesn't match the expected, found: %v", url)
|
t.Fatalf("Auth code URL doesn't match the expected, found: %v", url)
|
||||||
}
|
}
|
||||||
|
@ -86,8 +86,8 @@ func TestExchangeRequest(t *testing.T) {
|
||||||
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
f := newTestFlow(ts.URL)
|
opts := newOpts(ts.URL)
|
||||||
tr, err := f.NewTransportFromCode("exchange-code")
|
tr, err := opts.NewTransportFromCode("exchange-code")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -131,8 +131,8 @@ func TestExchangeRequest_JSONResponse(t *testing.T) {
|
||||||
w.Write([]byte(`{"access_token": "90d64460d14870c08c81352a05dedd3465940a7c", "scope": "user", "token_type": "bearer"}`))
|
w.Write([]byte(`{"access_token": "90d64460d14870c08c81352a05dedd3465940a7c", "scope": "user", "token_type": "bearer"}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
f := newTestFlow(ts.URL)
|
opts := newOpts(ts.URL)
|
||||||
tr, err := f.NewTransportFromCode("exchange-code")
|
tr, err := opts.NewTransportFromCode("exchange-code")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -158,8 +158,8 @@ func TestExchangeRequest_BadResponse(t *testing.T) {
|
||||||
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
f := newTestFlow(ts.URL)
|
opts := newOpts(ts.URL)
|
||||||
tr, err := f.NewTransportFromCode("exchange-code")
|
tr, err := opts.NewTransportFromCode("exchange-code")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -175,8 +175,8 @@ func TestExchangeRequest_BadResponseType(t *testing.T) {
|
||||||
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
f := newTestFlow(ts.URL)
|
opts := newOpts(ts.URL)
|
||||||
tr, err := f.NewTransportFromCode("exchange-code")
|
tr, err := opts.NewTransportFromCode("exchange-code")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -197,12 +197,15 @@ func TestExchangeRequest_NonBasicAuth(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c := &http.Client{Transport: tr}
|
c := &http.Client{Transport: tr}
|
||||||
f, _ := New(
|
opts, err := New(
|
||||||
Client("CLIENT_ID", ""),
|
Client("CLIENT_ID", ""),
|
||||||
Endpoint("https://accounts.google.com/auth", "https://accounts.google.com/token"),
|
Endpoint("https://accounts.google.com/auth", "https://accounts.google.com/token"),
|
||||||
HTTPClient(c),
|
HTTPClient(c),
|
||||||
)
|
)
|
||||||
f.NewTransportFromCode("code")
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
opts.NewTransportFromCode("code")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenRefreshRequest(t *testing.T) {
|
func TestTokenRefreshRequest(t *testing.T) {
|
||||||
|
@ -223,9 +226,9 @@ func TestTokenRefreshRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
f := newTestFlow(ts.URL)
|
opts := newOpts(ts.URL)
|
||||||
tr := f.NewTransport()
|
tr := opts.NewTransport()
|
||||||
tr.SetToken(&Token{RefreshToken: "REFRESH_TOKEN"})
|
tr.token = &Token{RefreshToken: "REFRESH_TOKEN"}
|
||||||
c := http.Client{Transport: tr}
|
c := http.Client{Transport: tr}
|
||||||
c.Get(ts.URL + "/somethingelse")
|
c.Get(ts.URL + "/somethingelse")
|
||||||
}
|
}
|
||||||
|
@ -248,8 +251,8 @@ func TestFetchWithNoRefreshToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
f := newTestFlow(ts.URL)
|
opts := newOpts(ts.URL)
|
||||||
tr := f.NewTransport()
|
tr := opts.NewTransport()
|
||||||
c := http.Client{Transport: tr}
|
c := http.Client{Transport: tr}
|
||||||
_, err := c.Get(ts.URL + "/somethingelse")
|
_, err := c.Get(ts.URL + "/somethingelse")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -258,12 +261,14 @@ func TestFetchWithNoRefreshToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNoToken(t *testing.T) {
|
func TestCacheNoToken(t *testing.T) {
|
||||||
f, _ := New(
|
opts, err := New(
|
||||||
Client("CLIENT_ID", "CLIENT_SECRET"),
|
Client("CLIENT_ID", "CLIENT_SECRET"),
|
||||||
Endpoint("/auth", "/token"),
|
Endpoint("/auth", "/token"),
|
||||||
Cache(&mockCache{token: nil, readErr: nil}),
|
|
||||||
)
|
)
|
||||||
tr, err := f.NewTransportFromCache()
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
tr, err := opts.NewTransportFromTokenStore(&mockCache{token: nil, readErr: nil})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("No error expected, %v is found", err)
|
t.Errorf("No error expected, %v is found", err)
|
||||||
}
|
}
|
||||||
|
|
23
transport.go
23
transport.go
|
@ -87,19 +87,19 @@ func newTransport(base http.RoundTripper, opts *Options, token *Token) *Transpor
|
||||||
// RoundTrip authorizes and authenticates the request with an
|
// RoundTrip authorizes and authenticates the request with an
|
||||||
// access token. If no token exists or token is expired,
|
// access token. If no token exists or token is expired,
|
||||||
// tries to refresh/fetch a new token.
|
// tries to refresh/fetch a new token.
|
||||||
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
token := t.Token()
|
token := t.token
|
||||||
|
|
||||||
if token == nil || token.Expired() {
|
if token == nil || token.Expired() {
|
||||||
// Check if the token is refreshable.
|
// Check if the token is refreshable.
|
||||||
// If token is refreshable, don't return an error,
|
// If token is refreshable, don't return an error,
|
||||||
// rather refresh.
|
// rather refresh.
|
||||||
if err := t.RefreshToken(); err != nil {
|
if err := t.refreshToken(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
token = t.Token()
|
token = t.token
|
||||||
if t.opts.Cache != nil {
|
if t.opts.TokenStore != nil {
|
||||||
t.opts.Cache.Write(token)
|
t.opts.TokenStore.WriteToken(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,17 +123,10 @@ func (t *Transport) Token() *Token {
|
||||||
return t.token
|
return t.token
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetToken sets a token to the transport in a thread-safe way.
|
// refreshToken retrieves a new token, if a refreshing/fetching
|
||||||
func (t *Transport) SetToken(v *Token) {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
t.token = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 *Transport) RefreshToken() error {
|
func (t *Transport) refreshToken() error {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
token, err := t.opts.TokenFetcherFunc(t.token)
|
token, err := t.opts.TokenFetcherFunc(t.token)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче