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 }