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 }