From 7decae71fc030965db5bd8b1e3126f5713067688 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Fri, 15 May 2020 16:40:13 -0500 Subject: [PATCH] untested first pass on ensureScopes --- api/client.go | 35 +++++++++++++++++++++++++++++++++++ command/gist.go | 6 ++++-- command/root.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 375c45d15..167170bb0 100644 --- a/api/client.go +++ b/api/client.go @@ -145,6 +145,41 @@ func (gr GraphQLErrorResponse) Error() string { return fmt.Sprintf("graphql error: '%s'", strings.Join(errorMessages, ", ")) } +// Returns whether or not scopes are present, appID, and error +func (c Client) HasScopes(wantedScopes ...string) (bool, string, error) { + url := "https://api.github.com/user" + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return false, "", err + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + + res, err := c.http.Do(req) + if err != nil { + return false, "", err + } + defer res.Body.Close() + + appID := res.Header.Get("X-Oauth-Client-Id") + hasScopes := strings.Split(res.Header.Get("X-Oauth-Scopes"), ",") + + found := 0 + for _, s := range hasScopes { + for _, w := range wantedScopes { + if w == strings.TrimSpace(s) { + found++ + } + } + } + + if found == len(wantedScopes) { + return true, appID, nil + } + + return false, appID, nil +} + // GraphQL performs a GraphQL request and parses the response func (c Client) GraphQL(query string, variables map[string]interface{}, data interface{}) error { url := "https://api.github.com/graphql" diff --git a/command/gist.go b/command/gist.go index edd0b2f14..d32b3cfc8 100644 --- a/command/gist.go +++ b/command/gist.go @@ -34,8 +34,10 @@ func gistCreate(cmd *cobra.Command, args []string) error { return err } - // TODO ?? - ok, err := client.EnsureScope("gist") + client, err = ensureScopes(ctx, client, "gist") + if err != nil { + return err + } description, err := cmd.Flags().GetString("description") if err != nil { diff --git a/command/root.go b/command/root.go index b9960eb0e..59e52bcf4 100644 --- a/command/root.go +++ b/command/root.go @@ -1,6 +1,7 @@ package command import ( + "errors" "fmt" "io" "os" @@ -184,6 +185,49 @@ var apiClientForContext = func(ctx context.Context) (*api.Client, error) { return api.NewClient(opts...), nil } +func ensureScopes(ctx context.Context, client *api.Client, wantedScopes ...string) (*api.Client, error) { + hasScopes, appID, err := client.HasScopes(wantedScopes...) + if err != nil { + return client, err + } + + if hasScopes { + return client, nil + } + + if config.IsGitHubApp(appID) && utils.IsTerminal(os.Stdin) && utils.IsTerminal(os.Stderr) { + newToken, loginHandle, err := config.AuthFlow("Notice: additional authorization required") + if err != nil { + return client, err + } + cfg, err := ctx.Config() + if err != nil { + return client, err + } + _ = cfg.Set(defaultHostname, "oauth_token", newToken) + _ = cfg.Set(defaultHostname, "user", loginHandle) + // update config file on disk + err = cfg.Write() + if err != nil { + return client, err + } + // update configuration in memory + config.AuthFlowComplete() + reloadedClient, err := apiClientForContext(ctx) + if err != nil { + return client, err + } + + return reloadedClient, nil + } else { + fmt.Fprintln(os.Stderr, fmt.Sprintf("Warning: gh now requires the `%s` OAuth scope(s).", wantedScopes)) + fmt.Fprintln(os.Stderr, fmt.Sprintf("Visit https://github.com/settings/tokens and edit your token to enable %s", wantedScopes)) + fmt.Fprintln(os.Stderr, "or generate a new token and paste it via `gh config set -h github.com oauth_token MYTOKEN`") + return client, errors.New("Unable to reauthenticate") + } + +} + func apiVerboseLog() api.ClientOption { logTraffic := strings.Contains(os.Getenv("DEBUG"), "api") colorize := utils.IsTerminal(os.Stderr)