2018-05-14 10:24:58 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2019-11-29 12:59:40 +01: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"
2019-11-28 14:39:38 +01:00
"github.com/mattermost/mattermost-server/v5/model"
2018-05-14 10:24:58 -04:00
)
func TestIncomingWebhook ( t * testing . T ) {
th := Setup ( ) . InitBasic ( )
defer th . TearDown ( )
2019-01-31 08:12:01 -05:00
if ! * th . App . Config ( ) . ServiceSettings . EnableIncomingWebhooks {
2018-05-16 13:43:22 -04:00
_ , err := http . Post ( ApiClient . Url + "/hooks/123" , "" , strings . NewReader ( "123" ) )
2018-05-14 10:24:58 -04:00
assert . NotNil ( t , err , "should have errored - webhooks turned off" )
return
}
hook , err := th . App . CreateIncomingWebhookForChannel ( th . BasicUser . Id , th . BasicChannel , & model . IncomingWebhook { ChannelId : th . BasicChannel . Id } )
require . Nil ( t , err )
2018-05-16 13:43:22 -04:00
url := ApiClient . Url + "/hooks/" + hook . Id
2018-05-14 10:24:58 -04:00
tooLongText := ""
for i := 0 ; i < 8200 ; i ++ {
tooLongText += "a"
}
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 ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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 ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode != http . StatusOK , "should have errored - no text to 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 ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode != http . StatusOK , "should have errored - bad channel" )
2018-05-14 10:24:58 -04:00
payload = "payload={\"text\": \"test text\"}"
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( ApiClient . Url + "/hooks/abc123" , "application/x-www-form-urlencoded" , strings . NewReader ( payload ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode != http . StatusOK , "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\"}" ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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 + "\"}" ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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 ) ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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 ) ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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 ) ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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\"}" ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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 + "\"}" ) )
2018-05-14 10:24:58 -04:00
assert . Nil ( 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-16 13:43:22 -04:00
resp , err = http . Post ( url , "application/json" , strings . NewReader ( "{\"text\":\"" + tooLongText + "\"}" ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
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--"
2018-05-16 13:43:22 -04:00
resp , err = http . Post ( ApiClient . Url + "/hooks/" + hook . Id , "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" , strings . NewReader ( payloadMultiPart ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
2018-05-14 10:24:58 -04:00
} )
2018-07-06 09:07:36 +01:00
t . Run ( "WebhookExperimentalReadOnly" , func ( t * testing . T ) {
2018-05-14 10:24:58 -04:00
th . App . SetLicense ( model . NewTestLicense ( ) )
2018-07-06 09:07:36 +01:00
th . App . UpdateConfig ( func ( cfg * model . Config ) { * cfg . TeamSettings . ExperimentalTownSquareIsReadOnly = true } )
// Read only default channel should fail.
resp , err := http . Post ( url , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , model . DEFAULT_CHANNEL ) ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode != http . StatusOK )
// None-default channel should still work.
resp , err = http . Post ( url , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , th . BasicChannel . Name ) ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
// System-Admin Owned Hook
adminHook , err := th . App . CreateIncomingWebhookForChannel ( th . SystemAdminUser . Id , th . BasicChannel , & model . IncomingWebhook { ChannelId : th . BasicChannel . Id } )
require . Nil ( t , err )
adminUrl := ApiClient . Url + "/hooks/" + adminHook . Id
resp , err = http . Post ( adminUrl , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , model . DEFAULT_CHANNEL ) ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
th . App . UpdateConfig ( func ( cfg * model . Config ) { * cfg . TeamSettings . ExperimentalTownSquareIsReadOnly = false } )
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 ) )
require . Nil ( t , err )
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 ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusOK )
2018-05-14 10:24:58 -04:00
} )
2018-05-22 19:06:14 +01:00
t . Run ( "ChannelLockedWebhook" , func ( t * testing . T ) {
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 )
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 18:55:24 +01:00
require . NotNil ( t , hook )
2018-05-22 19:06:14 +01:00
2019-01-30 18:55:24 +01:00
apiHookUrl := ApiClient . Url + "/hooks/" + hook . Id
2018-05-22 19:06:14 +01:00
payload := "payload={\"text\": \"test text\"}"
2019-01-30 18:55:24 +01:00
resp , err2 := http . Post ( apiHookUrl , "application/x-www-form-urlencoded" , strings . NewReader ( payload ) )
2018-05-22 19:06:14 +01:00
require . Nil ( t , err2 )
assert . True ( t , resp . StatusCode == http . StatusOK )
2019-01-30 18:55:24 +01:00
resp , err2 = http . Post ( apiHookUrl , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , th . BasicChannel . Name ) ) )
2018-05-22 19:06:14 +01:00
require . Nil ( t , err2 )
assert . True ( t , resp . StatusCode == http . StatusOK )
2019-01-30 18:55:24 +01:00
resp , err2 = http . Post ( apiHookUrl , "application/json" , strings . NewReader ( fmt . Sprintf ( "{\"text\":\"this is a test\", \"channel\":\"%s\"}" , channel . Name ) ) )
2018-05-22 19:06:14 +01:00
require . Nil ( t , err2 )
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\"}" ) )
require . Nil ( t , err )
assert . True ( t , resp . StatusCode == http . StatusNotImplemented )
2018-05-14 10:24:58 -04:00
} )
}
func TestCommandWebhooks ( t * testing . T ) {
th := Setup ( ) . InitBasic ( )
defer th . TearDown ( )
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" ,
Method : model . COMMAND_METHOD_POST ,
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 13:09:11 +03:00
require . Nil ( t , appErr )
2018-05-14 10:24:58 -04:00
2019-02-12 08:37:54 -05:00
resp , err := http . Post ( ApiClient . Url + "/hooks/commands/123123123123" , "application/json" , bytes . NewBufferString ( ` { "text":"this is a test"} ` ) )
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
2019-02-12 08:37:54 -05:00
resp , err = http . Post ( ApiClient . Url + "/hooks/commands/" + hook . Id , "application/json" , bytes . NewBufferString ( ` { "text":"invalid ` ) )
require . NoError ( t , err )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2018-05-14 10:24:58 -04:00
for i := 0 ; i < 5 ; i ++ {
2020-01-14 13:09:11 +03:00
response , appErr2 := http . Post ( ApiClient . Url + "/hooks/commands/" + hook . Id , "application/json" , bytes . NewBufferString ( ` { "text":"this is a test"} ` ) )
require . Nil ( t , appErr2 )
require . Equal ( t , http . StatusOK , response . StatusCode )
2018-05-14 10:24:58 -04:00
}
2020-01-14 13:09:11 +03:00
resp , _ = http . Post ( ApiClient . Url + "/hooks/commands/" + hook . Id , "application/json" , bytes . NewBufferString ( ` { "text":"this is a test"} ` ) )
require . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2018-05-14 10:24:58 -04:00
}