As we reevaluate how to best support and maintain Staging Ref in the future, we encourage development teams using this environment to highlight their use cases in the following issue: https://gitlab.com/gitlab-com/gl-infra/software-delivery/framework/software-delivery-framework-issue-tracker/-/issues/36.

Skip to content
Snippets Groups Projects
Commit 033c81d5 authored by Igor Drozdov's avatar Igor Drozdov Committed by Nick Thomas
Browse files

Go implementation for git-receive-pack

parent 12ca54c2
No related branches found
No related tags found
No related merge requests found
Showing
with 961 additions and 53 deletions
Loading
Loading
@@ -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"
)
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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"
Loading
Loading
@@ -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 {
Loading
Loading
@@ -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)
})
}
}
Loading
Loading
@@ -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")
})
}
Loading
Loading
@@ -4,6 +4,8 @@ import (
"errors"
"os"
"regexp"
"github.com/mattn/go-shellwords"
)
type CommandType string
Loading
Loading
@@ -11,6 +13,7 @@ type CommandType string
const (
Discover CommandType = "discover"
TwoFactorRecover CommandType = "2fa_recovery_codes"
ReceivePack CommandType = "git-receive-pack"
)
var (
Loading
Loading
@@ -21,7 +24,7 @@ var (
type CommandArgs struct {
GitlabUsername string
GitlabKeyId string
SshCommand string
SshArgs []string
CommandType CommandType
}
Loading
Loading
@@ -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) {
Loading
Loading
@@ -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])
}
}
Loading
Loading
@@ -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"
)
Loading
Loading
@@ -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{
Loading
Loading
@@ -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},
},
}
Loading
Loading
@@ -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)
})
}
}
Loading
Loading
@@ -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")
})
}
Loading
Loading
@@ -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"
Loading
Loading
@@ -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 {
Loading
Loading
@@ -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 {
Loading
Loading
@@ -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)
})
}
}
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
}
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())
}
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)
})
}
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())
}
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)
}
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())
}
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)
}
}
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())
}
Loading
Loading
@@ -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 {
Loading
Loading
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
}
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
}
Loading
Loading
@@ -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)
Loading
Loading
@@ -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()
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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"
Loading
Loading
@@ -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",
Loading
Loading
@@ -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"
Loading
Loading
Loading
Loading
@@ -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)
Loading
Loading
Loading
Loading
@@ -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)
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment