github.com/adacta-ru/mattermost-server/v6@v6.0.0/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/adacta-ru/mattermost-server/v6/model"
    17  )
    18  
    19  func TestIncomingWebhook(t *testing.T) {
    20  	th := Setup(t).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.Equal(t, http.StatusOK, resp.StatusCode)
    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.NotEqual(t, http.StatusOK, resp.StatusCode, "should have errored - no text 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.NotEqual(t, http.StatusOK, resp.StatusCode, "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.NotEqual(t, http.StatusOK, resp.StatusCode, "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.Equal(t, http.StatusOK, resp.StatusCode)
    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.Equal(t, http.StatusOK, resp.StatusCode)
    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.Equal(t, http.StatusOK, resp.StatusCode)
    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.Equal(t, http.StatusOK, resp.StatusCode)
    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.Equal(t, http.StatusOK, resp.StatusCode)
    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.Equal(t, http.StatusOK, resp.StatusCode)
    85  
    86  		resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\""+text+"\"}"))
    87  		assert.Nil(t, err)
    88  		assert.Equal(t, http.StatusOK, resp.StatusCode)
    89  
    90  		resp, err = http.Post(url, "AppLicaTion/x-www-Form-urlencoded", strings.NewReader("payload={\"text\":\""+text+"\"}"))
    91  		assert.Nil(t, err)
    92  		assert.Equal(t, http.StatusOK, resp.StatusCode)
    93  
    94  		resp, err = http.Post(url, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader("payload={\"text\":\""+text+"\"}"))
    95  		assert.Nil(t, err)
    96  		assert.Equal(t, http.StatusOK, resp.StatusCode)
    97  
    98  		resp, err = http.Post(url, "application/x-www-form-urlencoded; charset=utf-8", strings.NewReader("payload={\"text\":\""+text+"\"}"))
    99  		assert.Nil(t, err)
   100  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   101  
   102  		resp, err = http.Post(url, "application/x-www-form-urlencoded wrongtext", strings.NewReader("payload={\"text\":\""+text+"\"}"))
   103  		assert.Nil(t, err)
   104  		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
   105  
   106  		resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+tooLongText+"\"}"))
   107  		require.Nil(t, err)
   108  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   109  
   110  		resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("{\"text\":\""+tooLongText+"\"}"))
   111  		assert.Nil(t, err)
   112  		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
   113  
   114  		resp, err = http.Post(url, "application/json", strings.NewReader("payload={\"text\":\""+text+"\"}"))
   115  		assert.Nil(t, err)
   116  		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
   117  
   118  		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--"
   119  		resp, err = http.Post(ApiClient.Url+"/hooks/"+hook.Id, "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", strings.NewReader(payloadMultiPart))
   120  		require.Nil(t, err)
   121  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   122  
   123  		resp, err = http.Post(url, "mimetype/wrong", strings.NewReader("payload={\"text\":\""+text+"\"}"))
   124  		assert.Nil(t, err)
   125  		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
   126  
   127  		resp, err = http.Post(url, "", strings.NewReader("{\"text\":\""+text+"\"}"))
   128  		assert.Nil(t, err)
   129  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   130  	})
   131  
   132  	t.Run("WebhookExperimentalReadOnly", func(t *testing.T) {
   133  		th.App.Srv().SetLicense(model.NewTestLicense())
   134  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true })
   135  
   136  		// Read only default channel should fail.
   137  		resp, err := http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL)))
   138  		require.Nil(t, err)
   139  		assert.True(t, resp.StatusCode != http.StatusOK)
   140  
   141  		// None-default channel should still work.
   142  		resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name)))
   143  		require.Nil(t, err)
   144  		assert.True(t, resp.StatusCode == http.StatusOK)
   145  
   146  		// System-Admin Owned Hook
   147  		adminHook, err := th.App.CreateIncomingWebhookForChannel(th.SystemAdminUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id})
   148  		require.Nil(t, err)
   149  		adminUrl := ApiClient.Url + "/hooks/" + adminHook.Id
   150  
   151  		resp, err = http.Post(adminUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL)))
   152  		require.Nil(t, err)
   153  		assert.True(t, resp.StatusCode == http.StatusOK)
   154  
   155  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false })
   156  	})
   157  
   158  	t.Run("WebhookAttachments", func(t *testing.T) {
   159  		attachmentPayload := `{
   160  	       "text": "this is a test",
   161  	       "attachments": [
   162  	           {
   163  	               "fallback": "Required plain-text summary of the attachment.",
   164  
   165  	               "color": "#36a64f",
   166  
   167  	               "pretext": "Optional text that appears above the attachment block",
   168  
   169  	               "author_name": "Bobby Tables",
   170  	               "author_link": "http://flickr.com/bobby/",
   171  	               "author_icon": "http://flickr.com/icons/bobby.jpg",
   172  
   173  	               "title": "Slack API Documentation",
   174  	               "title_link": "https://api.slack.com/",
   175  
   176  	               "text": "Optional text that appears within the attachment",
   177  
   178  	               "fields": [
   179  	                   {
   180  	                       "title": "Priority",
   181  	                       "value": "High",
   182  	                       "short": false
   183  	                   }
   184  	               ],
   185  
   186  	               "image_url": "http://my-website.com/path/to/image.jpg",
   187  	               "thumb_url": "http://example.com/path/to/thumb.png"
   188  	           }
   189  	       ]
   190  	   }`
   191  
   192  		resp, err := http.Post(url, "application/json", strings.NewReader(attachmentPayload))
   193  		require.Nil(t, err)
   194  		assert.True(t, resp.StatusCode == http.StatusOK)
   195  
   196  		attachmentPayload = `{
   197  	       "text": "this is a test",
   198  	       "attachments": [
   199  	           {
   200  	               "fallback": "Required plain-text summary of the attachment.",
   201  
   202  	               "color": "#36a64f",
   203  
   204  	               "pretext": "Optional text that appears above the attachment block",
   205  
   206  	               "author_name": "Bobby Tables",
   207  	               "author_link": "http://flickr.com/bobby/",
   208  	               "author_icon": "http://flickr.com/icons/bobby.jpg",
   209  
   210  	               "title": "Slack API Documentation",
   211  	               "title_link": "https://api.slack.com/",
   212  
   213  	               "text": "` + tooLongText + `",
   214  
   215  	               "fields": [
   216  	                   {
   217  	                       "title": "Priority",
   218  	                       "value": "High",
   219  	                       "short": false
   220  	                   }
   221  	               ],
   222  
   223  	               "image_url": "http://my-website.com/path/to/image.jpg",
   224  	               "thumb_url": "http://example.com/path/to/thumb.png"
   225  	           }
   226  	       ]
   227  	   }`
   228  
   229  		resp, err = http.Post(url, "application/json", strings.NewReader(attachmentPayload))
   230  		require.Nil(t, err)
   231  		assert.True(t, resp.StatusCode == http.StatusOK)
   232  	})
   233  
   234  	t.Run("ChannelLockedWebhook", func(t *testing.T) {
   235  		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)
   236  		require.Nil(t, err)
   237  
   238  		hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id, ChannelLocked: true})
   239  		require.Nil(t, err)
   240  		require.NotNil(t, hook)
   241  
   242  		apiHookUrl := ApiClient.Url + "/hooks/" + hook.Id
   243  
   244  		payload := "payload={\"text\": \"test text\"}"
   245  		resp, err2 := http.Post(apiHookUrl, "application/x-www-form-urlencoded", strings.NewReader(payload))
   246  		require.Nil(t, err2)
   247  		assert.True(t, resp.StatusCode == http.StatusOK)
   248  
   249  		resp, err2 = http.Post(apiHookUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name)))
   250  		require.Nil(t, err2)
   251  		assert.True(t, resp.StatusCode == http.StatusOK)
   252  
   253  		resp, err2 = http.Post(apiHookUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", channel.Name)))
   254  		require.Nil(t, err2)
   255  		assert.True(t, resp.StatusCode == http.StatusForbidden)
   256  	})
   257  
   258  	t.Run("DisableWebhooks", func(t *testing.T) {
   259  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = false })
   260  		resp, err := http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}"))
   261  		require.Nil(t, err)
   262  		assert.True(t, resp.StatusCode == http.StatusNotImplemented)
   263  	})
   264  }
   265  
   266  func TestCommandWebhooks(t *testing.T) {
   267  	th := Setup(t).InitBasic()
   268  	defer th.TearDown()
   269  
   270  	cmd, appErr := th.App.CreateCommand(&model.Command{
   271  		CreatorId: th.BasicUser.Id,
   272  		TeamId:    th.BasicTeam.Id,
   273  		URL:       "http://nowhere.com",
   274  		Method:    model.COMMAND_METHOD_POST,
   275  		Trigger:   "delayed"})
   276  	require.Nil(t, appErr)
   277  
   278  	args := &model.CommandArgs{
   279  		TeamId:    th.BasicTeam.Id,
   280  		UserId:    th.BasicUser.Id,
   281  		ChannelId: th.BasicChannel.Id,
   282  	}
   283  
   284  	hook, appErr := th.App.CreateCommandWebhook(cmd.Id, args)
   285  	require.Nil(t, appErr)
   286  
   287  	resp, err := http.Post(ApiClient.Url+"/hooks/commands/123123123123", "application/json", bytes.NewBufferString(`{"text":"this is a test"}`))
   288  	require.NoError(t, err)
   289  	assert.Equal(t, http.StatusNotFound, resp.StatusCode, "expected not-found for non-existent hook")
   290  
   291  	resp, err = http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"invalid`))
   292  	require.NoError(t, err)
   293  	assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
   294  
   295  	for i := 0; i < 5; i++ {
   296  		response, appErr2 := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`))
   297  		require.Nil(t, appErr2)
   298  		require.Equal(t, http.StatusOK, response.StatusCode)
   299  	}
   300  
   301  	resp, _ = http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`))
   302  	require.Equal(t, http.StatusBadRequest, resp.StatusCode)
   303  }