From a1656dffa98fbc8865e476b214e4e0c562547d39 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Mon, 14 May 2018 13:24:22 -0400 Subject: [PATCH] MM-10201: querystring for get slash commands (#8779) * pass GET slash command payloads through query string Previously, both GET and POST requests received the payload via the body, but this was incorrect for GET requests. Now, the payloads for GET requests is sent via the query string. * reorder tests for clarity * switch command tests to use httptest servers * restore original test command endpoints --- api4/command_test.go | 191 ++++++++++++++++++++++++++++++------------- app/command.go | 8 +- 2 files changed, 137 insertions(+), 62 deletions(-) diff --git a/api4/command_test.go b/api4/command_test.go index 8c4ce5d504..0d37d74405 100644 --- a/api4/command_test.go +++ b/api4/command_test.go @@ -5,9 +5,13 @@ package api4 import ( "fmt" - "strings" + "net/http" + "net/http/httptest" + "net/url" "testing" + "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/model" ) @@ -392,7 +396,7 @@ func TestRegenToken(t *testing.T) { } } -func TestExecuteCommand(t *testing.T) { +func TestExecuteInvalidCommand(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() Client := th.Client @@ -407,56 +411,19 @@ func TestExecuteCommand(t *testing.T) { }) }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost" }) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "127.0.0.0/8" }) - postCmd := &model.Command{ - CreatorId: th.BasicUser.Id, - TeamId: th.BasicTeam.Id, - URL: fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port) + model.API_URL_SUFFIX_V4 + "/teams/command_test", - Method: model.COMMAND_METHOD_POST, - Trigger: "postcommand", - } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rc := &model.CommandResponse{} - if _, err := th.App.CreateCommand(postCmd); err != nil { - t.Fatal("failed to create post command") - } - - commandResponse, resp := Client.ExecuteCommand(channel.Id, "/postcommand") - CheckNoError(t, resp) - - if commandResponse == nil { - t.Fatal("command response should have returned") - } - - posts, err := th.App.GetPostsPage(channel.Id, 0, 10) - if err != nil || posts == nil || len(posts.Order) != 3 { - t.Fatal("Test command failed to send") - } - - cmdPosted := false - for _, post := range posts.Posts { - if strings.Contains(post.Message, "test command response") { - if post.Type != "custom_test" { - t.Fatal("wrong type set in slash command post") - } - - if post.Props["someprop"] != "somevalue" { - t.Fatal("wrong prop set in slash command post") - } - - cmdPosted = true - break - } - } - - if !cmdPosted { - t.Fatal("Test command response failed to post") - } + w.Write([]byte(rc.ToJson())) + })) + defer ts.Close() getCmd := &model.Command{ CreatorId: th.BasicUser.Id, TeamId: th.BasicTeam.Id, - URL: fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port) + model.API_URL_SUFFIX_V4 + "/teams/command_test", + URL: fmt.Sprintf("%s/%s/teams/command_test", ts.URL, model.API_URL_SUFFIX_V4), Method: model.COMMAND_METHOD_GET, Trigger: "getcommand", } @@ -465,19 +432,7 @@ func TestExecuteCommand(t *testing.T) { t.Fatal("failed to create get command") } - commandResponse, resp = Client.ExecuteCommand(channel.Id, "/getcommand") - CheckNoError(t, resp) - - if commandResponse == nil { - t.Fatal("command response should have returned") - } - - posts, err = th.App.GetPostsPage(channel.Id, 0, 10) - if err != nil || posts == nil || len(posts.Order) != 4 { - t.Fatal("Test command failed to send") - } - - _, resp = Client.ExecuteCommand(channel.Id, "") + _, resp := Client.ExecuteCommand(channel.Id, "") CheckBadRequestStatus(t, resp) _, resp = Client.ExecuteCommand(channel.Id, "/") @@ -504,6 +459,124 @@ func TestExecuteCommand(t *testing.T) { CheckNoError(t, resp) } +func TestExecuteGetCommand(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + Client := th.Client + channel := th.BasicChannel + + enableCommands := *th.App.Config().ServiceSettings.EnableCommands + allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands }) + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections + }) + }() + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "127.0.0.0/8" }) + + token := model.NewId() + expectedCommandResponse := &model.CommandResponse{ + Text: "test get command response", + ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, + Type: "custom_test", + Props: map[string]interface{}{"someprop": "somevalue"}, + } + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + + values, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + + require.Equal(t, token, values.Get("token")) + require.Equal(t, th.BasicTeam.Name, values.Get("team_domain")) + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(expectedCommandResponse.ToJson())) + })) + defer ts.Close() + + getCmd := &model.Command{ + CreatorId: th.BasicUser.Id, + TeamId: th.BasicTeam.Id, + URL: fmt.Sprintf("%s/%s/teams/command_test", ts.URL, model.API_URL_SUFFIX_V4), + Method: model.COMMAND_METHOD_GET, + Trigger: "getcommand", + Token: token, + } + + if _, err := th.App.CreateCommand(getCmd); err != nil { + t.Fatal("failed to create get command") + } + + commandResponse, resp := Client.ExecuteCommand(channel.Id, "/getcommand") + CheckNoError(t, resp) + + expectedCommandResponse.Props["from_webhook"] = "true" + require.Equal(t, expectedCommandResponse, commandResponse) +} + +func TestExecutePostCommand(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + Client := th.Client + channel := th.BasicChannel + + enableCommands := *th.App.Config().ServiceSettings.EnableCommands + allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands }) + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections + }) + }() + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "127.0.0.0/8" }) + + token := model.NewId() + expectedCommandResponse := &model.CommandResponse{ + Text: "test post command response", + ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, + Type: "custom_test", + Props: map[string]interface{}{"someprop": "somevalue"}, + } + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + r.ParseForm() + + require.Equal(t, token, r.FormValue("token")) + require.Equal(t, th.BasicTeam.Name, r.FormValue("team_domain")) + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(expectedCommandResponse.ToJson())) + })) + defer ts.Close() + + getCmd := &model.Command{ + CreatorId: th.BasicUser.Id, + TeamId: th.BasicTeam.Id, + URL: fmt.Sprintf("%s/%s/teams/command_test", ts.URL, model.API_URL_SUFFIX_V4), + Method: model.COMMAND_METHOD_POST, + Trigger: "postcommand", + Token: token, + } + + if _, err := th.App.CreateCommand(getCmd); err != nil { + t.Fatal("failed to create get command") + } + + commandResponse, resp := Client.ExecuteCommand(channel.Id, "/postcommand") + CheckNoError(t, resp) + + expectedCommandResponse.Props["from_webhook"] = "true" + require.Equal(t, expectedCommandResponse, commandResponse) + +} + func TestExecuteCommandAgainstChannelOnAnotherTeam(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/app/command.go b/app/command.go index 796d656a7f..92c35865ab 100644 --- a/app/command.go +++ b/app/command.go @@ -230,12 +230,14 @@ func (a *App) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, * p.Set("response_url", args.SiteURL+"/hooks/commands/"+hook.Id) } - method := "POST" + var req *http.Request if cmd.Method == model.COMMAND_METHOD_GET { - method = "GET" + req, _ = http.NewRequest(http.MethodGet, cmd.URL, nil) + req.URL.RawQuery = p.Encode() + } else { + req, _ = http.NewRequest(http.MethodPost, cmd.URL, strings.NewReader(p.Encode())) } - req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode())) req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Token "+cmd.Token) if cmd.Method == model.COMMAND_METHOD_POST {