diff --git a/lfsapi/auth.go b/lfsapi/auth.go index 2488d5d2..d86b2a69 100644 --- a/lfsapi/auth.go +++ b/lfsapi/auth.go @@ -21,29 +21,7 @@ var ( ) func (c *Client) DoWithAuth(remote string, req *http.Request) (*http.Response, error) { - credHelper := c.Credentials - if credHelper == nil { - var err error - credHelper, err = getCredentialHelper(c.osEnv, c.gitEnv) - if err != nil { - tracerx.Printf("error getting credential helper: %s", err) - } - if credHelper == nil { - credHelper = defaultCredentialHelper - } - } - - netrcFinder := c.Netrc - if netrcFinder == nil { - netrcFinder = defaultNetrcFinder - } - - ef := c.Endpoints - if ef == nil { - ef = defaultEndpointFinder - } - - apiEndpoint, access, creds, credsURL, err := getCreds(credHelper, netrcFinder, ef, remote, req) + apiEndpoint, access, credHelper, credsURL, creds, err := c.getCreds(remote, req) if err != nil { return nil, err } @@ -101,32 +79,48 @@ func (c *Client) doWithCreds(req *http.Request, credHelper CredentialHelper, cre // 3. The Git Remote URL, which should be something like "https://git.com/repo.git" // This URL is used for the Git Credential Helper. This way existing https // Git remote credentials can be re-used for LFS. -func getCreds(credHelper CredentialHelper, netrcFinder NetrcFinder, ef EndpointFinder, remote string, req *http.Request) (Endpoint, Access, Creds, *url.URL, error) { +func (c *Client) getCreds(remote string, req *http.Request) (Endpoint, Access, CredentialHelper, *url.URL, Creds, error) { + ef := c.Endpoints + if ef == nil { + ef = defaultEndpointFinder + } + + netrcFinder := c.Netrc + if netrcFinder == nil { + netrcFinder = defaultNetrcFinder + } + operation := getReqOperation(req) apiEndpoint := ef.Endpoint(operation, remote) access := ef.AccessFor(apiEndpoint.Url) if access != NTLMAccess { if requestHasAuth(req) || setAuthFromNetrc(netrcFinder, req) || access == NoneAccess { - return apiEndpoint, access, nil, nil, nil + return apiEndpoint, access, nullCreds, nil, nil, nil } credsURL, err := getCredURLForAPI(ef, operation, remote, apiEndpoint, req) if err != nil { - return apiEndpoint, access, nil, nil, errors.Wrap(err, "creds") + return apiEndpoint, access, nullCreds, nil, nil, errors.Wrap(err, "creds") } if credsURL == nil { - return apiEndpoint, access, nil, nil, nil + return apiEndpoint, access, nullCreds, nil, nil, nil } - creds, err := fillGitCreds(credHelper, ef, req, credsURL) - return apiEndpoint, access, creds, credsURL, err + credHelper, creds, err := c.getGitCreds(ef, req, credsURL) + if err == nil { + tracerx.Printf("Filled credentials for %s", credsURL) + setRequestAuth(req, creds["username"], creds["password"]) + } + return apiEndpoint, access, credHelper, credsURL, creds, err } + // NTLM ONLY + credsURL, err := url.Parse(apiEndpoint.Url) if err != nil { - return apiEndpoint, access, nil, nil, errors.Wrap(err, "creds") + return apiEndpoint, access, nullCreds, nil, nil, errors.Wrap(err, "creds") } if netrcMachine := getAuthFromNetrc(netrcFinder, req); netrcMachine != nil { @@ -138,20 +132,16 @@ func getCreds(credHelper CredentialHelper, netrcFinder NetrcFinder, ef EndpointF "source": "netrc", } - return apiEndpoint, access, creds, credsURL, nil + return apiEndpoint, access, nullCreds, credsURL, creds, nil } - creds, err := getGitCreds(credHelper, ef, req, credsURL) - return apiEndpoint, access, creds, credsURL, err + // NTLM uses creds to create the session + credHelper, creds, err := c.getGitCreds(ef, req, credsURL) + return apiEndpoint, access, credHelper, credsURL, creds, err } -func getGitCreds(credHelper CredentialHelper, ef EndpointFinder, req *http.Request, u *url.URL) (Creds, error) { - path := strings.TrimPrefix(u.Path, "/") - input := Creds{"protocol": u.Scheme, "host": u.Host, "path": path} - if u.User != nil && u.User.Username() != "" { - input["username"] = u.User.Username() - } - +func (c *Client) getGitCreds(ef EndpointFinder, req *http.Request, u *url.URL) (CredentialHelper, Creds, error) { + credHelper, input := c.getCredentialHelper(u) creds, err := credHelper.Fill(input) if creds == nil || len(creds) < 1 { errmsg := fmt.Sprintf("Git credentials for %s not found", u) @@ -163,17 +153,7 @@ func getGitCreds(credHelper CredentialHelper, ef EndpointFinder, req *http.Reque err = errors.New(errmsg) } - return creds, err -} - -func fillGitCreds(credHelper CredentialHelper, ef EndpointFinder, req *http.Request, u *url.URL) (Creds, error) { - creds, err := getGitCreds(credHelper, ef, req, u) - if err == nil { - tracerx.Printf("Filled credentials for %s", u) - setRequestAuth(req, creds["username"], creds["password"]) - } - - return creds, err + return credHelper, creds, err } func getAuthFromNetrc(netrcFinder NetrcFinder, req *http.Request) *netrc.Machine { diff --git a/lfsapi/auth_test.go b/lfsapi/auth_test.go index daedcdc1..e55e45e0 100644 --- a/lfsapi/auth_test.go +++ b/lfsapi/auth_test.go @@ -517,8 +517,10 @@ func TestGetCreds(t *testing.T) { }, } - credHelper := &fakeCredentialFiller{} - netrcFinder := &fakeNetrc{} + client := &Client{ + Credentials: &fakeCredentialFiller{}, + Netrc: &fakeNetrc{}, + } for desc, test := range tests { t.Log(desc) req, err := http.NewRequest(test.Method, test.Href, nil) @@ -531,8 +533,8 @@ func TestGetCreds(t *testing.T) { req.Header.Set(key, value) } - ef := NewEndpointFinder(NewContext(nil, nil, test.Config)) - endpoint, access, creds, credsURL, err := getCreds(credHelper, netrcFinder, ef, test.Remote, req) + client.Endpoints = NewEndpointFinder(NewContext(nil, nil, test.Config)) + endpoint, access, _, credsURL, creds, err := client.getCreds(test.Remote, req) if !assert.Nil(t, err) { continue } diff --git a/lfsapi/creds.go b/lfsapi/creds.go index 187043b1..745e5af7 100644 --- a/lfsapi/creds.go +++ b/lfsapi/creds.go @@ -8,86 +8,61 @@ import ( "strings" "sync" - "github.com/git-lfs/git-lfs/config" "github.com/git-lfs/git-lfs/errors" "github.com/rubyist/tracerx" ) -// credsConfig supplies configuration options pertaining to the authorization -// process in package lfsapi. -type credsConfig struct { - // AskPass is a string containing an executable name as well as a - // program arguments. - // - // See: https://git-scm.com/docs/gitcredentials#_requesting_credentials - // for more. - AskPass string `os:"GIT_ASKPASS" git:"core.askpass" os:"SSH_ASKPASS"` - // Helper is a string defining the credential helper that Git should use. - Helper string `git:"credential.helper"` - // Cached is a boolean determining whether or not to enable the - // credential cacher. - Cached bool - // SkipPrompt is a boolean determining whether or not to prompt the user - // for a password. - SkipPrompt bool `os:"GIT_TERMINAL_PROMPT"` -} - // getCredentialHelper parses a 'credsConfig' from the git and OS environments, // returning the appropriate CredentialHelper to authenticate requests with. // // It returns an error if any configuration was invalid, or otherwise // un-useable. -func getCredentialHelper(osEnv, gitEnv config.Environment) (CredentialHelper, error) { - ccfg, err := getCredentialConfig(osEnv, gitEnv) - if err != nil { - return nil, err +func (c *Client) getCredentialHelper(u *url.URL) (CredentialHelper, Creds) { + path := strings.TrimPrefix(u.Path, "/") + input := Creds{"protocol": u.Scheme, "host": u.Host, "path": path} + if u.User != nil && u.User.Username() != "" { + input["username"] = u.User.Username() } + if c.Credentials != nil { + return c.Credentials, input + } + + askpass, ok := c.osEnv.Get("GIT_ASKPASS") + if !ok { + askpass, ok = c.gitEnv.Get("core.askpass") + } + if !ok { + askpass, ok = c.osEnv.Get("SSH_ASKPASS") + } + helper, _ := c.gitEnv.Get("credential.helper") + cached := c.gitEnv.Bool("lfs.cachecredentials", true) + skipPrompt := c.osEnv.Bool("GIT_TERMINAL_PROMPT", false) + var hs []CredentialHelper - if len(ccfg.Helper) == 0 && len(ccfg.AskPass) > 0 { + if len(helper) == 0 && len(askpass) > 0 { hs = append(hs, &AskPassCredentialHelper{ - Program: ccfg.AskPass, + Program: askpass, }) } var h CredentialHelper h = &commandCredentialHelper{ - SkipPrompt: ccfg.SkipPrompt, + SkipPrompt: skipPrompt, } - if ccfg.Cached { + if cached { h = withCredentialCache(h) } hs = append(hs, h) switch len(hs) { case 0: - return nil, nil + return defaultCredentialHelper, input case 1: - return hs[0], nil + return hs[0], input } - return CredentialHelpers(hs), nil -} - -// getCredentialConfig parses a *credsConfig given the OS and Git -// configurations. -func getCredentialConfig(o, g config.Environment) (*credsConfig, error) { - askpass, ok := o.Get("GIT_ASKPASS") - if !ok { - askpass, ok = g.Get("core.askpass") - } - if !ok { - askpass, ok = o.Get("SSH_ASKPASS") - } - helper, _ := g.Get("credential.helper") - what := &credsConfig{ - AskPass: askpass, - Helper: helper, - Cached: g.Bool("lfs.cachecredentials", true), - SkipPrompt: o.Bool("GIT_TERMINAL_PROMPT", false), - } - - return what, nil + return CredentialHelpers(hs), input } // CredentialHelpers is a []CredentialHelper that iterates through each @@ -332,6 +307,8 @@ func (h *commandCredentialHelper) Reject(creds Creds) error { } func (h *commandCredentialHelper) Approve(creds Creds) error { + tracerx.Printf("creds: git credential approve (%q, %q, %q)", + creds["protocol"], creds["host"], creds["path"]) _, err := h.exec("approve", creds) return err } @@ -383,3 +360,22 @@ func (h *commandCredentialHelper) exec(subcommand string, input Creds) (Creds, e return creds, nil } + +type nullCredentialHelper struct{} + +var ( + nullCredError = errors.New("No credential helper configured") + nullCreds = &nullCredentialHelper{} +) + +func (h *nullCredentialHelper) Fill(input Creds) (Creds, error) { + return nil, nullCredError +} + +func (h *nullCredentialHelper) Approve(creds Creds) error { + return nil +} + +func (h *nullCredentialHelper) Reject(creds Creds) error { + return nil +}