1
2
3
4
5
6 package auth
7
8 import (
9 "cmd/go/internal/base"
10 "cmd/go/internal/cfg"
11 "fmt"
12 "log"
13 "net/http"
14 "os"
15 "path"
16 "path/filepath"
17 "slices"
18 "strings"
19 "sync"
20 )
21
22 var (
23 credentialCache sync.Map
24 authOnce sync.Once
25 )
26
27
28
29
30
31 func AddCredentials(client *http.Client, req *http.Request, prefix string) bool {
32 if req.URL.Scheme != "https" {
33 panic("GOAUTH called without https")
34 }
35 if cfg.GOAUTH == "off" {
36 return false
37 }
38
39 authOnce.Do(func() {
40 runGoAuth(client, "")
41 })
42 if prefix != "" {
43
44 runGoAuth(client, prefix)
45 }
46 currentPrefix := strings.TrimPrefix(req.URL.String(), "https://")
47
48 for currentPrefix != "/" && currentPrefix != "." && currentPrefix != "" {
49 if loadCredential(req, currentPrefix) {
50 return true
51 }
52
53
54 currentPrefix = path.Dir(currentPrefix)
55 }
56 return false
57 }
58
59
60
61
62 func runGoAuth(client *http.Client, prefix string) {
63 var cmdErrs []error
64 goAuthCmds := strings.Split(cfg.GOAUTH, ";")
65
66
67 slices.Reverse(goAuthCmds)
68 for _, cmdStr := range goAuthCmds {
69 cmdStr = strings.TrimSpace(cmdStr)
70 cmdParts := strings.Fields(cmdStr)
71 if len(cmdParts) == 0 {
72 base.Fatalf("GOAUTH encountered an empty command (GOAUTH=%s)", cfg.GOAUTH)
73 }
74 switch cmdParts[0] {
75 case "off":
76 if len(goAuthCmds) != 1 {
77 base.Fatalf("GOAUTH=off cannot be combined with other authentication commands (GOAUTH=%s)", cfg.GOAUTH)
78 }
79 return
80 case "netrc":
81 lines, err := readNetrc()
82 if err != nil {
83 base.Fatalf("could not parse netrc (GOAUTH=%s): %v", cfg.GOAUTH, err)
84 }
85 for _, l := range lines {
86 r := http.Request{Header: make(http.Header)}
87 r.SetBasicAuth(l.login, l.password)
88 storeCredential([]string{l.machine}, r.Header)
89 }
90 case "git":
91 if len(cmdParts) != 2 {
92 base.Fatalf("GOAUTH=git dir method requires an absolute path to the git working directory")
93 }
94 dir := cmdParts[1]
95 if !filepath.IsAbs(dir) {
96 base.Fatalf("GOAUTH=git dir method requires an absolute path to the git working directory, dir is not absolute")
97 }
98 fs, err := os.Stat(dir)
99 if err != nil {
100 base.Fatalf("GOAUTH=git encountered an error; cannot stat %s: %v", dir, err)
101 }
102 if !fs.IsDir() {
103 base.Fatalf("GOAUTH=git dir method requires an absolute path to the git working directory, dir is not a directory")
104 }
105
106 if prefix == "" {
107
108
109 continue
110 }
111 prefix, header, err := runGitAuth(client, dir, prefix)
112 if err != nil {
113
114
115 cmdErrs = append(cmdErrs, fmt.Errorf("GOAUTH=%s: %v", cmdStr, err))
116 } else {
117 storeCredential([]string{strings.TrimPrefix(prefix, "https://")}, header)
118 }
119 default:
120 base.Fatalf("unimplemented: %s", cmdStr)
121 }
122 }
123
124
125 if cfg.BuildX && prefix != "" {
126 if _, ok := credentialCache.Load(prefix); !ok && len(cmdErrs) > 0 {
127 log.Printf("GOAUTH encountered errors for %s:", prefix)
128 for _, err := range cmdErrs {
129 log.Printf(" %v", err)
130 }
131 }
132 }
133 }
134
135
136
137 func loadCredential(req *http.Request, prefix string) bool {
138 headers, ok := credentialCache.Load(prefix)
139 if !ok {
140 return false
141 }
142 for key, values := range headers.(http.Header) {
143 for _, value := range values {
144 req.Header.Add(key, value)
145 }
146 }
147 return true
148 }
149
150
151
152 func storeCredential(prefixes []string, header http.Header) {
153 for _, prefix := range prefixes {
154 if len(header) == 0 {
155 credentialCache.Delete(prefix)
156 } else {
157 credentialCache.Store(prefix, header)
158 }
159 }
160 }
161
View as plain text