Git Lfs NTLM Work

* Switched the NTLM toggle to use the same url access pattern as private batch auth
* Updated the NTLM session to pull the credentials from the cred helper
This commit is contained in:
William Hipschman 2015-10-05 15:26:39 -04:00
parent 21159f8baa
commit d71daeeb04
5 changed files with 118 additions and 95 deletions

@ -14,6 +14,7 @@ import (
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)
@ -227,10 +228,17 @@ func Batch(objects []*objectResource, operation string) ([]*objectResource, erro
return nil, newRetriableError(err)
}
if IsAuthError(err) {
Config.SetAccess("basic")
tracerx.Printf("api: batch not authorized, submitting with auth")
return Batch(objects, operation)
tracerx.Printf("BATCH---------HEADER---: %s", res.Header["Www-Authenticate"][0][0:4])
if IsAuthError(err){
if strings.ToLower(res.Header["Www-Authenticate"][0][0:4]) == "ntlm" {
Config.SetAccess("ntlm")
tracerx.Printf("api: response indicates ntlm, submitting with ntlm auth")
return Batch(objects, operation)
} else {
Config.SetAccess("basic")
tracerx.Printf("api: batch not authorized, submitting with auth")
return Batch(objects, operation)
}
}
switch res.StatusCode {
@ -286,9 +294,15 @@ func UploadCheck(oidPath string) (*objectResource, error) {
if err != nil {
if IsAuthError(err) {
Config.SetAccess("basic")
tracerx.Printf("api: upload check not authorized, submitting with auth")
return UploadCheck(oidPath)
if strings.ToLower(res.Header["Www-Authenticate"][0][0:4]) == "ntlm" {
Config.SetAccess("ntlm")
tracerx.Printf("api: response indicates ntlm, submitting with ntlm auth")
return UploadCheck(oidPath)
} else{
Config.SetAccess("basic")
tracerx.Printf("api: upload check not authorized, submitting with auth")
return UploadCheck(oidPath)
}
}
return nil, newRetriableError(err)
@ -460,9 +474,17 @@ func doAPIRequest(req *http.Request, useCreds bool) (*http.Response, error) {
// doHttpRequest runs the given HTTP request. LFS or Storage API requests should
// use doApiBatchRequest() or doStorageRequest() instead.
func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
res, err := Config.HttpClient().Do(req)
if Config.NTLM() {
tracerx.Printf("ENTER doHttpRequest")
defer tracerx.Printf("LEAVE doHttpRequest")
var(
res *http.Response
err error
)
if Config.NtlmAccess() {
res, err = DoNTLMRequest(req, true)
} else {
res, err = Config.HttpClient().Do(req)
@ -478,7 +500,13 @@ func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
}
if err != nil {
err = Error(err)
if IsAuthError(err) && res.Header["Www-Authenticate"][0][0:4] == "ntlm" {
Config.SetAccess("ntlm")
tracerx.Printf("api: response indicates ntlm, submitting with ntlm auth")
doHttpRequest(req, creds)
} else {
err = Error(err)
}
} else {
err = handleResponse(res, creds)
}
@ -497,7 +525,7 @@ func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
func doApiRequestWithRedirects(req *http.Request, via []*http.Request, useCreds bool) (*http.Response, error) {
var creds Creds
if useCreds {
c, err := getCredsForAPI(req)
c, err := getCredsForAPI(req, false)
if err != nil {
return nil, err
}
@ -724,7 +752,7 @@ func setRequestAuthFromUrl(req *http.Request, u *url.URL) bool {
}
func setRequestAuth(req *http.Request, user, pass string) {
if(Config.NTLM()){
if(Config.NtlmAccess()){
//no-op. The NTLM manager will handle auth headers
} else {
if len(user) == 0 && len(pass) == 0 {
@ -759,9 +787,3 @@ func setErrorHeaderContext(err error, prefix string, head http.Header) {
}
}
}
func ntlmHandshake(){
if !Config.NTLM(){
panic("NTLM is not enabled but an NTLM handshake was attempted")
}
}

@ -129,34 +129,6 @@ func (c *Configuration) ConcurrentTransfers() int {
return uploads
}
func (c *Configuration) NTLM() bool {
if v, ok := c.GitConfig("lfs.ntlm"); ok {
if v == "true" || v == "" {
return true
}
// Any numeric value except 0 is considered true
if n, err := strconv.Atoi(v); err == nil && n != 0 {
return true
}
}
return false
}
func (c *Configuration) BatchTransfer() bool {
if v, ok := c.GitConfig("lfs.batch"); ok {
if v == "true" || v == "" {
return true
}
// Any numeric value except 0 is considered true
if n, err := strconv.Atoi(v); err == nil && n != 0 {
return true
}
}
return false
}
func (c *Configuration) BatchTransfer() bool {
value, ok := c.GitConfig("lfs.batch")
if !ok || len(value) == 0 {
@ -171,12 +143,16 @@ func (c *Configuration) BatchTransfer() bool {
return useBatch
}
func (c *Configuration) NtlmAccess() bool {
return c.Access() == "ntlm"
}
// PrivateAccess will retrieve the access value and return true if
// the value is set to private. When a repo is marked as having private
// access, the http requests for the batch api will fetch the credentials
// before running, otherwise the request will run without credentials.
func (c *Configuration) PrivateAccess() bool {
return c.Access() != "none"
return c.Access() != "none" && c.Access() != "ntlm"
}
// Access returns the access auth type.

@ -7,6 +7,7 @@ import (
"net/url"
"os/exec"
"strings"
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)
// getCreds gets the credentials for the given request's URL, and sets its
@ -14,6 +15,7 @@ import (
// getCredsForAPI(), but skips checking the LFS url or git remote.
func getCreds(req *http.Request) (Creds, error) {
if skipCredsCheck(req) {
tracerx.Printf("getCreds: skipping")
return nil, nil
}
@ -30,8 +32,9 @@ func getCreds(req *http.Request) (Creds, error) {
// This prefers the Git remote URL for checking credentials so that users only
// have to enter their passwords once for Git and Git LFS. It uses the same
// URL path that Git does, in case 'useHttpPath' is enabled in the Git config.
func getCredsForAPI(req *http.Request) (Creds, error) {
if skipCredsCheck(req) {
func getCredsForAPI(req *http.Request, skip bool) (Creds, error) {
if skip && skipCredsCheck(req) {
return nil, nil
}
@ -47,6 +50,10 @@ func getCredsForAPI(req *http.Request) (Creds, error) {
return fillCredentials(req, credsUrl)
}
func getCredsForNTLM(req *http.Request) (Creds, error){
return getCredsForAPI(req, false);
}
func getCredURLForAPI(req *http.Request) (*url.URL, error) {
apiUrl, err := Config.ObjectUrl("")
if err != nil {
@ -60,7 +67,7 @@ func getCredURLForAPI(req *http.Request) (*url.URL, error) {
return req.URL, nil
}
if setRequestAuthFromUrl(req, apiUrl) {
if !Config.NtlmAccess() && setRequestAuthFromUrl(req, apiUrl) {
return nil, nil
}
@ -75,7 +82,7 @@ func getCredURLForAPI(req *http.Request) (*url.URL, error) {
if gitRemoteUrl.Scheme == apiUrl.Scheme &&
gitRemoteUrl.Host == apiUrl.Host {
if setRequestAuthFromUrl(req, gitRemoteUrl) {
if !Config.NtlmAccess() && setRequestAuthFromUrl(req, gitRemoteUrl) {
return nil, nil
}
@ -83,7 +90,6 @@ func getCredURLForAPI(req *http.Request) (*url.URL, error) {
}
}
}
return credsUrl, nil
}
@ -105,7 +111,7 @@ func fillCredentials(req *http.Request, u *url.URL) (Creds, error) {
creds, err := execCreds(input, "fill")
if creds != nil && err == nil {
if creds != nil && err == nil && !Config.NtlmAccess() {
setRequestAuth(req, creds["username"], creds["password"])
}

@ -1,35 +1,30 @@
package lfs
import (
"io"
//"bufio"
//"net"
"net/http"
//"encoding/xml"
"encoding/base64"
"bytes"
"io/ioutil"
"fmt"
//"os"
"encoding/base64"
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
"io"
"io/ioutil"
"net/http"
// "strings"
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)
type myReader struct {
*bytes.Buffer
}
// So that it implements the io.ReadCloser interface
func (m myReader) Close() error { return nil }
func (c *Configuration) NTLMSession() ntlm.ClientSession {
func (c *Configuration) NTLMSession(creds Creds) ntlm.ClientSession {
if c.ntlmSession != nil {
return c.ntlmSession
}
tracerx.Printf("creds---------------------")
for key, val := range creds{
tracerx.Printf("%s:%s", key, val)
}
tracerx.Printf("-------------------------creds")
var session, _ = ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
session.SetUserInfo("user","pass","domain")
session.SetUserInfo(creds["username"], creds["password"], "NORTHAMERICA")
c.ntlmSession = session
@ -38,9 +33,8 @@ func (c *Configuration) NTLMSession() ntlm.ClientSession {
func DoNTLMRequest(request *http.Request, retry bool) (*http.Response, error) {
if !Config.NTLM() {
return nil, Error(fmt.Errorf("NTLM is not enabled"))
}
tracerx.Printf("ENTER DoNTLMRequest")
defer tracerx.Printf("LEAVE DoNTLMRequest")
handReq := cloneRequest(request)
res, nil := InitHandShake(handReq)
@ -48,25 +42,31 @@ func DoNTLMRequest(request *http.Request, retry bool) (*http.Response, error) {
//If the status is 401 then we need to re-authenticate, otherwise it was successful
if res.StatusCode == 401 {
creds, _ := getCredsForNTLM(request)
negotiateReq := cloneRequest(request)
challengeMessage := Negotiate(negotiateReq, getNegotiateMessage())
challengeMessage := negotiate(negotiateReq, getNegotiateMessage())
challengeReq := cloneRequest(request)
res, nil := Challenge(challengeReq, challengeMessage)
res, _ := challenge(challengeReq, challengeMessage, creds)
//If the status is 401 then we need to re-authenticate
if res.StatusCode == 401 && retry == true {
return DoNTLMRequest(challengeReq, false)
}
saveCredentials(creds, res)
return res, nil
}
return res, nil
}
func InitHandShake(request *http.Request) (*http.Response, error){
tracerx.Printf("ENTER InitHandShake")
defer tracerx.Printf("LEAVE InitHandShake")
var response, err = Config.HttpClient().Do(request)
if err != nil {
@ -76,7 +76,10 @@ func InitHandShake(request *http.Request) (*http.Response, error){
return response, nil
}
func Negotiate(request *http.Request, message string) []byte{
func negotiate(request *http.Request, message string) []byte{
tracerx.Printf("ENTER negotiate")
defer tracerx.Printf("LEAVE negotiate")
request.Header.Add("Authorization", message)
var response, err = Config.HttpClient().Do(request)
@ -85,18 +88,21 @@ func Negotiate(request *http.Request, message string) []byte{
panic(err.Error())
}
ret := ParseChallengeMessage(response)
ret := parseChallengeResponse(response)
//Always close negotiate to keep the connection alive
//We never return the response from negotiate so we
//can't trust decodeApiResponse to decode it
//can't trust decodeApiResponse to close it
io.Copy(ioutil.Discard, response.Body)
response.Body.Close()
return ret;
}
func Challenge(request *http.Request, challengeBytes []byte) (*http.Response, error){
func challenge(request *http.Request, challengeBytes []byte, creds Creds) (*http.Response, error){
tracerx.Printf("ENTER challenge")
defer tracerx.Printf("LEAVE challenge")
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
@ -104,14 +110,14 @@ func Challenge(request *http.Request, challengeBytes []byte) (*http.Response, er
return nil, Error(err)
}
Config.NTLMSession().ProcessChallengeMessage(challenge)
authenticate, err := Config.NTLMSession().GenerateAuthenticateMessage()
Config.NTLMSession(creds).ProcessChallengeMessage(challenge)
authenticate, err := Config.NTLMSession(creds).GenerateAuthenticateMessage()
if err != nil {
return nil, Error(err)
}
authenticateMessage := ConcatS("NTLM ", base64.StdEncoding.EncodeToString(authenticate.Bytes()))
authenticateMessage := concatS("NTLM ", base64.StdEncoding.EncodeToString(authenticate.Bytes()))
request.Header.Add("Authorization", authenticateMessage)
response, err := Config.HttpClient().Do(request)
@ -119,7 +125,10 @@ func Challenge(request *http.Request, challengeBytes []byte) (*http.Response, er
return response, nil
}
func ParseChallengeMessage(response *http.Response) []byte{
func parseChallengeResponse(response *http.Response) []byte{
tracerx.Printf("ENTER parseChallengeResponse")
defer tracerx.Printf("LEAVE parseChallengeResponse")
if headers, ok := response.Header["Www-Authenticate"]; ok{
@ -138,6 +147,9 @@ func ParseChallengeMessage(response *http.Response) []byte{
func cloneRequest(request *http.Request) *http.Request {
tracerx.Printf("ENTER cloneRequest")
defer tracerx.Printf("LEAVE cloneRequest")
var rdr1, rdr2 myReader
var clonedReq *http.Request
@ -172,7 +184,7 @@ func getNegotiateMessage() string{
return "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"
}
func ConcatS(ar ...string) string {
func concatS(ar ...string) string {
var buffer bytes.Buffer
@ -183,6 +195,13 @@ func ConcatS(ar ...string) string {
return buffer.String()
}
func Concat(ar ...[]byte) []byte {
func concat(ar ...[]byte) []byte {
return bytes.Join(ar, nil)
}
type myReader struct {
*bytes.Buffer
}
// So that myReader implements the io.ReadCloser interface
func (m myReader) Close() error { return nil }

@ -130,7 +130,7 @@ func (q *TransferQueue) Watch() chan string {
func (q *TransferQueue) individualApiRoutine(apiWaiter chan interface{}) {
for t := range q.apic {
obj, err := t.Check()
if err != nil {`
if err != nil {
tracerx.Printf("tq-willhi: t.Check() failed %s", t.Check)
if q.canRetry(err) {