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:
parent
21159f8baa
commit
d71daeeb04
@ -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"])
|
||||
}
|
||||
|
||||
|
89
lfs/ntlm.go
89
lfs/ntlm.go
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user