From 717448a0963685dfcfb56c62356aa0a088e31169 Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Mon, 5 Aug 2019 13:34:12 -0700 Subject: [PATCH] Improved OAuth2l Client Interface (#70) -Uses advanced go-flags library to parse command-line flags, which improves organization, flexibility, and better built-in help doc. -Added explicit flags for GUAC arguments such as scope and audience. -Added support for a new "curl" command. -Fixed variadic bug in sso.go that incorrectly handles multiple scopes. --- README.md | 244 +++++++++++++++++++++++++++++---------- main.go | 309 ++++++++++++++++++++++++++++++++++++++++++-------- util/curl.go | 43 +++++++ util/sso.go | 14 +-- util/tasks.go | 30 ++++- 5 files changed, 516 insertions(+), 124 deletions(-) create mode 100644 util/curl.go diff --git a/README.md b/README.md index 6d7023a..71f3663 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,21 @@ accounts and service accounts in different environments: * When running inside user context that has an active Google Cloud SDK (gcloud) session, it uses the current gcloud credentials. -* When running with command option `--json xxx`, where `xxx` points to +* When running with command option `--credentials xxx`, where `xxx` points to a JSON credential file downloaded from [Google Cloud Console](https://console.cloud.google.com/apis/credentials), `oauth2l` uses the file to start an OAuth session. The file can be either a service account key or an OAuth client ID. -* When running with command option `--sso {email}`, it invokes an - external `sso` command to retrieve Single Sign-on (SSO) access token. +* When running with command option `--type jwt --audience xxx` and a service + account key, a JWT token signed by the service account key will be generated. + +* When running with command option `--type sso --email xxx`, `oauth2l` invokes + an external `sso` command to retrieve Single Sign-on (SSO) access token. + +* By default, retrieved tokens will be cached and stored in "~/.oauth2l". + The cache location can be overridden via `--cache xxx`. To disable + caching, set cache location to empty (""). ## Quickstart @@ -50,59 +57,16 @@ https://github.com/golang/go/wiki/GOPATH)) ```bash # Get the package from Github -$ go get github.com/google/oauth2l +$ go get -u github.com/google/oauth2l # Install the package into your $GOPATH/bin/ $ go install github.com/google/oauth2l # Fetch the access token from your credentials with cloud-platform scope -$ ~/go/bin/oauth2l fetch --json ~/your_credentials.json cloud-platform +$ ~/go/bin/oauth2l fetch --credentials ~/your_credentials.json --scope cloud-platform # Or you can run if you $GOPATH/bin is already in your $PATH -$ oauth2l fetch --json ~/your_credentials.json cloud-platform -``` - -## Command Options - -### --json - -Specifies an OAuth credential file, either an OAuth client ID or a Service -Account key, to start the OAuth flow. You can download the file from -[Google Cloud Console](https://console.cloud.google.com/apis/credentials). - -```bash -$ oauth2l fetch --json ~/service_account.json cloud-platform -``` - -### --sso and --sso_cli - -Using an external Single Sign-on (SSO) command to fetch OAuth token. -The command outputs an OAuth access token to its stdout. The default -command is for Google's corporate SSO. For example: - -```bash -$ sso me@example.com scope1 scope2 -``` - -Then use oauth2l with the SSO CLI: - -```bash -$ oauth2l header --sso me@example.com --sso_cli /usr/bin/sso cloud-platform -$ oauth2l header --sso me@google.com cloud-platform -``` - -### --jwt - -When this option is set and the json file specified in the `--json` option -is a service account key file, a JWT token signed by the service account -private key will be generated. When this option is set, no scope list is -needed but a single JWT audience must be provided. See how to construct the -audience [here](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth). - -Example: - -```bash -oauth2l fetch --jwt --json ~/service_account.json https://pubsub.googleapis.com/google.pubsub.v1.Publisher +$ oauth2l fetch --credentials ~/your_credentials.json --scope cloud-platform ``` ## Commands @@ -116,7 +80,7 @@ the following command prints access token for the following OAuth2 scopes: * https://www.googleapis.com/auth/cloud-platform ```bash -$ oauth2l fetch userinfo.email cloud-platform +$ oauth2l fetch --scope userinfo.email,cloud-platform ya29.zyxwvutsrqpnmolkjihgfedcba ``` @@ -125,23 +89,25 @@ ya29.zyxwvutsrqpnmolkjihgfedcba The same as `fetch`, except the output is in HTTP header format: ```bash -$ oauth2l header userinfo.email +$ oauth2l header --scope cloud-platform Authorization: Bearer ya29.zyxwvutsrqpnmolkjihgfedcba ``` -The `header` command is designed to be easy to use with `curl`. For example, -the following command uses the PubSub API to list all PubSub topics. +The `header` command is designed to be easy to use with the `curl` CLI. For +example, the following command uses the PubSub API to list all PubSub topics. ```bash -$ curl -H "$(oauth2l header pubsub)" https://pubsub.googleapis.com/v1/projects/my-project-id/topics +$ curl -H "$(oauth2l header --scope pubsub)" https://pubsub.googleapis.com/v1/projects/my-project-id/topics ``` -If you need to call Google APIs frequently using `curl`, you can define a -shell alias for it. For example: +### curl + +This is a shortcut command that fetches an access token for the specified OAuth +scopes and uses the token to make a curl request (via 'usr/bin/curl' by +default). Additional flags after "--" will be treated as curl flags. ```bash -$ alias gcurl='curl -H "$(oauth2l header cloud-platform)" -H "Content-Type: application/json" ' -$ gcurl 'https://pubsub.googleapis.com/v1/projects/my-project-id/topics' +$ oauth2l curl --scope cloud-platform,pubsub --url https://pubsub.googleapis.com/v1/projects/my-project-id/topics -- -i ``` ### info @@ -153,7 +119,7 @@ and expiration time. If the token has either the address of the authenticated identity. ```bash -$ oauth2l info $(oauth2l fetch pubsub) +$ oauth2l info --token $(oauth2l fetch --scope pubsub) { "expires_in": 3599, "scope": "https://www.googleapis.com/auth/pubsub", @@ -168,10 +134,10 @@ Test a token. This sets an exit code of 0 for a valid token and 1 otherwise, which can be useful in shell pipelines. ```bash -$ oauth2l test ya29.zyxwvutsrqpnmolkjihgfedcba +$ oauth2l test --token ya29.zyxwvutsrqpnmolkjihgfedcba $ echo $? 0 -$ oauth2l test ya29.justkiddingmadethisoneup +$ oauth2l test --token ya29.justkiddingmadethisoneup $ echo $? 1 ``` @@ -179,8 +145,162 @@ $ echo $? ### reset Reset all tokens cached locally. We cache previously retrieved tokens in the -file `~/.oauth2l.token`. +file `~/.oauth2l` by default. ```bash $ oauth2l reset ``` + +## Command Options + +### --help + +Prints help messages for the main program or a specific command. + +```bash +$ oauth2l --help +``` + +```bash +$ oauth2l fetch --help +``` + +### --credentials + +Specifies an OAuth credential file (either an OAuth client ID or a Service +Account key) to start the OAuth flow. You can download the file from +[Google Cloud Console](https://console.cloud.google.com/apis/credentials). + +```bash +$ oauth2l fetch --credentials ~/service_account.json --scope cloud-platform +``` + +If this option is not supplied, it will be read from the environment variable +GOOGLE_APPLICATION_CREDENTIALS. For more information, please read +[Getting started with Authentication](https://cloud.google.com/docs/authentication/getting-started). + +```bash +$ export GOOGLE_APPLICATION_CREDENTIALS="~/service_account.json" +$ oauth2l fetch --scope cloud-platform +``` + +### --type + +The authentication type. The currently supported types are "oauth", "jwt", or +"sso". Defaults to "oauth". + +#### oauth + +When oauth is selected, the tool will fetch an OAuth access token through one +of two different flows. If service account key is provided, 2-legged OAuth flow +is performed. If OAuth Client ID is provided, 3-legged OAuth flow is performed, +which requires user consent. Learn about the different types of OAuth +[here](https://developers.google.com/identity/protocols/OAuth2). + +```bash +$ oauth2l fetch --type oauth --credentials ~/client_credentials.json --scope cloud-platform +``` + +#### jwt + +When jwt is selected and the json file specified in the `--credentials` option +is a service account key file, a JWT token signed by the service account +private key will be generated. When using this option, no scope parameter is +needed but a single JWT audience must be provided. See how to construct the +audience [here](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth). + +```bash +$ oauth2l fetch --type jwt --credentials ~/service_account.json --audience https://pubsub.googleapis.com/ +``` + +#### sso + +When sso is selected, the tool will use an external Single Sign-on (SSO) +CLI to fetch an OAuth access token. The default SSO CLI only works with +Google's corporate SSO. An email is required in addition to scope. + +To use oauth2l with the default SSO CLI: + +```bash +$ oauth2l header --type sso --email me@google.com --scope cloud-platform +``` + +To use oauth2l with a custom SSO CLI: + +```bash +$ oauth2l header --type sso --ssocli /usr/bin/sso --email me@google.com --scope cloud-platform +``` + +Note: The custom SSO CLI should have the following interface: + +```bash +$ /usr/bin/sso me@example.com scope1 scope2 +``` + +### --scope + +The scope(s) that will be authorized by the OAuth access token. Required for +oauth and sso authentication types. When using multiple scopes, provide the +the parameter as a comma-delimited list and do not include spaces. (Alternatively, +multiple scopes can be specified as a space-delimited string surrounded in quotes.) + +```bash +$ oauth2l fetch --scope cloud-platform,pubsub +``` + +### --audience + +The single audience to include in the signed JWT token. Required for jwt +authentication type. + +```bash +$ oauth2l fetch --type jwt --audience https://pubsub.googleapis.com/ +``` + +### --email + +The email associated with SSO. Required for sso authentication type. + +```bash +$ oauth2l fetch --type sso --email me@google.com --scope cloud-platform +``` + +### --ssocli + +Path to SSO CLI. For optional use with "sso" authentication type. + +```bash +$ oauth2l fetch --type sso --ssocli /usr/bin/sso --email me@google.com --scope cloud-platform +``` + +### --cache + +Path to token cache file. Disables caching if set to empty (""). Defaults to ~/.oauth2l if not configured. + +```bash +$ oauth2l fetch --cache ~/different_path/.oauth2l --scope cloud-platform +``` + +### fetch --output_format + +Token's output format for "fetch" command. One of bare, header, json, json_compact, pretty. Default is bare. + +```bash +$ oauth2l fetch --output_format pretty --scope cloud-platform +``` + +### curl --url + +URL endpoint for curl request. Required for "curl" command. + +```bash +$ oauth2l curl --scope cloud-platform --url https://pubsub.googleapis.com/v1/projects/my-project-id/topics +``` + +### curl --curlcli + +Path to Curl CLI. For optional use with "curl" command. + +```bash +$ oauth2l curl --curlcli /usr/bin/curl --type sso --email me@google.com --scope cloud-platform --url https://pubsub.googleapis.com/v1/projects/my-project-id/topics +``` \ No newline at end of file diff --git a/main.go b/main.go index 24b3b9d..66c0d43 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ // -// Copyright 2018 Google Inc. +// Copyright 2019 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,28 +15,97 @@ package main import ( - "flag" "fmt" "io/ioutil" "os" "strings" - + "regexp" "github.com/google/oauth2l/sgauth" "github.com/google/oauth2l/util" + "github.com/jessevdk/go-flags" ) -var ( +const ( // Common prefix for google oauth scope scopePrefix = "https://www.googleapis.com/auth/" - cmds = []string{"fetch", "header", "info", "test"} ) -func help(f *flag.FlagSet) { - fmt.Println("Usage: oauth2l fetch|header|info|test|reset [flags]... [scope|aud|email]...") - f.PrintDefaults() - os.Exit(0) +var ( + // Holds the parsed command-line flags + opts commandOptions + + // Multiple scopes are separate by comma, space, or comma-space. + scopeDelimiter = regexp.MustCompile("[, ] *") +) + +// Top level command-line flags (first argument after program name). +type commandOptions struct { + Fetch fetchOptions `command:"fetch" description:"Fetch an access token."` + Header headerOptions `command:"header" description:"Fetch an access token and return it in header format."` + Curl curlOptions `command:"curl" description:"Fetch an access token and use it to make a curl request."` + Info infoOptions `command:"info" description:"Display info about an OAuth access token."` + Test infoOptions `command:"test" description:"Tests an OAuth access token. Returns 0 for valid token."` + Reset resetOptions `command:"reset" description:"Resets the cache."` +} + +// Common options for "fetch", "header", and "curl" commands. +type commonFetchOptions struct { + // Currently there are 3 authentication types that are mutually exclusive: + // + // oauth - Executes 2LO flow for Service Account and 3LO flow for OAuth Client ID. Returns OAuth token. + // jwt - Signs claims (in JWT format) using PK. Returns signature as token. Only works for Service Account. + // sso - Exchanges LOAS credential to OAuth token. + AuthType string `long:"type" choice:"oauth" choice:"jwt" choice:"sso" description:"The authentication type." default:"oauth"` + + // GUAC parameters + Credentials string `long:"credentials" description:"Credentials file containing OAuth Client Id or Service Account Key. Optional if environment variable GOOGLE_APPLICATION_CREDENTIALS is set."` + Scope string `long:"scope" description:"List of OAuth scopes requested. Required for oauth and sso authentication type. Comma delimited."` + Audience string `long:"audience" description:"Audience used for JWT self-signed token. Required for jwt authentication type."` + Email string `long:"email" description:"Email associated with SSO. Required for sso authentication type."` + + // Client parameters + SsoCli string `long:"ssocli" description:"Path to SSO CLI. Optional."` + + // Cache is declared as a pointer type and can be one of nil, empty (""), or a custom file path. + Cache *string `long:"cache" description:"Path to the credential cache file. Disables caching if set to empty. Defaults to ~/.oauth2l."` + + // Deprecated flags kept for backwards compatibility. Hidden from help page. + Json string `long:"json" description:"Deprecated. Same as --credentials." hidden:"true"` + Jwt bool `long:"jwt" description:"Deprecated. Same as --type jwt." hidden:"true"` + Sso bool `long:"sso" description:"Deprecated. Same as --type sso." hidden:"true"` + OldFormat string `long:"credentials_format" choice:"bare" choice:"header" choice:"json" choice:"json_compact" choice:"pretty" description:"Deprecated. Same as --output_format" hidden:"true"` +} + +// Additional options for "fetch" command. +type fetchOptions struct { + commonFetchOptions + Format string `long:"output_format" choice:"bare" choice:"header" choice:"json" choice:"json_compact" choice:"pretty" description:"Token's output format." default:"bare"` +} + +// Additional options for "header" command. +type headerOptions struct { + commonFetchOptions +} + +// Additional options for "curl" command. +type curlOptions struct { + commonFetchOptions + CurlCli string `long:"curlcli" description:"Path to Curl CLI. Optional."` + Url string `long:"url" description:"URL endpoint for the curl request." required:"true"` +} + +// Options for "info" and "test" commands. +type infoOptions struct { + Token string `long:"token" description:"OAuth access token to analyze."` +} + +// Options for "reset" command. +type resetOptions struct { + // Cache is declared as a pointer type and can be one of nil or a custom file path. + Cache *string `long:"cache" description:"Path to the credential cache file to remove. Defaults to ~/.oauth2l."` } +// Reads and returns content of JSON file. func readJSON(file string) (string, error) { if file != "" { secretBytes, err := ioutil.ReadFile(file) @@ -70,37 +139,110 @@ func parseScopes(scopes []string) string { return strings.Join(scopes, " ") } -func main() { - // Configure the CLI - flagSet := flag.NewFlagSet("fetch", flag.ExitOnError) - helpFlag := flagSet.Bool("help", false, "Print help message.") - flagSet.BoolVar(helpFlag, "h", false, "") - jsonFile := flagSet.String("json", "", "Path to secret json file.") - format := flagSet.String("credentials_format", "bare", "Output format. The supported formats are: "+ - "bare(default), header, json, json_compact, pretty") - flagSet.StringVar(format, "f", "bare", "") - jwtFlag := flagSet.Bool("jwt", false, "Use JWT auth flow") - ssoFlag := flagSet.Bool("sso", false, "Use SSO auth flow") - ssocli := flagSet.String("ssocli", "", "Path to SSO CLI") - flagSet.StringVar(&util.CacheLocation, "cache", util.CacheLocation, "Path to the credential cache file. Disables caching if set to empty.") - - if len(os.Args) < 2 { - help(flagSet) +// Overrides default cache location if configured. +func setCacheLocation(cache *string) { + if cache != nil { + util.CacheLocation = *cache + } +} + +// Extracts the common fetch options based on chosen command. +func getCommonFetchOptions (cmdOpts commandOptions, cmd string) commonFetchOptions { + var commonOpts commonFetchOptions + switch cmd { + case "fetch": + commonOpts = cmdOpts.Fetch.commonFetchOptions + case "header": + commonOpts = cmdOpts.Header.commonFetchOptions + case "curl": + commonOpts = cmdOpts.Curl.commonFetchOptions + } + return commonOpts +} + +// Get the authentication type, with backward compatibility. +func getAuthTypeWithFallback (commonOpts commonFetchOptions) string { + authType := commonOpts.AuthType + if commonOpts.Jwt { + authType = "jwt" + } else if commonOpts.Sso { + authType = "sso" + } + return authType +} + +// Get the credentials file, with backward compatibility. +func getCredentialsWithFallback (commonOpts commonFetchOptions) string { + credentials := commonOpts.Credentials + if commonOpts.Json != "" { + credentials = commonOpts.Json + } + return credentials +} + +// Get the fetch output format, with backward compatibility. +func getOutputFormatWithFallback (fetchOpts fetchOptions) string { + format := fetchOpts.Format + if fetchOpts.OldFormat != "" { + format = fetchOpts.OldFormat } + return format +} - flagSet.Parse(os.Args[2:]) +// Converts scope argument to string slice, with backward compatibility. +func getScopesWithFallback (scope string, remainingArgs ...string) []string { + var scopes []string + // Fallback to reading scope from remaining args + if scope == "" { + scopes = remainingArgs + } else { + scopes = scopeDelimiter.Split(scope, -1) + } + return scopes +} - if *helpFlag { - help(flagSet) +// Construct taskArgs based on chosen command. +func getTaskArgs(cmd, curlcli, url, format string, remainingArgs ...string) []string { + var taskArgs []string + switch cmd { + case "curl": + taskArgs = append([]string{curlcli, url}, remainingArgs...) + case "fetch": + taskArgs = []string{format} } + return taskArgs +} - // Get the command keyword from the first argument. - cmd := os.Args[1] +// Extracts the info options based on chosen command. +func getInfoOptions (cmdOpts commandOptions, cmd string) infoOptions { + var infoOpts infoOptions + switch cmd { + case "info": + infoOpts = cmdOpts.Info + case "test": + infoOpts = cmdOpts.Test + } + return infoOpts +} + +func main() { + // Parse command-line flags via "go-flags" library + parser := flags.NewParser(&opts, flags.Default) + + // Arguments that are not recognized by the parser are stored in remainingArgs. + remainingArgs, err := parser.Parse() + if err != nil { + os.Exit(0) + } + + // Get the name of the selected command + cmd := parser.Active.Name // Tasks that fetch the access token. - fetchTasks := map[string]func(*sgauth.Settings, string){ + fetchTasks := map[string]func(*sgauth.Settings, ...string){ "fetch": util.Fetch, "header": util.Header, + "curl": util.Curl, } // Tasks that verify the existing token. @@ -110,35 +252,91 @@ func main() { } if task, ok := fetchTasks[cmd]; ok { - if *jwtFlag { + commonOpts := getCommonFetchOptions(opts, cmd) + authType := getAuthTypeWithFallback(commonOpts) + credentials := getCredentialsWithFallback(commonOpts) + scope := commonOpts.Scope + audience := commonOpts.Audience + email := commonOpts.Email + ssocli := commonOpts.SsoCli + setCacheLocation(commonOpts.Cache) + format := getOutputFormatWithFallback(opts.Fetch) + curlcli := opts.Curl.CurlCli + url := opts.Curl.Url + + if authType == "jwt"{ // JWT flow - json, err := readJSON(*jsonFile) + json, err := readJSON(credentials) if err != nil { - fmt.Println("Failed to open file: " + *jsonFile) + fmt.Println("Failed to open file: " + credentials) fmt.Println(err.Error()) return } + // Fallback to reading audience from first remaining arg + if audience == "" { + if len(remainingArgs) > 0 { + audience = remainingArgs[0] + } else { + fmt.Println("Missing audience argument for JWT") + return + } + } + settings := &sgauth.Settings{ CredentialsJSON: json, - Audience: flagSet.Args()[len(flagSet.Args())-1], + Audience: audience, + } + + taskArgs := getTaskArgs(cmd, curlcli, url, format, remainingArgs...) + task(settings, taskArgs...) + } else if authType == "sso" { + // Fallback to reading email from first remaining arg + argProcessedIndex := 0 + if email == "" { + if len(remainingArgs) > 0 { + email = remainingArgs[argProcessedIndex] + argProcessedIndex++ + } else { + fmt.Println("Missing email argument for SSO") + return + } } - task(settings, *format) - } else if *ssoFlag { + + scopes := getScopesWithFallback(scope, remainingArgs[argProcessedIndex:]...) + if len(scopes) < 1 { + fmt.Println("Missing scope argument for SSO") + return + } + // SSO flow - util.SSOFetch(flagSet.Args()[0], *ssocli, cmd, - parseScopes(flagSet.Args()[1:])) + token, err := util.SSOFetch(email, ssocli, cmd, + parseScopes(scopes)) + if err != nil { + fmt.Println("Failed to fetch SSO token") + return + } + header := util.BuildHeader("Bearer", token) + + switch cmd { + case "curl": + util.CurlCommand(curlcli, header, url, remainingArgs...) + case "header": + fmt.Println(header) + default: + fmt.Println(token) + } } else { // OAuth flow - if len(flagSet.Args()) < 1 { + scopes := getScopesWithFallback(scope, remainingArgs...) + if len(scopes) < 1 { fmt.Println("Missing scope argument for OAuth 2.0") - help(flagSet) return } - json, err := readJSON(*jsonFile) + json, err := readJSON(credentials) if err != nil { - fmt.Println("Failed to open file: " + *jsonFile) + fmt.Println("Failed to open file: " + credentials) fmt.Println(err.Error()) return } @@ -147,18 +345,31 @@ func main() { // For 2LO flow OAuthFlowHandler and State are not needed. settings := &sgauth.Settings{ CredentialsJSON: json, - Scope: parseScopes(flagSet.Args()), + Scope: parseScopes(scopes), OAuthFlowHandler: defaultAuthorizeFlowHandler, State: "state", } - task(settings, *format) + + taskArgs := getTaskArgs(cmd, curlcli, url, format, remainingArgs...) + task(settings, taskArgs...) } } else if task, ok := infoTasks[cmd]; ok { - task(flagSet.Args()[len(flagSet.Args())-1]) + infoOpts := getInfoOptions(opts, cmd) + token := infoOpts.Token + + // Fallback to reading token from remaining args. + if token == "" { + if len(remainingArgs) > 0 { + token = remainingArgs[0] + } else { + fmt.Println("Missing token to analyze") + return + } + } + + task(token) } else if cmd == "reset" { + setCacheLocation(opts.Reset.Cache) util.Reset() - } else { - // Unknown command, print usage. - help(flagSet) } } diff --git a/util/curl.go b/util/curl.go new file mode 100644 index 0000000..c87aca0 --- /dev/null +++ b/util/curl.go @@ -0,0 +1,43 @@ +// +// Copyright 2019 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 ( + "os/exec" + "bytes" + "fmt" +) + +const ( + defaultCurlCli = "/usr/bin/curl" +) + +// Executes curl command with provided header and params. +func CurlCommand(cli string, header string, url string, extraArgs ...string) { + if cli == "" { + cli = defaultCurlCli + } + requiredArgs := []string{"-H", header, url} + cmdArgs := append(requiredArgs, extraArgs...) + + cmd := exec.Command(cli, cmdArgs...) + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + fmt.Println(err) + } + print(out.String()) +} diff --git a/util/sso.go b/util/sso.go index 430a6b1..fab0d60 100644 --- a/util/sso.go +++ b/util/sso.go @@ -18,27 +18,27 @@ import ( "os/exec" "bytes" "fmt" + "strings" ) const ( defaultCli = "/google/data/ro/teams/oneplatform/sso" ) -// Fetches the access token using SSO CLI. -func SSOFetch(email string, cli string, task string, scope string) { +// Fetches and returns OAuth access token using SSO CLI. +func SSOFetch(email string, cli string, task string, scope string) (string, error) { if cli == "" { cli = defaultCli } - cmd := exec.Command(cli, email, scope) + cmdArgs := append([]string{email}, strings.Split(scope, " ")...) + cmd := exec.Command(cli, cmdArgs...) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { fmt.Println(err) - } - if task == "header" { - printHeader("Bearer", out.String()) + return "", err } else { - println(out.String()) + return out.String(), nil } } diff --git a/util/tasks.go b/util/tasks.go index a6ed335..93b3bb5 100644 --- a/util/tasks.go +++ b/util/tasks.go @@ -41,17 +41,30 @@ const ( // Fetches and prints the token in plain text with the given settings // using Google Authenticator. -func Fetch(settings *sgauth.Settings, format string) { +func Fetch(settings *sgauth.Settings, args ...string) { + format := args[0] printToken(fetchToken(settings), format, getCredentialType(settings)) } // Fetches and prints the token in header format with the given settings // using Google Authenticator. -func Header(settings *sgauth.Settings, format string) { +func Header(settings *sgauth.Settings, args ... string) { Fetch(settings, formatHeader) } -// Fetch the information of the given token. +// Fetches token with the given settings using Google Authenticator +// and use the token as header to make curl request. +func Curl(settings *sgauth.Settings, args ...string) { + token := fetchToken(settings) + if token != nil { + header := BuildHeader(token.TokenType, token.AccessToken) + curlcli := args[0] + url := args[1] + CurlCommand(curlcli, header, url, args[2:]...) + } +} + +// Fetches the information of the given token. func Info(token string) { info, err := getTokenInfo(token) if err != nil { @@ -61,7 +74,7 @@ func Info(token string) { } } -// Test the given token. Returns 0 for valid tokens. +// Tests the given token. Returns 0 for valid tokens. // Otherwise returns 1. func Test(token string) { _, err := getTokenInfo(token) @@ -72,7 +85,7 @@ func Test(token string) { } } -// Reset the cache +// Resets the cache. func Reset() { err := ClearCache() if err != nil { @@ -80,6 +93,11 @@ func Reset() { } } +// Returns the given token in standard header format. +func BuildHeader(tokenType string, token string) string { + return fmt.Sprintf("Authorization: %s %s", tokenType, token) +} + func getTokenInfo(token string) (string, error) { c := http.DefaultClient resp, err := c.Get(googleTokenInfoURLPrefix + token) @@ -154,5 +172,5 @@ func printToken(token *sgauth.Token, format string, credType string) { } func printHeader(tokenType string, token string) { - fmt.Printf("Authorization: %s %s\n", tokenType, token) + fmt.Println(BuildHeader(tokenType, token)) }