c752dcdf51
In a future commit, we're going to be accessing credential handling from multiple packages. To avoid an import loop, move credential handling into its own package. Update all the callers of the credential handling code to use a qualified name. Where there is a local variable called "creds", which would conflict with our package name, rename it "cred" instead.
141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
package lfsapi
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/git-lfs/git-lfs/creds"
|
|
"github.com/git-lfs/git-lfs/errors"
|
|
)
|
|
|
|
type ntmlCredentials struct {
|
|
domain string
|
|
username string
|
|
password string
|
|
}
|
|
|
|
func (c *Client) doWithNTLM(req *http.Request, credHelper creds.CredentialHelper, creds creds.Creds, credsURL *url.URL) (*http.Response, error) {
|
|
res, err := c.do(req, "", nil)
|
|
if err != nil && !errors.IsAuthError(err) {
|
|
return res, err
|
|
}
|
|
|
|
if res.StatusCode != 401 {
|
|
return res, nil
|
|
}
|
|
|
|
return c.ntlmReAuth(req, credHelper, creds, true)
|
|
}
|
|
|
|
// If the status is 401 then we need to re-authenticate
|
|
func (c *Client) ntlmReAuth(req *http.Request, credHelper creds.CredentialHelper, creds creds.Creds, retry bool) (*http.Response, error) {
|
|
ntmlCreds, err := ntlmGetCredentials(creds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err := c.ntlmAuthenticateRequest(req, ntmlCreds)
|
|
if err != nil && !errors.IsAuthError(err) {
|
|
return res, err
|
|
}
|
|
|
|
switch res.StatusCode {
|
|
case 401:
|
|
credHelper.Reject(creds)
|
|
if retry {
|
|
return c.ntlmReAuth(req, credHelper, creds, false)
|
|
}
|
|
case 403:
|
|
credHelper.Reject(creds)
|
|
default:
|
|
if res.StatusCode < 300 && res.StatusCode > 199 {
|
|
credHelper.Approve(creds)
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (c *Client) ntlmSendType1Message(req *http.Request, message []byte) (*http.Response, []byte, error) {
|
|
res, err := c.ntlmSendMessage(req, message)
|
|
if err != nil && !errors.IsAuthError(err) {
|
|
return res, nil, err
|
|
}
|
|
|
|
io.Copy(ioutil.Discard, res.Body)
|
|
res.Body.Close()
|
|
|
|
by, err := parseChallengeResponse(res)
|
|
return res, by, err
|
|
}
|
|
|
|
func (c *Client) ntlmSendType3Message(req *http.Request, authenticate []byte) (*http.Response, error) {
|
|
return c.ntlmSendMessage(req, authenticate)
|
|
}
|
|
|
|
func (c *Client) ntlmSendMessage(req *http.Request, message []byte) (*http.Response, error) {
|
|
body, err := rewoundRequestBody(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Body = body
|
|
|
|
msg := base64.StdEncoding.EncodeToString(message)
|
|
req.Header.Set("Authorization", "NTLM "+msg)
|
|
return c.do(req, "", nil)
|
|
}
|
|
|
|
func parseChallengeResponse(res *http.Response) ([]byte, error) {
|
|
header := res.Header.Get("Www-Authenticate")
|
|
if len(header) < 6 {
|
|
return nil, fmt.Errorf("Invalid NTLM challenge response: %q", header)
|
|
}
|
|
|
|
//parse out the "NTLM " at the beginning of the response
|
|
challenge := header[5:]
|
|
val, err := base64.StdEncoding.DecodeString(challenge)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(val), nil
|
|
}
|
|
|
|
func rewoundRequestBody(req *http.Request) (io.ReadCloser, error) {
|
|
if req.Body == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
body, ok := req.Body.(ReadSeekCloser)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Request body must implement io.ReadCloser and io.Seeker. Got: %T", body)
|
|
}
|
|
|
|
_, err := body.Seek(0, io.SeekStart)
|
|
return body, err
|
|
}
|
|
|
|
func ntlmGetCredentials(creds creds.Creds) (*ntmlCredentials, error) {
|
|
username := creds["username"]
|
|
password := creds["password"]
|
|
|
|
if username == "" && password == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
splits := strings.Split(username, "\\")
|
|
if len(splits) != 2 {
|
|
return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. It is currently '%s'", username)
|
|
}
|
|
|
|
domain := strings.ToUpper(splits[0])
|
|
username = splits[1]
|
|
|
|
return &ntmlCredentials{domain: domain, username: username, password: password}, nil
|
|
}
|