1
2
3
4
5
6 package auth
7
8 import (
9 "bufio"
10 "bytes"
11 "cmd/internal/quoted"
12 "fmt"
13 "io"
14 "maps"
15 "net/http"
16 "net/textproto"
17 "os/exec"
18 "strings"
19 )
20
21
22
23
24
25
26 func runAuthCommand(command string, url string, res *http.Response) (map[string]http.Header, error) {
27 if command == "" {
28 panic("GOAUTH invoked an empty authenticator command:" + command)
29 }
30 cmd, err := buildCommand(command)
31 if err != nil {
32 return nil, err
33 }
34 if url != "" {
35 cmd.Args = append(cmd.Args, url)
36 }
37 cmd.Stderr = new(strings.Builder)
38 if res != nil && writeResponseToStdin(cmd, res) != nil {
39 return nil, fmt.Errorf("could not run command %s: %v\n%s", command, err, cmd.Stderr)
40 }
41 out, err := cmd.Output()
42 if err != nil {
43 return nil, fmt.Errorf("could not run command %s: %v\n%s", command, err, cmd.Stderr)
44 }
45 credentials, err := parseUserAuth(bytes.NewReader(out))
46 if err != nil {
47 return nil, fmt.Errorf("cannot parse output of GOAUTH command %s: %v", command, err)
48 }
49 return credentials, nil
50 }
51
52
53
54
55
56
57 func parseUserAuth(data io.Reader) (map[string]http.Header, error) {
58 credentials := make(map[string]http.Header)
59 reader := textproto.NewReader(bufio.NewReader(data))
60 for {
61
62 if _, err := reader.R.Peek(1); err == io.EOF {
63 return credentials, nil
64 }
65 urls, err := readURLs(reader)
66 if err != nil {
67 return nil, err
68 }
69 if len(urls) == 0 {
70 return nil, fmt.Errorf("invalid format: expected url prefix")
71 }
72 mimeHeader, err := reader.ReadMIMEHeader()
73 if err != nil {
74 return nil, err
75 }
76 header := http.Header(mimeHeader)
77
78 credentialMap := mapHeadersToPrefixes(urls, header)
79 maps.Copy(credentials, credentialMap)
80 }
81 }
82
83
84
85
86 func readURLs(reader *textproto.Reader) (urls []string, err error) {
87 for {
88 line, err := reader.ReadLine()
89 if err != nil {
90 return nil, err
91 }
92 trimmedLine := strings.TrimSpace(line)
93 if trimmedLine != line {
94 return nil, fmt.Errorf("invalid format: leading or trailing white space")
95 }
96 if strings.HasPrefix(line, "https://") {
97 urls = append(urls, line)
98 } else if line == "" {
99 return urls, nil
100 } else {
101 return nil, fmt.Errorf("invalid format: expected url prefix or empty line")
102 }
103 }
104 }
105
106
107
108 func mapHeadersToPrefixes(prefixes []string, header http.Header) map[string]http.Header {
109 prefixToHeaders := make(map[string]http.Header, len(prefixes))
110 for _, p := range prefixes {
111 p = strings.TrimPrefix(p, "https://")
112 prefixToHeaders[p] = header.Clone()
113 }
114 return prefixToHeaders
115 }
116
117 func buildCommand(command string) (*exec.Cmd, error) {
118 words, err := quoted.Split(command)
119 if err != nil {
120 return nil, fmt.Errorf("cannot parse GOAUTH command %s: %v", command, err)
121 }
122 cmd := exec.Command(words[0], words[1:]...)
123 return cmd, nil
124 }
125
126
127 func writeResponseToStdin(cmd *exec.Cmd, res *http.Response) error {
128 var output strings.Builder
129 output.WriteString(res.Proto + " " + res.Status + "\n")
130 if err := res.Header.Write(&output); err != nil {
131 return err
132 }
133 output.WriteString("\n")
134 cmd.Stdin = strings.NewReader(output.String())
135 return nil
136 }
137
View as plain text