github.com/secman-team/gh-api@v1.8.2/pkg/cmd/auth/shared/git_credential.go (about)

     1  package shared
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/AlecAivazis/survey/v2"
    10  	"github.com/MakeNowJust/heredoc"
    11  	"github.com/secman-team/gh-api/git"
    12  	"github.com/secman-team/gh-api/core/run"
    13  	"github.com/secman-team/gh-api/pkg/prompt"
    14  	"github.com/google/shlex"
    15  )
    16  
    17  type GitCredentialFlow struct {
    18  	Executable string
    19  
    20  	shouldSetup bool
    21  	helper      string
    22  	scopes      []string
    23  }
    24  
    25  func (flow *GitCredentialFlow) Prompt(hostname string) error {
    26  	flow.helper, _ = gitCredentialHelper(hostname)
    27  	if isOurCredentialHelper(flow.helper) {
    28  		flow.scopes = append(flow.scopes, "workflow")
    29  		return nil
    30  	}
    31  
    32  	err := prompt.SurveyAskOne(&survey.Confirm{
    33  		Message: "Authenticate Git with your GitHub credentials?",
    34  		Default: true,
    35  	}, &flow.shouldSetup)
    36  	if err != nil {
    37  		return fmt.Errorf("could not prompt: %w", err)
    38  	}
    39  	if flow.shouldSetup {
    40  		flow.scopes = append(flow.scopes, "workflow")
    41  	}
    42  
    43  	return nil
    44  }
    45  
    46  func (flow *GitCredentialFlow) Scopes() []string {
    47  	return flow.scopes
    48  }
    49  
    50  func (flow *GitCredentialFlow) ShouldSetup() bool {
    51  	return flow.shouldSetup
    52  }
    53  
    54  func (flow *GitCredentialFlow) Setup(hostname, username, authToken string) error {
    55  	return flow.gitCredentialSetup(hostname, username, authToken)
    56  }
    57  
    58  func (flow *GitCredentialFlow) gitCredentialSetup(hostname, username, password string) error {
    59  	if flow.helper == "" {
    60  		// first use a blank value to indicate to git we want to sever the chain of credential helpers
    61  		preConfigureCmd, err := git.GitCommand("config", "--global", gitCredentialHelperKey(hostname), "")
    62  		if err != nil {
    63  			return err
    64  		}
    65  		if err = run.PrepareCmd(preConfigureCmd).Run(); err != nil {
    66  			return err
    67  		}
    68  
    69  		// use GitHub CLI as a credential helper (for this host only)
    70  		configureCmd, err := git.GitCommand(
    71  			"config", "--global", "--add",
    72  			gitCredentialHelperKey(hostname),
    73  			fmt.Sprintf("!%s auth git-credential", shellQuote(flow.Executable)),
    74  		)
    75  		if err != nil {
    76  			return err
    77  		}
    78  		return run.PrepareCmd(configureCmd).Run()
    79  	}
    80  
    81  	// clear previous cached credentials
    82  	rejectCmd, err := git.GitCommand("credential", "reject")
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	rejectCmd.Stdin = bytes.NewBufferString(heredoc.Docf(`
    88  		protocol=https
    89  		host=%s
    90  	`, hostname))
    91  
    92  	err = run.PrepareCmd(rejectCmd).Run()
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	approveCmd, err := git.GitCommand("credential", "approve")
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	approveCmd.Stdin = bytes.NewBufferString(heredoc.Docf(`
   103  		protocol=https
   104  		host=%s
   105  		username=%s
   106  		password=%s
   107  	`, hostname, username, password))
   108  
   109  	err = run.PrepareCmd(approveCmd).Run()
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func gitCredentialHelperKey(hostname string) string {
   118  	return fmt.Sprintf("credential.https://%s.helper", hostname)
   119  }
   120  
   121  func gitCredentialHelper(hostname string) (helper string, err error) {
   122  	helper, err = git.Config(gitCredentialHelperKey(hostname))
   123  	if helper != "" {
   124  		return
   125  	}
   126  	helper, err = git.Config("credential.helper")
   127  	return
   128  }
   129  
   130  func isOurCredentialHelper(cmd string) bool {
   131  	if !strings.HasPrefix(cmd, "!") {
   132  		return false
   133  	}
   134  
   135  	args, err := shlex.Split(cmd[1:])
   136  	if err != nil || len(args) == 0 {
   137  		return false
   138  	}
   139  
   140  	return strings.TrimSuffix(filepath.Base(args[0]), ".exe") == "gh"
   141  }
   142  
   143  func shellQuote(s string) string {
   144  	if strings.ContainsAny(s, " $") {
   145  		return "'" + s + "'"
   146  	}
   147  	return s
   148  }