git-lfs/lfs/ntlm.go

216 lines
4.7 KiB
Go
Raw Normal View History

package lfs
import (
"bytes"
"encoding/base64"
2015-10-07 18:30:53 +00:00
"errors"
2015-10-16 15:06:17 +00:00
"fmt"
2015-10-07 20:54:23 +00:00
"github.com/github/git-lfs/vendor/_nuts/github.com/ThomsonReutersEikon/go-ntlm/ntlm"
"io"
"io/ioutil"
"net/http"
"strings"
)
2015-10-07 18:30:53 +00:00
func (c *Configuration) ntlmClientSession(creds Creds) (ntlm.ClientSession, error) {
if c.ntlmSession != nil {
2015-10-07 18:30:53 +00:00
return c.ntlmSession, nil
}
splits := strings.Split(creds["username"], "\\")
2015-10-07 20:54:23 +00:00
if len(splits) != 2 {
2015-10-16 15:06:17 +00:00
errorMessage := fmt.Sprintf("Your user name must be of the form DOMAIN\\user. It is currently %s", creds["username"], "string")
return nil, errors.New(errorMessage)
2015-10-07 18:30:53 +00:00
}
2015-10-07 20:54:23 +00:00
2015-10-08 20:18:24 +00:00
session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-05 19:55:24 +00:00
session.SetUserInfo(splits[1], creds["password"], strings.ToUpper(splits[0]))
c.ntlmSession = session
2015-10-07 18:30:53 +00:00
return session, nil
}
2015-10-07 20:54:23 +00:00
func DoNTLMRequest(request *http.Request, retry bool) (*http.Response, error) {
2015-10-07 18:30:53 +00:00
handReq, err := cloneRequest(request)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
res, err := InitHandShake(handReq)
2015-10-07 20:54:23 +00:00
if err != nil && res == nil {
2015-10-08 20:18:24 +00:00
return nil, err
2015-10-07 18:30:53 +00:00
}
//If the status is 401 then we need to re-authenticate, otherwise it was successful
if res.StatusCode == 401 {
2015-10-07 20:54:23 +00:00
creds, err := getCredsForAPI(request)
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
negotiateReq, err := cloneRequest(request)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
challengeMessage, err := negotiate(negotiateReq, getNegotiateMessage())
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
challengeReq, err := cloneRequest(request)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
res, err := challenge(challengeReq, challengeMessage, creds)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-08 20:18:24 +00:00
return nil, err
2015-10-07 18:30:53 +00:00
}
2015-10-07 20:54:23 +00:00
2015-09-01 14:28:43 +00:00
//If the status is 401 then we need to re-authenticate
if res.StatusCode == 401 && retry == true {
return DoNTLMRequest(challengeReq, false)
}
2015-10-07 20:54:23 +00:00
saveCredentials(creds, res)
return res, nil
}
2015-10-08 20:18:24 +00:00
return res, nil
}
2015-10-07 20:54:23 +00:00
func InitHandShake(request *http.Request) (*http.Response, error) {
2015-10-07 18:30:53 +00:00
return Config.HttpClient().Do(request)
}
2015-10-07 20:54:23 +00:00
func negotiate(request *http.Request, message string) ([]byte, error) {
request.Header.Add("Authorization", message)
2015-10-08 20:18:24 +00:00
res, err := Config.HttpClient().Do(request)
2015-10-16 15:41:28 +00:00
if res == nil && err != nil {
2015-10-08 20:18:24 +00:00
return nil, err
}
ret, err := parseChallengeResponse(res)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-08 20:18:24 +00:00
defer res.Body.Close()
defer io.Copy(ioutil.Discard, res.Body)
2015-10-16 15:41:28 +00:00
return ret, nil
}
2015-10-07 20:54:23 +00:00
func challenge(request *http.Request, challengeBytes []byte, creds Creds) (*http.Response, error) {
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
session, err := Config.ntlmClientSession(creds)
if err != nil {
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
session.ProcessChallengeMessage(challenge)
authenticate, err := session.GenerateAuthenticateMessage()
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
authenticateMessage := concatS("NTLM ", base64.StdEncoding.EncodeToString(authenticate.Bytes()))
request.Header.Add("Authorization", authenticateMessage)
2015-10-07 18:30:53 +00:00
return Config.HttpClient().Do(request)
}
2015-10-07 20:54:23 +00:00
func parseChallengeResponse(response *http.Response) ([]byte, error) {
2015-10-07 18:30:53 +00:00
header := response.Header.Get("Www-Authenticate")
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
//parse out the "NTLM " at the beginning of the response
challenge := header[5:]
val, err := base64.StdEncoding.DecodeString(challenge)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
return []byte(val), nil
}
2015-10-07 18:30:53 +00:00
func cloneRequest(request *http.Request) (*http.Request, error) {
2015-08-31 18:34:17 +00:00
var rdr1, rdr2 myReader
var clonedReq *http.Request
2015-10-07 18:30:53 +00:00
var err error
2015-10-07 20:54:23 +00:00
2015-08-31 18:34:17 +00:00
if request.Body != nil {
2015-09-01 14:28:43 +00:00
//If we have a body (POST/PUT etc.)
2015-08-31 18:34:17 +00:00
//We need to do some magic to copy the request without closing the body stream
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
buf, err := ioutil.ReadAll(request.Body)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-08-31 18:34:17 +00:00
rdr1 = myReader{bytes.NewBuffer(buf)}
2015-10-07 20:54:23 +00:00
rdr2 = myReader{bytes.NewBuffer(buf)}
request.Body = rdr2 // OK since rdr2 implements the io.ReadCloser interface
clonedReq, err = http.NewRequest(request.Method, request.URL.String(), rdr1)
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-10-07 20:54:23 +00:00
2015-10-07 18:30:53 +00:00
} else {
clonedReq, err = http.NewRequest(request.Method, request.URL.String(), nil)
2015-10-07 20:54:23 +00:00
if err != nil {
2015-10-07 18:30:53 +00:00
return nil, err
}
2015-08-31 18:34:17 +00:00
}
2015-10-07 20:54:23 +00:00
2015-08-28 20:40:41 +00:00
for k, v := range request.Header {
2015-10-07 20:54:23 +00:00
clonedReq.Header.Add(k, v[0])
2015-08-28 20:40:41 +00:00
}
2015-10-07 20:54:23 +00:00
clonedReq.ContentLength = request.ContentLength
2015-10-07 18:30:53 +00:00
return clonedReq, nil
2015-08-28 20:40:41 +00:00
}
2015-10-07 20:54:23 +00:00
func getNegotiateMessage() string {
return "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"
}
func concatS(ar ...string) string {
2015-08-28 20:40:41 +00:00
2015-10-07 20:54:23 +00:00
var buffer bytes.Buffer
for _, s := range ar {
buffer.WriteString(s)
}
2015-08-28 20:40:41 +00:00
2015-10-07 20:54:23 +00:00
return buffer.String()
2015-08-28 20:40:41 +00:00
}
func concat(ar ...[]byte) []byte {
return bytes.Join(ar, nil)
}
type myReader struct {
2015-10-07 20:54:23 +00:00
*bytes.Buffer
}
// So that myReader implements the io.ReadCloser interface
2015-10-16 15:41:28 +00:00
func (m myReader) Close() error { return nil }