github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/http_authenticator.go (about) 1 /* 2 * Copyright (c) 2015, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package upstreamproxy 21 22 import ( 23 "fmt" 24 "net/http" 25 "strings" 26 ) 27 28 type HttpAuthState int 29 30 const ( 31 HTTP_AUTH_STATE_UNCHALLENGED HttpAuthState = iota 32 HTTP_AUTH_STATE_CHALLENGED 33 HTTP_AUTH_STATE_FAILURE 34 HTTP_AUTH_STATE_SUCCESS 35 ) 36 37 type HttpAuthenticator interface { 38 PreAuthenticate(req *http.Request) error 39 Authenticate(req *http.Request, resp *http.Response) error 40 IsConnectionBased() bool 41 IsComplete() bool 42 Reset() 43 } 44 45 func parseAuthChallenge(resp *http.Response) (map[string]string, error) { 46 challenges := make(map[string]string) 47 headers := resp.Header[http.CanonicalHeaderKey("proxy-authenticate")] 48 49 for _, val := range headers { 50 s := strings.SplitN(val, " ", 2) 51 if len(s) == 2 { 52 challenges[s[0]] = s[1] 53 } 54 if len(s) == 1 && s[0] != "" { 55 challenges[s[0]] = "" 56 } 57 } 58 if len(challenges) == 0 { 59 return nil, proxyError(fmt.Errorf("no valid challenges in the Proxy-Authenticate header")) 60 } 61 return challenges, nil 62 } 63 64 func NewHttpAuthenticator(resp *http.Response, username, password string) (HttpAuthenticator, error) { 65 66 challenges, err := parseAuthChallenge(resp) 67 if err != nil { 68 // Already wrapped in proxyError 69 return nil, err 70 } 71 72 // NTLM > Digest > Basic 73 if _, ok := challenges["NTLM"]; ok { 74 return newNTLMAuthenticator(username, password), nil 75 } else if _, ok := challenges["Digest"]; ok { 76 return newDigestAuthenticator(username, password), nil 77 } else if _, ok := challenges["Basic"]; ok { 78 return newBasicAuthenticator(username, password), nil 79 } 80 81 // Unsupported scheme 82 schemes := make([]string, 0, len(challenges)) 83 for scheme := range challenges { 84 schemes = append(schemes, scheme) 85 } 86 return nil, proxyError(fmt.Errorf("unsupported proxy authentication scheme in %v", schemes)) 87 }