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 }