diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..fae7217 --- /dev/null +++ b/cache.go @@ -0,0 +1,58 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oauth2 + +import ( + "encoding/json" + "io/ioutil" +) + +// Cache represents a token cacher. +type Cache interface { + // Read reads a cache token from the specified file. + Read() (token *Token, err error) + // Write writes a token to the specified file. + Write(token *Token) (err error) +} + +// NewFileCache creates a new file cache. +func NewFileCache(filename string) *FileCache { + return &FileCache{filename: filename} +} + +// FileCache represents a file based token cacher. +type FileCache struct { + filename string +} + +// Read reads a cache token from the specified file. +func (f *FileCache) Read() (token *Token, err error) { + data, err := ioutil.ReadFile(f.filename) + if err != nil { + return nil, err + } + token = &Token{} + err = json.Unmarshal(data, &token) + return token, err +} + +// Write writes a token to the specified file. +func (f *FileCache) Write(token *Token) error { + data, err := json.Marshal(token) + if err != nil { + return err + } + return ioutil.WriteFile(f.filename, data, 0644) +} diff --git a/jwt.go b/jwt.go index 08ede79..8568884 100644 --- a/jwt.go +++ b/jwt.go @@ -56,6 +56,7 @@ type JWTConfig struct { opts *JWTOptions aud string signature []byte + cache Cache } // Options returns JWT options. @@ -143,3 +144,8 @@ func (c *JWTConfig) FetchToken(existing *Token) (token *Token, err error) { token.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second) return } + +// Cache returns a cache if specified, otherwise nil. +func (c *JWTConfig) Cache() Cache { + return c.cache +} diff --git a/oauth2.go b/oauth2.go index cc39e05..9d005d3 100644 --- a/oauth2.go +++ b/oauth2.go @@ -64,6 +64,7 @@ type TokenFetcher interface { // If the implementation doesn't know how to retrieve a new token, // it returns an error. FetchToken(existing *Token) (*Token, error) + Cache() Cache } // Options represents options to provide OAuth 2.0 client credentials @@ -128,6 +129,8 @@ type Config struct { authURL string // TokenURL is the URL used to retrieve OAuth tokens. tokenURL string + + cache Cache } // Options returns options. @@ -214,6 +217,11 @@ func (c *Config) FetchToken(existing *Token) (*Token, error) { return existing, err } +// Cache returns a cache if specified, otherwise nil. +func (c *Config) Cache() Cache { + return c.cache +} + // Checks if all required configuration fields have non-zero values. func (c *Config) validate() error { if c.opts.ClientID == "" { diff --git a/transport.go b/transport.go index c0b07d4..3fe8167 100644 --- a/transport.go +++ b/transport.go @@ -94,6 +94,12 @@ func NewAuthorizedTransport(fetcher TokenFetcher, token *Token) Transport { // If token is expired, tries to refresh/fetch a new token. func (t *authorizedTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { token := t.Token() + cache := t.fetcher.Cache() + + if token == nil && cache != nil { + // Try to read from cache initially + token, _ := cache.Read() + } if token == nil || token.Expired() { // Check if the token is refreshable. // If token is refreshable, don't return an error, @@ -159,6 +165,11 @@ func (t *authorizedTransport) RefreshToken() error { } t.token = token + cache := t.fetcher.Cache() + if cache != nil { + cache.Write(token) + } + return nil }