Skip to content

Commit 3dc1f00

Browse files
committed
Add scaffolding
1 parent 4895e01 commit 3dc1f00

File tree

6 files changed

+172
-0
lines changed

6 files changed

+172
-0
lines changed

cli/gitaskpass/gitaskpass.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package gitaskpass
2+
3+
import (
4+
"net/url"
5+
"regexp"
6+
"strings"
7+
8+
"golang.org/x/xerrors"
9+
)
10+
11+
// https://github.com/microsoft/vscode/blob/328646ebc2f5016a1c67e0b23a0734bd598ec5a8/extensions/git/src/askpass-main.ts#L46
12+
var hostReplace = regexp.MustCompile(`^["']+|["':]+$`)
13+
14+
// CheckCommand returns true if the command arguments and environment
15+
// match those when the GIT_ASKPASS command is invoked by git.
16+
func CheckCommand(args, env []string) bool {
17+
if len(args) != 1 || (!strings.HasPrefix(args[0], "Username ") && !strings.HasPrefix(args[0], "Password ")) {
18+
return false
19+
}
20+
for _, e := range env {
21+
if strings.HasPrefix(e, "GIT_PREFIX=") {
22+
return true
23+
}
24+
}
25+
return false
26+
}
27+
28+
// Parse returns the user and host from a git askpass prompt. For
29+
// example: "user1" and "https://github.com". Note that for HTTP
30+
// protocols, the URL will never contain a path.
31+
//
32+
// For details on how the prompt is formatted, see `credential_ask_one`:
33+
// https://github.com/git/git/blob/bbe21b64a08f89475d8a3818e20c111378daa621/credential.c#L173-L191
34+
func Parse(prompt string) (user string, host string, err error) {
35+
parts := strings.Split(prompt, " ")
36+
if len(parts) < 3 {
37+
return "", "", xerrors.Errorf("askpass prompt must contain 3 words; got %d: %q", len(parts), prompt)
38+
}
39+
40+
switch parts[0] {
41+
case "Username", "Password":
42+
default:
43+
return "", "", xerrors.Errorf("unknown prompt type: %q", prompt)
44+
}
45+
46+
host = parts[2]
47+
host = hostReplace.ReplaceAllString(host, "")
48+
49+
// Validate the input URL to ensure it's in an expected format.
50+
u, err := url.Parse(host)
51+
if err != nil {
52+
return "", "", xerrors.Errorf("parse host failed: %w", err)
53+
}
54+
55+
switch u.Scheme {
56+
case "http", "https":
57+
default:
58+
return "", "", xerrors.Errorf("unsupported scheme: %q", u.Scheme)
59+
}
60+
61+
if u.Host == "" {
62+
return "", "", xerrors.Errorf("host is empty")
63+
}
64+
65+
user = u.User.Username()
66+
u.User = nil
67+
host = u.String()
68+
69+
return user, host, nil
70+
}

cli/gitaskpass/gitaskpass_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package gitaskpass_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/coder/coder/cli/gitaskpass"
9+
)
10+
11+
func TestCheckCommand(t *testing.T) {
12+
t.Parallel()
13+
t.Run("Success", func(t *testing.T) {
14+
t.Parallel()
15+
valid := gitaskpass.CheckCommand([]string{"Username "}, []string{"GIT_PREFIX=/example"})
16+
require.True(t, valid)
17+
})
18+
t.Run("Failure", func(t *testing.T) {
19+
t.Parallel()
20+
valid := gitaskpass.CheckCommand([]string{}, []string{})
21+
require.False(t, valid)
22+
})
23+
}
24+
25+
func TestParse(t *testing.T) {
26+
t.Parallel()
27+
for _, tc := range []struct {
28+
in string
29+
wantUser string
30+
wantHost string
31+
}{
32+
{
33+
in: "Username for 'https://github.com': ",
34+
wantUser: "",
35+
wantHost: "https://github.com",
36+
},
37+
{
38+
in: "Username for 'https://enterprise.github.com': ",
39+
wantUser: "",
40+
wantHost: "https://enterprise.github.com",
41+
},
42+
{
43+
in: "Username for 'http://wow.io': ",
44+
wantUser: "",
45+
wantHost: "http://wow.io",
46+
},
47+
{
48+
in: "Password for 'https://myuser@github.com': ",
49+
wantUser: "myuser",
50+
wantHost: "https://github.com",
51+
},
52+
{
53+
in: "Password for 'https://myuser@enterprise.github.com': ",
54+
wantUser: "myuser",
55+
wantHost: "https://enterprise.github.com",
56+
},
57+
{
58+
in: "Password for 'http://myuser@wow.io': ",
59+
wantUser: "myuser",
60+
wantHost: "http://wow.io",
61+
},
62+
} {
63+
tc := tc
64+
t.Run(tc.in, func(t *testing.T) {
65+
t.Parallel()
66+
user, host, err := gitaskpass.Parse(tc.in)
67+
require.NoError(t, err)
68+
require.Equal(t, tc.wantUser, user)
69+
require.Equal(t, tc.wantHost, host)
70+
})
71+
}
72+
}

coderd/database/dump.sql

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE git_provider_links;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE TABLE IF NOT EXISTS git_provider_links (
2+
user_id uuid NOT NULL,
3+
url text NOT NULL,
4+
created_at timestamptz NOT NULL,
5+
updated_at timestamptz NOT NULL,
6+
oauth_access_token text NOT NULL,
7+
oauth_refresh_token text NOT NULL,
8+
oauth_expiry text NOT NULL
9+
);

coderd/database/models.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)