github.com/greenpau/go-authcrunch@v1.1.4/cmd/authdbctl/connect.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "fmt" 19 "github.com/urfave/cli/v2" 20 "go.uber.org/zap" 21 "golang.org/x/net/html" 22 "log" 23 "net/http" 24 "net/url" 25 "strings" 26 ) 27 28 func connect(c *cli.Context) error { 29 wr := new(wrapper) 30 if err := wr.configure(c); err != nil { 31 return err 32 } 33 34 var formKind, formURI string 35 var formData url.Values 36 var counter int 37 for { 38 counter++ 39 if counter > 25 { 40 return fmt.Errorf("reached max attempts threshold") 41 } 42 43 if formKind == "" { 44 formKind = "login" 45 formURI = "/login" 46 formData = url.Values{} 47 formData.Set("username", wr.config.Username) 48 formData.Set("realm", wr.config.Realm) 49 50 req, _ := http.NewRequest(http.MethodGet, wr.config.BaseURL+"/", nil) 51 wr.browser.Do(req) 52 } 53 54 req, _ := http.NewRequest(http.MethodPost, wr.config.BaseURL+formURI, strings.NewReader(formData.Encode())) 55 req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 56 respBody, resp, err := wr.browser.Do(req) 57 if err != nil { 58 return fmt.Errorf("failed connecting to auth portal sandbox: %v", err) 59 } 60 61 redirectURL := resp.Header.Get("Location") 62 63 if redirectURL != "" { 64 wr.logger.Debug("request redirected", zap.String("redirect_url", redirectURL)) 65 req, _ := http.NewRequest(http.MethodGet, redirectURL, nil) 66 respBody, resp, err = wr.browser.Do(req) 67 for _, cookie := range resp.Cookies() { 68 if cookie.Name == wr.config.CookieName { 69 wr.config.token = cookie.Value 70 } 71 } 72 73 } 74 75 respData, err := parseResponse(respBody) 76 if err != nil { 77 return fmt.Errorf("failed parsing auth portal sandbox response: %v", err) 78 } 79 80 if len(respData) > 0 { 81 wr.logger.Debug("received response data", zap.Any("data", respData)) 82 } 83 84 formKind = respData["form_kind"] 85 var terminateLoop, continueLoop bool 86 switch formKind { 87 case "login", "password-auth": 88 formURI = respData["form_action"] 89 case "": 90 if redirectURL != "" && !strings.Contains(redirectURL, "/sandbox/") { 91 continueLoop = true 92 } else { 93 // Inspect headers and cookies for the presence of auth token. 94 terminateLoop = true 95 } 96 default: 97 return fmt.Errorf("the %q form is unsupported", formKind) 98 } 99 100 if terminateLoop { 101 wr.logger.Debug("logged in successfully") 102 break 103 } 104 105 if continueLoop { 106 continue 107 } 108 109 formData = url.Values{} 110 for k, v := range respData { 111 if !strings.HasPrefix(k, "input_") { 112 continue 113 } 114 k = strings.TrimPrefix(k, "input_") 115 if v != "" { 116 formData.Set(k, v) 117 } else { 118 switch { 119 case (k == "secret") && (formKind == "password-auth") && (wr.config.Password == ""): 120 input, err := wr.readUserInput("password") 121 if err != nil { 122 return err 123 } 124 formData.Set(k, string(input)) 125 case (k == "secret") && (formKind == "password-auth"): 126 formData.Set(k, wr.config.Password) 127 default: 128 return fmt.Errorf("the %q input in %q form is unsupported: %v", k, formKind, respData) 129 } 130 } 131 } 132 133 wr.logger.Debug( 134 "prepared form inputs", 135 zap.String("form_kind", formKind), 136 zap.String("form_uri", formURI), 137 zap.Any("data", formData), 138 ) 139 } 140 141 if wr.config.token != "" { 142 wr.logger.Debug( 143 "auth token acquired", 144 zap.String("token", wr.config.token), 145 ) 146 if err := wr.commitToken(); err != nil { 147 return err 148 } 149 log.Printf("auth token acquired: %s", wr.config.TokenPath) 150 return nil 151 } 152 return fmt.Errorf("failed to obtain auth token") 153 } 154 155 func parseResponse(s string) (map[string]string, error) { 156 m := make(map[string]string) 157 doc, err := html.Parse(strings.NewReader(s)) 158 if err != nil { 159 return nil, err 160 } 161 162 var f func(*html.Node) error 163 f = func(n *html.Node) error { 164 if n.Type == html.ElementNode && n.Data == "form" { 165 if err := parseResponseForm(m, n); err != nil { 166 return err 167 } 168 } 169 for c := n.FirstChild; c != nil; c = c.NextSibling { 170 if err := f(c); err != nil { 171 return err 172 } 173 } 174 return nil 175 } 176 if err := f(doc); err != nil { 177 return nil, err 178 } 179 180 return m, nil 181 } 182 183 func parseResponseForm(m map[string]string, doc *html.Node) error { 184 var formKind string 185 for _, a := range doc.Attr { 186 switch a.Key { 187 case "class", "action": 188 m["form_"+a.Key] = a.Val 189 } 190 if a.Key == "action" { 191 switch { 192 case strings.HasSuffix(a.Val, "/password-auth"): 193 formKind = "password-auth" 194 default: 195 return fmt.Errorf("detected unsupported form: %s", a.Val) 196 } 197 m["form_kind"] = formKind 198 } 199 } 200 201 if _, exists := m["form_kind"]; !exists { 202 return fmt.Errorf("failed to identify form kind") 203 } 204 205 var f func(*html.Node) error 206 f = func(n *html.Node) error { 207 if n.Data == "input" { 208 var elemKey, elemVal string 209 elem := make(map[string]string) 210 for _, a := range n.Attr { 211 switch a.Key { 212 case "id", "name", "value": 213 elem[a.Key] = a.Val 214 } 215 } 216 if _, exists := elem["name"]; !exists { 217 return fmt.Errorf("input has no name field: %v", elem) 218 } 219 elemKey = elem["name"] 220 221 if v, exists := elem["value"]; exists { 222 elemVal = v 223 } 224 m["input_"+elemKey] = elemVal 225 } 226 227 for c := n.FirstChild; c != nil; c = c.NextSibling { 228 if err := f(c); err != nil { 229 return err 230 } 231 } 232 return nil 233 } 234 if err := f(doc); err != nil { 235 return err 236 } 237 return nil 238 }