If git credential helper is non-defined, set gh as credential helper
This commit is contained in:
parent
e36c9029d3
commit
d56d92c908
4 changed files with 196 additions and 23 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
|
@ -378,7 +379,7 @@ func (c *fileConfig) configForHost(hostname string) (*HostConfig, error) {
|
|||
}
|
||||
|
||||
for _, hc := range hosts {
|
||||
if hc.Host == hostname {
|
||||
if strings.EqualFold(hc.Host, hostname) {
|
||||
return hc, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
gitCredentialCmd "github.com/cli/cli/pkg/cmd/auth/gitcredential"
|
||||
authLoginCmd "github.com/cli/cli/pkg/cmd/auth/login"
|
||||
authLogoutCmd "github.com/cli/cli/pkg/cmd/auth/logout"
|
||||
authRefreshCmd "github.com/cli/cli/pkg/cmd/auth/refresh"
|
||||
|
|
@ -22,6 +23,7 @@ func NewCmdAuth(f *cmdutil.Factory) *cobra.Command {
|
|||
cmd.AddCommand(authLogoutCmd.NewCmdLogout(f, nil))
|
||||
cmd.AddCommand(authStatusCmd.NewCmdStatus(f, nil))
|
||||
cmd.AddCommand(authRefreshCmd.NewCmdRefresh(f, nil))
|
||||
cmd.AddCommand(gitCredentialCmd.NewCmdCredential(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
112
pkg/cmd/auth/gitcredential/helper.go
Normal file
112
pkg/cmd/auth/gitcredential/helper.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package login
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CredentialOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (config.Config, error)
|
||||
|
||||
Operation string
|
||||
}
|
||||
|
||||
func NewCmdCredential(f *cmdutil.Factory, runF func(*CredentialOptions) error) *cobra.Command {
|
||||
opts := &CredentialOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "git-credential",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Implements git credential helper protocol",
|
||||
Hidden: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Operation = args[0]
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return helperRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func helperRun(opts *CredentialOptions) error {
|
||||
if opts.Operation == "store" {
|
||||
// We pretend to implement the "store" operation, but do nothing since we already have a cached token.
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
if opts.Operation != "get" {
|
||||
return fmt.Errorf("gh auth git-credential: %q operation not supported", opts.Operation)
|
||||
}
|
||||
|
||||
wants := map[string]string{}
|
||||
|
||||
s := bufio.NewScanner(opts.IO.In)
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if line == "" {
|
||||
break
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
wants[parts[0]] = parts[1]
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uv := wants["url"]; uv != "" {
|
||||
u, err := url.Parse(uv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wants["protocol"] = u.Scheme
|
||||
wants["host"] = u.Host
|
||||
wants["path"] = u.Path
|
||||
wants["username"] = u.User.Username()
|
||||
wants["password"], _ = u.User.Password()
|
||||
}
|
||||
|
||||
if wants["protocol"] != "https" {
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gotUser, _ := cfg.Get(wants["host"], "user")
|
||||
gotToken, _ := cfg.Get(wants["host"], "oauth_token")
|
||||
if gotUser == "" || gotToken == "" {
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
if wants["username"] != "" && !strings.EqualFold(wants["username"], gotUser) {
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
fmt.Fprint(opts.IO.Out, "protocol=https\n")
|
||||
fmt.Fprintf(opts.IO.Out, "host=%s\n", wants["host"])
|
||||
fmt.Fprintf(opts.IO.Out, "username=%s\n", gotUser)
|
||||
fmt.Fprintf(opts.IO.Out, "password=%s\n", gotToken)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
|
|
@ -19,6 +20,7 @@ import (
|
|||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -314,32 +316,62 @@ func loginRun(opts *LoginOptions) error {
|
|||
}
|
||||
|
||||
if opts.Interactive && gitProtocol == "https" {
|
||||
var primeCredentials bool
|
||||
err = prompt.SurveyAskOne(&survey.Confirm{
|
||||
Message: "Set up git for passwordless push/pull operations?",
|
||||
Default: true,
|
||||
}, &primeCredentials)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
if primeCredentials {
|
||||
gitCredential, err := git.GitCommand("credential", "approve")
|
||||
helper, _ := gitCredentialHelper(hostname)
|
||||
if !isOurCredentialHelper(helper) {
|
||||
var primeCredentials bool
|
||||
err = prompt.SurveyAskOne(&survey.Confirm{
|
||||
Message: "Set up git for passwordless push/pull operations?",
|
||||
Default: true,
|
||||
}, &primeCredentials)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
credentialStdin := &bytes.Buffer{}
|
||||
gitCredential.Stdin = credentialStdin
|
||||
|
||||
password, _ := cfg.Get(hostname, "oauth_token")
|
||||
fmt.Fprint(credentialStdin, "protocol=https\n")
|
||||
fmt.Fprintf(credentialStdin, "host=%s\n", hostname)
|
||||
fmt.Fprintf(credentialStdin, "username=%s\n", username)
|
||||
fmt.Fprintf(credentialStdin, "password=%s\n", password)
|
||||
fmt.Fprint(credentialStdin, "\n")
|
||||
if primeCredentials {
|
||||
if helper == "" {
|
||||
configureCmd, err := git.GitCommand("config", "--global", gitCredentialHelperKey(hostname), "!gh auth git-credential")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = run.PrepareCmd(gitCredential).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
err = run.PrepareCmd(configureCmd).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
rejectCmd, err := git.GitCommand("credential", "reject")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rejectCmd.Stdin = bytes.NewBufferString(fmt.Sprintf(heredoc.Doc(`
|
||||
protocol=https
|
||||
host=%s
|
||||
`), hostname))
|
||||
|
||||
err = run.PrepareCmd(rejectCmd).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
approveCmd, err := git.GitCommand("credential", "approve")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password, _ := cfg.Get(hostname, "oauth_token")
|
||||
approveCmd.Stdin = bytes.NewBufferString(fmt.Sprintf(heredoc.Doc(`
|
||||
protocol=https
|
||||
host=%s
|
||||
username=%s
|
||||
password=%s
|
||||
`), hostname, username, password))
|
||||
|
||||
err = run.PrepareCmd(approveCmd).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -358,3 +390,29 @@ func getAccessTokenTip(hostname string) string {
|
|||
Tip: you can generate a Personal Access Token here https://%s/settings/tokens
|
||||
The minimum required scopes are 'repo' and 'read:org'.`, ghHostname)
|
||||
}
|
||||
|
||||
func gitCredentialHelperKey(hostname string) string {
|
||||
return fmt.Sprintf("credential.https://%s.helper", hostname)
|
||||
}
|
||||
|
||||
func gitCredentialHelper(hostname string) (helper string, err error) {
|
||||
helper, err = git.Config(gitCredentialHelperKey(hostname))
|
||||
if helper != "" {
|
||||
return
|
||||
}
|
||||
helper, err = git.Config("credential.helper")
|
||||
return
|
||||
}
|
||||
|
||||
func isOurCredentialHelper(cmd string) bool {
|
||||
if !strings.HasPrefix(cmd, "!") {
|
||||
return false
|
||||
}
|
||||
|
||||
args, err := shlex.Split(cmd[1:])
|
||||
if err != nil || len(args) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(filepath.Base(args[0]), ".exe") == "gh"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue