github.com/RajatVaryani/mattermost-server@v5.11.1+incompatible/web/webhook_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package web
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"net/http"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/mattermost/mattermost-server/model"
    17  )
    18  
    19  func TestIncomingWebhook(t *testing.T) {
    20  	th := Setup().InitBasic()
    21  	defer th.TearDown()
    22  
    23  	if !*th.App.Config().ServiceSettings.EnableIncomingWebhooks {
    24  		_, err := http.Post(ApiClient.Url+"/hooks/123", "", strings.NewReader("123"))
    25  		assert.NotNil(t, err, "should have errored - webhooks turned off")
    26  		return
    27  	}
    28  
    29  	hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id})
    30  	require.Nil(t, err)
    31  
    32  	url := ApiClient.Url + "/hooks/" + hook.Id
    33  
    34  	tooLongText := ""
    35  	for i := 0; i < 8200; i++ {
    36  		tooLongText += "a"
    37  	}
    38  
    39  	t.Run("WebhookBasics", func(t *testing.T) {
    40  		payload := "payload={\"text\": \"test text\"}"
    41  		resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload))
    42  		require.Nil(t, err)
    43  		assert.True(t, resp.StatusCode == http.StatusOK)
    44  
    45  		payload = "payload={\"text\": \"\"}"
    46  		resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload))
    47  		require.Nil(t, err)
    48  		assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - no text to post")
    49  
    50  		payload = "payload={\"text\": \"test text\", \"channel\": \"junk\"}"
    51  		resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload))
    52  		require.Nil(t, err)
    53  		assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad channel")
    54  
    55  		payload = "payload={\"text\": \"test text\"}"
    56  		resp, err = http.Post(ApiClient.Url+"/hooks/abc123", "application/x-www-form-urlencoded", strings.NewReader(payload))
    57  		require.Nil(t, err)
    58  		assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad hook")
    59  
    60  		resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}"))
    61  		require.Nil(t, err)
    62  		assert.True(t, resp.StatusCode == http.StatusOK)
    63  
    64  		text := `this is a \"test\"
    65  	that contains a newline and a tab`
    66  		resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+text+"\"}"))
    67  		require.Nil(t, err)
    68  		assert.True(t, resp.StatusCode == http.StatusOK)
    69  
    70  		resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name)))
    71  		require.Nil(t, err)
    72  		assert.True(t, resp.StatusCode == http.StatusOK)
    73  
    74  		resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"#%s\"}", th.BasicChannel.Name)))
    75  		require.Nil(t, err)
    76  		assert.True(t, resp.StatusCode == http.StatusOK)
    77  
    78  		resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"@%s\"}", th.BasicUser.Username)))
    79  		require.Nil(t, err)
    80  		assert.True(t, resp.StatusCode == http.StatusOK)
    81  
    82  		resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\"this is a test\"}"))
    83  		require.Nil(t, err)
    84  		assert.True(t, resp.StatusCode == http.StatusOK)
    85  
    86  		resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\""+text+"\"}"))
    87  		assert.Nil(t, err)
    88  		assert.True(t, resp.StatusCode == http.StatusOK)
    89  
    90  		resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+tooLongText+"\"}"))
    91  		require.Nil(t, err)
    92  		assert.True(t, resp.StatusCode == http.StatusOK)
    93  
    94  		payloadMultiPart := "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"username\"\r\n\r\nwebhook-bot\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nthis is a test :tada:\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"
    95  		resp, err = http.Post(ApiClient.Url+"/hooks/"+hook.Id, "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", strings.NewReader(payloadMultiPart))
    96  		require.Nil(t, err)
    97  		assert.True(t, resp.StatusCode == http.StatusOK)
    98  	})
    99  
   100  	t.Run("WebhookExperimentalReadOnly", func(t *testing.T) {
   101  		th.App.SetLicense(model.NewTestLicense())
   102  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true })
   103  
   104  		// Read only default channel should fail.
   105  		resp, err := http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL)))
   106  		require.Nil(t, err)
   107  		assert.True(t, resp.StatusCode != http.StatusOK)
   108  
   109  		// None-default channel should still work.
   110  		resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name)))
   111  		require.Nil(t, err)
   112  		assert.True(t, resp.StatusCode == http.StatusOK)
   113  
   114  		// System-Admin Owned Hook
   115  		adminHook, err := th.App.CreateIncomingWebhookForChannel(th.SystemAdminUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id})
   116  		require.Nil(t, err)
   117  		adminUrl := ApiClient.Url + "/hooks/" + adminHook.Id
   118  
   119  		resp, err = http.Post(adminUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL)))
   120  		require.Nil(t, err)
   121  		assert.True(t, resp.StatusCode == http.StatusOK)
   122  
   123  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false })
   124  	})
   125  
   126  	t.Run("WebhookAttachments", func(t *testing.T) {
   127  		attachmentPayload := `{
   128  	       "text": "this is a test",
   129  	       "attachments": [
   130  	           {
   131  	               "fallback": "Required plain-text summary of the attachment.",
   132  
   133  	               "color": "#36a64f",
   134  
   135  	               "pretext": "Optional text that appears above the attachment block",
   136  
   137  	               "author_name": "Bobby Tables",
   138  	               "author_link": "http://flickr.com/bobby/",
   139  	               "author_icon": "http://flickr.com/icons/bobby.jpg",
   140  
   141  	               "title": "Slack API Documentation",
   142  	               "title_link": "https://api.slack.com/",
   143  
   144  	               "text": "Optional text that appears within the attachment",
   145  
   146  	               "fields": [
   147  	                   {
   148  	                       "title": "Priority",
   149  	                       "value": "High",
   150  	                       "short": false
   151  	                   }
   152  	               ],
   153  
   154  	               "image_url": "http://my-website.com/path/to/image.jpg",
   155  	               "thumb_url": "http://example.com/path/to/thumb.png"
   156  	           }
   157  	       ]
   158  	   }`
   159  
   160  		resp, err := http.Post(url, "application/json", strings.NewReader(attachmentPayload))
   161  		require.Nil(t, err)
   162  		assert.True(t, resp.StatusCode == http.StatusOK)
   163  
   164  		attachmentPayload = `{
   165  	       "text": "this is a test",
   166  	       "attachments": [
   167  	           {
   168  	               "fallback": "Required plain-text summary of the attachment.",
   169  
   170  	               "color": "#36a64f",
   171  
   172  	               "pretext": "Optional text that appears above the attachment block",
   173  
   174  	               "author_name": "Bobby Tables",
   175  	               "author_link": "http://flickr.com/bobby/",
   176  	               "author_icon": "http://flickr.com/icons/bobby.jpg",
   177  
   178  	               "title": "Slack API Documentation",
   179  	               "title_link": "https://api.slack.com/",
   180  
   181  	               "text": "` + tooLongText + `",
   182  
   183  	               "fields": [
   184  	                   {
   185  	                       "title": "Priority",
   186  	                       "value": "High",
   187  	                       "short": false
   188  	                   }
   189  	               ],
   190  
   191  	               "image_url": "http://my-website.com/path/to/image.jpg",
   192  	               "thumb_url": "http://example.com/path/to/thumb.png"
   193  	           }
   194  	       ]
   195  	   }`
   196  
   197  		resp, err = http.Post(url, "application/json", strings.NewReader(attachmentPayload))
   198  		require.Nil(t, err)
   199  		assert.True(t, resp.StatusCode == http.StatusOK)
   200  	})
   201  
   202  	t.Run("ChannelLockedWebhook", func(t *testing.T) {
   203  		channel, err := th.App.CreateChannel(&model.Channel{TeamId: th.BasicTeam.Id, Name: model.NewId(), DisplayName: model.NewId(), Type: model.CHANNEL_OPEN, CreatorId: th.BasicUser.Id}, true)
   204  		require.Nil(t, err)
   205  
   206  		hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id, ChannelLocked: true})
   207  		require.Nil(t, err)
   208  		require.NotNil(t, hook)
   209  
   210  		apiHookUrl := ApiClient.Url + "/hooks/" + hook.Id
   211  
   212  		payload := "payload={\"text\": \"test text\"}"
   213  		resp, err2 := http.Post(apiHookUrl, "application/x-www-form-urlencoded", strings.NewReader(payload))
   214  		require.Nil(t, err2)
   215  		assert.True(t, resp.StatusCode == http.StatusOK)
   216  
   217  		resp, err2 = http.Post(apiHookUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name)))
   218  		require.Nil(t, err2)
   219  		assert.True(t, resp.StatusCode == http.StatusOK)
   220  
   221  		resp, err2 = http.Post(apiHookUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", channel.Name)))
   222  		require.Nil(t, err2)
   223  		assert.True(t, resp.StatusCode == http.StatusForbidden)
   224  	})
   225  
   226  	t.Run("DisableWebhooks", func(t *testing.T) {
   227  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = false })
   228  		resp, err := http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}"))
   229  		require.Nil(t, err)
   230  		assert.True(t, resp.StatusCode == http.StatusNotImplemented)
   231  	})
   232  }
   233  
   234  func TestCommandWebhooks(t *testing.T) {
   235  	th := Setup().InitBasic()
   236  	defer th.TearDown()
   237  
   238  	cmd, appErr := th.App.CreateCommand(&model.Command{
   239  		CreatorId: th.BasicUser.Id,
   240  		TeamId:    th.BasicTeam.Id,
   241  		URL:       "http://nowhere.com",
   242  		Method:    model.COMMAND_METHOD_POST,
   243  		Trigger:   "delayed"})
   244  	require.Nil(t, appErr)
   245  
   246  	args := &model.CommandArgs{
   247  		TeamId:    th.BasicTeam.Id,
   248  		UserId:    th.BasicUser.Id,
   249  		ChannelId: th.BasicChannel.Id,
   250  	}
   251  
   252  	hook, appErr := th.App.CreateCommandWebhook(cmd.Id, args)
   253  	if appErr != nil {
   254  		t.Fatal(appErr)
   255  	}
   256  
   257  	resp, err := http.Post(ApiClient.Url+"/hooks/commands/123123123123", "application/json", bytes.NewBufferString(`{"text":"this is a test"}`))
   258  	require.NoError(t, err)
   259  	assert.Equal(t, http.StatusNotFound, resp.StatusCode, "expected not-found for non-existent hook")
   260  
   261  	resp, err = http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"invalid`))
   262  	require.NoError(t, err)
   263  	assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
   264  
   265  	for i := 0; i < 5; i++ {
   266  		if resp, appErr := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)); err != nil || resp.StatusCode != http.StatusOK {
   267  			t.Fatal(appErr)
   268  		}
   269  	}
   270  
   271  	if resp, _ := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)); resp.StatusCode != http.StatusBadRequest {
   272  		t.Fatal("expected error for sixth usage")
   273  	}
   274  }