diff options
Diffstat (limited to 'go/internal/command')
-rw-r--r-- | go/internal/command/command.go | 3 | ||||
-rw-r--r-- | go/internal/command/command_test.go | 23 | ||||
-rw-r--r-- | go/internal/command/commandargs/command_args.go | 43 | ||||
-rw-r--r-- | go/internal/command/commandargs/command_args_test.go | 69 | ||||
-rw-r--r-- | go/internal/command/discover/discover_test.go | 15 | ||||
-rw-r--r-- | go/internal/command/receivepack/customaction.go | 99 | ||||
-rw-r--r-- | go/internal/command/receivepack/customaction_test.go | 105 | ||||
-rw-r--r-- | go/internal/command/receivepack/gitalycall.go | 47 | ||||
-rw-r--r-- | go/internal/command/receivepack/gitalycall_test.go | 40 | ||||
-rw-r--r-- | go/internal/command/receivepack/receivepack.go | 45 | ||||
-rw-r--r-- | go/internal/command/receivepack/receivepack_test.go | 46 | ||||
-rw-r--r-- | go/internal/command/shared/accessverifier/accessverifier.go | 45 | ||||
-rw-r--r-- | go/internal/command/shared/accessverifier/accessverifier_test.go | 82 | ||||
-rw-r--r-- | go/internal/command/twofactorrecover/twofactorrecover_test.go | 3 |
14 files changed, 625 insertions, 40 deletions
diff --git a/go/internal/command/command.go b/go/internal/command/command.go index 0ceb7fc..9589f2f 100644 --- a/go/internal/command/command.go +++ b/go/internal/command/command.go @@ -5,6 +5,7 @@ import ( "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/discover" "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/fallback" "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/receivepack" "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/twofactorrecover" "gitlab.com/gitlab-org/gitlab-shell/go/internal/config" ) @@ -33,6 +34,8 @@ func buildCommand(args *commandargs.CommandArgs, config *config.Config, readWrit return &discover.Command{Config: config, Args: args, ReadWriter: readWriter} case commandargs.TwoFactorRecover: return &twofactorrecover.Command{Config: config, Args: args, ReadWriter: readWriter} + case commandargs.ReceivePack: + return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter} } return nil diff --git a/go/internal/command/command_test.go b/go/internal/command/command_test.go index 228dc7a..99069c7 100644 --- a/go/internal/command/command_test.go +++ b/go/internal/command/command_test.go @@ -3,9 +3,11 @@ package command import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/discover" "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/fallback" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/receivepack" "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/twofactorrecover" "gitlab.com/gitlab-org/gitlab-shell/go/internal/config" "gitlab.com/gitlab-org/gitlab-shell/go/internal/testhelper" @@ -58,6 +60,19 @@ func TestNew(t *testing.T) { }, expectedType: &twofactorrecover.Command{}, }, + { + desc: "it returns a ReceivePack command if the feature is enabled", + arguments: []string{}, + config: &config.Config{ + GitlabUrl: "http+unix://gitlab.socket", + Migration: config.MigrationConfig{Enabled: true, Features: []string{"git-receive-pack"}}, + }, + environment: map[string]string{ + "SSH_CONNECTION": "1", + "SSH_ORIGINAL_COMMAND": "git-receive-pack", + }, + expectedType: &receivepack.Command{}, + }, } for _, tc := range testCases { @@ -67,8 +82,8 @@ func TestNew(t *testing.T) { command, err := New(tc.arguments, tc.config, nil) - assert.NoError(t, err) - assert.IsType(t, tc.expectedType, command) + require.NoError(t, err) + require.IsType(t, tc.expectedType, command) }) } } @@ -80,6 +95,6 @@ func TestFailingNew(t *testing.T) { _, err := New([]string{}, &config.Config{}, nil) - assert.Error(t, err, "Only ssh allowed") + require.Error(t, err, "Only ssh allowed") }) } diff --git a/go/internal/command/commandargs/command_args.go b/go/internal/command/commandargs/command_args.go index e801889..7e241ea 100644 --- a/go/internal/command/commandargs/command_args.go +++ b/go/internal/command/commandargs/command_args.go @@ -4,6 +4,8 @@ import ( "errors" "os" "regexp" + + "github.com/mattn/go-shellwords" ) type CommandType string @@ -11,6 +13,7 @@ type CommandType string const ( Discover CommandType = "discover" TwoFactorRecover CommandType = "2fa_recovery_codes" + ReceivePack CommandType = "git-receive-pack" ) var ( @@ -21,7 +24,7 @@ var ( type CommandArgs struct { GitlabUsername string GitlabKeyId string - SshCommand string + SshArgs []string CommandType CommandType } @@ -30,12 +33,15 @@ func Parse(arguments []string) (*CommandArgs, error) { return nil, errors.New("Only ssh allowed") } - info := &CommandArgs{} + args := &CommandArgs{} + args.parseWho(arguments) - info.parseWho(arguments) - info.parseCommand(os.Getenv("SSH_ORIGINAL_COMMAND")) + if err := args.parseCommand(os.Getenv("SSH_ORIGINAL_COMMAND")); err != nil { + return nil, errors.New("Invalid ssh command") + } + args.defineCommandType() - return info, nil + return args, nil } func (c *CommandArgs) parseWho(arguments []string) { @@ -74,14 +80,29 @@ func tryParseUsername(argument string) string { return "" } -func (c *CommandArgs) parseCommand(commandString string) { - c.SshCommand = commandString +func (c *CommandArgs) parseCommand(commandString string) error { + args, err := shellwords.Parse(commandString) + if err != nil { + return err + } - if commandString == "" { - c.CommandType = Discover + // Handle Git for Windows 2.14 using "git upload-pack" instead of git-upload-pack + if len(args) > 1 && args[0] == "git" { + command := args[0] + "-" + args[1] + commandArgs := args[2:] + + args = append([]string{command}, commandArgs...) } - if CommandType(commandString) == TwoFactorRecover { - c.CommandType = TwoFactorRecover + c.SshArgs = args + + return nil +} + +func (c *CommandArgs) defineCommandType() { + if len(c.SshArgs) == 0 { + c.CommandType = Discover + } else { + c.CommandType = CommandType(c.SshArgs[0]) } } diff --git a/go/internal/command/commandargs/command_args_test.go b/go/internal/command/commandargs/command_args_test.go index 10c46fe..01202c0 100644 --- a/go/internal/command/commandargs/command_args_test.go +++ b/go/internal/command/commandargs/command_args_test.go @@ -3,7 +3,8 @@ package commandargs import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/testhelper" ) @@ -22,23 +23,16 @@ func TestParseSuccess(t *testing.T) { "SSH_CONNECTION": "1", "SSH_ORIGINAL_COMMAND": "", }, - expectedArgs: &CommandArgs{CommandType: Discover}, + expectedArgs: &CommandArgs{SshArgs: []string{}, CommandType: Discover}, }, { - desc: "It passes on the original ssh command from the environment", - environment: map[string]string{ - "SSH_CONNECTION": "1", - "SSH_ORIGINAL_COMMAND": "hello world", - }, - expectedArgs: &CommandArgs{SshCommand: "hello world"}, - }, { desc: "It finds the key id in any passed arguments", environment: map[string]string{ "SSH_CONNECTION": "1", "SSH_ORIGINAL_COMMAND": "", }, arguments: []string{"hello", "key-123"}, - expectedArgs: &CommandArgs{CommandType: Discover, GitlabKeyId: "123"}, + expectedArgs: &CommandArgs{SshArgs: []string{}, CommandType: Discover, GitlabKeyId: "123"}, }, { desc: "It finds the username in any passed arguments", environment: map[string]string{ @@ -46,7 +40,42 @@ func TestParseSuccess(t *testing.T) { "SSH_ORIGINAL_COMMAND": "", }, arguments: []string{"hello", "username-jane-doe"}, - expectedArgs: &CommandArgs{CommandType: Discover, GitlabUsername: "jane-doe"}, + expectedArgs: &CommandArgs{SshArgs: []string{}, CommandType: Discover, GitlabUsername: "jane-doe"}, + }, { + desc: "It parses 2fa_recovery_codes command", + environment: map[string]string{ + "SSH_CONNECTION": "1", + "SSH_ORIGINAL_COMMAND": "2fa_recovery_codes", + }, + expectedArgs: &CommandArgs{SshArgs: []string{"2fa_recovery_codes"}, CommandType: TwoFactorRecover}, + }, { + desc: "It parses git-receive-pack command", + environment: map[string]string{ + "SSH_CONNECTION": "1", + "SSH_ORIGINAL_COMMAND": "git-receive-pack group/repo", + }, + expectedArgs: &CommandArgs{SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack}, + }, { + desc: "It parses git-receive-pack command and a project with single quotes", + environment: map[string]string{ + "SSH_CONNECTION": "1", + "SSH_ORIGINAL_COMMAND": "git receive-pack 'group/repo'", + }, + expectedArgs: &CommandArgs{SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack}, + }, { + desc: `It parses "git receive-pack" command`, + environment: map[string]string{ + "SSH_CONNECTION": "1", + "SSH_ORIGINAL_COMMAND": `git receive-pack "group/repo"`, + }, + expectedArgs: &CommandArgs{SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack}, + }, { + desc: `It parses a command followed by control characters`, + environment: map[string]string{ + "SSH_CONNECTION": "1", + "SSH_ORIGINAL_COMMAND": `git-receive-pack group/repo; any command`, + }, + expectedArgs: &CommandArgs{SshArgs: []string{"git-receive-pack", "group/repo"}, CommandType: ReceivePack}, }, } @@ -57,8 +86,8 @@ func TestParseSuccess(t *testing.T) { result, err := Parse(tc.arguments) - assert.NoError(t, err) - assert.Equal(t, tc.expectedArgs, result) + require.NoError(t, err) + require.Equal(t, tc.expectedArgs, result) }) } } @@ -67,7 +96,19 @@ func TestParseFailure(t *testing.T) { t.Run("It fails if SSH connection is not set", func(t *testing.T) { _, err := Parse([]string{}) - assert.Error(t, err, "Only ssh allowed") + require.Error(t, err, "Only ssh allowed") }) + t.Run("It fails if SSH command is invalid", func(t *testing.T) { + environment := map[string]string{ + "SSH_CONNECTION": "1", + "SSH_ORIGINAL_COMMAND": `git receive-pack "`, + } + restoreEnv := testhelper.TempEnv(environment) + defer restoreEnv() + + _, err := Parse([]string{}) + + require.Error(t, err, "Invalid ssh command") + }) } diff --git a/go/internal/command/discover/discover_test.go b/go/internal/command/discover/discover_test.go index 40c0d4e..284610a 100644 --- a/go/internal/command/discover/discover_test.go +++ b/go/internal/command/discover/discover_test.go @@ -7,7 +7,6 @@ import ( "net/http" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" @@ -45,8 +44,7 @@ var ( ) func TestExecute(t *testing.T) { - cleanup, url, err := testserver.StartSocketHttpServer(requests) - require.NoError(t, err) + url, cleanup := testserver.StartSocketHttpServer(t, requests) defer cleanup() testCases := []struct { @@ -87,15 +85,14 @@ func TestExecute(t *testing.T) { err := cmd.Execute() - assert.NoError(t, err) - assert.Equal(t, tc.expectedOutput, buffer.String()) + require.NoError(t, err) + require.Equal(t, tc.expectedOutput, buffer.String()) }) } } func TestFailingExecute(t *testing.T) { - cleanup, url, err := testserver.StartSocketHttpServer(requests) - require.NoError(t, err) + url, cleanup := testserver.StartSocketHttpServer(t, requests) defer cleanup() testCases := []struct { @@ -131,8 +128,8 @@ func TestFailingExecute(t *testing.T) { err := cmd.Execute() - assert.Empty(t, buffer.String()) - assert.EqualError(t, err, tc.expectedError) + require.Empty(t, buffer.String()) + require.EqualError(t, err, tc.expectedError) }) } } diff --git a/go/internal/command/receivepack/customaction.go b/go/internal/command/receivepack/customaction.go new file mode 100644 index 0000000..8623437 --- /dev/null +++ b/go/internal/command/receivepack/customaction.go @@ -0,0 +1,99 @@ +package receivepack + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" + + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/accessverifier" +) + +type Request struct { + SecretToken []byte `json:"secret_token"` + Data accessverifier.CustomPayloadData `json:"data"` + Output []byte `json:"output"` +} + +type Response struct { + Result []byte `json:"result"` + Message string `json:"message"` +} + +func (c *Command) processCustomAction(response *accessverifier.Response) error { + data := response.Payload.Data + apiEndpoints := data.ApiEndpoints + + if len(apiEndpoints) == 0 { + return errors.New("Custom action error: Empty API endpoints") + } + + c.displayInfoMessage(data.InfoMessage) + + return c.processApiEndpoints(response) +} + +func (c *Command) displayInfoMessage(infoMessage string) { + messages := strings.Split(infoMessage, "\n") + + for _, msg := range messages { + fmt.Fprintf(c.ReadWriter.ErrOut, "> GitLab: %v\n", msg) + } +} + +func (c *Command) processApiEndpoints(response *accessverifier.Response) error { + client, err := gitlabnet.GetClient(c.Config) + + if err != nil { + return err + } + + data := response.Payload.Data + request := &Request{Data: data} + request.Data.UserId = response.Who + + for _, endpoint := range data.ApiEndpoints { + response, err := c.performRequest(client, endpoint, request) + if err != nil { + return err + } + + if err = c.displayResult(response.Result); err != nil { + return err + } + + // In the context of the git push sequence of events, it's necessary to read + // stdin in order to capture output to pass onto subsequent commands + output, err := ioutil.ReadAll(c.ReadWriter.In) + if err != nil { + return err + } + request.Output = output + } + + return nil +} + +func (c *Command) performRequest(client *gitlabnet.GitlabClient, endpoint string, request *Request) (*Response, error) { + response, err := client.DoRequest(http.MethodPost, endpoint, request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + cr := &Response{} + if err := gitlabnet.ParseJSON(response, cr); err != nil { + return nil, err + } + + return cr, nil +} + +func (c *Command) displayResult(result []byte) error { + _, err := io.Copy(c.ReadWriter.Out, bytes.NewReader(result)) + return err +} diff --git a/go/internal/command/receivepack/customaction_test.go b/go/internal/command/receivepack/customaction_test.go new file mode 100644 index 0000000..80e849c --- /dev/null +++ b/go/internal/command/receivepack/customaction_test.go @@ -0,0 +1,105 @@ +package receivepack + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/accessverifier" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/testserver" +) + +func TestCustomReceivePack(t *testing.T) { + repo := "group/repo" + keyId := "1" + + 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 request *accessverifier.Request + require.NoError(t, json.Unmarshal(b, &request)) + + require.Equal(t, "1", request.KeyId) + + body := map[string]interface{}{ + "status": true, + "gl_id": "1", + "payload": map[string]interface{}{ + "action": "geo_proxy_to_primary", + "data": map[string]interface{}{ + "api_endpoints": []string{"/geo/proxy_git_push_ssh/info_refs", "/geo/proxy_git_push_ssh/push"}, + "gl_username": "custom", + "primary_repo": "https://repo/path", + "info_message": "info_message\none more message", + }, + }, + } + w.WriteHeader(http.StatusMultipleChoices) + require.NoError(t, json.NewEncoder(w).Encode(body)) + }, + }, + { + Path: "/geo/proxy_git_push_ssh/info_refs", + Handler: func(w http.ResponseWriter, r *http.Request) { + b, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + + var request *Request + require.NoError(t, json.Unmarshal(b, &request)) + + require.Equal(t, request.Data.UserId, "key-"+keyId) + require.Empty(t, request.Output) + + err = json.NewEncoder(w).Encode(Response{Result: []byte("custom")}) + require.NoError(t, err) + }, + }, + { + Path: "/geo/proxy_git_push_ssh/push", + Handler: func(w http.ResponseWriter, r *http.Request) { + b, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + + var request *Request + require.NoError(t, json.Unmarshal(b, &request)) + + require.Equal(t, request.Data.UserId, "key-"+keyId) + require.Equal(t, "input", string(request.Output)) + + err = json.NewEncoder(w).Encode(Response{Result: []byte("output")}) + require.NoError(t, err) + }, + }, + } + + url, cleanup := testserver.StartSocketHttpServer(t, requests) + defer cleanup() + + outBuf := &bytes.Buffer{} + errBuf := &bytes.Buffer{} + input := bytes.NewBufferString("input") + + cmd := &Command{ + Config: &config.Config{GitlabUrl: url}, + Args: &commandargs.CommandArgs{GitlabKeyId: keyId, CommandType: commandargs.ReceivePack, SshArgs: []string{"git-receive-pack", repo}}, + ReadWriter: &readwriter.ReadWriter{ErrOut: errBuf, Out: outBuf, In: input}, + } + + require.NoError(t, cmd.Execute()) + + // expect printing of info message, "custom" string from the first request + // and "output" string from the second request + require.Equal(t, "> GitLab: info_message\n> GitLab: one more message\n", errBuf.String()) + require.Equal(t, "customoutput", outBuf.String()) +} diff --git a/go/internal/command/receivepack/gitalycall.go b/go/internal/command/receivepack/gitalycall.go new file mode 100644 index 0000000..22652d7 --- /dev/null +++ b/go/internal/command/receivepack/gitalycall.go @@ -0,0 +1,47 @@ +package receivepack + +import ( + "context" + + "google.golang.org/grpc" + + pb "gitlab.com/gitlab-org/gitaly-proto/go/gitalypb" + "gitlab.com/gitlab-org/gitaly/client" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/shared/accessverifier" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/handler" +) + +func (c *Command) performGitalyCall(response *accessverifier.Response) error { + gc := &handler.GitalyCommand{ + Config: c.Config, + ServiceName: string(commandargs.ReceivePack), + Address: response.Gitaly.Address, + Token: response.Gitaly.Token, + } + + repo := response.Gitaly.Repo + request := &pb.SSHReceivePackRequest{ + Repository: &pb.Repository{ + StorageName: repo.StorageName, + RelativePath: repo.RelativePath, + GitObjectDirectory: repo.GitObjectDirectory, + GitAlternateObjectDirectories: repo.GitAlternateObjectDirectories, + GlRepository: repo.RepoName, + GlProjectPath: repo.ProjectPath, + }, + GlId: response.UserId, + GlRepository: response.Repo, + GlUsername: response.Username, + GitProtocol: response.GitProtocol, + GitConfigOptions: response.GitConfigOptions, + } + + return gc.RunGitalyCommand(func(ctx context.Context, conn *grpc.ClientConn) (int32, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + rw := c.ReadWriter + return client.ReceivePack(ctx, conn, rw.In, rw.Out, rw.ErrOut, request) + }) +} diff --git a/go/internal/command/receivepack/gitalycall_test.go b/go/internal/command/receivepack/gitalycall_test.go new file mode 100644 index 0000000..0914be6 --- /dev/null +++ b/go/internal/command/receivepack/gitalycall_test.go @@ -0,0 +1,40 @@ +package receivepack + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter" + "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/requesthandlers" +) + +func TestReceivePack(t *testing.T) { + gitalyAddress, cleanup := testserver.StartGitalyServer(t) + defer cleanup() + + requests := requesthandlers.BuildAllowedWithGitalyHandlers(t, gitalyAddress) + url, cleanup := testserver.StartHttpServer(t, requests) + defer cleanup() + + output := &bytes.Buffer{} + input := &bytes.Buffer{} + + userId := "1" + repo := "group/repo" + + cmd := &Command{ + Config: &config.Config{GitlabUrl: url}, + Args: &commandargs.CommandArgs{GitlabKeyId: userId, CommandType: commandargs.ReceivePack, SshArgs: []string{"git-receive-pack", repo}}, + ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input}, + } + + err := cmd.Execute() + require.NoError(t, err) + + require.Equal(t, "ReceivePack: "+userId+" "+repo, output.String()) +} diff --git a/go/internal/command/receivepack/receivepack.go b/go/internal/command/receivepack/receivepack.go new file mode 100644 index 0000000..d1ff3f8 --- /dev/null +++ b/go/internal/command/receivepack/receivepack.go @@ -0,0 +1,45 @@ +package receivepack + +import ( + "errors" + + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/shared/accessverifier" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/config" +) + +var ( + disallowedCommandError = errors.New("> GitLab: Disallowed command") +) + +type Command struct { + Config *config.Config + Args *commandargs.CommandArgs + ReadWriter *readwriter.ReadWriter +} + +func (c *Command) Execute() error { + args := c.Args.SshArgs + if len(args) != 2 { + return disallowedCommandError + } + + repo := args[1] + response, err := c.verifyAccess(repo) + if err != nil { + return err + } + + if response.IsCustomAction() { + return c.processCustomAction(response) + } + + return c.performGitalyCall(response) +} + +func (c *Command) verifyAccess(repo string) (*accessverifier.Response, error) { + cmd := accessverifier.Command{c.Config, c.Args, c.ReadWriter} + + return cmd.Verify(c.Args.CommandType, repo) +} diff --git a/go/internal/command/receivepack/receivepack_test.go b/go/internal/command/receivepack/receivepack_test.go new file mode 100644 index 0000000..874bac3 --- /dev/null +++ b/go/internal/command/receivepack/receivepack_test.go @@ -0,0 +1,46 @@ +package receivepack + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/testserver" +) + +func TestForbiddenAccess(t *testing.T) { + requests := []testserver.TestRequestHandler{ + { + Path: "/api/v4/internal/allowed", + Handler: func(w http.ResponseWriter, r *http.Request) { + body := map[string]interface{}{ + "status": false, + "message": "Disallowed by API call", + } + w.WriteHeader(http.StatusForbidden) + require.NoError(t, json.NewEncoder(w).Encode(body)) + }, + }, + } + + url, cleanup := testserver.StartHttpServer(t, requests) + defer cleanup() + + output := &bytes.Buffer{} + input := bytes.NewBufferString("input") + + cmd := &Command{ + Config: &config.Config{GitlabUrl: url}, + Args: &commandargs.CommandArgs{GitlabKeyId: "disallowed", SshArgs: []string{"git-receive-pack", "group/repo"}}, + ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output, In: input}, + } + + err := cmd.Execute() + require.Equal(t, "Disallowed by API call", err.Error()) +} diff --git a/go/internal/command/shared/accessverifier/accessverifier.go b/go/internal/command/shared/accessverifier/accessverifier.go new file mode 100644 index 0000000..6d13789 --- /dev/null +++ b/go/internal/command/shared/accessverifier/accessverifier.go @@ -0,0 +1,45 @@ +package accessverifier + +import ( + "errors" + "fmt" + + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/accessverifier" +) + +type Response = accessverifier.Response + +type Command struct { + Config *config.Config + Args *commandargs.CommandArgs + ReadWriter *readwriter.ReadWriter +} + +func (c *Command) Verify(action commandargs.CommandType, repo string) (*Response, error) { + client, err := accessverifier.NewClient(c.Config) + if err != nil { + return nil, err + } + + response, err := client.Verify(c.Args, action, repo) + if err != nil { + return nil, err + } + + c.displayConsoleMessages(response.ConsoleMessages) + + if !response.Success { + return nil, errors.New(response.Message) + } + + return response, nil +} + +func (c *Command) displayConsoleMessages(messages []string) { + for _, msg := range messages { + fmt.Fprintf(c.ReadWriter.ErrOut, "> GitLab: %v\n", msg) + } +} diff --git a/go/internal/command/shared/accessverifier/accessverifier_test.go b/go/internal/command/shared/accessverifier/accessverifier_test.go new file mode 100644 index 0000000..dd95ded --- /dev/null +++ b/go/internal/command/shared/accessverifier/accessverifier_test.go @@ -0,0 +1,82 @@ +package accessverifier + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/readwriter" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/config" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/accessverifier" + "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/testserver" +) + +var ( + repo = "group/repo" + action = commandargs.ReceivePack +) + +func setup(t *testing.T) (*Command, *bytes.Buffer, *bytes.Buffer, func()) { + 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 *accessverifier.Request + err = json.Unmarshal(b, &requestBody) + require.NoError(t, err) + + if requestBody.KeyId == "1" { + body := map[string]interface{}{ + "gl_console_messages": []string{"console", "message"}, + } + require.NoError(t, json.NewEncoder(w).Encode(body)) + } else { + body := map[string]interface{}{ + "status": false, + "message": "missing user", + } + require.NoError(t, json.NewEncoder(w).Encode(body)) + } + }, + }, + } + + url, cleanup := testserver.StartSocketHttpServer(t, requests) + + errBuf := &bytes.Buffer{} + outBuf := &bytes.Buffer{} + + readWriter := &readwriter.ReadWriter{Out: outBuf, ErrOut: errBuf} + cmd := &Command{Config: &config.Config{GitlabUrl: url}, ReadWriter: readWriter} + + return cmd, errBuf, outBuf, cleanup +} + +func TestMissingUser(t *testing.T) { + cmd, _, _, cleanup := setup(t) + defer cleanup() + + cmd.Args = &commandargs.CommandArgs{GitlabKeyId: "2"} + _, err := cmd.Verify(action, repo) + + require.Equal(t, "missing user", err.Error()) +} + +func TestConsoleMessages(t *testing.T) { + cmd, errBuf, outBuf, cleanup := setup(t) + defer cleanup() + + cmd.Args = &commandargs.CommandArgs{GitlabKeyId: "1"} + cmd.Verify(action, repo) + + require.Equal(t, "> GitLab: console\n> GitLab: message\n", errBuf.String()) + require.Empty(t, outBuf.String()) +} diff --git a/go/internal/command/twofactorrecover/twofactorrecover_test.go b/go/internal/command/twofactorrecover/twofactorrecover_test.go index bcca12a..6238e0d 100644 --- a/go/internal/command/twofactorrecover/twofactorrecover_test.go +++ b/go/internal/command/twofactorrecover/twofactorrecover_test.go @@ -64,8 +64,7 @@ const ( func TestExecute(t *testing.T) { setup(t) - cleanup, url, err := testserver.StartSocketHttpServer(requests) - require.NoError(t, err) + url, cleanup := testserver.StartSocketHttpServer(t, requests) defer cleanup() testCases := []struct { |