summaryrefslogtreecommitdiff
path: root/go/internal/command
diff options
context:
space:
mode:
authorIgor Drozdov <idrozdov@gitlab.com>2019-04-28 20:42:19 +0300
committerIgor Drozdov <idrozdov@gitlab.com>2019-06-06 13:21:58 +0300
commit5aa947ed705b48b2980894012eb32e7ee5147b5e (patch)
tree3557c79227b01376a90f381c5f60f0ef42727baa /go/internal/command
parenteb2b186f7d209a638b7523c674bc79cbafe764b6 (diff)
downloadgitlab-shell-id-git-lfs-authenticate.tar.gz
Go implementation for LFS authenticateid-git-lfs-authenticate
Diffstat (limited to 'go/internal/command')
-rw-r--r--go/internal/command/command.go3
-rw-r--r--go/internal/command/command_test.go13
-rw-r--r--go/internal/command/commandargs/command_args.go1
-rw-r--r--go/internal/command/commandargs/command_args_test.go7
-rw-r--r--go/internal/command/lfsauthenticate/lfsauthenticate.go104
-rw-r--r--go/internal/command/lfsauthenticate/lfsauthenticate_test.go153
6 files changed, 281 insertions, 0 deletions
diff --git a/go/internal/command/command.go b/go/internal/command/command.go
index 7bc1994..a1dde42 100644
--- a/go/internal/command/command.go
+++ b/go/internal/command/command.go
@@ -4,6 +4,7 @@ import (
"gitlab.com/gitlab-org/gitlab-shell/go/internal/command/commandargs"
"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/lfsauthenticate"
"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"
@@ -38,6 +39,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.LfsAuthenticate:
+ return &lfsauthenticate.Command{Config: config, Args: args, ReadWriter: readWriter}
case commandargs.ReceivePack:
return &receivepack.Command{Config: config, Args: args, ReadWriter: readWriter}
case commandargs.UploadPack:
diff --git a/go/internal/command/command_test.go b/go/internal/command/command_test.go
index cbdfc56..07260dd 100644
--- a/go/internal/command/command_test.go
+++ b/go/internal/command/command_test.go
@@ -7,6 +7,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/lfsauthenticate"
"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/command/uploadarchive"
@@ -59,6 +60,18 @@ func TestNew(t *testing.T) {
expectedType: &twofactorrecover.Command{},
},
{
+ desc: "it returns an LfsAuthenticate command if the feature is enabled",
+ config: &config.Config{
+ GitlabUrl: "http+unix://gitlab.socket",
+ Migration: config.MigrationConfig{Enabled: true, Features: []string{"git-lfs-authenticate"}},
+ },
+ environment: map[string]string{
+ "SSH_CONNECTION": "1",
+ "SSH_ORIGINAL_COMMAND": "git-lfs-authenticate",
+ },
+ expectedType: &lfsauthenticate.Command{},
+ },
+ {
desc: "it returns a ReceivePack command if the feature is enabled",
config: &config.Config{
GitlabUrl: "http+unix://gitlab.socket",
diff --git a/go/internal/command/commandargs/command_args.go b/go/internal/command/commandargs/command_args.go
index fd9d741..d8fe32d 100644
--- a/go/internal/command/commandargs/command_args.go
+++ b/go/internal/command/commandargs/command_args.go
@@ -13,6 +13,7 @@ type CommandType string
const (
Discover CommandType = "discover"
TwoFactorRecover CommandType = "2fa_recovery_codes"
+ LfsAuthenticate CommandType = "git-lfs-authenticate"
ReceivePack CommandType = "git-receive-pack"
UploadPack CommandType = "git-upload-pack"
UploadArchive CommandType = "git-upload-archive"
diff --git a/go/internal/command/commandargs/command_args_test.go b/go/internal/command/commandargs/command_args_test.go
index 7c360ad..e60bb92 100644
--- a/go/internal/command/commandargs/command_args_test.go
+++ b/go/internal/command/commandargs/command_args_test.go
@@ -90,6 +90,13 @@ func TestParseSuccess(t *testing.T) {
"SSH_ORIGINAL_COMMAND": "git-upload-archive 'group/repo'",
},
expectedArgs: &CommandArgs{SshArgs: []string{"git-upload-archive", "group/repo"}, CommandType: UploadArchive},
+ }, {
+ desc: "It parses git-lfs-authenticate command",
+ environment: map[string]string{
+ "SSH_CONNECTION": "1",
+ "SSH_ORIGINAL_COMMAND": "git-lfs-authenticate 'group/repo' download",
+ },
+ expectedArgs: &CommandArgs{SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}, CommandType: LfsAuthenticate},
},
}
diff --git a/go/internal/command/lfsauthenticate/lfsauthenticate.go b/go/internal/command/lfsauthenticate/lfsauthenticate.go
new file mode 100644
index 0000000..c1dc45f
--- /dev/null
+++ b/go/internal/command/lfsauthenticate/lfsauthenticate.go
@@ -0,0 +1,104 @@
+package lfsauthenticate
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "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/command/shared/accessverifier"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/command/shared/disallowedcommand"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/config"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/lfsauthenticate"
+)
+
+const (
+ downloadAction = "download"
+ uploadAction = "upload"
+)
+
+type Command struct {
+ Config *config.Config
+ Args *commandargs.CommandArgs
+ ReadWriter *readwriter.ReadWriter
+}
+
+type PayloadHeader struct {
+ Auth string `json:"Authorization"`
+}
+
+type Payload struct {
+ Header PayloadHeader `json:"header"`
+ Href string `json:"href"`
+ ExpiresIn int `json:"expires_in,omitempty"`
+}
+
+func (c *Command) Execute() error {
+ args := c.Args.SshArgs
+ if len(args) < 3 {
+ return disallowedcommand.Error
+ }
+
+ repo := args[1]
+ action, err := actionToCommandType(args[2])
+ if err != nil {
+ return err
+ }
+
+ accessResponse, err := c.verifyAccess(action, repo)
+ if err != nil {
+ return err
+ }
+
+ payload, err := c.authenticate(action, repo, accessResponse.UserId)
+ if err != nil {
+ // return nothing just like Ruby's GitlabShell#lfs_authenticate does
+ return nil
+ }
+
+ fmt.Fprintf(c.ReadWriter.Out, "%s\n", payload)
+
+ return nil
+}
+
+func actionToCommandType(action string) (commandargs.CommandType, error) {
+ var accessAction commandargs.CommandType
+ switch action {
+ case downloadAction:
+ accessAction = commandargs.UploadPack
+ case uploadAction:
+ accessAction = commandargs.ReceivePack
+ default:
+ return "", disallowedcommand.Error
+ }
+
+ return accessAction, nil
+}
+
+func (c *Command) verifyAccess(action commandargs.CommandType, repo string) (*accessverifier.Response, error) {
+ cmd := accessverifier.Command{c.Config, c.Args, c.ReadWriter}
+
+ return cmd.Verify(action, repo)
+}
+
+func (c *Command) authenticate(action commandargs.CommandType, repo, userId string) ([]byte, error) {
+ client, err := lfsauthenticate.NewClient(c.Config, c.Args)
+ if err != nil {
+ return nil, err
+ }
+
+ response, err := client.Authenticate(action, repo, userId)
+ if err != nil {
+ return nil, err
+ }
+
+ basicAuth := base64.StdEncoding.EncodeToString([]byte(response.Username + ":" + response.LfsToken))
+ payload := &Payload{
+ Header: PayloadHeader{Auth: "Basic " + basicAuth},
+ Href: response.RepoPath + "/info/lfs",
+ ExpiresIn: response.ExpiresIn,
+ }
+
+ return json.Marshal(payload)
+}
diff --git a/go/internal/command/lfsauthenticate/lfsauthenticate_test.go b/go/internal/command/lfsauthenticate/lfsauthenticate_test.go
new file mode 100644
index 0000000..30da94b
--- /dev/null
+++ b/go/internal/command/lfsauthenticate/lfsauthenticate_test.go
@@ -0,0 +1,153 @@
+package lfsauthenticate
+
+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/lfsauthenticate"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/gitlabnet/testserver"
+ "gitlab.com/gitlab-org/gitlab-shell/go/internal/testhelper/requesthandlers"
+)
+
+func TestFailedRequests(t *testing.T) {
+ requests := requesthandlers.BuildDisallowedByApiHandlers(t)
+ url, cleanup := testserver.StartHttpServer(t, requests)
+ defer cleanup()
+
+ testCases := []struct {
+ desc string
+ arguments *commandargs.CommandArgs
+ expectedOutput string
+ }{
+ {
+ desc: "With missing arguments",
+ arguments: &commandargs.CommandArgs{},
+ expectedOutput: "> GitLab: Disallowed command",
+ },
+ {
+ desc: "With disallowed command",
+ arguments: &commandargs.CommandArgs{GitlabKeyId: "1", SshArgs: []string{"git-lfs-authenticate", "group/repo", "unknown"}},
+ expectedOutput: "> GitLab: Disallowed command",
+ },
+ {
+ desc: "With disallowed user",
+ arguments: &commandargs.CommandArgs{GitlabKeyId: "disallowed", SshArgs: []string{"git-lfs-authenticate", "group/repo", "download"}},
+ expectedOutput: "Disallowed by API call",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ output := &bytes.Buffer{}
+ cmd := &Command{
+ Config: &config.Config{GitlabUrl: url},
+ Args: tc.arguments,
+ ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output},
+ }
+
+ err := cmd.Execute()
+ require.Error(t, err)
+
+ require.Equal(t, tc.expectedOutput, err.Error())
+ })
+ }
+}
+
+func TestLfsAuthenticateRequests(t *testing.T) {
+ userId := "123"
+
+ requests := []testserver.TestRequestHandler{
+ {
+ Path: "/api/v4/internal/lfs_authenticate",
+ Handler: func(w http.ResponseWriter, r *http.Request) {
+ b, err := ioutil.ReadAll(r.Body)
+ defer r.Body.Close()
+ require.NoError(t, err)
+
+ var request *lfsauthenticate.Request
+ require.NoError(t, json.Unmarshal(b, &request))
+
+ if request.UserId == userId {
+ body := map[string]interface{}{
+ "username": "john",
+ "lfs_token": "sometoken",
+ "repository_http_path": "https://gitlab.com/repo/path",
+ "expires_in": 1800,
+ }
+ require.NoError(t, json.NewEncoder(w).Encode(body))
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ }
+ },
+ },
+ {
+ Path: "/api/v4/internal/allowed",
+ Handler: func(w http.ResponseWriter, r *http.Request) {
+ b, err := ioutil.ReadAll(r.Body)
+ defer r.Body.Close()
+ require.NoError(t, err)
+
+ var request *accessverifier.Request
+ require.NoError(t, json.Unmarshal(b, &request))
+
+ var glId string
+ if request.Username == "somename" {
+ glId = userId
+ } else {
+ glId = "100"
+ }
+
+ body := map[string]interface{}{
+ "gl_id": glId,
+ "status": true,
+ }
+ require.NoError(t, json.NewEncoder(w).Encode(body))
+ },
+ },
+ }
+
+ url, cleanup := testserver.StartHttpServer(t, requests)
+ defer cleanup()
+
+ testCases := []struct {
+ desc string
+ username string
+ expectedOutput string
+ }{
+ {
+ desc: "With successful response from API",
+ username: "somename",
+ expectedOutput: "{\"header\":{\"Authorization\":\"Basic am9objpzb21ldG9rZW4=\"},\"href\":\"https://gitlab.com/repo/path/info/lfs\",\"expires_in\":1800}\n",
+ },
+ {
+ desc: "With forbidden response from API",
+ username: "anothername",
+ expectedOutput: "",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ output := &bytes.Buffer{}
+ cmd := &Command{
+ Config: &config.Config{GitlabUrl: url},
+ Args: &commandargs.CommandArgs{GitlabUsername: tc.username, SshArgs: []string{"git-lfs-authenticate", "group/repo", "upload"}},
+ ReadWriter: &readwriter.ReadWriter{ErrOut: output, Out: output},
+ }
+
+ err := cmd.Execute()
+ require.NoError(t, err)
+
+ require.Equal(t, tc.expectedOutput, output.String())
+ })
+ }
+}