commands/auth/authutils/git_credentials.go (100 lines of code) (raw):
package authutils
import (
"bytes"
"fmt"
"path/filepath"
"strings"
"gitlab.com/gitlab-org/cli/internal/run"
"gitlab.com/gitlab-org/cli/pkg/git"
"gitlab.com/gitlab-org/cli/pkg/prompt"
"github.com/AlecAivazis/survey/v2"
"github.com/MakeNowJust/heredoc/v2"
"github.com/google/shlex"
)
type GitCredentialFlow struct {
Executable string
shouldSetup bool
helper string
}
func (gc *GitCredentialFlow) Prompt(hostname, protocol string) error {
gc.helper, _ = gitCredentialHelper(hostname, protocol)
if isOurCredentialHelper(gc.helper) {
return nil
}
err := prompt.AskOne(&survey.Confirm{
Message: "Authenticate Git with your GitLab credentials?",
Default: true,
}, &gc.shouldSetup)
if err != nil {
return fmt.Errorf("could not prompt: %w", err)
}
return nil
}
func (gc *GitCredentialFlow) ShouldSetup() bool {
return gc.shouldSetup
}
func (gc *GitCredentialFlow) Setup(hostname, protocol, username, authToken string) error {
return gc.gitCredentialSetup(hostname, protocol, username, authToken)
}
func (gc *GitCredentialFlow) gitCredentialSetup(hostname, protocol, username, password string) error {
if gc.helper == "" {
// first use a blank value to indicate to git we want to sever the chain of credential helpers
preConfigureCmd := git.GitCommand("config", "--global", gitCredentialHelperKey(hostname, protocol), "")
if err := run.PrepareCmd(preConfigureCmd).Run(); err != nil {
return err
}
// use glab as a credential helper (for this host only)
configureCmd := git.GitCommand(
"config", "--global", "--add",
gitCredentialHelperKey(hostname, protocol),
fmt.Sprintf("!%s auth git-credential", shellQuote(gc.Executable)),
)
return run.PrepareCmd(configureCmd).Run()
}
// clear previous cached credentials
rejectCmd := git.GitCommand("credential", "reject")
rejectCmd.Stdin = bytes.NewBufferString(heredoc.Docf(`
protocol=%s
host=%s
`, protocol, hostname))
err := run.PrepareCmd(rejectCmd).Run()
if err != nil {
return err
}
approveCmd := git.GitCommand("credential", "approve")
approveCmd.Stdin = bytes.NewBufferString(heredoc.Docf(`
protocol=https
host=%s
username=%s
password=%s
`, hostname, username, password))
err = run.PrepareCmd(approveCmd).Run()
if err != nil {
return err
}
return nil
}
func gitCredentialHelperKey(hostname, protocol string) string {
return fmt.Sprintf("credential.%s://%s.helper", protocol, hostname)
}
func gitCredentialHelper(hostname, protocol string) (helper string, err error) {
helper, err = git.Config(gitCredentialHelperKey(hostname, protocol))
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") == "glab"
}
func shellQuote(s string) string {
if strings.ContainsAny(s, " $") {
return "'" + s + "'"
}
return s
}