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