Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
255 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package git | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
"sync" | ||
) | ||
|
||
type netrcLine struct { | ||
machine string | ||
login string | ||
password string | ||
} | ||
|
||
type NetrcMachine struct { | ||
Login string | ||
Password string | ||
} | ||
|
||
var ( | ||
netrcOnce sync.Once | ||
netrc map[string]NetrcMachine | ||
netrcErr error | ||
) | ||
|
||
func NetrcMachines() map[string]NetrcMachine { | ||
netrcOnce.Do(readNetrc) | ||
|
||
return netrc | ||
} | ||
|
||
func NetrcCredentials(machine string) (NetrcMachine, error) { | ||
if cred, ok := netrc[machine]; ok { | ||
return cred, nil | ||
} | ||
|
||
return NetrcMachine{}, fmt.Errorf("No credentials found for %s", machine) | ||
} | ||
|
||
func parseNetrc(data string) map[string]NetrcMachine { | ||
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html | ||
// for documentation on the .netrc format. | ||
nrc := make(map[string]NetrcMachine) | ||
var l netrcLine | ||
inMacro := false | ||
for _, line := range strings.Split(data, "\n") { | ||
if inMacro { | ||
if line == "" { | ||
inMacro = false | ||
} | ||
continue | ||
} | ||
|
||
f := strings.Fields(line) | ||
i := 0 | ||
for ; i < len(f)-1; i += 2 { | ||
// Reset at each "machine" token. | ||
// “The auto-login process searches the .netrc file for a machine token | ||
// that matches […]. Once a match is made, the subsequent .netrc tokens | ||
// are processed, stopping when the end of file is reached or another | ||
// machine or a default token is encountered.” | ||
switch f[i] { | ||
case "machine": | ||
l = netrcLine{machine: f[i+1]} | ||
case "default": | ||
break | ||
case "login": | ||
l.login = f[i+1] | ||
case "password": | ||
l.password = f[i+1] | ||
case "macdef": | ||
// “A macro is defined with the specified name; its contents begin with | ||
// the next .netrc line and continue until a null line (consecutive | ||
// new-line characters) is encountered.” | ||
inMacro = true | ||
} | ||
|
||
if l.machine != "" && l.login != "" && l.password != "" { | ||
nrc[l.machine] = NetrcMachine{l.login, l.password} | ||
l = netrcLine{} | ||
} | ||
} | ||
|
||
if i < len(f) && f[i] == "default" { | ||
// “There can be only one default token, and it must be after all machine tokens.” | ||
break | ||
} | ||
} | ||
|
||
return nrc | ||
} | ||
|
||
func netrcPath() (string, error) { | ||
if env := os.Getenv("NETRC"); env != "" { | ||
return env, nil | ||
} | ||
dir, err := os.UserHomeDir() | ||
if err != nil { | ||
return "", err | ||
} | ||
base := ".netrc" | ||
if runtime.GOOS == "windows" { | ||
base = "_netrc" | ||
} | ||
return filepath.Join(dir, base), nil | ||
} | ||
|
||
func readNetrc() { | ||
path, err := netrcPath() | ||
if err != nil { | ||
netrcErr = err | ||
return | ||
} | ||
|
||
data, err := os.ReadFile(path) | ||
if err != nil { | ||
if !os.IsNotExist(err) { | ||
netrcErr = err | ||
} | ||
return | ||
} | ||
|
||
netrc = parseNetrc(string(data)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package git | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
var testNetrc = ` | ||
machine incomplete | ||
password none | ||
machine api.github.com | ||
login user | ||
password pwd | ||
machine incomlete.host | ||
login justlogin | ||
machine test.host | ||
login user2 | ||
password pwd2 | ||
machine oneline login user3 password pwd3 | ||
machine ignore.host macdef ignore | ||
login nobody | ||
password nothing | ||
machine hasmacro.too macdef ignore-next-lines login user4 password pwd4 | ||
login nobody | ||
password nothing | ||
default | ||
login anonymous | ||
password gopher@golang.org | ||
machine after.default | ||
login oops | ||
password too-late-in-file | ||
` | ||
|
||
func TestParseNetrc(t *testing.T) { | ||
lines := parseNetrc(testNetrc) | ||
want := map[string]NetrcMachine{ | ||
"api.github.com": {"user", "pwd"}, | ||
"test.host": {"user2", "pwd2"}, | ||
"oneline": {"user3", "pwd3"}, | ||
"hasmacro.too": {"user4", "pwd4"}, | ||
} | ||
|
||
if !reflect.DeepEqual(lines, want) { | ||
t.Errorf("parseNetrc:\nhave %q\nwant %q", lines, want) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package git | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
|
||
"github.com/go-git/go-git/v5/plumbing/transport/ssh" | ||
"github.com/kevinburke/ssh_config" | ||
) | ||
|
||
type SSHMachine struct { | ||
User string | ||
Keys *ssh.PublicKeys | ||
} | ||
|
||
func SSHCredentials(machine string) (SSHMachine, error) { | ||
pk, err := ssh_config.GetStrict(machine, "IdentityFile") | ||
if err != nil { | ||
return SSHMachine{}, err | ||
} | ||
if strings.HasPrefix(pk, "~") { | ||
if hdir, err := os.UserHomeDir(); err == nil { | ||
pk = strings.Replace(pk, "~", hdir, 1) | ||
} | ||
} | ||
usr := ssh_config.Get(machine, "User") | ||
if usr == "" { | ||
usr = "git" | ||
} | ||
|
||
pks, err := ssh.NewPublicKeysFromFile(usr, pk, "") | ||
if err != nil { | ||
return SSHMachine{}, err | ||
} | ||
|
||
return SSHMachine{usr, pks}, nil | ||
} |