github.com/git-lfs/git-lfs@v2.5.2+incompatible/lfsapi/ntlm_test.go (about)

     1  package lfsapi
     2  
     3  import (
     4  	"encoding/base64"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"net/url"
     9  	"strings"
    10  	"sync/atomic"
    11  	"testing"
    12  
    13  	"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestNtlmAuth(t *testing.T) {
    19  	session, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
    20  	require.Nil(t, err)
    21  	session.SetUserInfo("ntlmuser", "ntlmpass", "NTLMDOMAIN")
    22  
    23  	var called uint32
    24  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    25  		reqIndex := atomic.LoadUint32(&called)
    26  		atomic.AddUint32(&called, 1)
    27  
    28  		authHeader := req.Header.Get("Authorization")
    29  		t.Logf("REQUEST %d: %s %s", reqIndex, req.Method, req.URL)
    30  		t.Logf("AUTH: %q", authHeader)
    31  
    32  		// assert full body is sent each time
    33  		by, err := ioutil.ReadAll(req.Body)
    34  		req.Body.Close()
    35  		if assert.Nil(t, err) {
    36  			assert.Equal(t, "ntlm", string(by))
    37  		}
    38  
    39  		switch called {
    40  		case 1:
    41  			w.Header().Set("Www-Authenticate", "ntlm")
    42  			w.WriteHeader(401)
    43  		case 2:
    44  			assert.True(t, strings.HasPrefix(req.Header.Get("Authorization"), "NTLM "))
    45  			neg := authHeader[5:] // strip "ntlm " prefix
    46  			_, err := base64.StdEncoding.DecodeString(neg)
    47  			if !assert.Nil(t, err) {
    48  				t.Logf("neg base64 error: %+v", err)
    49  				w.WriteHeader(500)
    50  				return
    51  			}
    52  
    53  			// ntlm implementation can't currently parse the negotiate message
    54  			ch, err := session.GenerateChallengeMessage()
    55  			if !assert.Nil(t, err) {
    56  				t.Logf("challenge gen error: %+v", err)
    57  				w.WriteHeader(500)
    58  				return
    59  			}
    60  			chMsg := base64.StdEncoding.EncodeToString(ch.Bytes())
    61  			w.Header().Set("Www-Authenticate", "ntlm "+chMsg)
    62  			w.WriteHeader(401)
    63  		default: // should be an auth msg
    64  			authHeader := req.Header.Get("Authorization")
    65  			assert.True(t, strings.HasPrefix(strings.ToUpper(authHeader), "NTLM "))
    66  			auth := authHeader[5:] // strip "ntlm " prefix
    67  			val, err := base64.StdEncoding.DecodeString(auth)
    68  			if !assert.Nil(t, err) {
    69  				t.Logf("auth base64 error: %+v", err)
    70  				w.WriteHeader(500)
    71  				return
    72  			}
    73  
    74  			authMsg, err := ntlm.ParseAuthenticateMessage(val, 2)
    75  			if !assert.Nil(t, err) {
    76  				t.Logf("auth parse error: %+v", err)
    77  				w.WriteHeader(500)
    78  				return
    79  			}
    80  
    81  			err = session.ProcessAuthenticateMessage(authMsg)
    82  			if !assert.Nil(t, err) {
    83  				t.Logf("auth process error: %+v", err)
    84  				w.WriteHeader(500)
    85  				return
    86  			}
    87  			w.WriteHeader(200)
    88  
    89  		}
    90  	}))
    91  	defer srv.Close()
    92  
    93  	req, err := http.NewRequest("POST", srv.URL+"/ntlm", NewByteBody([]byte("ntlm")))
    94  	require.Nil(t, err)
    95  
    96  	credHelper := newMockCredentialHelper()
    97  	cli, err := NewClient(NewContext(nil, nil, map[string]string{
    98  		"lfs.url":                         srv.URL + "/ntlm",
    99  		"lfs." + srv.URL + "/ntlm.access": "ntlm",
   100  	}))
   101  	cli.Credentials = credHelper
   102  	require.Nil(t, err)
   103  
   104  	// ntlm support pulls domain and login info from git credentials
   105  	srvURL, err := url.Parse(srv.URL)
   106  	require.Nil(t, err)
   107  	creds := Creds{
   108  		"protocol": srvURL.Scheme,
   109  		"host":     srvURL.Host,
   110  		"username": "ntlmdomain\\ntlmuser",
   111  		"password": "ntlmpass",
   112  	}
   113  	credHelper.Approve(creds)
   114  
   115  	res, err := cli.DoWithAuth("remote", req)
   116  	require.Nil(t, err)
   117  	assert.Equal(t, 200, res.StatusCode)
   118  	assert.True(t, credHelper.IsApproved(creds))
   119  }
   120  
   121  func TestNtlmGetCredentials(t *testing.T) {
   122  	creds := Creds{"username": "MOOSEDOMAIN\\canadian", "password": "MooseAntlersYeah"}
   123  	ntmlCreds, err := ntlmGetCredentials(creds)
   124  	assert.Nil(t, err)
   125  	assert.NotNil(t, ntmlCreds)
   126  	assert.Equal(t, "MOOSEDOMAIN", ntmlCreds.domain)
   127  	assert.Equal(t, "canadian", ntmlCreds.username)
   128  	assert.Equal(t, "MooseAntlersYeah", ntmlCreds.password)
   129  
   130  	creds = Creds{"username": "", "password": ""}
   131  	ntmlCreds, err = ntlmGetCredentials(creds)
   132  	assert.Nil(t, err)
   133  	assert.Nil(t, ntmlCreds)
   134  }
   135  
   136  func TestNtlmGetCredentialsBadCreds(t *testing.T) {
   137  	creds := Creds{"username": "badusername", "password": "MooseAntlersYeah"}
   138  	_, err := ntlmGetCredentials(creds)
   139  	assert.NotNil(t, err)
   140  }
   141  
   142  func TestNtlmHeaderParseValid(t *testing.T) {
   143  	res := http.Response{}
   144  	res.Header = make(map[string][]string)
   145  	res.Header.Add("Www-Authenticate", "NTLM "+base64.StdEncoding.EncodeToString([]byte("I am a moose")))
   146  	bytes, err := parseChallengeResponse(&res)
   147  	assert.Nil(t, err)
   148  	assert.False(t, strings.HasPrefix(string(bytes), "NTLM"))
   149  }
   150  
   151  func TestNtlmHeaderParseInvalidLength(t *testing.T) {
   152  	res := http.Response{}
   153  	res.Header = make(map[string][]string)
   154  	res.Header.Add("Www-Authenticate", "NTL")
   155  	ret, err := parseChallengeResponse(&res)
   156  	assert.NotNil(t, err)
   157  	assert.Nil(t, ret)
   158  }
   159  
   160  func TestNtlmHeaderParseInvalid(t *testing.T) {
   161  	res := http.Response{}
   162  	res.Header = make(map[string][]string)
   163  	res.Header.Add("Www-Authenticate", base64.StdEncoding.EncodeToString([]byte("NTLM I am a moose")))
   164  	ret, err := parseChallengeResponse(&res)
   165  	assert.NotNil(t, err)
   166  	assert.Nil(t, ret)
   167  }
   168  
   169  func assertRequestsEqual(t *testing.T, req1 *http.Request, req2 *http.Request, req1Body []byte) {
   170  	assert.Equal(t, req1.Method, req2.Method)
   171  
   172  	for k, v := range req1.Header {
   173  		assert.Equal(t, v, req2.Header[k])
   174  	}
   175  
   176  	if req1.Body == nil {
   177  		assert.Nil(t, req2.Body)
   178  	} else {
   179  		bytes2, _ := ioutil.ReadAll(req2.Body)
   180  		assert.Equal(t, req1Body, bytes2)
   181  	}
   182  }