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"