git-lfs/creds/netrc.go
brian m. carlson 5d5f90e286
creds: allow multiple values for a key
In the upcoming changes to the credential helper protocol, we'll be able
to specify multiple values for the same key.  In order to make this
work, let's make the credential structure able to handle multiple
values.

Note that if there are multiple values, we'll only use the first one for
most keys.  That simplifies the logic and allows us to gracefully handle
future extensions to the protocol.
2023-06-02 13:25:59 +00:00

141 lines
3.5 KiB
Go

package creds
import (
"net"
"os"
"path/filepath"
"strings"
"sync"
"github.com/git-lfs/git-lfs/v3/config"
"github.com/git-lfs/go-netrc/netrc"
"github.com/rubyist/tracerx"
)
type NetrcFinder interface {
FindMachine(string, 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, loginName string) *netrc.Machine {
return nil
}
// NetrcCredentialHelper retrieves credentials from a .netrc file
type netrcCredentialHelper struct {
netrcFinder NetrcFinder
mu sync.Mutex
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(firstEntryForKey(what, "host"))
if err != nil {
return nil, credHelperNoOp
}
c.mu.Lock()
defer c.mu.Unlock()
if c.skip[host] {
return nil, credHelperNoOp
}
if machine := c.netrcFinder.FindMachine(host, firstEntryForKey(what, "username")); machine != nil {
creds := make(Creds)
creds["username"] = []string{machine.Login}
creds["password"] = []string{machine.Password}
creds["protocol"] = what["protocol"]
creds["host"] = what["host"]
creds["scheme"] = what["scheme"]
creds["path"] = what["path"]
creds["source"] = []string{"netrc"}
tracerx.Printf("netrc: git credential fill (%q, %q, %q, %q)",
firstEntryForKey(what, "protocol"),
firstEntryForKey(what, "host"), machine.Login,
firstEntryForKey(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 firstEntryForKey(what, "source") == "netrc" {
host, err := getNetrcHostname(firstEntryForKey(what, "host"))
if err != nil {
return credHelperNoOp
}
tracerx.Printf("netrc: git credential approve (%q, %q, %q)",
firstEntryForKey(what, "protocol"),
firstEntryForKey(what, "host"),
firstEntryForKey(what, "path"))
c.mu.Lock()
c.skip[host] = false
c.mu.Unlock()
return nil
}
return credHelperNoOp
}
func (c *netrcCredentialHelper) Reject(what Creds) error {
if firstEntryForKey(what, "source") == "netrc" {
host, err := getNetrcHostname(what["host"][0])
if err != nil {
return credHelperNoOp
}
tracerx.Printf("netrc: git credential reject (%q, %q, %q)",
what["protocol"], what["host"], what["path"])
c.mu.Lock()
c.skip[host] = true
c.mu.Unlock()
return nil
}
return credHelperNoOp
}