Skip to content

Commit

Permalink
Merge pull request #442 from blurpy/feature/git_bearer_token
Browse files Browse the repository at this point in the history
Support specifying bearerToken for git http token authentication
  • Loading branch information
darkowlzz authored Jan 20, 2023
2 parents bfb6385 + 659695f commit 7ef01b0
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 30 deletions.
19 changes: 16 additions & 3 deletions git/gogit/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ func (g *Client) validateUrl(u string) error {
return fmt.Errorf("cannot parse url: %w", err)
}

if g.authOpts != nil {
httpOrHttps := g.authOpts.Transport == git.HTTP || g.authOpts.Transport == git.HTTPS
hasUsernameOrPassword := g.authOpts.Username != "" || g.authOpts.Password != ""
hasBearerToken := g.authOpts.BearerToken != ""

if httpOrHttps && hasBearerToken && hasUsernameOrPassword {
return errors.New("basic auth and bearer token cannot be set at the same time")
}
}

if g.credentialsOverHTTP {
return nil
}
Expand All @@ -254,9 +264,12 @@ func (g *Client) validateUrl(u string) error {
return errors.New("URL cannot contain credentials when using HTTP")
}

if httpOrEmpty && g.authOpts != nil &&
(g.authOpts.Username != "" || g.authOpts.Password != "") {
return errors.New("basic auth cannot be sent over HTTP")
if httpOrEmpty && g.authOpts != nil {
if g.authOpts.Username != "" || g.authOpts.Password != "" {
return errors.New("basic auth cannot be sent over HTTP")
} else if g.authOpts.BearerToken != "" {
return errors.New("bearer token cannot be sent over HTTP")
}
}

return nil
Expand Down
103 changes: 103 additions & 0 deletions git/gogit/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,3 +653,106 @@ func TestHead(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(hash.String()).To(Equal(cc))
}

func TestValidateUrl(t *testing.T) {
tests := []struct {
name string
transport git.TransportType
username string
password string
bearerToken string
url string
credentialsOverHttp bool
expectedError string
}{
{
name: "blocked: basic auth over http",
transport: git.HTTP,
username: "user",
password: "pass",
url: "http://url",
expectedError: "basic auth cannot be sent over HTTP",
},
{
name: "allowed: basic auth over http with insecure enabled",
transport: git.HTTP,
username: "user",
password: "pass",
url: "http://url",
credentialsOverHttp: true,
},
{
name: "allowed: basic auth over https",
transport: git.HTTPS,
username: "user",
password: "pass",
url: "https://url",
},
{
name: "blocked: bearer token over http",
transport: git.HTTP,
bearerToken: "token",
url: "http://url",
expectedError: "bearer token cannot be sent over HTTP",
},
{
name: "allowed: bearer token over http with insecure enabled",
transport: git.HTTP,
bearerToken: "token",
url: "http://url",
credentialsOverHttp: true,
},
{
name: "allowed: bearer token over https",
transport: git.HTTPS,
bearerToken: "token",
url: "https://url",
},
{
name: "blocked: basic auth and bearer token at the same time over http",
transport: git.HTTP,
username: "user",
password: "pass",
bearerToken: "token",
url: "http://url",
expectedError: "basic auth and bearer token cannot be set at the same time",
},
{
name: "blocked: basic auth and bearer token at the same time over https",
transport: git.HTTPS,
username: "user",
password: "pass",
bearerToken: "token",
url: "https://url",
expectedError: "basic auth and bearer token cannot be set at the same time",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

opts := []ClientOption{WithDiskStorage()}
if tt.credentialsOverHttp {
opts = append(opts, WithInsecureCredentialsOverHTTP())
}

ggc, err := NewClient(t.TempDir(), &git.AuthOptions{
Transport: tt.transport,
Username: tt.username,
Password: tt.password,
BearerToken: tt.bearerToken,
}, opts...)
g.Expect(err).ToNot(HaveOccurred())

err = ggc.validateUrl(tt.url)

if tt.expectedError == "" {
g.Expect(err).To(BeNil())
} else {
g.Expect(err).ToNot(BeNil())
g.Expect(err.Error()).To(ContainSubstring(tt.expectedError))
}
})
}
}
20 changes: 17 additions & 3 deletions git/gogit/clone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,7 @@ func TestClone_CredentialsOverHttp(t *testing.T) {
name string
username string
password string
bearerToken string
allowCredentialsOverHttp bool
transformURL func(string) string
expectCloneErr string
Expand All @@ -1009,6 +1010,11 @@ func TestClone_CredentialsOverHttp(t *testing.T) {
password: "pass",
expectCloneErr: "basic auth cannot be sent over HTTP",
},
{
name: "blocked: bearer token over HTTP",
bearerToken: "token",
expectCloneErr: "bearer token cannot be sent over HTTP",
},
{
name: "blocked: URL based credential over HTTP (name)",
transformURL: func(s string) string {
Expand Down Expand Up @@ -1069,6 +1075,13 @@ func TestClone_CredentialsOverHttp(t *testing.T) {
allowCredentialsOverHttp: true,
expectRequest: true,
},
{
name: "allowed: bearer token over HTTP",
bearerToken: "token",
expectCloneErr: "unable to clone",
allowCredentialsOverHttp: true,
expectRequest: true,
},
{
name: "allowed: URL based credential over HTTP (name)",
transformURL: func(s string) string {
Expand Down Expand Up @@ -1129,9 +1142,10 @@ func TestClone_CredentialsOverHttp(t *testing.T) {
}

ggc, err := NewClient(tmpDir, &git.AuthOptions{
Transport: git.HTTP,
Username: tt.username,
Password: tt.password,
Transport: git.HTTP,
Username: tt.username,
Password: tt.password,
BearerToken: tt.bearerToken,
}, opts...)

g.Expect(err).ToNot(HaveOccurred())
Expand Down
4 changes: 4 additions & 0 deletions git/gogit/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ func transportAuth(opts *git.AuthOptions, fallbackToDefaultKnownHosts bool) (tra
Username: opts.Username,
Password: opts.Password,
}, nil
} else if opts.BearerToken != "" {
return &http.TokenAuth{
Token: opts.BearerToken,
}, nil
}
return nil, nil
case git.SSH:
Expand Down
24 changes: 24 additions & 0 deletions git/gogit/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ func Test_transportAuth(t *testing.T) {
}))
},
},
{
name: "HTTP bearer token",
opts: &git.AuthOptions{
Transport: git.HTTP,
BearerToken: "http-token",
},
wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) {
g.Expect(t).To(Equal(&http.TokenAuth{
Token: opts.BearerToken,
}))
},
},
{
name: "HTTPS basic auth",
opts: &git.AuthOptions{
Expand All @@ -125,6 +137,18 @@ func Test_transportAuth(t *testing.T) {
}))
},
},
{
name: "HTTPS bearer token",
opts: &git.AuthOptions{
Transport: git.HTTPS,
BearerToken: "https-token",
},
wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) {
g.Expect(t).To(Equal(&http.TokenAuth{
Token: opts.BearerToken,
}))
},
},
{
name: "SSH private key",
opts: &git.AuthOptions{
Expand Down
49 changes: 29 additions & 20 deletions git/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ const (
// AuthOptions are the authentication options for the Transport of
// communication with a remote origin.
type AuthOptions struct {
Transport TransportType
Host string
Username string
Password string
Identity []byte
KnownHosts []byte
CAFile []byte
Transport TransportType
Host string
Username string
Password string
BearerToken string
Identity []byte
KnownHosts []byte
CAFile []byte
}

// KexAlgos hosts the key exchange algorithms to be used for SSH connections.
Expand Down Expand Up @@ -86,24 +87,32 @@ func (o AuthOptions) Validate() error {
func NewAuthOptions(u url.URL, data map[string][]byte) (*AuthOptions, error) {
opts := newAuthOptions(u)
if len(data) > 0 {
opts.Username = string(data["username"])
opts.Password = string(data["password"])
opts.CAFile = data["caFile"]
opts.Identity = data["identity"]
opts.KnownHosts = data["known_hosts"]
if opts.Transport == SSH {
opts.Identity = data["identity"]
opts.KnownHosts = data["known_hosts"]
opts.Username = u.User.Username()
opts.Password = string(data["password"])
// We fallback to using "git" as the username when cloning Git
// repositories through SSH since that's the conventional username used
// by Git providers.
if opts.Username == "" {
opts.Username = DefaultPublicKeyAuthUser
}
} else if token, found := data["bearerToken"]; found {
opts.CAFile = data["caFile"]
opts.BearerToken = string(token)
} else {
opts.CAFile = data["caFile"]
opts.Username = string(data["username"])
opts.Password = string(data["password"])
}
}

if opts.Username == "" {
if opts.Transport != SSH && opts.Username == "" {
opts.Username = u.User.Username()
}

// We fallback to using "git" as the username when cloning Git
// repositories through SSH since that's the conventional username used
// by Git providers.
if opts.Username == "" && opts.Transport == SSH {
opts.Username = DefaultPublicKeyAuthUser
}
if opts.Password == "" {
if opts.Transport != SSH && opts.Password == "" {
opts.Password, _ = u.User.Password()
}

Expand Down
Loading

0 comments on commit 7ef01b0

Please sign in to comment.