summaryrefslogtreecommitdiff
path: root/go/internal/gitlabnet
diff options
context:
space:
mode:
Diffstat (limited to 'go/internal/gitlabnet')
-rw-r--r--go/internal/gitlabnet/accessverifier/client.go119
-rw-r--r--go/internal/gitlabnet/accessverifier/client_test.go208
-rw-r--r--go/internal/gitlabnet/client.go10
-rw-r--r--go/internal/gitlabnet/client_test.go6
-rw-r--r--go/internal/gitlabnet/discover/client_test.go3
-rw-r--r--go/internal/gitlabnet/httpclient_test.go3
-rw-r--r--go/internal/gitlabnet/httpsclient_test.go3
-rw-r--r--go/internal/gitlabnet/testserver/gitalyserver.go63
-rw-r--r--go/internal/gitlabnet/testserver/testserver.go29
-rw-r--r--go/internal/gitlabnet/twofactorrecover/client_test.go3
10 files changed, 414 insertions, 33 deletions
diff --git a/go/internal/gitlabnet/accessverifier/client.go b/go/internal/gitlabnet/accessverifier/client.go
new file mode 100644
index 0000000..ebe8545
--- /dev/null
+++ b/go/internal/gitlabnet/accessverifier/client.go
@@ -0,0 +1,119 @@
+package accessverifier
+
+import (
+ "fmt"
+ "net/http"
+
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/config"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet"
+)
+
+const (
+ protocol = "ssh"
+ anyChanges = "_any"
+)
+
+type Client struct {
+ client *gitlabnet.GitlabClient
+}
+
+type Request struct {
+ Action commandargs.CommandType `json:"action"`
+ Repo string `json:"project"`
+ Changes string `json:"changes"`
+ Protocol string `json:"protocol"`
+ KeyId string `json:"key_id,omitempty"`
+ Username string `json:"username,omitempty"`
+}
+
+type GitalyRepo struct {
+ StorageName string `json:"storage_name"`
+ RelativePath string `json:"relative_path"`
+ GitObjectDirectory string `json:"git_object_directory"`
+ GitAlternateObjectDirectories []string `json:"git_alternate_object_directories"`
+ RepoName string `json:"gl_repository"`
+ ProjectPath string `json:"gl_project_path"`
+}
+
+type Gitaly struct {
+ Repo GitalyRepo `json:"repository"`
+ Address string `json:"address"`
+ Token string `json:"token"`
+}
+
+type CustomPayloadData struct {
+ ApiEndpoints []string `json:"api_endpoints"`
+ Username string `json:"gl_username"`
+ PrimaryRepo string `json:"primary_repo"`
+ InfoMessage string `json:"info_message"`
+ UserId string `json:"gl_id,omitempty"`
+}
+
+type CustomPayload struct {
+ Action string `json:"action"`
+ Data CustomPayloadData `json:"data"`
+}
+
+type Response struct {
+ Success bool `json:"status"`
+ Message string `json:"message"`
+ Repo string `json:"gl_repository"`
+ UserId string `json:"gl_id"`
+ Username string `json:"gl_username"`
+ GitConfigOptions []string `json:"git_config_options"`
+ Gitaly Gitaly `json:"gitaly"`
+ GitProtocol string `json:"git_protocol"`
+ Payload CustomPayload `json:"payload"`
+ ConsoleMessages []string `json:"gl_console_messages"`
+ Who string
+ StatusCode int
+}
+
+func NewClient(config *config.Config) (*Client, error) {
+ client, err := gitlabnet.GetClient(config)
+ if err != nil {
+ return nil, fmt.Errorf("Error creating http client: %v", err)
+ }
+
+ return &Client{client: client}, nil
+}
+
+func (c *Client) Verify(args *commandargs.CommandArgs, action commandargs.CommandType, repo string) (*Response, error) {
+ request := &Request{Action: action, Repo: repo, Protocol: protocol, Changes: anyChanges}
+
+ if args.GitlabUsername != "" {
+ request.Username = args.GitlabUsername
+ } else {
+ request.KeyId = args.GitlabKeyId
+ }
+
+ response, err := c.client.Post("/allowed", request)
+ if err != nil {
+ return nil, err
+ }
+ defer response.Body.Close()
+
+ return parse(response, args)
+}
+
+func parse(hr *http.Response, args *commandargs.CommandArgs) (*Response, error) {
+ response := &Response{}
+ if err := gitlabnet.ParseJSON(hr, response); err != nil {
+ return nil, err
+ }
+
+ if args.GitlabKeyId != "" {
+ response.Who = "key-" + args.GitlabKeyId
+ } else {
+ response.Who = response.UserId
+ }
+
+ response.StatusCode = hr.StatusCode
+
+ return response, nil
+}
+
+func (r *Response) IsCustomAction() bool {
+ return r.StatusCode == http.StatusMultipleChoices
+}
diff --git a/go/internal/gitlabnet/accessverifier/client_test.go b/go/internal/gitlabnet/accessverifier/client_test.go
new file mode 100644
index 0000000..a759919
--- /dev/null
+++ b/go/internal/gitlabnet/accessverifier/client_test.go
@@ -0,0 +1,208 @@
+package accessverifier
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "path"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/config"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/testserver"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/testhelper"
+)
+
+var (
+ repo = "group/private"
+ action = commandargs.ReceivePack
+)
+
+func buildExpectedResponse(who string) *Response {
+ response := &Response{
+ Success: true,
+ UserId: "user-1",
+ Repo: "project-26",
+ Username: "root",
+ GitConfigOptions: []string{"option"},
+ Gitaly: Gitaly{
+ Repo: GitalyRepo{
+ StorageName: "default",
+ RelativePath: "@hashed/5f/9c/5f9c4ab08cac7457e9111a30e4664920607ea2c115a1433d7be98e97e64244ca.git",
+ GitObjectDirectory: "path/to/git_object_directory",
+ GitAlternateObjectDirectories: []string{"path/to/git_alternate_object_directory"},
+ RepoName: "project-26",
+ ProjectPath: repo,
+ },
+ Address: "unix:gitaly.socket",
+ Token: "token",
+ },
+ GitProtocol: "protocol",
+ Payload: CustomPayload{},
+ ConsoleMessages: []string{"console", "message"},
+ Who: who,
+ StatusCode: 200,
+ }
+
+ return response
+}
+
+func TestSuccessfulResponses(t *testing.T) {
+ client, cleanup := setup(t)
+ defer cleanup()
+
+ testCases := []struct {
+ desc string
+ args *commandargs.CommandArgs
+ who string
+ }{
+ {
+ desc: "Provide key id within the request",
+ args: &commandargs.CommandArgs{GitlabKeyId: "1"},
+ who: "key-1",
+ }, {
+ desc: "Provide username within the request",
+ args: &commandargs.CommandArgs{GitlabUsername: "first"},
+ who: "user-1",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ result, err := client.Verify(tc.args, action, repo)
+ require.NoError(t, err)
+
+ response := buildExpectedResponse(tc.who)
+ require.Equal(t, response, result)
+ })
+ }
+}
+
+func TestGetCustomAction(t *testing.T) {
+ client, cleanup := setup(t)
+ defer cleanup()
+
+ args := &commandargs.CommandArgs{GitlabUsername: "custom"}
+ result, err := client.Verify(args, action, repo)
+ require.NoError(t, err)
+
+ response := buildExpectedResponse("user-1")
+ response.Payload = CustomPayload{
+ Action: "geo_proxy_to_primary",
+ Data: CustomPayloadData{
+ ApiEndpoints: []string{"geo/proxy_git_push_ssh/info_refs", "geo/proxy_git_push_ssh/push"},
+ Username: "custom",
+ PrimaryRepo: "https://repo/path",
+ InfoMessage: "message",
+ },
+ }
+ response.StatusCode = 300
+
+ require.True(t, response.IsCustomAction())
+ require.Equal(t, response, result)
+}
+
+func TestErrorResponses(t *testing.T) {
+ client, cleanup := setup(t)
+ defer cleanup()
+
+ testCases := []struct {
+ desc string
+ fakeId string
+ expectedError string
+ }{
+ {
+ desc: "A response with an error message",
+ fakeId: "2",
+ expectedError: "Not allowed!",
+ },
+ {
+ desc: "A response with bad JSON",
+ fakeId: "3",
+ expectedError: "Parsing failed",
+ },
+ {
+ desc: "An error response without message",
+ fakeId: "4",
+ expectedError: "Internal API error (403)",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ args := &commandargs.CommandArgs{GitlabKeyId: tc.fakeId}
+ resp, err := client.Verify(args, action, repo)
+
+ require.EqualError(t, err, tc.expectedError)
+ require.Nil(t, resp)
+ })
+ }
+}
+
+func setup(t *testing.T) (*Client, func()) {
+ testDirCleanup, err := testhelper.PrepareTestRootDir()
+ require.NoError(t, err)
+ defer testDirCleanup()
+
+ body, err := ioutil.ReadFile(path.Join(testhelper.TestRoot, "responses/allowed.json"))
+ require.NoError(t, err)
+
+ allowedWithPayloadPath := path.Join(testhelper.TestRoot, "responses/allowed_with_payload.json")
+ bodyWithPayload, err := ioutil.ReadFile(allowedWithPayloadPath)
+ require.NoError(t, err)
+
+ requests := []testserver.TestRequestHandler{
+ {
+ Path: "/api/v4/internal/allowed",
+ Handler: func(w http.ResponseWriter, r *http.Request) {
+ b, err := ioutil.ReadAll(r.Body)
+ require.NoError(t, err)
+
+ var requestBody *Request
+ require.NoError(t, json.Unmarshal(b, &requestBody))
+
+ switch requestBody.Username {
+ case "first":
+ _, err = w.Write(body)
+ require.NoError(t, err)
+ case "second":
+ errBody := map[string]interface{}{
+ "status": false,
+ "message": "missing user",
+ }
+ require.NoError(t, json.NewEncoder(w).Encode(errBody))
+ case "custom":
+ w.WriteHeader(http.StatusMultipleChoices)
+ _, err = w.Write(bodyWithPayload)
+ require.NoError(t, err)
+ }
+
+ switch requestBody.KeyId {
+ case "1":
+ _, err = w.Write(body)
+ require.NoError(t, err)
+ case "2":
+ w.WriteHeader(http.StatusForbidden)
+ errBody := &gitlabnet.ErrorResponse{
+ Message: "Not allowed!",
+ }
+ require.NoError(t, json.NewEncoder(w).Encode(errBody))
+ case "3":
+ w.Write([]byte("{ \"message\": \"broken json!\""))
+ case "4":
+ w.WriteHeader(http.StatusForbidden)
+ }
+ },
+ },
+ }
+
+ url, cleanup := testserver.StartSocketHttpServer(t, requests)
+
+ client, err := NewClient(&config.Config{GitlabUrl: url})
+ require.NoError(t, err)
+
+ return client, cleanup
+}
diff --git a/go/internal/gitlabnet/client.go b/go/internal/gitlabnet/client.go
index 86add04..dacb1d6 100644
--- a/go/internal/gitlabnet/client.go
+++ b/go/internal/gitlabnet/client.go
@@ -53,8 +53,6 @@ func normalizePath(path string) string {
}
func newRequest(method, host, path string, data interface{}) (*http.Request, error) {
- path = normalizePath(path)
-
var jsonReader io.Reader
if data != nil {
jsonData, err := json.Marshal(data)
@@ -74,7 +72,7 @@ func newRequest(method, host, path string, data interface{}) (*http.Request, err
}
func parseError(resp *http.Response) error {
- if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
+ if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
return nil
}
defer resp.Body.Close()
@@ -89,14 +87,14 @@ func parseError(resp *http.Response) error {
}
func (c *GitlabClient) Get(path string) (*http.Response, error) {
- return c.doRequest("GET", path, nil)
+ return c.DoRequest(http.MethodGet, normalizePath(path), nil)
}
func (c *GitlabClient) Post(path string, data interface{}) (*http.Response, error) {
- return c.doRequest("POST", path, data)
+ return c.DoRequest(http.MethodPost, normalizePath(path), data)
}
-func (c *GitlabClient) doRequest(method, path string, data interface{}) (*http.Response, error) {
+func (c *GitlabClient) DoRequest(method, path string, data interface{}) (*http.Response, error) {
request, err := newRequest(method, c.host, path, data)
if err != nil {
return nil, err
diff --git a/go/internal/gitlabnet/client_test.go b/go/internal/gitlabnet/client_test.go
index d817239..cf42195 100644
--- a/go/internal/gitlabnet/client_test.go
+++ b/go/internal/gitlabnet/client_test.go
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+
"gitlab.com/gitlab-org/gitlab-shell/go/internal/config"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/testserver"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/testhelper"
@@ -71,7 +72,7 @@ func TestClients(t *testing.T) {
testCases := []struct {
desc string
config *config.Config
- server func([]testserver.TestRequestHandler) (func(), string, error)
+ server func(*testing.T, []testserver.TestRequestHandler) (string, func())
}{
{
desc: "Socket client",
@@ -94,9 +95,8 @@ func TestClients(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
- cleanup, url, err := tc.server(requests)
+ url, cleanup := tc.server(t, requests)
defer cleanup()
- require.NoError(t, err)
tc.config.GitlabUrl = url
tc.config.Secret = "sssh, it's a secret"
diff --git a/go/internal/gitlabnet/discover/client_test.go b/go/internal/gitlabnet/discover/client_test.go
index 8eabdd7..b98a28e 100644
--- a/go/internal/gitlabnet/discover/client_test.go
+++ b/go/internal/gitlabnet/discover/client_test.go
@@ -128,8 +128,7 @@ func TestErrorResponses(t *testing.T) {
}
func setup(t *testing.T) (*Client, func()) {
- cleanup, url, err := testserver.StartSocketHttpServer(requests)
- require.NoError(t, err)
+ url, cleanup := testserver.StartSocketHttpServer(t, requests)
client, err := NewClient(&config.Config{GitlabUrl: url})
require.NoError(t, err)
diff --git a/go/internal/gitlabnet/httpclient_test.go b/go/internal/gitlabnet/httpclient_test.go
index 885a6d1..9b635bd 100644
--- a/go/internal/gitlabnet/httpclient_test.go
+++ b/go/internal/gitlabnet/httpclient_test.go
@@ -86,8 +86,7 @@ func TestEmptyBasicAuthSettings(t *testing.T) {
}
func setup(t *testing.T, config *config.Config, requests []testserver.TestRequestHandler) (*GitlabClient, func()) {
- cleanup, url, err := testserver.StartHttpServer(requests)
- require.NoError(t, err)
+ url, cleanup := testserver.StartHttpServer(t, requests)
config.GitlabUrl = url
client, err := GetClient(config)
diff --git a/go/internal/gitlabnet/httpsclient_test.go b/go/internal/gitlabnet/httpsclient_test.go
index b9baad8..04901df 100644
--- a/go/internal/gitlabnet/httpsclient_test.go
+++ b/go/internal/gitlabnet/httpsclient_test.go
@@ -115,8 +115,7 @@ func setupWithRequests(t *testing.T, config *config.Config) (*GitlabClient, func
},
}
- cleanup, url, err := testserver.StartHttpsServer(requests)
- require.NoError(t, err)
+ url, cleanup := testserver.StartHttpsServer(t, requests)
config.GitlabUrl = url
client, err := GetClient(config)
diff --git a/go/internal/gitlabnet/testserver/gitalyserver.go b/go/internal/gitlabnet/testserver/gitalyserver.go
new file mode 100644
index 0000000..141a518
--- /dev/null
+++ b/go/internal/gitlabnet/testserver/gitalyserver.go
@@ -0,0 +1,63 @@
+package testserver
+
+import (
+ "io/ioutil"
+ "net"
+ "os"
+ "path"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "google.golang.org/grpc"
+
+ pb "gitlab.com/gitlab-org/gitaly-proto/go/gitalypb"
+)
+
+type testGitalyServer struct{}
+
+func (s *testGitalyServer) SSHReceivePack(stream pb.SSHService_SSHReceivePackServer) error {
+ req, err := stream.Recv()
+
+ if err != nil {
+ return err
+ }
+
+ response := []byte("ReceivePack: " + req.GlId + " " + req.Repository.GlRepository)
+ stream.Send(&pb.SSHReceivePackResponse{Stdout: response})
+
+ return nil
+}
+
+func (s *testGitalyServer) SSHUploadPack(stream pb.SSHService_SSHUploadPackServer) error {
+ return nil
+}
+
+func (s *testGitalyServer) SSHUploadArchive(stream pb.SSHService_SSHUploadArchiveServer) error {
+ return nil
+}
+
+func StartGitalyServer(t *testing.T) (string, func()) {
+ tempDir, _ := ioutil.TempDir("", "gitlab-shell-test-api")
+ gitalySocketPath := path.Join(tempDir, "gitaly.sock")
+
+ err := os.MkdirAll(filepath.Dir(gitalySocketPath), 0700)
+ require.NoError(t, err)
+
+ server := grpc.NewServer()
+
+ listener, err := net.Listen("unix", gitalySocketPath)
+ require.NoError(t, err)
+
+ pb.RegisterSSHServiceServer(server, &testGitalyServer{})
+
+ go server.Serve(listener)
+
+ gitalySocketUrl := "unix:" + gitalySocketPath
+ cleanup := func() {
+ server.Stop()
+ os.RemoveAll(tempDir)
+ }
+
+ return gitalySocketUrl, cleanup
+}
diff --git a/go/internal/gitlabnet/testserver/testserver.go b/go/internal/gitlabnet/testserver/testserver.go
index bf896e6..bf59ce4 100644
--- a/go/internal/gitlabnet/testserver/testserver.go
+++ b/go/internal/gitlabnet/testserver/testserver.go
@@ -10,6 +10,9 @@ import (
"os"
"path"
"path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/testhelper"
)
@@ -24,15 +27,12 @@ type TestRequestHandler struct {
Handler func(w http.ResponseWriter, r *http.Request)
}
-func StartSocketHttpServer(handlers []TestRequestHandler) (func(), string, error) {
- if err := os.MkdirAll(filepath.Dir(testSocket), 0700); err != nil {
- return nil, "", err
- }
+func StartSocketHttpServer(t *testing.T, handlers []TestRequestHandler) (string, func()) {
+ err := os.MkdirAll(filepath.Dir(testSocket), 0700)
+ require.NoError(t, err)
socketListener, err := net.Listen("unix", testSocket)
- if err != nil {
- return nil, "", err
- }
+ require.NoError(t, err)
server := http.Server{
Handler: buildHandler(handlers),
@@ -44,30 +44,27 @@ func StartSocketHttpServer(handlers []TestRequestHandler) (func(), string, error
url := "http+unix://" + testSocket
- return cleanupSocket, url, nil
+ return url, cleanupSocket
}
-func StartHttpServer(handlers []TestRequestHandler) (func(), string, error) {
+func StartHttpServer(t *testing.T, handlers []TestRequestHandler) (string, func()) {
server := httptest.NewServer(buildHandler(handlers))
- return server.Close, server.URL, nil
+ return server.URL, server.Close
}
-func StartHttpsServer(handlers []TestRequestHandler) (func(), string, error) {
+func StartHttpsServer(t *testing.T, handlers []TestRequestHandler) (string, func()) {
crt := path.Join(testhelper.TestRoot, "certs/valid/server.crt")
key := path.Join(testhelper.TestRoot, "certs/valid/server.key")
server := httptest.NewUnstartedServer(buildHandler(handlers))
cer, err := tls.LoadX509KeyPair(crt, key)
-
- if err != nil {
- return nil, "", err
- }
+ require.NoError(t, err)
server.TLS = &tls.Config{Certificates: []tls.Certificate{cer}}
server.StartTLS()
- return server.Close, server.URL, nil
+ return server.URL, server.Close
}
func cleanupSocket() {
diff --git a/go/internal/gitlabnet/twofactorrecover/client_test.go b/go/internal/gitlabnet/twofactorrecover/client_test.go
index 56f7958..4b15ac5 100644
--- a/go/internal/gitlabnet/twofactorrecover/client_test.go
+++ b/go/internal/gitlabnet/twofactorrecover/client_test.go
@@ -149,8 +149,7 @@ func TestErrorResponses(t *testing.T) {
func setup(t *testing.T) (*Client, func()) {
initialize(t)
- cleanup, url, err := testserver.StartSocketHttpServer(requests)
- require.NoError(t, err)
+ url, cleanup := testserver.StartSocketHttpServer(t, requests)
client, err := NewClient(&config.Config{GitlabUrl: url})
require.NoError(t, err)