2018-05-14 10:24:58 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2019-11-29 06:59:40 -05:00
// See LICENSE.txt for license information.
2018-05-14 10:24:58 -04:00
package web
import (
"bytes"
"fmt"
"net/http"
2018-05-16 13:43:22 -04:00
"strings"
2018-05-14 10:24:58 -04:00
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2023-06-11 01:24:35 -04:00
"github.com/mattermost/mattermost/server/public/model"
2018-05-14 10:24:58 -04:00
)
func TestIncomingWebhook ( t * testing . T ) {
2025-05-07 06:41:10 -04:00
th := Setup ( t ) . InitBasic ( t )
2018-05-14 10:24:58 -04:00
2019-01-31 08:12:01 -05:00
if ! * th . App . Config ( ) . ServiceSettings . EnableIncomingWebhooks {
2021-08-16 13:46:44 -04:00
_ , err := http . Post ( apiClient . URL + "/hooks/123" , "" , strings . NewReader ( "123" ) )
2021-02-26 01:30:22 -05:00
assert . Error ( t , err , "should have errored - webhooks turned off" )
2018-05-14 10:24:58 -04:00
return
}
hook , err := th . App . CreateIncomingWebhookForChannel ( th . BasicUser . Id , th . BasicChannel , & model . IncomingWebhook { ChannelId : th . BasicChannel . Id } )
require . Nil ( t , err )
2021-08-16 13:46:44 -04:00
url := apiClient . URL + "/hooks/" + hook . Id
2018-05-14 10:24:58 -04:00
2026-03-30 12:41:32 -04:00
var tooLongTextBuilder strings . Builder
2025-07-18 06:54:51 -04:00
for range 8200 {
2026-03-30 12:41:32 -04:00
tooLongTextBuilder . WriteString ( "a" )
2018-05-14 10:24:58 -04:00
}
2026-03-30 12:41:32 -04:00
tooLongText := tooLongTextBuilder . String ( )
2018-05-14 10:24:58 -04:00
t . Run ( "WebhookBasics" , func ( t * testing . T ) {
payload := "payload={\"text\": \"test text\"}"
2018-05-16 13:43:22 -04:00
resp , err := http . Post ( url , "application/x-www-form-urlencoded" , strings . NewReader ( payload ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
payload = "payload={\"text\": \"\"}"
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded" , strings . NewReader ( payload ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . NotEqual ( t , http . StatusOK , resp . StatusCode , "should have errored - no text post" )
2018-05-14 10:24:58 -04:00
payload = "payload={\"text\": \"test text\", \"channel\": \"junk\"}"
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded" , strings . NewReader ( payload ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . NotEqual ( t , http . StatusOK , resp . StatusCode , "should have errored - bad channel" )
2018-05-14 10:24:58 -04:00
payload = "payload={\"text\": \"test text\"}"
2021-08-16 13:46:44 -04:00
resp , err = http . Post ( apiClient . URL + "/hooks/abc123" , "application/x-www-form-urlencoded" , strings . NewReader ( payload ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . NotEqual ( t , http . StatusOK , resp . StatusCode , "should have errored - bad hook" )
2018-05-14 10:24:58 -04:00
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( "{\"text\":\"this is a test\"}" ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
text := ` this is a \ "test\"
that contains a newline and a tab `
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( "{\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , th . BasicChannel . Name ) ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"#%s\"}" , th . BasicChannel . Name ) ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"@%s\"}" , th . BasicUser . Username ) ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded" , strings . NewReader ( "payload={\"text\":\"this is a test\"}" ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded" , strings . NewReader ( "payload={\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2020-03-07 03:43:57 -05:00
resp , err = http . Post ( url , "AppLicaTion/x-www-Form-urlencoded" , strings . NewReader ( "payload={\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2020-03-07 03:43:57 -05:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded;charset=utf-8" , strings . NewReader ( "payload={\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2020-03-07 03:43:57 -05:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded; charset=utf-8" , strings . NewReader ( "payload={\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2020-03-07 03:43:57 -05:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded wrongtext" , strings . NewReader ( "payload={\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2020-03-07 03:43:57 -05:00
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( "{\"text\":\"" + tooLongText + "\"}" ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2020-03-07 03:43:57 -05:00
resp , err = http . Post ( url , "application/x-www-form-urlencoded" , strings . NewReader ( "{\"text\":\"" + tooLongText + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2020-03-07 03:43:57 -05:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( "payload={\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2020-03-07 03:43:57 -05:00
2018-05-14 10:24:58 -04:00
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--"
2021-08-16 13:46:44 -04:00
resp , err = http . Post ( apiClient . URL + "/hooks/" + hook . Id , "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" , strings . NewReader ( payloadMultiPart ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2020-03-07 03:43:57 -05:00
resp , err = http . Post ( url , "mimetype/wrong" , strings . NewReader ( "payload={\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
resp , err = http . Post ( url , "" , strings . NewReader ( "{\"text\":\"" + text + "\"}" ) )
2021-02-26 01:30:22 -05:00
assert . NoError ( t , err )
2020-06-01 15:07:02 -04:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2018-05-14 10:24:58 -04:00
} )
t . Run ( "WebhookAttachments" , func ( t * testing . T ) {
attachmentPayload := ` {
"text" : "this is a test" ,
"attachments" : [
{
"fallback" : "Required plain-text summary of the attachment." ,
"color" : "#36a64f" ,
"pretext" : "Optional text that appears above the attachment block" ,
"author_name" : "Bobby Tables" ,
"author_link" : "http://flickr.com/bobby/" ,
"author_icon" : "http://flickr.com/icons/bobby.jpg" ,
"title" : "Slack API Documentation" ,
"title_link" : "https://api.slack.com/" ,
"text" : "Optional text that appears within the attachment" ,
"fields" : [
{
"title" : "Priority" ,
"value" : "High" ,
"short" : false
}
] ,
"image_url" : "http://my-website.com/path/to/image.jpg" ,
"thumb_url" : "http://example.com/path/to/thumb.png"
}
]
} `
2018-05-16 13:43:22 -04:00
resp , err := http . Post ( url , "application/json" , strings . NewReader ( attachmentPayload ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2018-05-16 13:43:22 -04:00
assert . True ( t , resp . StatusCode == http . StatusOK )
2018-05-14 10:24:58 -04:00
attachmentPayload = ` {
"text" : "this is a test" ,
"attachments" : [
{
"fallback" : "Required plain-text summary of the attachment." ,
"color" : "#36a64f" ,
"pretext" : "Optional text that appears above the attachment block" ,
"author_name" : "Bobby Tables" ,
"author_link" : "http://flickr.com/bobby/" ,
"author_icon" : "http://flickr.com/icons/bobby.jpg" ,
"title" : "Slack API Documentation" ,
"title_link" : "https://api.slack.com/" ,
"text" : "` + tooLongText + `" ,
"fields" : [
{
"title" : "Priority" ,
"value" : "High" ,
"short" : false
}
] ,
"image_url" : "http://my-website.com/path/to/image.jpg" ,
"thumb_url" : "http://example.com/path/to/thumb.png"
}
]
} `
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( attachmentPayload ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2018-05-16 13:43:22 -04:00
assert . True ( t , resp . StatusCode == http . StatusOK )
2018-05-14 10:24:58 -04:00
} )
2018-05-22 14:06:14 -04:00
t . Run ( "ChannelLockedWebhook" , func ( t * testing . T ) {
2021-07-12 14:05:36 -04:00
channel , err := th . App . CreateChannel ( th . Context , & model . Channel { TeamId : th . BasicTeam . Id , Name : model . NewId ( ) , DisplayName : model . NewId ( ) , Type : model . ChannelTypeOpen , CreatorId : th . BasicUser . Id } , true )
2018-05-22 14:06:14 -04:00
require . Nil ( t , err )
hook , err := th . App . CreateIncomingWebhookForChannel ( th . BasicUser . Id , th . BasicChannel , & model . IncomingWebhook { ChannelId : th . BasicChannel . Id , ChannelLocked : true } )
require . Nil ( t , err )
2019-01-30 12:55:24 -05:00
require . NotNil ( t , hook )
2018-05-22 14:06:14 -04:00
2021-08-16 13:46:44 -04:00
apiHookURL := apiClient . URL + "/hooks/" + hook . Id
2018-05-22 14:06:14 -04:00
payload := "payload={\"text\": \"test text\"}"
2021-08-16 13:46:44 -04:00
resp , err2 := http . Post ( apiHookURL , "application/x-www-form-urlencoded" , strings . NewReader ( payload ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err2 )
2018-05-22 14:06:14 -04:00
assert . True ( t , resp . StatusCode == http . StatusOK )
2021-08-16 13:46:44 -04:00
resp , err2 = http . Post ( apiHookURL , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , th . BasicChannel . Name ) ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err2 )
2018-05-22 14:06:14 -04:00
assert . True ( t , resp . StatusCode == http . StatusOK )
2021-08-16 13:46:44 -04:00
resp , err2 = http . Post ( apiHookURL , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , channel . Name ) ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err2 )
2018-05-22 14:06:14 -04:00
assert . True ( t , resp . StatusCode == http . StatusForbidden )
} )
2018-05-14 10:24:58 -04:00
t . Run ( "DisableWebhooks" , func ( t * testing . T ) {
2019-01-31 08:12:01 -05:00
th . App . UpdateConfig ( func ( cfg * model . Config ) { * cfg . ServiceSettings . EnableIncomingWebhooks = false } )
2018-05-16 13:43:22 -04:00
resp , err := http . Post ( url , "application/json" , strings . NewReader ( "{\"text\":\"this is a test\"}" ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err )
2018-05-16 13:43:22 -04:00
assert . True ( t , resp . StatusCode == http . StatusNotImplemented )
2018-05-14 10:24:58 -04:00
} )
}
func TestCommandWebhooks ( t * testing . T ) {
2025-05-07 06:41:10 -04:00
th := Setup ( t ) . InitBasic ( t )
2018-05-14 10:24:58 -04:00
2019-02-12 08:37:54 -05:00
cmd , appErr := th . App . CreateCommand ( & model . Command {
2018-05-14 10:24:58 -04:00
CreatorId : th . BasicUser . Id ,
TeamId : th . BasicTeam . Id ,
URL : "http://nowhere.com" ,
2021-07-12 14:05:36 -04:00
Method : model . CommandMethodPost ,
2018-05-14 10:24:58 -04:00
Trigger : "delayed" } )
2019-02-12 08:37:54 -05:00
require . Nil ( t , appErr )
2018-05-14 10:24:58 -04:00
args := & model . CommandArgs {
TeamId : th . BasicTeam . Id ,
UserId : th . BasicUser . Id ,
ChannelId : th . BasicChannel . Id ,
}
2019-02-12 08:37:54 -05:00
hook , appErr := th . App . CreateCommandWebhook ( cmd . Id , args )
2020-01-14 05:09:11 -05:00
require . Nil ( t , appErr )
2018-05-14 10:24:58 -04:00
2021-08-16 13:46:44 -04:00
resp , err := http . Post ( apiClient . URL + "/hooks/commands/123123123123" , "application/json" , bytes . NewBufferString ( ` { "text":"this is a test"} ` ) )
2019-02-12 08:37:54 -05:00
require . NoError ( t , err )
assert . Equal ( t , http . StatusNotFound , resp . StatusCode , "expected not-found for non-existent hook" )
2018-05-14 10:24:58 -04:00
2021-08-16 13:46:44 -04:00
resp , err = http . Post ( apiClient . URL + "/hooks/commands/" + hook . Id , "application/json" , bytes . NewBufferString ( ` { "text":"invalid ` ) )
2019-02-12 08:37:54 -05:00
require . NoError ( t , err )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2018-05-14 10:24:58 -04:00
2025-07-18 06:54:51 -04:00
for range 5 {
2021-08-16 13:46:44 -04:00
response , err2 := http . Post ( apiClient . URL + "/hooks/commands/" + hook . Id , "application/json" , bytes . NewBufferString ( ` { "text":"this is a test"} ` ) )
2021-02-26 01:30:22 -05:00
require . NoError ( t , err2 )
2020-01-14 05:09:11 -05:00
require . Equal ( t , http . StatusOK , response . StatusCode )
2018-05-14 10:24:58 -04:00
}
2021-08-16 13:46:44 -04:00
resp , _ = http . Post ( apiClient . URL + "/hooks/commands/" + hook . Id , "application/json" , bytes . NewBufferString ( ` { "text":"this is a test"} ` ) )
2020-01-14 05:09:11 -05:00
require . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2018-05-14 10:24:58 -04:00
}