github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/integration_action_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/mattermost/mattermost-server/v5/model"
    20  )
    21  
    22  // Test for MM-13598 where an invalid integration URL was causing a crash
    23  func TestPostActionInvalidURL(t *testing.T) {
    24  	th := Setup(t).InitBasic()
    25  	defer th.TearDown()
    26  
    27  	th.App.UpdateConfig(func(cfg *model.Config) {
    28  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
    29  	})
    30  
    31  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    32  		request := model.PostActionIntegrationRequestFromJson(r.Body)
    33  		assert.NotNil(t, request)
    34  	}))
    35  	defer ts.Close()
    36  
    37  	interactivePost := model.Post{
    38  		Message:       "Interactive post",
    39  		ChannelId:     th.BasicChannel.Id,
    40  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
    41  		UserId:        th.BasicUser.Id,
    42  		Props: model.StringInterface{
    43  			"attachments": []*model.SlackAttachment{
    44  				{
    45  					Text: "hello",
    46  					Actions: []*model.PostAction{
    47  						{
    48  							Integration: &model.PostActionIntegration{
    49  								URL: ":test",
    50  							},
    51  							Name: "action",
    52  							Type: "some_type",
    53  						},
    54  					},
    55  				},
    56  			},
    57  		},
    58  	}
    59  
    60  	post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
    61  	require.Nil(t, err)
    62  	attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
    63  	require.True(t, ok)
    64  	require.NotEmpty(t, attachments[0].Actions)
    65  	require.NotEmpty(t, attachments[0].Actions[0].Id)
    66  
    67  	_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
    68  	require.NotNil(t, err)
    69  	require.True(t, strings.Contains(err.Error(), "missing protocol scheme"))
    70  }
    71  
    72  func TestPostAction(t *testing.T) {
    73  	testCases := []struct {
    74  		Description string
    75  		Channel     func(th *TestHelper) *model.Channel
    76  	}{
    77  		{"public channel", func(th *TestHelper) *model.Channel {
    78  			return th.BasicChannel
    79  		}},
    80  		{"direct channel", func(th *TestHelper) *model.Channel {
    81  			user1 := th.CreateUser()
    82  
    83  			return th.CreateDmChannel(user1)
    84  		}},
    85  		{"group channel", func(th *TestHelper) *model.Channel {
    86  			user1 := th.CreateUser()
    87  			user2 := th.CreateUser()
    88  
    89  			return th.CreateGroupChannel(user1, user2)
    90  		}},
    91  	}
    92  
    93  	for _, testCase := range testCases {
    94  		t.Run(testCase.Description, func(t *testing.T) {
    95  			th := Setup(t).InitBasic()
    96  			defer th.TearDown()
    97  
    98  			channel := testCase.Channel(th)
    99  
   100  			th.App.UpdateConfig(func(cfg *model.Config) {
   101  				*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
   102  			})
   103  
   104  			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   105  				request := model.PostActionIntegrationRequestFromJson(r.Body)
   106  				assert.NotNil(t, request)
   107  
   108  				assert.Equal(t, request.UserId, th.BasicUser.Id)
   109  				assert.Equal(t, request.UserName, th.BasicUser.Username)
   110  				assert.Equal(t, request.ChannelId, channel.Id)
   111  				assert.Equal(t, request.ChannelName, channel.Name)
   112  				if channel.Type == model.CHANNEL_DIRECT || channel.Type == model.CHANNEL_GROUP {
   113  					assert.Empty(t, request.TeamId)
   114  					assert.Empty(t, request.TeamName)
   115  				} else {
   116  					assert.Equal(t, request.TeamId, th.BasicTeam.Id)
   117  					assert.Equal(t, request.TeamName, th.BasicTeam.Name)
   118  				}
   119  				assert.True(t, request.TriggerId != "")
   120  				if request.Type == model.POST_ACTION_TYPE_SELECT {
   121  					assert.Equal(t, request.DataSource, "some_source")
   122  					assert.Equal(t, request.Context["selected_option"], "selected")
   123  				} else {
   124  					assert.Equal(t, request.DataSource, "")
   125  				}
   126  				assert.Equal(t, "foo", request.Context["s"])
   127  				assert.EqualValues(t, 3, request.Context["n"])
   128  				fmt.Fprintf(w, `{"post": {"message": "updated"}, "ephemeral_text": "foo"}`)
   129  			}))
   130  			defer ts.Close()
   131  
   132  			interactivePost := model.Post{
   133  				Message:       "Interactive post",
   134  				ChannelId:     channel.Id,
   135  				PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   136  				UserId:        th.BasicUser.Id,
   137  				Props: model.StringInterface{
   138  					"attachments": []*model.SlackAttachment{
   139  						{
   140  							Text: "hello",
   141  							Actions: []*model.PostAction{
   142  								{
   143  									Integration: &model.PostActionIntegration{
   144  										Context: model.StringInterface{
   145  											"s": "foo",
   146  											"n": 3,
   147  										},
   148  										URL: ts.URL,
   149  									},
   150  									Name:       "action",
   151  									Type:       "some_type",
   152  									DataSource: "some_source",
   153  								},
   154  							},
   155  						},
   156  					},
   157  				},
   158  			}
   159  
   160  			post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   161  			require.Nil(t, err)
   162  
   163  			attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   164  			require.True(t, ok)
   165  
   166  			require.NotEmpty(t, attachments[0].Actions)
   167  			require.NotEmpty(t, attachments[0].Actions[0].Id)
   168  
   169  			menuPost := model.Post{
   170  				Message:       "Interactive post",
   171  				ChannelId:     channel.Id,
   172  				PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   173  				UserId:        th.BasicUser.Id,
   174  				Props: model.StringInterface{
   175  					"attachments": []*model.SlackAttachment{
   176  						{
   177  							Text: "hello",
   178  							Actions: []*model.PostAction{
   179  								{
   180  									Integration: &model.PostActionIntegration{
   181  										Context: model.StringInterface{
   182  											"s": "foo",
   183  											"n": 3,
   184  										},
   185  										URL: ts.URL,
   186  									},
   187  									Name:       "action",
   188  									Type:       model.POST_ACTION_TYPE_SELECT,
   189  									DataSource: "some_source",
   190  								},
   191  							},
   192  						},
   193  					},
   194  				},
   195  			}
   196  
   197  			post2, err := th.App.CreatePostAsUser(&menuPost, "", true)
   198  			require.Nil(t, err)
   199  
   200  			attachments2, ok := post2.GetProp("attachments").([]*model.SlackAttachment)
   201  			require.True(t, ok)
   202  
   203  			require.NotEmpty(t, attachments2[0].Actions)
   204  			require.NotEmpty(t, attachments2[0].Actions[0].Id)
   205  
   206  			clientTriggerId, err := th.App.DoPostAction(post.Id, "notavalidid", th.BasicUser.Id, "")
   207  			require.NotNil(t, err)
   208  			assert.Equal(t, http.StatusNotFound, err.StatusCode)
   209  			assert.True(t, clientTriggerId == "")
   210  
   211  			clientTriggerId, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   212  			require.Nil(t, err)
   213  			assert.True(t, len(clientTriggerId) == 26)
   214  
   215  			clientTriggerId, err = th.App.DoPostAction(post2.Id, attachments2[0].Actions[0].Id, th.BasicUser.Id, "selected")
   216  			require.Nil(t, err)
   217  			assert.True(t, len(clientTriggerId) == 26)
   218  
   219  			th.App.UpdateConfig(func(cfg *model.Config) {
   220  				*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   221  			})
   222  
   223  			_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   224  			require.NotNil(t, err)
   225  			require.True(t, strings.Contains(err.Error(), "address forbidden"))
   226  
   227  			interactivePostPlugin := model.Post{
   228  				Message:       "Interactive post",
   229  				ChannelId:     channel.Id,
   230  				PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   231  				UserId:        th.BasicUser.Id,
   232  				Props: model.StringInterface{
   233  					"attachments": []*model.SlackAttachment{
   234  						{
   235  							Text: "hello",
   236  							Actions: []*model.PostAction{
   237  								{
   238  									Integration: &model.PostActionIntegration{
   239  										Context: model.StringInterface{
   240  											"s": "foo",
   241  											"n": 3,
   242  										},
   243  										URL: ts.URL + "/plugins/myplugin/myaction",
   244  									},
   245  									Name:       "action",
   246  									Type:       "some_type",
   247  									DataSource: "some_source",
   248  								},
   249  							},
   250  						},
   251  					},
   252  				},
   253  			}
   254  
   255  			postplugin, err := th.App.CreatePostAsUser(&interactivePostPlugin, "", true)
   256  			require.Nil(t, err)
   257  
   258  			attachmentsPlugin, ok := postplugin.GetProp("attachments").([]*model.SlackAttachment)
   259  			require.True(t, ok)
   260  
   261  			_, err = th.App.DoPostAction(postplugin.Id, attachmentsPlugin[0].Actions[0].Id, th.BasicUser.Id, "")
   262  			require.Nil(t, err)
   263  
   264  			th.App.UpdateConfig(func(cfg *model.Config) {
   265  				*cfg.ServiceSettings.SiteURL = "http://127.1.1.1"
   266  			})
   267  
   268  			interactivePostSiteURL := model.Post{
   269  				Message:       "Interactive post",
   270  				ChannelId:     channel.Id,
   271  				PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   272  				UserId:        th.BasicUser.Id,
   273  				Props: model.StringInterface{
   274  					"attachments": []*model.SlackAttachment{
   275  						{
   276  							Text: "hello",
   277  							Actions: []*model.PostAction{
   278  								{
   279  									Integration: &model.PostActionIntegration{
   280  										Context: model.StringInterface{
   281  											"s": "foo",
   282  											"n": 3,
   283  										},
   284  										URL: "http://127.1.1.1/plugins/myplugin/myaction",
   285  									},
   286  									Name:       "action",
   287  									Type:       "some_type",
   288  									DataSource: "some_source",
   289  								},
   290  							},
   291  						},
   292  					},
   293  				},
   294  			}
   295  
   296  			postSiteURL, err := th.App.CreatePostAsUser(&interactivePostSiteURL, "", true)
   297  			require.Nil(t, err)
   298  
   299  			attachmentsSiteURL, ok := postSiteURL.GetProp("attachments").([]*model.SlackAttachment)
   300  			require.True(t, ok)
   301  
   302  			_, err = th.App.DoPostAction(postSiteURL.Id, attachmentsSiteURL[0].Actions[0].Id, th.BasicUser.Id, "")
   303  			require.NotNil(t, err)
   304  			require.False(t, strings.Contains(err.Error(), "address forbidden"))
   305  
   306  			th.App.UpdateConfig(func(cfg *model.Config) {
   307  				*cfg.ServiceSettings.SiteURL = ts.URL + "/subpath"
   308  			})
   309  
   310  			interactivePostSubpath := model.Post{
   311  				Message:       "Interactive post",
   312  				ChannelId:     channel.Id,
   313  				PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   314  				UserId:        th.BasicUser.Id,
   315  				Props: model.StringInterface{
   316  					"attachments": []*model.SlackAttachment{
   317  						{
   318  							Text: "hello",
   319  							Actions: []*model.PostAction{
   320  								{
   321  									Integration: &model.PostActionIntegration{
   322  										Context: model.StringInterface{
   323  											"s": "foo",
   324  											"n": 3,
   325  										},
   326  										URL: ts.URL + "/subpath/plugins/myplugin/myaction",
   327  									},
   328  									Name:       "action",
   329  									Type:       "some_type",
   330  									DataSource: "some_source",
   331  								},
   332  							},
   333  						},
   334  					},
   335  				},
   336  			}
   337  
   338  			postSubpath, err := th.App.CreatePostAsUser(&interactivePostSubpath, "", true)
   339  			require.Nil(t, err)
   340  
   341  			attachmentsSubpath, ok := postSubpath.GetProp("attachments").([]*model.SlackAttachment)
   342  			require.True(t, ok)
   343  
   344  			_, err = th.App.DoPostAction(postSubpath.Id, attachmentsSubpath[0].Actions[0].Id, th.BasicUser.Id, "")
   345  			require.Nil(t, err)
   346  
   347  		})
   348  	}
   349  }
   350  
   351  func TestPostActionProps(t *testing.T) {
   352  	th := Setup(t).InitBasic()
   353  	defer th.TearDown()
   354  
   355  	th.App.UpdateConfig(func(cfg *model.Config) {
   356  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
   357  	})
   358  
   359  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   360  		request := model.PostActionIntegrationRequestFromJson(r.Body)
   361  		assert.NotNil(t, request)
   362  
   363  		fmt.Fprintf(w, `{
   364  			"update": {
   365  				"message": "updated",
   366  				"has_reactions": true,
   367  				"is_pinned": false,
   368  				"props": {
   369  					"from_webhook":true,
   370  					"override_username":"new_override_user",
   371  					"override_icon_url":"new_override_icon",
   372  					"A":"AA"
   373  				}
   374  			},
   375  			"ephemeral_text": "foo"
   376  		}`)
   377  	}))
   378  	defer ts.Close()
   379  
   380  	interactivePost := model.Post{
   381  		Message:       "Interactive post",
   382  		ChannelId:     th.BasicChannel.Id,
   383  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   384  		UserId:        th.BasicUser.Id,
   385  		HasReactions:  false,
   386  		IsPinned:      true,
   387  		Props: model.StringInterface{
   388  			"attachments": []*model.SlackAttachment{
   389  				{
   390  					Text: "hello",
   391  					Actions: []*model.PostAction{
   392  						{
   393  							Integration: &model.PostActionIntegration{
   394  								Context: model.StringInterface{
   395  									"s": "foo",
   396  									"n": 3,
   397  								},
   398  								URL: ts.URL,
   399  							},
   400  							Name:       "action",
   401  							Type:       "some_type",
   402  							DataSource: "some_source",
   403  						},
   404  					},
   405  				},
   406  			},
   407  			"override_icon_url": "old_override_icon",
   408  			"from_webhook":      false,
   409  			"B":                 "BB",
   410  		},
   411  	}
   412  
   413  	post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   414  	require.Nil(t, err)
   415  	attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   416  	require.True(t, ok)
   417  
   418  	clientTriggerId, err := th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   419  	require.Nil(t, err)
   420  	assert.True(t, len(clientTriggerId) == 26)
   421  
   422  	newPost, nErr := th.App.Srv().Store.Post().GetSingle(post.Id)
   423  	require.NoError(t, nErr)
   424  
   425  	assert.True(t, newPost.IsPinned)
   426  	assert.False(t, newPost.HasReactions)
   427  	assert.Nil(t, newPost.GetProp("B"))
   428  	assert.Nil(t, newPost.GetProp("override_username"))
   429  	assert.Equal(t, "AA", newPost.GetProp("A"))
   430  	assert.Equal(t, "old_override_icon", newPost.GetProp("override_icon_url"))
   431  	assert.Equal(t, false, newPost.GetProp("from_webhook"))
   432  }
   433  
   434  func TestSubmitInteractiveDialog(t *testing.T) {
   435  	th := Setup(t).InitBasic()
   436  	defer th.TearDown()
   437  
   438  	th.App.UpdateConfig(func(cfg *model.Config) {
   439  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
   440  	})
   441  
   442  	submit := model.SubmitDialogRequest{
   443  		UserId:     th.BasicUser.Id,
   444  		ChannelId:  th.BasicChannel.Id,
   445  		TeamId:     th.BasicTeam.Id,
   446  		CallbackId: "someid",
   447  		State:      "somestate",
   448  		Submission: map[string]interface{}{
   449  			"name1": "value1",
   450  		},
   451  	}
   452  
   453  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   454  		var request model.SubmitDialogRequest
   455  		err := json.NewDecoder(r.Body).Decode(&request)
   456  		require.NoError(t, err)
   457  		assert.NotNil(t, request)
   458  
   459  		assert.Equal(t, request.URL, "")
   460  		assert.Equal(t, request.UserId, submit.UserId)
   461  		assert.Equal(t, request.ChannelId, submit.ChannelId)
   462  		assert.Equal(t, request.TeamId, submit.TeamId)
   463  		assert.Equal(t, request.CallbackId, submit.CallbackId)
   464  		assert.Equal(t, request.State, submit.State)
   465  		val, ok := request.Submission["name1"].(string)
   466  		require.True(t, ok)
   467  		assert.Equal(t, "value1", val)
   468  
   469  		resp := model.SubmitDialogResponse{
   470  			Error:  "some generic error",
   471  			Errors: map[string]string{"name1": "some error"},
   472  		}
   473  
   474  		b, _ := json.Marshal(resp)
   475  
   476  		w.Write(b)
   477  	}))
   478  	defer ts.Close()
   479  
   480  	setupPluginApiTest(t,
   481  		`
   482  		package main
   483  
   484  		import (
   485  			"net/http"
   486  			"github.com/mattermost/mattermost-server/v5/plugin"
   487  			"github.com/mattermost/mattermost-server/v5/model"
   488  		)
   489  
   490  		type MyPlugin struct {
   491  			plugin.MattermostPlugin
   492  		}
   493  
   494  		func (p *MyPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
   495  			errReply := "some error"
   496   			if r.URL.Query().Get("abc") == "xyz" {
   497  				errReply = "some other error"
   498  			}
   499  			response := &model.SubmitDialogResponse{
   500  				Errors: map[string]string{"name1": errReply},
   501  			}
   502  			w.WriteHeader(http.StatusOK)
   503  			_, _ = w.Write(response.ToJson())
   504  		}
   505  
   506  		func main() {
   507  			plugin.ClientMain(&MyPlugin{})
   508  		}
   509  		`, `{"id": "myplugin", "backend": {"executable": "backend.exe"}}`, "myplugin", th.App)
   510  
   511  	hooks, err2 := th.App.GetPluginsEnvironment().HooksForPlugin("myplugin")
   512  	require.NoError(t, err2)
   513  	require.NotNil(t, hooks)
   514  
   515  	submit.URL = ts.URL
   516  
   517  	resp, err := th.App.SubmitInteractiveDialog(submit)
   518  	assert.Nil(t, err)
   519  	require.NotNil(t, resp)
   520  	assert.Equal(t, "some generic error", resp.Error)
   521  	assert.Equal(t, "some error", resp.Errors["name1"])
   522  
   523  	submit.URL = ""
   524  	resp, err = th.App.SubmitInteractiveDialog(submit)
   525  	assert.NotNil(t, err)
   526  	assert.Nil(t, resp)
   527  
   528  	th.App.UpdateConfig(func(cfg *model.Config) {
   529  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   530  		*cfg.ServiceSettings.SiteURL = ts.URL
   531  	})
   532  
   533  	submit.URL = "/notvalid/myplugin/myaction"
   534  	resp, err = th.App.SubmitInteractiveDialog(submit)
   535  	assert.NotNil(t, err)
   536  	require.Nil(t, resp)
   537  
   538  	submit.URL = "/plugins/myplugin/myaction"
   539  	resp, err = th.App.SubmitInteractiveDialog(submit)
   540  	assert.Nil(t, err)
   541  	require.NotNil(t, resp)
   542  	assert.Equal(t, "some error", resp.Errors["name1"])
   543  
   544  	submit.URL = "/plugins/myplugin/myaction?abc=xyz"
   545  	resp, err = th.App.SubmitInteractiveDialog(submit)
   546  	assert.Nil(t, err)
   547  	require.NotNil(t, resp)
   548  	assert.Equal(t, "some other error", resp.Errors["name1"])
   549  }
   550  
   551  func TestPostActionRelativeURL(t *testing.T) {
   552  	th := Setup(t).InitBasic()
   553  	defer th.TearDown()
   554  
   555  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   556  		request := model.PostActionIntegrationRequestFromJson(r.Body)
   557  		assert.NotNil(t, request)
   558  		fmt.Fprintf(w, `{"post": {"message": "updated"}, "ephemeral_text": "foo"}`)
   559  	}))
   560  	defer ts.Close()
   561  
   562  	t.Run("invalid relative URL", func(t *testing.T) {
   563  		th.App.UpdateConfig(func(cfg *model.Config) {
   564  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   565  			*cfg.ServiceSettings.SiteURL = ts.URL
   566  		})
   567  
   568  		interactivePost := model.Post{
   569  			Message:       "Interactive post",
   570  			ChannelId:     th.BasicChannel.Id,
   571  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   572  			UserId:        th.BasicUser.Id,
   573  			Props: model.StringInterface{
   574  				"attachments": []*model.SlackAttachment{
   575  					{
   576  						Text: "hello",
   577  						Actions: []*model.PostAction{
   578  							{
   579  								Integration: &model.PostActionIntegration{
   580  									URL: "/notaplugin/some/path",
   581  								},
   582  								Name: "action",
   583  								Type: "some_type",
   584  							},
   585  						},
   586  					},
   587  				},
   588  			},
   589  		}
   590  
   591  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   592  		require.Nil(t, err)
   593  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   594  		require.True(t, ok)
   595  		require.NotEmpty(t, attachments[0].Actions)
   596  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   597  
   598  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   599  		require.NotNil(t, err)
   600  	})
   601  
   602  	t.Run("valid relative URL without SiteURL set", func(t *testing.T) {
   603  		th.App.UpdateConfig(func(cfg *model.Config) {
   604  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   605  			*cfg.ServiceSettings.SiteURL = ""
   606  		})
   607  
   608  		interactivePost := model.Post{
   609  			Message:       "Interactive post",
   610  			ChannelId:     th.BasicChannel.Id,
   611  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   612  			UserId:        th.BasicUser.Id,
   613  			Props: model.StringInterface{
   614  				"attachments": []*model.SlackAttachment{
   615  					{
   616  						Text: "hello",
   617  						Actions: []*model.PostAction{
   618  							{
   619  								Integration: &model.PostActionIntegration{
   620  									URL: "/plugins/myplugin/myaction",
   621  								},
   622  								Name: "action",
   623  								Type: "some_type",
   624  							},
   625  						},
   626  					},
   627  				},
   628  			},
   629  		}
   630  
   631  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   632  		require.Nil(t, err)
   633  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   634  		require.True(t, ok)
   635  		require.NotEmpty(t, attachments[0].Actions)
   636  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   637  
   638  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   639  		require.NotNil(t, err)
   640  	})
   641  
   642  	t.Run("valid relative URL with SiteURL set", func(t *testing.T) {
   643  		th.App.UpdateConfig(func(cfg *model.Config) {
   644  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   645  			*cfg.ServiceSettings.SiteURL = ts.URL
   646  		})
   647  
   648  		interactivePost := model.Post{
   649  			Message:       "Interactive post",
   650  			ChannelId:     th.BasicChannel.Id,
   651  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   652  			UserId:        th.BasicUser.Id,
   653  			Props: model.StringInterface{
   654  				"attachments": []*model.SlackAttachment{
   655  					{
   656  						Text: "hello",
   657  						Actions: []*model.PostAction{
   658  							{
   659  								Integration: &model.PostActionIntegration{
   660  									URL: "/plugins/myplugin/myaction",
   661  								},
   662  								Name: "action",
   663  								Type: "some_type",
   664  							},
   665  						},
   666  					},
   667  				},
   668  			},
   669  		}
   670  
   671  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   672  		require.Nil(t, err)
   673  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   674  		require.True(t, ok)
   675  		require.NotEmpty(t, attachments[0].Actions)
   676  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   677  
   678  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   679  		require.NotNil(t, err)
   680  
   681  	})
   682  
   683  	t.Run("valid (but dirty) relative URL with SiteURL set", func(t *testing.T) {
   684  		th.App.UpdateConfig(func(cfg *model.Config) {
   685  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   686  			*cfg.ServiceSettings.SiteURL = ts.URL
   687  		})
   688  
   689  		interactivePost := model.Post{
   690  			Message:       "Interactive post",
   691  			ChannelId:     th.BasicChannel.Id,
   692  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   693  			UserId:        th.BasicUser.Id,
   694  			Props: model.StringInterface{
   695  				"attachments": []*model.SlackAttachment{
   696  					{
   697  						Text: "hello",
   698  						Actions: []*model.PostAction{
   699  							{
   700  								Integration: &model.PostActionIntegration{
   701  									URL: "//plugins/myplugin///myaction",
   702  								},
   703  								Name: "action",
   704  								Type: "some_type",
   705  							},
   706  						},
   707  					},
   708  				},
   709  			},
   710  		}
   711  
   712  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   713  		require.Nil(t, err)
   714  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   715  		require.True(t, ok)
   716  		require.NotEmpty(t, attachments[0].Actions)
   717  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   718  
   719  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   720  		require.NotNil(t, err)
   721  	})
   722  
   723  	t.Run("valid relative URL with SiteURL set and no leading slash", func(t *testing.T) {
   724  		th.App.UpdateConfig(func(cfg *model.Config) {
   725  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   726  			*cfg.ServiceSettings.SiteURL = ts.URL
   727  		})
   728  
   729  		interactivePost := model.Post{
   730  			Message:       "Interactive post",
   731  			ChannelId:     th.BasicChannel.Id,
   732  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   733  			UserId:        th.BasicUser.Id,
   734  			Props: model.StringInterface{
   735  				"attachments": []*model.SlackAttachment{
   736  					{
   737  						Text: "hello",
   738  						Actions: []*model.PostAction{
   739  							{
   740  								Integration: &model.PostActionIntegration{
   741  									URL: "plugins/myplugin/myaction",
   742  								},
   743  								Name: "action",
   744  								Type: "some_type",
   745  							},
   746  						},
   747  					},
   748  				},
   749  			},
   750  		}
   751  
   752  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   753  		require.Nil(t, err)
   754  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   755  		require.True(t, ok)
   756  		require.NotEmpty(t, attachments[0].Actions)
   757  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   758  
   759  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   760  		require.NotNil(t, err)
   761  	})
   762  }
   763  
   764  func TestPostActionRelativePluginURL(t *testing.T) {
   765  	th := Setup(t).InitBasic()
   766  	defer th.TearDown()
   767  
   768  	setupPluginApiTest(t,
   769  		`
   770  		package main
   771  
   772  		import (
   773  			"net/http"
   774  			"github.com/mattermost/mattermost-server/v5/plugin"
   775  			"github.com/mattermost/mattermost-server/v5/model"
   776  		)
   777  
   778  		type MyPlugin struct {
   779  			plugin.MattermostPlugin
   780  		}
   781  
   782  		func (p *MyPlugin) 	ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
   783  			response := &model.PostActionIntegrationResponse{}
   784  			w.WriteHeader(http.StatusOK)
   785  			_, _ = w.Write(response.ToJson())
   786  		}
   787  
   788  		func main() {
   789  			plugin.ClientMain(&MyPlugin{})
   790  		}
   791  		`, `{"id": "myplugin", "backend": {"executable": "backend.exe"}}`, "myplugin", th.App)
   792  
   793  	hooks, err2 := th.App.GetPluginsEnvironment().HooksForPlugin("myplugin")
   794  	require.NoError(t, err2)
   795  	require.NotNil(t, hooks)
   796  
   797  	t.Run("invalid relative URL", func(t *testing.T) {
   798  		th.App.UpdateConfig(func(cfg *model.Config) {
   799  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   800  			*cfg.ServiceSettings.SiteURL = ""
   801  		})
   802  
   803  		interactivePost := model.Post{
   804  			Message:       "Interactive post",
   805  			ChannelId:     th.BasicChannel.Id,
   806  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   807  			UserId:        th.BasicUser.Id,
   808  			Props: model.StringInterface{
   809  				"attachments": []*model.SlackAttachment{
   810  					{
   811  						Text: "hello",
   812  						Actions: []*model.PostAction{
   813  							{
   814  								Integration: &model.PostActionIntegration{
   815  									URL: "/notaplugin/some/path",
   816  								},
   817  								Name: "action",
   818  								Type: "some_type",
   819  							},
   820  						},
   821  					},
   822  				},
   823  			},
   824  		}
   825  
   826  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   827  		require.Nil(t, err)
   828  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   829  		require.True(t, ok)
   830  		require.NotEmpty(t, attachments[0].Actions)
   831  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   832  
   833  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   834  		require.NotNil(t, err)
   835  	})
   836  
   837  	t.Run("valid relative URL", func(t *testing.T) {
   838  		th.App.UpdateConfig(func(cfg *model.Config) {
   839  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   840  			*cfg.ServiceSettings.SiteURL = ""
   841  		})
   842  
   843  		interactivePost := model.Post{
   844  			Message:       "Interactive post",
   845  			ChannelId:     th.BasicChannel.Id,
   846  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   847  			UserId:        th.BasicUser.Id,
   848  			Props: model.StringInterface{
   849  				"attachments": []*model.SlackAttachment{
   850  					{
   851  						Text: "hello",
   852  						Actions: []*model.PostAction{
   853  							{
   854  								Integration: &model.PostActionIntegration{
   855  									URL: "/plugins/myplugin/myaction",
   856  								},
   857  								Name: "action",
   858  								Type: "some_type",
   859  							},
   860  						},
   861  					},
   862  				},
   863  			},
   864  		}
   865  
   866  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   867  		require.Nil(t, err)
   868  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   869  		require.True(t, ok)
   870  		require.NotEmpty(t, attachments[0].Actions)
   871  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   872  
   873  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   874  		require.Nil(t, err)
   875  	})
   876  
   877  	t.Run("valid (but dirty) relative URL", func(t *testing.T) {
   878  		th.App.UpdateConfig(func(cfg *model.Config) {
   879  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   880  			*cfg.ServiceSettings.SiteURL = ""
   881  		})
   882  
   883  		interactivePost := model.Post{
   884  			Message:       "Interactive post",
   885  			ChannelId:     th.BasicChannel.Id,
   886  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   887  			UserId:        th.BasicUser.Id,
   888  			Props: model.StringInterface{
   889  				"attachments": []*model.SlackAttachment{
   890  					{
   891  						Text: "hello",
   892  						Actions: []*model.PostAction{
   893  							{
   894  								Integration: &model.PostActionIntegration{
   895  									URL: "//plugins/myplugin///myaction",
   896  								},
   897  								Name: "action",
   898  								Type: "some_type",
   899  							},
   900  						},
   901  					},
   902  				},
   903  			},
   904  		}
   905  
   906  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   907  		require.Nil(t, err)
   908  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   909  		require.True(t, ok)
   910  		require.NotEmpty(t, attachments[0].Actions)
   911  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   912  
   913  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   914  		require.Nil(t, err)
   915  	})
   916  
   917  	t.Run("valid relative URL and no leading slash", func(t *testing.T) {
   918  		th.App.UpdateConfig(func(cfg *model.Config) {
   919  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   920  			*cfg.ServiceSettings.SiteURL = ""
   921  		})
   922  
   923  		interactivePost := model.Post{
   924  			Message:       "Interactive post",
   925  			ChannelId:     th.BasicChannel.Id,
   926  			PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   927  			UserId:        th.BasicUser.Id,
   928  			Props: model.StringInterface{
   929  				"attachments": []*model.SlackAttachment{
   930  					{
   931  						Text: "hello",
   932  						Actions: []*model.PostAction{
   933  							{
   934  								Integration: &model.PostActionIntegration{
   935  									URL: "plugins/myplugin/myaction",
   936  								},
   937  								Name: "action",
   938  								Type: "some_type",
   939  							},
   940  						},
   941  					},
   942  				},
   943  			},
   944  		}
   945  
   946  		post, err := th.App.CreatePostAsUser(&interactivePost, "", true)
   947  		require.Nil(t, err)
   948  		attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
   949  		require.True(t, ok)
   950  		require.NotEmpty(t, attachments[0].Actions)
   951  		require.NotEmpty(t, attachments[0].Actions[0].Id)
   952  
   953  		_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   954  		require.Nil(t, err)
   955  	})
   956  }
   957  
   958  func TestDoPluginRequest(t *testing.T) {
   959  	th := Setup(t)
   960  	defer th.TearDown()
   961  
   962  	th.App.UpdateConfig(func(cfg *model.Config) {
   963  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
   964  	})
   965  
   966  	setupPluginApiTest(t,
   967  		`
   968  		package main
   969  
   970  		import (
   971  			"net/http"
   972  			"reflect"
   973  			"sort"
   974  
   975  			"github.com/mattermost/mattermost-server/v5/plugin"
   976  		)
   977  
   978  		type MyPlugin struct {
   979  			plugin.MattermostPlugin
   980  		}
   981  
   982  		func (p *MyPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
   983  			q := r.URL.Query()
   984  			if q.Get("abc") != "xyz" {
   985  				w.WriteHeader(http.StatusInternalServerError)
   986  				_, _ = w.Write([]byte("could not find param abc=xyz"))
   987  				return
   988  			}
   989  
   990  			multiple := q["multiple"]
   991  			if len(multiple) != 3 {
   992  				w.WriteHeader(http.StatusInternalServerError)
   993  				_, _ = w.Write([]byte("param multiple should have 3 values"))
   994  				return
   995  			}
   996  			sort.Strings(multiple)
   997  			if !reflect.DeepEqual(multiple, []string{"1 first", "2 second", "3 third"}) {
   998  				w.WriteHeader(http.StatusInternalServerError)
   999  				_, _ = w.Write([]byte("param multiple not correct"))
  1000  				return
  1001  			}
  1002  
  1003  			w.WriteHeader(http.StatusOK)
  1004  			_, _ = w.Write([]byte("OK"))
  1005  		}
  1006  
  1007  		func main() {
  1008  			plugin.ClientMain(&MyPlugin{})
  1009  		}
  1010  		`, `{"id": "myplugin", "backend": {"executable": "backend.exe"}}`, "myplugin", th.App)
  1011  
  1012  	hooks, err2 := th.App.GetPluginsEnvironment().HooksForPlugin("myplugin")
  1013  	require.NoError(t, err2)
  1014  	require.NotNil(t, hooks)
  1015  
  1016  	resp, err := th.App.doPluginRequest("GET", "/plugins/myplugin", nil, nil)
  1017  	assert.Nil(t, err)
  1018  	require.NotNil(t, resp)
  1019  	body, _ := ioutil.ReadAll(resp.Body)
  1020  	assert.Equal(t, "could not find param abc=xyz", string(body))
  1021  
  1022  	resp, err = th.App.doPluginRequest("GET", "/plugins/myplugin?abc=xyz", nil, nil)
  1023  	assert.Nil(t, err)
  1024  	require.NotNil(t, resp)
  1025  	body, _ = ioutil.ReadAll(resp.Body)
  1026  	assert.Equal(t, "param multiple should have 3 values", string(body))
  1027  
  1028  	resp, err = th.App.doPluginRequest("GET", "/plugins/myplugin",
  1029  		url.Values{"abc": []string{"xyz"}, "multiple": []string{"1 first", "2 second", "3 third"}}, nil)
  1030  	assert.Nil(t, err)
  1031  	require.NotNil(t, resp)
  1032  	body, _ = ioutil.ReadAll(resp.Body)
  1033  	assert.Equal(t, "OK", string(body))
  1034  
  1035  	resp, err = th.App.doPluginRequest("GET", "/plugins/myplugin?abc=xyz&multiple=1%20first",
  1036  		url.Values{"multiple": []string{"2 second", "3 third"}}, nil)
  1037  	assert.Nil(t, err)
  1038  	require.NotNil(t, resp)
  1039  	body, _ = ioutil.ReadAll(resp.Body)
  1040  	assert.Equal(t, "OK", string(body))
  1041  
  1042  	resp, err = th.App.doPluginRequest("GET", "/plugins/myplugin?abc=xyz&multiple=1%20first&multiple=3%20third",
  1043  		url.Values{"multiple": []string{"2 second"}}, nil)
  1044  	assert.Nil(t, err)
  1045  	require.NotNil(t, resp)
  1046  	body, _ = ioutil.ReadAll(resp.Body)
  1047  	assert.Equal(t, "OK", string(body))
  1048  
  1049  	resp, err = th.App.doPluginRequest("GET", "/plugins/myplugin?multiple=1%20first&multiple=3%20third",
  1050  		url.Values{"multiple": []string{"2 second"}, "abc": []string{"xyz"}}, nil)
  1051  	assert.Nil(t, err)
  1052  	require.NotNil(t, resp)
  1053  	body, _ = ioutil.ReadAll(resp.Body)
  1054  	assert.Equal(t, "OK", string(body))
  1055  
  1056  	resp, err = th.App.doPluginRequest("GET", "/plugins/myplugin?multiple=1%20first&multiple=3%20third",
  1057  		url.Values{"multiple": []string{"4 fourth"}, "abc": []string{"xyz"}}, nil)
  1058  	assert.Nil(t, err)
  1059  	require.NotNil(t, resp)
  1060  	body, _ = ioutil.ReadAll(resp.Body)
  1061  	assert.Equal(t, "param multiple not correct", string(body))
  1062  }