Skip to content
Permalink
Browse files

creds: Add new NetrcCredentialHelper

This commit adds a new credential helper, NetrcCredentialHelper,
to retrieve credentials from a .netrc file. This replaces the
old .netrc authorization behaviour, which was done directly from
the `doWithAuth()` code.

Additionally, this commit moves all of the `.netrc` functionality
out of `lfsapi` and into `creds`. This was done both because
`creds` is now the logical place for the `.netrc` functionality,
and to prevent an import cycle between `creds` and `lfsapi`.
  • Loading branch information...
PastelMobileSuit committed Oct 8, 2018
1 parent e7b6b33 commit 1026ff073cdd0a05859788e7b9db2c5b7d032970
@@ -41,6 +41,7 @@ func bufferCreds(c Creds) *bytes.Buffer {
}

type CredentialHelperContext struct {
netrcCredHelper *netrcCredentialHelper
commandCredHelper *commandCredentialHelper
askpassCredHelper *AskPassCredentialHelper
cachingCredHelper *credentialCacher
@@ -51,6 +52,8 @@ type CredentialHelperContext struct {
func NewCredentialHelperContext(gitEnv config.Environment, osEnv config.Environment) *CredentialHelperContext {
c := &CredentialHelperContext{urlConfig: config.NewURLConfig(gitEnv)}

c.netrcCredHelper = newNetrcCredentialHelper(osEnv)

askpass, ok := osEnv.Get("GIT_ASKPASS")
if !ok {
askpass, ok = gitEnv.Get("core.askpass")
@@ -95,7 +98,10 @@ func (ctxt *CredentialHelperContext) GetCredentialHelper(helper CredentialHelper
return helper, input
}

helpers := make([]CredentialHelper, 0, 3)
helpers := make([]CredentialHelper, 0, 4)
if ctxt.netrcCredHelper != nil {
helpers = append(helpers, ctxt.netrcCredHelper)
}
if ctxt.cachingCredHelper != nil {
helpers = append(helpers, ctxt.cachingCredHelper)
}
@@ -1,5 +1,5 @@
// +build !windows

package lfsapi
package creds

var netrcBasename = ".netrc"
@@ -1,5 +1,5 @@
// +build windows

package lfsapi
package creds

var netrcBasename = "_netrc"
@@ -0,0 +1,130 @@
package creds

import (
"net"
"os"
"path/filepath"
"strings"

"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/go-netrc/netrc"
"github.com/rubyist/tracerx"
)

type NetrcFinder interface {
FindMachine(string) *netrc.Machine
}

func ParseNetrc(osEnv config.Environment) (NetrcFinder, string, error) {
home, _ := osEnv.Get("HOME")
if len(home) == 0 {
return &noFinder{}, "", nil
}

nrcfilename := filepath.Join(home, netrcBasename)
if _, err := os.Stat(nrcfilename); err != nil {
return &noFinder{}, nrcfilename, nil
}

f, err := netrc.ParseFile(nrcfilename)
return f, nrcfilename, err
}

type noFinder struct{}

func (f *noFinder) FindMachine(host string) *netrc.Machine {
return nil
}

// NetrcCredentialHelper retrieves credentials from a .netrc file
type netrcCredentialHelper struct {
netrcFinder NetrcFinder
skip map[string]bool
}

var defaultNetrcFinder = &noFinder{}

// NewNetrcCredentialHelper creates a new netrc credential helper using a
// .netrc file gleaned from the OS environment
func newNetrcCredentialHelper(osEnv config.Environment) *netrcCredentialHelper {
netrcFinder, netrcfile, err := ParseNetrc(osEnv)
if err != nil {
tracerx.Printf("bad netrc file %s: %s", netrcfile, err)
return nil
}

if netrcFinder == nil {
netrcFinder = defaultNetrcFinder
}

return &netrcCredentialHelper{netrcFinder: netrcFinder, skip: make(map[string]bool)}
}

func (c *netrcCredentialHelper) Fill(what Creds) (Creds, error) {
host, err := getNetrcHostname(what["host"])
if err != nil {
return nil, credHelperNoOp
}

if c.skip[host] {
return nil, credHelperNoOp
}

if machine := c.netrcFinder.FindMachine(host); machine != nil {
creds := make(Creds)
creds["username"] = machine.Login
creds["password"] = machine.Password
creds["protocol"] = what["protocol"]
creds["host"] = what["host"]
creds["scheme"] = what["scheme"]
creds["path"] = what["path"]
creds["source"] = "netrc"
tracerx.Printf("netrc: git credential fill (%q, %q, %q)",
what["protocol"], what["host"], what["path"])
return creds, nil
}

return nil, credHelperNoOp
}

func getNetrcHostname(hostname string) (string, error) {
if strings.Contains(hostname, ":") {
host, _, err := net.SplitHostPort(hostname)
if err != nil {
tracerx.Printf("netrc: error parsing %q: %s", hostname, err)
return "", err
}
return host, nil
}

return hostname, nil
}

func (c *netrcCredentialHelper) Approve(what Creds) error {
if what["source"] == "netrc" {
host, err := getNetrcHostname(what["host"])
if err != nil {
return credHelperNoOp
}
tracerx.Printf("netrc: git credential approve (%q, %q, %q)",
what["protocol"], what["host"], what["path"])
c.skip[host] = false
return nil
}
return credHelperNoOp
}

func (c *netrcCredentialHelper) Reject(what Creds) error {
if what["source"] == "netrc" {
host, err := getNetrcHostname(what["host"])
if err != nil {
return credHelperNoOp
}

tracerx.Printf("netrc: git credential reject (%q, %q, %q)",
what["protocol"], what["host"], what["path"])
c.skip[host] = true
return nil
}
return credHelperNoOp
}
@@ -0,0 +1,82 @@
package creds

import (
"strings"
"testing"

"github.com/git-lfs/go-netrc/netrc"
)

func TestNetrcWithHostAndPort(t *testing.T) {
var netrcHelper netrcCredentialHelper
netrcHelper.netrcFinder = &fakeNetrc{}

what := make(Creds)
what["protocol"] = "http"
what["host"] = "netrc-host:123"
what["path"] = "/foo/bar"

creds, err := netrcHelper.Fill(what)
if err != nil {
t.Fatalf("error retrieving netrc credentials: %s", err)
}

username := creds["username"]
if username != "abc" {
t.Fatalf("bad username: %s", username)
}

password := creds["password"]
if password != "def" {
t.Fatalf("bad password: %s", password)
}
}

func TestNetrcWithHost(t *testing.T) {
var netrcHelper netrcCredentialHelper
netrcHelper.netrcFinder = &fakeNetrc{}

what := make(Creds)
what["protocol"] = "http"
what["host"] = "netrc-host"
what["path"] = "/foo/bar"

creds, err := netrcHelper.Fill(what)
if err != nil {
t.Fatalf("error retrieving netrc credentials: %s", err)
}

username := creds["username"]
if username != "abc" {
t.Fatalf("bad username: %s", username)
}

password := creds["password"]
if password != "def" {
t.Fatalf("bad password: %s", password)
}
}

func TestNetrcWithBadHost(t *testing.T) {
var netrcHelper netrcCredentialHelper
netrcHelper.netrcFinder = &fakeNetrc{}

what := make(Creds)
what["protocol"] = "http"
what["host"] = "other-host"
what["path"] = "/foo/bar"

_, err := netrcHelper.Fill(what)
if err != credHelperNoOp {
t.Fatalf("expected no-op for unknown host other-host")
}
}

type fakeNetrc struct{}

func (n *fakeNetrc) FindMachine(host string) *netrc.Machine {
if strings.Contains(host, "netrc") {
return &netrc.Machine{Login: "abc", Password: "def"}
}
return nil
}
@@ -3,7 +3,6 @@ package lfsapi
import (
"encoding/base64"
"fmt"
"net"
"net/http"
"net/url"
"os"
@@ -12,12 +11,10 @@ import (
"github.com/git-lfs/git-lfs/creds"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/lfshttp"
"github.com/git-lfs/go-netrc/netrc"
"github.com/rubyist/tracerx"
)

var (
defaultNetrcFinder = &noFinder{}
defaultEndpointFinder = NewEndpointFinder(nil)
)

@@ -134,16 +131,11 @@ func (c *Client) getCreds(remote string, access Access, req *http.Request) (cred
ef = defaultEndpointFinder
}

netrcFinder := c.Netrc
if netrcFinder == nil {
netrcFinder = defaultNetrcFinder
}

operation := getReqOperation(req)
apiEndpoint := ef.Endpoint(operation, remote)

if access.Mode() != NTLMAccess {
if requestHasAuth(req) || setAuthFromNetrc(netrcFinder, req) || access.Mode() == NoneAccess {
if requestHasAuth(req) || access.Mode() == NoneAccess {
return creds.NullCreds, nil, nil, nil
}

@@ -171,18 +163,6 @@ func (c *Client) getCreds(remote string, access Access, req *http.Request) (cred
return creds.NullCreds, nil, nil, errors.Wrap(err, "creds")
}

if netrcMachine := getAuthFromNetrc(netrcFinder, req); netrcMachine != nil {
cred := creds.Creds{
"protocol": credsURL.Scheme,
"host": credsURL.Host,
"username": netrcMachine.Login,
"password": netrcMachine.Password,
"source": "netrc",
}

return creds.NullCreds, credsURL, cred, nil
}

// NTLM uses creds to create the session
credHelper, creds, err := c.getGitCreds(ef, req, credsURL)
return credHelper, credsURL, creds, err
@@ -204,33 +184,6 @@ func (c *Client) getGitCreds(ef EndpointFinder, req *http.Request, u *url.URL) (
return credHelper, creds, err
}

func getAuthFromNetrc(netrcFinder NetrcFinder, req *http.Request) *netrc.Machine {
hostname := req.URL.Host
var host string

if strings.Contains(hostname, ":") {
var err error
host, _, err = net.SplitHostPort(hostname)
if err != nil {
tracerx.Printf("netrc: error parsing %q: %s", hostname, err)
return nil
}
} else {
host = hostname
}

return netrcFinder.FindMachine(host)
}

func setAuthFromNetrc(netrcFinder NetrcFinder, req *http.Request) bool {
if machine := getAuthFromNetrc(netrcFinder, req); machine != nil {
setRequestAuth(req, machine.Login, machine.Password)
return true
}

return false
}

func getCredURLForAPI(ef EndpointFinder, operation, remote string, apiEndpoint lfshttp.Endpoint, req *http.Request) (*url.URL, error) {
apiURL, err := url.Parse(apiEndpoint.Url)
if err != nil {
Oops, something went wrong.

0 comments on commit 1026ff0

Please sign in to comment.
You can’t perform that action at this time.