Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: Paulo Gomes <pjbgf@linux.com>
  • Loading branch information
pjbgf committed Jul 1, 2023
1 parent bc994d4 commit a62ff9e
Show file tree
Hide file tree
Showing 8 changed files with 1,270 additions and 0 deletions.
439 changes: 439 additions & 0 deletions plumbing/transport/httpv2/common.go

Large diffs are not rendered by default.

271 changes: 271 additions & 0 deletions plumbing/transport/httpv2/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
package http

import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"net/http/cgi"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/go-git/go-git/v5/plumbing/transport"

fixtures "github.com/go-git/go-git-fixtures/v4"
. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type ClientSuite struct {
Endpoint *transport.Endpoint
EmptyAuth transport.AuthMethod
}

var _ = Suite(&ClientSuite{})

func (s *ClientSuite) SetUpSuite(c *C) {
var err error
s.Endpoint, err = transport.NewEndpoint(
"https://github.com/git-fixtures/basic",
)
c.Assert(err, IsNil)
}

func (s *UploadPackSuite) TestNewClient(c *C) {
roundTripper := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
cl := &http.Client{Transport: roundTripper}
r, ok := NewClient(cl).(*client)
c.Assert(ok, Equals, true)
c.Assert(r.c, Equals, cl)
}

func (s *ClientSuite) TestNewBasicAuth(c *C) {
a := &BasicAuth{"foo", "qux"}

c.Assert(a.Name(), Equals, "http-basic-auth")
c.Assert(a.String(), Equals, "http-basic-auth - foo:*******")
}

func (s *ClientSuite) TestNewTokenAuth(c *C) {
a := &TokenAuth{"OAUTH-TOKEN-TEXT"}

c.Assert(a.Name(), Equals, "http-token-auth")
c.Assert(a.String(), Equals, "http-token-auth - *******")

// Check header is set correctly
req, err := http.NewRequest("GET", "https://github.com/git-fixtures/basic", nil)
c.Assert(err, Equals, nil)
a.SetAuth(req)
c.Assert(req.Header.Get("Authorization"), Equals, "Bearer OAUTH-TOKEN-TEXT")
}

func (s *ClientSuite) TestNewErrOK(c *C) {
res := &http.Response{StatusCode: http.StatusOK}
err := NewErr(res)
c.Assert(err, IsNil)
}

func (s *ClientSuite) TestNewErrUnauthorized(c *C) {
s.testNewHTTPError(c, http.StatusUnauthorized, "authentication required")
}

func (s *ClientSuite) TestNewErrForbidden(c *C) {
s.testNewHTTPError(c, http.StatusForbidden, "authorization failed")
}

func (s *ClientSuite) TestNewErrNotFound(c *C) {
s.testNewHTTPError(c, http.StatusNotFound, "repository not found")
}

func (s *ClientSuite) TestNewHTTPError40x(c *C) {
s.testNewHTTPError(c, http.StatusPaymentRequired,
"unexpected client error.*")
}

func (s *ClientSuite) Test_newSession(c *C) {
cl := NewClientWithOptions(nil, &ClientOptions{
CacheMaxEntries: 2,
}).(*client)

insecureEP := s.Endpoint
insecureEP.InsecureSkipTLS = true
session, err := newSession(cl, insecureEP, nil)
c.Assert(err, IsNil)

sessionTransport := session.client.Transport.(*http.Transport)
c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true)
t, ok := cl.fetchTransport(transportOptions{
insecureSkipTLS: true,
})
// transport should be cached.
c.Assert(ok, Equals, true)
// cached transport should be the one that's used.
c.Assert(sessionTransport, Equals, t)

caEndpoint := insecureEP
caEndpoint.CaBundle = []byte("this is the way")
session, err = newSession(cl, caEndpoint, nil)
c.Assert(err, IsNil)

sessionTransport = session.client.Transport.(*http.Transport)
c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true)
c.Assert(sessionTransport.TLSClientConfig.RootCAs, NotNil)
t, ok = cl.fetchTransport(transportOptions{
insecureSkipTLS: true,
caBundle: "this is the way",
})
// transport should be cached.
c.Assert(ok, Equals, true)
// cached transport should be the one that's used.
c.Assert(sessionTransport, Equals, t)

session, err = newSession(cl, caEndpoint, nil)
c.Assert(err, IsNil)
sessionTransport = session.client.Transport.(*http.Transport)
// transport that's going to be used should be cached already.
c.Assert(sessionTransport, Equals, t)
// no new transport got cached.
c.Assert(cl.transports.Len(), Equals, 2)

// if the cache does not exist, the transport should still be correctly configured.
cl.transports = nil
session, err = newSession(cl, insecureEP, nil)
c.Assert(err, IsNil)

sessionTransport = session.client.Transport.(*http.Transport)
c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true)
}

func (s *ClientSuite) testNewHTTPError(c *C, code int, msg string) {
req, _ := http.NewRequest("GET", "foo", nil)
res := &http.Response{
StatusCode: code,
Request: req,
}

err := NewErr(res)
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, msg)
}

func (s *ClientSuite) TestSetAuth(c *C) {
auth := &BasicAuth{}
r, err := DefaultClient.NewUploadPackSession(s.Endpoint, auth)
c.Assert(err, IsNil)
c.Assert(auth, Equals, r.(*upSession).auth)
}

type mockAuth struct{}

func (*mockAuth) Name() string { return "" }
func (*mockAuth) String() string { return "" }

func (s *ClientSuite) TestSetAuthWrongType(c *C) {
_, err := DefaultClient.NewUploadPackSession(s.Endpoint, &mockAuth{})
c.Assert(err, Equals, transport.ErrInvalidAuthMethod)
}

func (s *ClientSuite) TestModifyEndpointIfRedirect(c *C) {
sess := &session{endpoint: nil}
u, _ := url.Parse("https://example.com/info/refs")
res := &http.Response{Request: &http.Request{URL: u}}
c.Assert(func() {
sess.ModifyEndpointIfRedirect(res)
}, PanicMatches, ".*nil pointer dereference.*")

sess = &session{endpoint: nil}
// no-op - should return and not panic
sess.ModifyEndpointIfRedirect(&http.Response{})

data := []struct {
url string
endpoint *transport.Endpoint
expected *transport.Endpoint
}{
{"https://example.com/foo/bar", nil, nil},
{"https://example.com/foo.git/info/refs",
&transport.Endpoint{},
&transport.Endpoint{Protocol: "https", Host: "example.com", Path: "/foo.git"}},
{"https://example.com:8080/foo.git/info/refs",
&transport.Endpoint{},
&transport.Endpoint{Protocol: "https", Host: "example.com", Port: 8080, Path: "/foo.git"}},
}

for _, d := range data {
u, _ := url.Parse(d.url)
sess := &session{endpoint: d.endpoint}
sess.ModifyEndpointIfRedirect(&http.Response{
Request: &http.Request{URL: u},
})
c.Assert(d.endpoint, DeepEquals, d.expected)
}
}

type BaseSuite struct {
fixtures.Suite

base string
host string
port int
}

func (s *BaseSuite) SetUpTest(c *C) {
l, err := net.Listen("tcp", "localhost:0")
c.Assert(err, IsNil)

base, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("go-git-http-%d", s.port))
c.Assert(err, IsNil)

s.port = l.Addr().(*net.TCPAddr).Port
s.base = filepath.Join(base, s.host)

err = os.MkdirAll(s.base, 0755)
c.Assert(err, IsNil)

cmd := exec.Command("git", "--exec-path")
out, err := cmd.CombinedOutput()
c.Assert(err, IsNil)

server := &http.Server{
Handler: &cgi.Handler{
Path: filepath.Join(strings.Trim(string(out), "\n"), "git-http-backend"),
Env: []string{"GIT_HTTP_EXPORT_ALL=true", fmt.Sprintf("GIT_PROJECT_ROOT=%s", s.base)},
},
}
go func() {
log.Fatal(server.Serve(l))
}()
}

func (s *BaseSuite) prepareRepository(c *C, f *fixtures.Fixture, name string) *transport.Endpoint {
fs := f.DotGit()

err := fixtures.EnsureIsBare(fs)
c.Assert(err, IsNil)

path := filepath.Join(s.base, name)
err = os.Rename(fs.Root(), path)
c.Assert(err, IsNil)

return s.newEndpoint(c, name)
}

func (s *BaseSuite) newEndpoint(c *C, name string) *transport.Endpoint {
ep, err := transport.NewEndpoint(fmt.Sprintf("http://localhost:%d/%s", s.port, name))
c.Assert(err, IsNil)

return ep
}

func (s *BaseSuite) TearDownTest(c *C) {
err := os.RemoveAll(s.base)
c.Assert(err, IsNil)
}
119 changes: 119 additions & 0 deletions plumbing/transport/httpv2/proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package http

import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
"sync/atomic"

"github.com/elazarl/goproxy"
fixtures "github.com/go-git/go-git-fixtures/v4"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http/internal/test"

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / test (master, ubuntu-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / version-matrix (1.19.x, ubuntu-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / test (v2.11.0, ubuntu-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / version-matrix (1.19.x, macos-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / version-matrix (1.19.x, windows-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / version-matrix (1.20.x, ubuntu-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / version-matrix (1.20.x, macos-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

Check failure on line 15 in plumbing/transport/httpv2/proxy_test.go

View workflow job for this annotation

GitHub Actions / version-matrix (1.20.x, windows-latest)

use of internal package github.com/go-git/go-git/v5/plumbing/transport/http/internal/test not allowed

. "gopkg.in/check.v1"
)

type ProxySuite struct {
u UploadPackSuite
fixtures.Suite
}

var _ = Suite(&ProxySuite{})

var proxiedRequests int32

func (s *ProxySuite) TestAdvertisedReferences(c *C) {
s.u.SetUpTest(c)
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
setupHTTPProxy(proxy, &proxiedRequests)
httpListener, err := net.Listen("tcp", ":0")
c.Assert(err, IsNil)
defer httpListener.Close()

httpProxyAddr := fmt.Sprintf("http://localhost:%d", httpListener.Addr().(*net.TCPAddr).Port)
proxyServer := http.Server{
Addr: httpProxyAddr,
Handler: proxy,
}
go proxyServer.Serve(httpListener)
defer proxyServer.Close()

endpoint := s.u.prepareRepository(c, fixtures.Basic().One(), "basic.git")
endpoint.Proxy = transport.ProxyOptions{
URL: httpProxyAddr,
Username: "user",
Password: "pass",
}

s.u.Client = NewClient(nil)
session, err := s.u.Client.NewUploadPackSession(endpoint, nil)
c.Assert(err, IsNil)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
info, err := session.AdvertisedReferencesContext(ctx)
c.Assert(err, IsNil)
c.Assert(info, NotNil)
proxyUsed := atomic.LoadInt32(&proxiedRequests) > 0
c.Assert(proxyUsed, Equals, true)

atomic.StoreInt32(&proxiedRequests, 0)
test.SetupHTTPSProxy(proxy, &proxiedRequests)
httpsListener, err := net.Listen("tcp", ":0")
c.Assert(err, IsNil)
defer httpsListener.Close()
httpsProxyAddr := fmt.Sprintf("https://localhost:%d", httpsListener.Addr().(*net.TCPAddr).Port)

tlsProxyServer := http.Server{
Addr: httpsProxyAddr,
Handler: proxy,
// Due to how golang manages http/2 when provided with custom TLS config,
// servers and clients running in the same process leads to issues.
// Ref: https://github.com/golang/go/issues/21336
TLSConfig: &tls.Config{
NextProtos: []string{"http/1.1"},
},
}
go tlsProxyServer.ServeTLS(httpsListener, "testdata/certs/server.crt", "testdata/certs/server.key")
defer tlsProxyServer.Close()

endpoint, err = transport.NewEndpoint("https://github.com/git-fixtures/basic.git")
c.Assert(err, IsNil)
endpoint.Proxy = transport.ProxyOptions{
URL: httpsProxyAddr,
Username: "user",
Password: "pass",
}
endpoint.InsecureSkipTLS = true

session, err = s.u.Client.NewUploadPackSession(endpoint, nil)
c.Assert(err, IsNil)

info, err = session.AdvertisedReferencesContext(ctx)
c.Assert(err, IsNil)
c.Assert(info, NotNil)
proxyUsed = atomic.LoadInt32(&proxiedRequests) > 0
c.Assert(proxyUsed, Equals, true)
}

func setupHTTPProxy(proxy *goproxy.ProxyHttpServer, proxiedRequests *int32) {
// The request is being forwarded to the local test git server in this handler.
var proxyHandler goproxy.FuncReqHandler = func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if strings.Contains(req.Host, "localhost") {
user, pass, _ := test.ParseBasicAuth(req.Header.Get("Proxy-Authorization"))
if user != "user" || pass != "pass" {
return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusUnauthorized, "")
}
atomic.AddInt32(proxiedRequests, 1)
return req, nil
}
// Reject if it isn't our request.
return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "")
}
proxy.OnRequest().Do(proxyHandler)
}
Loading

0 comments on commit a62ff9e

Please sign in to comment.