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 }