Allow gh auth git-credential to authenticate GitHub Gist requests (#3064)

* Allow `gh auth git-credential` to authenticate GitHub Gist requests

When there are stored credentials for `example.com`, allow using them to
authenticate requests to `gist.example.com` as well.

* Fix writing out of credential config

* remove unneccessary function

* actually delete

Co-authored-by: nate smith <vilmibm@github.com>
This commit is contained in:
Mislav Marohnić 2022-01-14 23:18:07 +01:00 committed by GitHub
parent 5c2ee024a2
commit 583af3e54c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 23 deletions

View file

@ -72,13 +72,23 @@ func RESTPrefix(hostname string) string {
}
func GistPrefix(hostname string) string {
prefix := "https://"
if strings.EqualFold(hostname, localhost) {
prefix = "http://"
}
return prefix + GistHost(hostname)
}
func GistHost(hostname string) string {
if IsEnterprise(hostname) {
return fmt.Sprintf("https://%s/gist/", hostname)
return fmt.Sprintf("%s/gist/", hostname)
}
if strings.EqualFold(hostname, localhost) {
return fmt.Sprintf("http://%s/gist/", hostname)
return fmt.Sprintf("%s/gist/", hostname)
}
return fmt.Sprintf("https://gist.%s/", hostname)
return fmt.Sprintf("gist.%s/", hostname)
}
func HostPrefix(hostname string) string {

View file

@ -100,12 +100,18 @@ func helperRun(opts *CredentialOptions) error {
return err
}
lookupHost := wants["host"]
var gotUser string
gotToken, source, _ := cfg.GetWithSource(wants["host"], "oauth_token")
gotToken, source, _ := cfg.GetWithSource(lookupHost, "oauth_token")
if gotToken == "" && strings.HasPrefix(lookupHost, "gist.") {
lookupHost = strings.TrimPrefix(lookupHost, "gist.")
gotToken, source, _ = cfg.GetWithSource(lookupHost, "oauth_token")
}
if strings.HasSuffix(source, "_TOKEN") {
gotUser = tokenUser
} else {
gotUser, _, _ = cfg.GetWithSource(wants["host"], "user")
gotUser, _, _ = cfg.GetWithSource(lookupHost, "user")
}
if gotUser == "" || gotToken == "" {

View file

@ -74,6 +74,32 @@ func Test_helperRun(t *testing.T) {
`),
wantStderr: "",
},
{
name: "gist host",
opts: CredentialOptions{
Operation: "get",
Config: func() (config, error) {
return tinyConfig{
"_source": "/Users/monalisa/.config/gh/hosts.yml",
"github.com:user": "monalisa",
"github.com:oauth_token": "OTOKEN",
}, nil
},
},
input: heredoc.Doc(`
protocol=https
host=gist.github.com
username=monalisa
`),
wantErr: false,
wantStdout: heredoc.Doc(`
protocol=https
host=gist.github.com
username=monalisa
password=OTOKEN
`),
wantStderr: "",
},
{
name: "url input",
opts: CredentialOptions{

View file

@ -63,25 +63,46 @@ func (flow *GitCredentialFlow) Setup(hostname, username, authToken string) error
func (flow *GitCredentialFlow) gitCredentialSetup(hostname, username, password string) error {
if flow.helper == "" {
// first use a blank value to indicate to git we want to sever the chain of credential helpers
preConfigureCmd, err := git.GitCommand("config", "--global", "--replace-all", gitCredentialHelperKey(hostname), "")
if err != nil {
return err
}
if err = run.PrepareCmd(preConfigureCmd).Run(); err != nil {
return err
credHelperKeys := []string{
gitCredentialHelperKey(hostname),
}
// use GitHub CLI as a credential helper (for this host only)
configureCmd, err := git.GitCommand(
"config", "--global", "--add",
gitCredentialHelperKey(hostname),
fmt.Sprintf("!%s auth git-credential", shellQuote(flow.Executable)),
)
if err != nil {
return err
gistHost := strings.TrimSuffix(ghinstance.GistHost(hostname), "/")
if strings.HasPrefix(gistHost, "gist.") {
credHelperKeys = append(credHelperKeys, gitCredentialHelperKey(gistHost))
}
return run.PrepareCmd(configureCmd).Run()
var configErr error
for _, credHelperKey := range credHelperKeys {
if configErr != nil {
break
}
// first use a blank value to indicate to git we want to sever the chain of credential helpers
preConfigureCmd, err := git.GitCommand("config", "--global", "--replace-all", credHelperKey, "")
if err != nil {
configErr = err
break
}
if err = run.PrepareCmd(preConfigureCmd).Run(); err != nil {
configErr = err
break
}
// second configure the actual helper for this host
configureCmd, err := git.GitCommand(
"config", "--global", "--add",
credHelperKey,
fmt.Sprintf("!%s auth git-credential", shellQuote(flow.Executable)),
)
if err != nil {
configErr = err
} else {
configErr = run.PrepareCmd(configureCmd).Run()
}
}
return configErr
}
// clear previous cached credentials
@ -121,7 +142,8 @@ func (flow *GitCredentialFlow) gitCredentialSetup(hostname, username, password s
}
func gitCredentialHelperKey(hostname string) string {
return fmt.Sprintf("credential.%s.helper", strings.TrimSuffix(ghinstance.HostPrefix(hostname), "/"))
host := strings.TrimSuffix(ghinstance.HostPrefix(hostname), "/")
return fmt.Sprintf("credential.%s.helper", host)
}
func gitCredentialHelper(hostname string) (helper string, err error) {

View file

@ -22,7 +22,54 @@ func TestGitCredentialSetup_configureExisting(t *testing.T) {
}
}
func TestGitCredentialSetup_setOurs(t *testing.T) {
func TestGitCredentialsSetup_setOurs_GH(t *testing.T) {
cs, restoreRun := run.Stub()
defer restoreRun(t)
cs.Register(`git config --global --replace-all credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "" {
t.Errorf("global credential helper configured to %q", val)
}
})
cs.Register(`git config --global --add credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "!/path/to/gh auth git-credential" {
t.Errorf("global credential helper configured to %q", val)
}
})
cs.Register(`git config --global --replace-all credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://gist.github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "" {
t.Errorf("global credential helper configured to %q", val)
}
})
cs.Register(`git config --global --add credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://gist.github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "!/path/to/gh auth git-credential" {
t.Errorf("global credential helper configured to %q", val)
}
})
f := GitCredentialFlow{
Executable: "/path/to/gh",
helper: "",
}
if err := f.gitCredentialSetup("github.com", "monalisa", "PASSWD"); err != nil {
t.Errorf("GitCredentialSetup() error = %v", err)
}
}
func TestGitCredentialSetup_setOurs_nonGH(t *testing.T) {
cs, restoreRun := run.Stub()
defer restoreRun(t)
cs.Register(`git config --global --replace-all credential\.`, 0, "", func(args []string) {