github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/lfsapi/ntlm.go (about) 1 package lfsapi 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "strings" 11 12 "github.com/ThomsonReutersEikon/go-ntlm/ntlm" 13 "github.com/git-lfs/git-lfs/errors" 14 ) 15 16 func (c *Client) doWithNTLM(req *http.Request, credHelper CredentialHelper, creds Creds, credsURL *url.URL) (*http.Response, error) { 17 res, err := c.do(req) 18 if err != nil && !errors.IsAuthError(err) { 19 return res, err 20 } 21 22 if res.StatusCode != 401 { 23 return res, nil 24 } 25 26 return c.ntlmReAuth(req, credHelper, creds, true) 27 } 28 29 // If the status is 401 then we need to re-authenticate 30 func (c *Client) ntlmReAuth(req *http.Request, credHelper CredentialHelper, creds Creds, retry bool) (*http.Response, error) { 31 body, err := rewoundRequestBody(req) 32 if err != nil { 33 return nil, err 34 } 35 req.Body = body 36 37 chRes, challengeMsg, err := c.ntlmNegotiate(req, ntlmNegotiateMessage) 38 if err != nil { 39 return chRes, err 40 } 41 42 body, err = rewoundRequestBody(req) 43 if err != nil { 44 return nil, err 45 } 46 req.Body = body 47 48 res, err := c.ntlmChallenge(req, challengeMsg, creds) 49 if err != nil { 50 return res, err 51 } 52 53 switch res.StatusCode { 54 case 401: 55 credHelper.Reject(creds) 56 if retry { 57 return c.ntlmReAuth(req, credHelper, creds, false) 58 } 59 case 403: 60 credHelper.Reject(creds) 61 default: 62 if res.StatusCode < 300 && res.StatusCode > 199 { 63 credHelper.Approve(creds) 64 } 65 } 66 67 return res, nil 68 } 69 70 func (c *Client) ntlmNegotiate(req *http.Request, message string) (*http.Response, []byte, error) { 71 req.Header.Add("Authorization", message) 72 res, err := c.do(req) 73 if err != nil && !errors.IsAuthError(err) { 74 return res, nil, err 75 } 76 77 io.Copy(ioutil.Discard, res.Body) 78 res.Body.Close() 79 80 by, err := parseChallengeResponse(res) 81 return res, by, err 82 } 83 84 func (c *Client) ntlmChallenge(req *http.Request, challengeBytes []byte, creds Creds) (*http.Response, error) { 85 challenge, err := ntlm.ParseChallengeMessage(challengeBytes) 86 if err != nil { 87 return nil, err 88 } 89 90 session, err := c.ntlmClientSession(creds) 91 if err != nil { 92 return nil, err 93 } 94 95 session.ProcessChallengeMessage(challenge) 96 authenticate, err := session.GenerateAuthenticateMessage() 97 if err != nil { 98 return nil, err 99 } 100 101 authMsg := base64.StdEncoding.EncodeToString(authenticate.Bytes()) 102 req.Header.Set("Authorization", "NTLM "+authMsg) 103 return c.do(req) 104 } 105 106 func (c *Client) ntlmClientSession(creds Creds) (ntlm.ClientSession, error) { 107 c.ntlmMu.Lock() 108 defer c.ntlmMu.Unlock() 109 110 splits := strings.Split(creds["username"], "\\") 111 if len(splits) != 2 { 112 return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. It is currently %s", creds["username"]) 113 } 114 115 domain := strings.ToUpper(splits[0]) 116 username := splits[1] 117 118 if c.ntlmSessions == nil { 119 c.ntlmSessions = make(map[string]ntlm.ClientSession) 120 } 121 122 if ses, ok := c.ntlmSessions[domain]; ok { 123 return ses, nil 124 } 125 126 session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode) 127 if err != nil { 128 return nil, err 129 } 130 131 session.SetUserInfo(username, creds["password"], strings.ToUpper(splits[0])) 132 c.ntlmSessions[domain] = session 133 return session, nil 134 } 135 136 func parseChallengeResponse(res *http.Response) ([]byte, error) { 137 header := res.Header.Get("Www-Authenticate") 138 if len(header) < 6 { 139 return nil, fmt.Errorf("Invalid NTLM challenge response: %q", header) 140 } 141 142 //parse out the "NTLM " at the beginning of the response 143 challenge := header[5:] 144 val, err := base64.StdEncoding.DecodeString(challenge) 145 146 if err != nil { 147 return nil, err 148 } 149 return []byte(val), nil 150 } 151 152 func rewoundRequestBody(req *http.Request) (io.ReadCloser, error) { 153 if req.Body == nil { 154 return nil, nil 155 } 156 157 body, ok := req.Body.(ReadSeekCloser) 158 if !ok { 159 return nil, fmt.Errorf("Request body must implement io.ReadCloser and io.Seeker. Got: %T", body) 160 } 161 162 _, err := body.Seek(0, io.SeekStart) 163 return body, err 164 } 165 166 const ntlmNegotiateMessage = "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"