github.com/git-lfs/git-lfs@v2.5.2+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/git-lfs/git-lfs/errors"
    13  )
    14  
    15  type ntmlCredentials struct {
    16  	domain   string
    17  	username string
    18  	password string
    19  }
    20  
    21  func (c *Client) doWithNTLM(req *http.Request, credHelper CredentialHelper, creds Creds, credsURL *url.URL) (*http.Response, error) {
    22  	res, err := c.do(req, "", nil)
    23  	if err != nil && !errors.IsAuthError(err) {
    24  		return res, err
    25  	}
    26  
    27  	if res.StatusCode != 401 {
    28  		return res, nil
    29  	}
    30  
    31  	return c.ntlmReAuth(req, credHelper, creds, true)
    32  }
    33  
    34  // If the status is 401 then we need to re-authenticate
    35  func (c *Client) ntlmReAuth(req *http.Request, credHelper CredentialHelper, creds Creds, retry bool) (*http.Response, error) {
    36  	ntmlCreds, err := ntlmGetCredentials(creds)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	res, err := c.ntlmAuthenticateRequest(req, ntmlCreds)
    42  	if err != nil && !errors.IsAuthError(err) {
    43  		return res, err
    44  	}
    45  
    46  	switch res.StatusCode {
    47  	case 401:
    48  		credHelper.Reject(creds)
    49  		if retry {
    50  			return c.ntlmReAuth(req, credHelper, creds, false)
    51  		}
    52  	case 403:
    53  		credHelper.Reject(creds)
    54  	default:
    55  		if res.StatusCode < 300 && res.StatusCode > 199 {
    56  			credHelper.Approve(creds)
    57  		}
    58  	}
    59  
    60  	return res, nil
    61  }
    62  
    63  func (c *Client) ntlmSendType1Message(req *http.Request, message []byte) (*http.Response, []byte, error) {
    64  	res, err := c.ntlmSendMessage(req, message)
    65  	if err != nil && !errors.IsAuthError(err) {
    66  		return res, nil, err
    67  	}
    68  
    69  	io.Copy(ioutil.Discard, res.Body)
    70  	res.Body.Close()
    71  
    72  	by, err := parseChallengeResponse(res)
    73  	return res, by, err
    74  }
    75  
    76  func (c *Client) ntlmSendType3Message(req *http.Request, authenticate []byte) (*http.Response, error) {
    77  	return c.ntlmSendMessage(req, authenticate)
    78  }
    79  
    80  func (c *Client) ntlmSendMessage(req *http.Request, message []byte) (*http.Response, error) {
    81  	body, err := rewoundRequestBody(req)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	req.Body = body
    86  
    87  	msg := base64.StdEncoding.EncodeToString(message)
    88  	req.Header.Set("Authorization", "NTLM "+msg)
    89  	return c.do(req, "", nil)
    90  }
    91  
    92  func parseChallengeResponse(res *http.Response) ([]byte, error) {
    93  	header := res.Header.Get("Www-Authenticate")
    94  	if len(header) < 6 {
    95  		return nil, fmt.Errorf("Invalid NTLM challenge response: %q", header)
    96  	}
    97  
    98  	//parse out the "NTLM " at the beginning of the response
    99  	challenge := header[5:]
   100  	val, err := base64.StdEncoding.DecodeString(challenge)
   101  
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	return []byte(val), nil
   106  }
   107  
   108  func rewoundRequestBody(req *http.Request) (io.ReadCloser, error) {
   109  	if req.Body == nil {
   110  		return nil, nil
   111  	}
   112  
   113  	body, ok := req.Body.(ReadSeekCloser)
   114  	if !ok {
   115  		return nil, fmt.Errorf("Request body must implement io.ReadCloser and io.Seeker. Got: %T", body)
   116  	}
   117  
   118  	_, err := body.Seek(0, io.SeekStart)
   119  	return body, err
   120  }
   121  
   122  func ntlmGetCredentials(creds Creds) (*ntmlCredentials, error) {
   123  	username := creds["username"]
   124  	password := creds["password"]
   125  
   126  	if username == "" && password == "" {
   127  		return nil, nil
   128  	}
   129  
   130  	splits := strings.Split(username, "\\")
   131  	if len(splits) != 2 {
   132  		return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. It is currently '%s'", username)
   133  	}
   134  
   135  	domain := strings.ToUpper(splits[0])
   136  	username = splits[1]
   137  
   138  	return &ntmlCredentials{domain: domain, username: username, password: password}, nil
   139  }