1
2
3
4
5
6
7
8
9
10 package auth
11
12 import (
13 "bytes"
14 "cmd/go/internal/base"
15 "cmd/go/internal/cfg"
16 "cmd/go/internal/web/intercept"
17 "fmt"
18 "log"
19 "net/http"
20 "net/url"
21 "os/exec"
22 "strings"
23 )
24
25 const maxTries = 3
26
27
28
29
30
31
32
33 func runGitAuth(client *http.Client, dir, url string) (string, http.Header, error) {
34 if url == "" {
35
36
37
38 return "", nil, fmt.Errorf("no explicit url was passed")
39 }
40 if dir == "" {
41
42
43 panic("'git' invoked in an arbitrary directory")
44 }
45 cmd := exec.Command("git", "credential", "fill")
46 cmd.Dir = dir
47 cmd.Stdin = strings.NewReader(fmt.Sprintf("url=%s\n", url))
48 out, err := cmd.CombinedOutput()
49 if err != nil {
50 return "", nil, fmt.Errorf("'git credential fill' failed (url=%s): %w\n%s", url, err, out)
51 }
52 parsedPrefix, username, password := parseGitAuth(out)
53 if parsedPrefix == "" {
54 return "", nil, fmt.Errorf("'git credential fill' failed for url=%s, could not parse url\n", url)
55 }
56
57 if !strings.HasPrefix(url, parsedPrefix) {
58 return "", nil, fmt.Errorf("requested a credential for %s, but 'git credential fill' provided one for %s\n", url, parsedPrefix)
59 }
60 req, err := http.NewRequest("HEAD", parsedPrefix, nil)
61 if err != nil {
62 return "", nil, fmt.Errorf("internal error constructing HTTP HEAD request: %v\n", err)
63 }
64 req.SetBasicAuth(username, password)
65
66
67
68
69
70
71
72 intercept.Request(req)
73 go updateGitCredentialHelper(client, req, out)
74
75
76
77 return parsedPrefix, req.Header, nil
78 }
79
80
81
82
83 func parseGitAuth(data []byte) (parsedPrefix, username, password string) {
84 prefix := new(url.URL)
85 for _, line := range strings.Split(string(data), "\n") {
86 key, value, ok := strings.Cut(strings.TrimSpace(line), "=")
87 if !ok {
88 continue
89 }
90 switch key {
91 case "protocol":
92 prefix.Scheme = value
93 case "host":
94 prefix.Host = value
95 case "path":
96 prefix.Path = value
97 case "username":
98 username = value
99 case "password":
100 password = value
101 case "url":
102
103
104
105 u, err := url.ParseRequestURI(value)
106 if err != nil {
107 if cfg.BuildX {
108 log.Printf("malformed URL from 'git credential fill' (%v): %q\n", err, value)
109
110 }
111 continue
112 }
113 prefix = u
114 }
115 }
116 return prefix.String(), username, password
117 }
118
119
120
121
122 func updateGitCredentialHelper(client *http.Client, req *http.Request, credentialOutput []byte) {
123 for range maxTries {
124 release, err := base.AcquireNet()
125 if err != nil {
126 return
127 }
128 res, err := client.Do(req)
129 if err != nil {
130 release()
131 continue
132 }
133 res.Body.Close()
134 release()
135 if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusUnauthorized {
136 approveOrRejectCredential(credentialOutput, res.StatusCode == http.StatusOK)
137 break
138 }
139 }
140 }
141
142
143
144 func approveOrRejectCredential(credentialOutput []byte, approve bool) {
145 action := "reject"
146 if approve {
147 action = "approve"
148 }
149 cmd := exec.Command("git", "credential", action)
150 cmd.Stdin = bytes.NewReader(credentialOutput)
151 cmd.Run()
152 }
153
View as plain text