From ac3cdab089ad182b7834d71e7f35f5192788b170 Mon Sep 17 00:00:00 2001 From: Shin Fan Date: Tue, 28 Aug 2018 17:38:14 -0700 Subject: [PATCH] Support token cache for OAuth and JWT token --- go/oauth2l/README.md | 9 +++ go/oauth2l/main.go | 6 +- go/oauth2l/util/cache.go | 131 +++++++++++++++++++++++++++++++++++++++ go/oauth2l/util/tasks.go | 34 +++++++--- 4 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 go/oauth2l/util/cache.go diff --git a/go/oauth2l/README.md b/go/oauth2l/README.md index e62a9cd..1391ce0 100644 --- a/go/oauth2l/README.md +++ b/go/oauth2l/README.md @@ -170,3 +170,12 @@ $ oauth2l test ya29.justkiddingmadethisoneup $ echo $? 1 ``` + +### reset + +Reset all tokens cached locally. We cache previously retrieved tokens in the +file `~/.oauth2l.token`. + +```bash +$ oauth2l reset +``` diff --git a/go/oauth2l/main.go b/go/oauth2l/main.go index 43eba38..1a33a02 100644 --- a/go/oauth2l/main.go +++ b/go/oauth2l/main.go @@ -31,7 +31,7 @@ var ( ) func help() { - fmt.Println("Usage: oauth2l {fetch|header|info|test} " + + fmt.Println("Usage: oauth2l {fetch|header|info|test|reset} " + "[--jwt] [--json] [--sso] [--ssocli] {scope|aud|email}") } @@ -69,7 +69,7 @@ func parseScopes(scopes []string) string { } func main() { - if len(os.Args) < 3 { + if len(os.Args) < 2 { help() return } @@ -144,6 +144,8 @@ func main() { } } else if task, ok := infoTasks[cmd]; ok { task(flagSet.Args()[len(flagSet.Args()) - 1]) + } else if cmd == "reset" { + util.Reset() } else { // Unknown command, print usage. help() diff --git a/go/oauth2l/util/cache.go b/go/oauth2l/util/cache.go new file mode 100644 index 0000000..9d3a218 --- /dev/null +++ b/go/oauth2l/util/cache.go @@ -0,0 +1,131 @@ +// +// Copyright 2018 Google Inc. +// +// 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 util + +import ( + "log" + "io/ioutil" + "encoding/json" + "os" + "github.com/google/oauth2l/go/sgauth" + "os/user" +) + +const ( + cacheFileName = ".oauth2l.token" +) + +// The key struct that used to identify an auth token fetch operation. +type CacheKey struct { + // The JSON credentials content downloaded from Google Cloud Console. + CredentialsJSON string + // If specified, use OAuth. Otherwise, JWT. + Scope string + // The audience field for JWT auth + Audience string + // The Google API key + APIKey string +} + +func LookupCache(settings *sgauth.Settings) (*sgauth.Token, error) { + var token sgauth.Token + var cache, err = loadCache() + if err != nil { + return nil, err + } + key, err := json.Marshal(createKey(settings)) + if err != nil { + return nil, err + } + val := cache[string(key)] + err = json.Unmarshal(val, &token) + if err != nil { + return nil, err + } + return &token, nil +} + +func InsertCache(settings *sgauth.Settings, token *sgauth.Token) error { + var cache, err = loadCache() + if err != nil { + return err + } + val, err := json.Marshal(*token) + if err != nil { + return err + } + key, err := json.Marshal(createKey(settings)) + if err != nil { + return err + } + cache[string(key)] = val + data, err := json.Marshal(cache) + if err != nil { + return err + } + return ioutil.WriteFile(cacheLocation(), data, 0666) +} + +func ClearCache() error { + if _, err := os.Stat(cacheLocation()); os.IsNotExist(err) { + // Noop if file does not exist. + return nil + } + return os.Remove(cacheLocation()) +} + +func loadCache() (map[string][]byte, error) { + if _, err := os.Stat(cacheLocation()); os.IsNotExist(err) { + // Create the cache file if not existing. + f, err := os.OpenFile(cacheLocation(), os.O_RDONLY|os.O_CREATE, 0666) + if err != nil { + log.Fatal(err) + return nil, err + } + f.Close() + } + data, err := ioutil.ReadFile(cacheLocation()) + if err != nil { + log.Fatal(err) + return nil, err + } + m := map[string][]byte{} + if len(data) > 0 { + err = json.Unmarshal(data, &m) + if err != nil { + log.Fatal(err) + return nil, err + } + } + return m, nil +} + +func cacheLocation() string { + usr, err := user.Current() + if err != nil { + log.Fatal( err ) + } + return usr.HomeDir + "/" + cacheFileName +} + +func createKey(settings *sgauth.Settings) CacheKey { + return CacheKey{ + CredentialsJSON: settings.CredentialsJSON, + Scope: settings.Scope, + Audience: settings.Audience, + APIKey: settings.APIKey, + } +} + diff --git a/go/oauth2l/util/tasks.go b/go/oauth2l/util/tasks.go index c27ddbe..9c74127 100644 --- a/go/oauth2l/util/tasks.go +++ b/go/oauth2l/util/tasks.go @@ -41,20 +41,14 @@ func PrintToken(tokenType string, token string, headerFormat bool) { // Fetches and prints the token in plain text with the given settings // using Google Authenticator. func Fetch(settings *sgauth.Settings) { - token, err := sgauth.FetchToken(context.Background(), settings) - if err != nil { - fmt.Println(err) - } + token := fetchToken(settings) PrintToken(token.TokenType, token.AccessToken, false) } // Fetches and prints the token in header format with the given settings // using Google Authenticator. func Header(settings *sgauth.Settings) { - token, err := sgauth.FetchToken(context.Background(), settings) - if err != nil { - fmt.Println(err) - } + token := fetchToken(settings) PrintToken(token.TokenType, token.AccessToken, true) } @@ -79,6 +73,14 @@ func Test(token string) { } } +// Reset the cache +func Reset() { + err := ClearCache() + if err != nil { + fmt.Print(err) + } +} + func getTokenInfo(token string) (string, error) { c := http.DefaultClient resp, err := c.Get(googleTokenInfoURLPrefix + token) @@ -91,3 +93,19 @@ func getTokenInfo(token string) (string, error) { } return string(data), err } + +func fetchToken(settings *sgauth.Settings) (*sgauth.Token) { + token, _ := LookupCache(settings) + if token != nil { + return token + } + token, err := sgauth.FetchToken(context.Background(), settings) + if err != nil { + fmt.Println(err) + } + err = InsertCache(settings, token) + if err != nil { + fmt.Println(err) + } + return token +}