github.com/vnforks/kid@v5.11.1+incompatible/app/integration_action_test.go (about)

     1  // Copyright (c) 2017-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  	"net/http"
    10  	"net/http/httptest"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/mattermost/mattermost-server/model"
    18  )
    19  
    20  // Test for MM-13598 where an invalid integration URL was causing a crash
    21  func TestPostActionInvalidURL(t *testing.T) {
    22  	th := Setup(t).InitBasic()
    23  	defer th.TearDown()
    24  
    25  	th.App.UpdateConfig(func(cfg *model.Config) {
    26  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
    27  	})
    28  
    29  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    30  		request := model.PostActionIntegrationRequestFromJson(r.Body)
    31  		assert.NotNil(t, request)
    32  	}))
    33  	defer ts.Close()
    34  
    35  	interactivePost := model.Post{
    36  		Message:       "Interactive post",
    37  		ChannelId:     th.BasicChannel.Id,
    38  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
    39  		UserId:        th.BasicUser.Id,
    40  		Props: model.StringInterface{
    41  			"attachments": []*model.SlackAttachment{
    42  				{
    43  					Text: "hello",
    44  					Actions: []*model.PostAction{
    45  						{
    46  							Integration: &model.PostActionIntegration{
    47  								URL: ":test",
    48  							},
    49  							Name: "action",
    50  							Type: "some_type",
    51  						},
    52  					},
    53  				},
    54  			},
    55  		},
    56  	}
    57  
    58  	post, err := th.App.CreatePostAsUser(&interactivePost, "")
    59  	require.Nil(t, err)
    60  	attachments, ok := post.Props["attachments"].([]*model.SlackAttachment)
    61  	require.True(t, ok)
    62  	require.NotEmpty(t, attachments[0].Actions)
    63  	require.NotEmpty(t, attachments[0].Actions[0].Id)
    64  
    65  	_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
    66  	require.NotNil(t, err)
    67  }
    68  
    69  func TestPostAction(t *testing.T) {
    70  	th := Setup(t).InitBasic()
    71  	defer th.TearDown()
    72  
    73  	th.App.UpdateConfig(func(cfg *model.Config) {
    74  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
    75  	})
    76  
    77  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    78  		request := model.PostActionIntegrationRequestFromJson(r.Body)
    79  		assert.NotNil(t, request)
    80  
    81  		assert.Equal(t, request.UserId, th.BasicUser.Id)
    82  		assert.Equal(t, request.ChannelId, th.BasicChannel.Id)
    83  		assert.Equal(t, request.TeamId, th.BasicTeam.Id)
    84  		assert.True(t, len(request.TriggerId) > 0)
    85  		if request.Type == model.POST_ACTION_TYPE_SELECT {
    86  			assert.Equal(t, request.DataSource, "some_source")
    87  			assert.Equal(t, request.Context["selected_option"], "selected")
    88  		} else {
    89  			assert.Equal(t, request.DataSource, "")
    90  		}
    91  		assert.Equal(t, "foo", request.Context["s"])
    92  		assert.EqualValues(t, 3, request.Context["n"])
    93  		fmt.Fprintf(w, `{"post": {"message": "updated"}, "ephemeral_text": "foo"}`)
    94  	}))
    95  	defer ts.Close()
    96  
    97  	interactivePost := model.Post{
    98  		Message:       "Interactive post",
    99  		ChannelId:     th.BasicChannel.Id,
   100  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   101  		UserId:        th.BasicUser.Id,
   102  		Props: model.StringInterface{
   103  			"attachments": []*model.SlackAttachment{
   104  				{
   105  					Text: "hello",
   106  					Actions: []*model.PostAction{
   107  						{
   108  							Integration: &model.PostActionIntegration{
   109  								Context: model.StringInterface{
   110  									"s": "foo",
   111  									"n": 3,
   112  								},
   113  								URL: ts.URL,
   114  							},
   115  							Name:       "action",
   116  							Type:       "some_type",
   117  							DataSource: "some_source",
   118  						},
   119  					},
   120  				},
   121  			},
   122  		},
   123  	}
   124  
   125  	post, err := th.App.CreatePostAsUser(&interactivePost, "")
   126  	require.Nil(t, err)
   127  
   128  	attachments, ok := post.Props["attachments"].([]*model.SlackAttachment)
   129  	require.True(t, ok)
   130  
   131  	require.NotEmpty(t, attachments[0].Actions)
   132  	require.NotEmpty(t, attachments[0].Actions[0].Id)
   133  
   134  	menuPost := model.Post{
   135  		Message:       "Interactive post",
   136  		ChannelId:     th.BasicChannel.Id,
   137  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   138  		UserId:        th.BasicUser.Id,
   139  		Props: model.StringInterface{
   140  			"attachments": []*model.SlackAttachment{
   141  				{
   142  					Text: "hello",
   143  					Actions: []*model.PostAction{
   144  						{
   145  							Integration: &model.PostActionIntegration{
   146  								Context: model.StringInterface{
   147  									"s": "foo",
   148  									"n": 3,
   149  								},
   150  								URL: ts.URL,
   151  							},
   152  							Name:       "action",
   153  							Type:       model.POST_ACTION_TYPE_SELECT,
   154  							DataSource: "some_source",
   155  						},
   156  					},
   157  				},
   158  			},
   159  		},
   160  	}
   161  
   162  	post2, err := th.App.CreatePostAsUser(&menuPost, "")
   163  	require.Nil(t, err)
   164  
   165  	attachments2, ok := post2.Props["attachments"].([]*model.SlackAttachment)
   166  	require.True(t, ok)
   167  
   168  	require.NotEmpty(t, attachments2[0].Actions)
   169  	require.NotEmpty(t, attachments2[0].Actions[0].Id)
   170  
   171  	clientTriggerId, err := th.App.DoPostAction(post.Id, "notavalidid", th.BasicUser.Id, "")
   172  	require.NotNil(t, err)
   173  	assert.Equal(t, http.StatusNotFound, err.StatusCode)
   174  	assert.True(t, clientTriggerId == "")
   175  
   176  	clientTriggerId, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   177  	require.Nil(t, err)
   178  	assert.True(t, len(clientTriggerId) == 26)
   179  
   180  	clientTriggerId, err = th.App.DoPostAction(post2.Id, attachments2[0].Actions[0].Id, th.BasicUser.Id, "selected")
   181  	require.Nil(t, err)
   182  	assert.True(t, len(clientTriggerId) == 26)
   183  
   184  	th.App.UpdateConfig(func(cfg *model.Config) {
   185  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
   186  	})
   187  
   188  	_, err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   189  	require.NotNil(t, err)
   190  	require.True(t, strings.Contains(err.Error(), "address forbidden"))
   191  
   192  	interactivePostPlugin := model.Post{
   193  		Message:       "Interactive post",
   194  		ChannelId:     th.BasicChannel.Id,
   195  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   196  		UserId:        th.BasicUser.Id,
   197  		Props: model.StringInterface{
   198  			"attachments": []*model.SlackAttachment{
   199  				{
   200  					Text: "hello",
   201  					Actions: []*model.PostAction{
   202  						{
   203  							Integration: &model.PostActionIntegration{
   204  								Context: model.StringInterface{
   205  									"s": "foo",
   206  									"n": 3,
   207  								},
   208  								URL: ts.URL + "/plugins/myplugin/myaction",
   209  							},
   210  							Name:       "action",
   211  							Type:       "some_type",
   212  							DataSource: "some_source",
   213  						},
   214  					},
   215  				},
   216  			},
   217  		},
   218  	}
   219  
   220  	postplugin, err := th.App.CreatePostAsUser(&interactivePostPlugin, "")
   221  	require.Nil(t, err)
   222  
   223  	attachmentsPlugin, ok := postplugin.Props["attachments"].([]*model.SlackAttachment)
   224  	require.True(t, ok)
   225  
   226  	_, err = th.App.DoPostAction(postplugin.Id, attachmentsPlugin[0].Actions[0].Id, th.BasicUser.Id, "")
   227  	require.Nil(t, err)
   228  
   229  	th.App.UpdateConfig(func(cfg *model.Config) {
   230  		*cfg.ServiceSettings.SiteURL = "http://127.1.1.1"
   231  	})
   232  
   233  	interactivePostSiteURL := model.Post{
   234  		Message:       "Interactive post",
   235  		ChannelId:     th.BasicChannel.Id,
   236  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   237  		UserId:        th.BasicUser.Id,
   238  		Props: model.StringInterface{
   239  			"attachments": []*model.SlackAttachment{
   240  				{
   241  					Text: "hello",
   242  					Actions: []*model.PostAction{
   243  						{
   244  							Integration: &model.PostActionIntegration{
   245  								Context: model.StringInterface{
   246  									"s": "foo",
   247  									"n": 3,
   248  								},
   249  								URL: "http://127.1.1.1/plugins/myplugin/myaction",
   250  							},
   251  							Name:       "action",
   252  							Type:       "some_type",
   253  							DataSource: "some_source",
   254  						},
   255  					},
   256  				},
   257  			},
   258  		},
   259  	}
   260  
   261  	postSiteURL, err := th.App.CreatePostAsUser(&interactivePostSiteURL, "")
   262  	require.Nil(t, err)
   263  
   264  	attachmentsSiteURL, ok := postSiteURL.Props["attachments"].([]*model.SlackAttachment)
   265  	require.True(t, ok)
   266  
   267  	_, err = th.App.DoPostAction(postSiteURL.Id, attachmentsSiteURL[0].Actions[0].Id, th.BasicUser.Id, "")
   268  	require.NotNil(t, err)
   269  	require.False(t, strings.Contains(err.Error(), "address forbidden"))
   270  
   271  	th.App.UpdateConfig(func(cfg *model.Config) {
   272  		*cfg.ServiceSettings.SiteURL = ts.URL + "/subpath"
   273  	})
   274  
   275  	interactivePostSubpath := model.Post{
   276  		Message:       "Interactive post",
   277  		ChannelId:     th.BasicChannel.Id,
   278  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   279  		UserId:        th.BasicUser.Id,
   280  		Props: model.StringInterface{
   281  			"attachments": []*model.SlackAttachment{
   282  				{
   283  					Text: "hello",
   284  					Actions: []*model.PostAction{
   285  						{
   286  							Integration: &model.PostActionIntegration{
   287  								Context: model.StringInterface{
   288  									"s": "foo",
   289  									"n": 3,
   290  								},
   291  								URL: ts.URL + "/subpath/plugins/myplugin/myaction",
   292  							},
   293  							Name:       "action",
   294  							Type:       "some_type",
   295  							DataSource: "some_source",
   296  						},
   297  					},
   298  				},
   299  			},
   300  		},
   301  	}
   302  
   303  	postSubpath, err := th.App.CreatePostAsUser(&interactivePostSubpath, "")
   304  	require.Nil(t, err)
   305  
   306  	attachmentsSubpath, ok := postSubpath.Props["attachments"].([]*model.SlackAttachment)
   307  	require.True(t, ok)
   308  
   309  	_, err = th.App.DoPostAction(postSubpath.Id, attachmentsSubpath[0].Actions[0].Id, th.BasicUser.Id, "")
   310  	require.Nil(t, err)
   311  }
   312  
   313  func TestPostActionProps(t *testing.T) {
   314  	th := Setup(t).InitBasic()
   315  	defer th.TearDown()
   316  
   317  	th.App.UpdateConfig(func(cfg *model.Config) {
   318  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
   319  	})
   320  
   321  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   322  		request := model.PostActionIntegrationRequestFromJson(r.Body)
   323  		assert.NotNil(t, request)
   324  
   325  		fmt.Fprintf(w, `{
   326  			"update": {
   327  				"message": "updated",
   328  				"has_reactions": true,
   329  				"is_pinned": false,
   330  				"props": {
   331  					"from_webhook":true,
   332  					"override_username":"new_override_user",
   333  					"override_icon_url":"new_override_icon",
   334  					"A":"AA"
   335  				}
   336  			},
   337  			"ephemeral_text": "foo"
   338  		}`)
   339  	}))
   340  	defer ts.Close()
   341  
   342  	interactivePost := model.Post{
   343  		Message:       "Interactive post",
   344  		ChannelId:     th.BasicChannel.Id,
   345  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   346  		UserId:        th.BasicUser.Id,
   347  		HasReactions:  false,
   348  		IsPinned:      true,
   349  		Props: model.StringInterface{
   350  			"attachments": []*model.SlackAttachment{
   351  				{
   352  					Text: "hello",
   353  					Actions: []*model.PostAction{
   354  						{
   355  							Integration: &model.PostActionIntegration{
   356  								Context: model.StringInterface{
   357  									"s": "foo",
   358  									"n": 3,
   359  								},
   360  								URL: ts.URL,
   361  							},
   362  							Name:       "action",
   363  							Type:       "some_type",
   364  							DataSource: "some_source",
   365  						},
   366  					},
   367  				},
   368  			},
   369  			"override_icon_url": "old_override_icon",
   370  			"from_webhook":      false,
   371  			"B":                 "BB",
   372  		},
   373  	}
   374  
   375  	post, err := th.App.CreatePostAsUser(&interactivePost, "")
   376  	require.Nil(t, err)
   377  	attachments, ok := post.Props["attachments"].([]*model.SlackAttachment)
   378  	require.True(t, ok)
   379  
   380  	clientTriggerId, err := th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "")
   381  	require.Nil(t, err)
   382  	assert.True(t, len(clientTriggerId) == 26)
   383  
   384  	pchan := th.App.Srv.Store.Post().GetSingle(post.Id)
   385  	result := <-pchan
   386  	require.Nil(t, result.Err)
   387  	newPost := result.Data.(*model.Post)
   388  
   389  	assert.True(t, newPost.IsPinned)
   390  	assert.False(t, newPost.HasReactions)
   391  	assert.Nil(t, newPost.Props["B"])
   392  	assert.Nil(t, newPost.Props["override_username"])
   393  	assert.Equal(t, "AA", newPost.Props["A"])
   394  	assert.Equal(t, "old_override_icon", newPost.Props["override_icon_url"])
   395  	assert.Equal(t, false, newPost.Props["from_webhook"])
   396  }
   397  
   398  func TestSubmitInteractiveDialog(t *testing.T) {
   399  	th := Setup(t).InitBasic()
   400  	defer th.TearDown()
   401  
   402  	th.App.UpdateConfig(func(cfg *model.Config) {
   403  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
   404  	})
   405  
   406  	submit := model.SubmitDialogRequest{
   407  		UserId:     th.BasicUser.Id,
   408  		ChannelId:  th.BasicChannel.Id,
   409  		TeamId:     th.BasicTeam.Id,
   410  		CallbackId: "someid",
   411  		State:      "somestate",
   412  		Submission: map[string]interface{}{
   413  			"name1": "value1",
   414  		},
   415  	}
   416  
   417  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   418  		var request model.SubmitDialogRequest
   419  		err := json.NewDecoder(r.Body).Decode(&request)
   420  		require.Nil(t, err)
   421  		assert.NotNil(t, request)
   422  
   423  		assert.Equal(t, request.URL, "")
   424  		assert.Equal(t, request.UserId, submit.UserId)
   425  		assert.Equal(t, request.ChannelId, submit.ChannelId)
   426  		assert.Equal(t, request.TeamId, submit.TeamId)
   427  		assert.Equal(t, request.CallbackId, submit.CallbackId)
   428  		assert.Equal(t, request.State, submit.State)
   429  		val, ok := request.Submission["name1"].(string)
   430  		require.True(t, ok)
   431  		assert.Equal(t, "value1", val)
   432  
   433  		resp := model.SubmitDialogResponse{
   434  			Errors: map[string]string{"name1": "some error"},
   435  		}
   436  
   437  		b, _ := json.Marshal(resp)
   438  
   439  		w.Write(b)
   440  	}))
   441  	defer ts.Close()
   442  
   443  	submit.URL = ts.URL
   444  
   445  	resp, err := th.App.SubmitInteractiveDialog(submit)
   446  	assert.Nil(t, err)
   447  	require.NotNil(t, resp)
   448  	assert.Equal(t, "some error", resp.Errors["name1"])
   449  
   450  	submit.URL = ""
   451  	resp, err = th.App.SubmitInteractiveDialog(submit)
   452  	assert.NotNil(t, err)
   453  	assert.Nil(t, resp)
   454  }