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 }