Added additional validation for slack attachment format on server (#5680)
Этот коммит содержится в:
коммит произвёл
George Goldberg
родитель
f3a266ee5d
Коммит
5d62b3661b
38
app/post.go
38
app/post.go
@@ -180,40 +180,20 @@ func handlePostEvents(post *model.Post, teamId string, triggerWebhooks bool) *mo
|
||||
|
||||
// This method only parses and processes the attachments,
|
||||
// all else should be set in the post which is passed
|
||||
func parseSlackAttachment(post *model.Post, attachments interface{}) {
|
||||
func parseSlackAttachment(post *model.Post, attachments []*model.SlackAttachment) {
|
||||
post.Type = model.POST_SLACK_ATTACHMENT
|
||||
|
||||
if list, success := attachments.([]interface{}); success {
|
||||
for i, aInt := range list {
|
||||
attachment := aInt.(map[string]interface{})
|
||||
if aText, ok := attachment["text"].(string); ok {
|
||||
aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})")
|
||||
attachment["text"] = aText
|
||||
list[i] = attachment
|
||||
}
|
||||
if aText, ok := attachment["pretext"].(string); ok {
|
||||
aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})")
|
||||
attachment["pretext"] = aText
|
||||
list[i] = attachment
|
||||
}
|
||||
if fVal, ok := attachment["fields"]; ok {
|
||||
if fields, ok := fVal.([]interface{}); ok {
|
||||
// parse attachment field links into Markdown format
|
||||
for j, fInt := range fields {
|
||||
field := fInt.(map[string]interface{})
|
||||
if fValue, ok := field["value"].(string); ok {
|
||||
fValue = linkWithTextRegex.ReplaceAllString(fValue, "[${2}](${1})")
|
||||
field["value"] = fValue
|
||||
fields[j] = field
|
||||
}
|
||||
}
|
||||
attachment["fields"] = fields
|
||||
list[i] = attachment
|
||||
}
|
||||
for _, attachment := range attachments {
|
||||
attachment.Text = parseSlackLinksToMarkdown(attachment.Text)
|
||||
attachment.Pretext = parseSlackLinksToMarkdown(attachment.Pretext)
|
||||
|
||||
for _, field := range attachment.Fields {
|
||||
if value, ok := field.Value.(string); ok {
|
||||
field.Value = parseSlackLinksToMarkdown(value)
|
||||
}
|
||||
}
|
||||
post.AddProp("attachments", list)
|
||||
}
|
||||
post.AddProp("attachments", attachments)
|
||||
}
|
||||
|
||||
func parseSlackLinksToMarkdown(text string) string {
|
||||
|
||||
@@ -5,6 +5,7 @@ package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -14,12 +15,12 @@ const (
|
||||
)
|
||||
|
||||
type CommandResponse struct {
|
||||
ResponseType string `json:"response_type"`
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
GotoLocation string `json:"goto_location"`
|
||||
Attachments interface{} `json:"attachments"`
|
||||
ResponseType string `json:"response_type"`
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
GotoLocation string `json:"goto_location"`
|
||||
Attachments []*SlackAttachment `json:"attachments"`
|
||||
}
|
||||
|
||||
func (o *CommandResponse) ToJson() string {
|
||||
@@ -34,10 +35,19 @@ func (o *CommandResponse) ToJson() string {
|
||||
func CommandResponseFromJson(data io.Reader) *CommandResponse {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o CommandResponse
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
|
||||
if err := decoder.Decode(&o); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure attachment fields are stored as strings
|
||||
for _, attachment := range o.Attachments {
|
||||
for _, field := range attachment.Fields {
|
||||
if field.Value != nil {
|
||||
field.Value = fmt.Sprintf("%v", field.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &o
|
||||
}
|
||||
|
||||
@@ -17,3 +17,68 @@ func TestCommandResponseJson(t *testing.T) {
|
||||
t.Fatal("Ids do not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandResponseFromJson(t *testing.T) {
|
||||
json := `{
|
||||
"response_type": "ephemeral",
|
||||
"text": "response text",
|
||||
"username": "response username",
|
||||
"icon_url": "response icon url",
|
||||
"goto_location": "response goto location",
|
||||
"attachments": [{
|
||||
"text": "attachment 1 text",
|
||||
"pretext": "attachment 1 pretext"
|
||||
},{
|
||||
"text": "attachment 2 text",
|
||||
"fields": [{
|
||||
"title": "field 1",
|
||||
"value": "value 1",
|
||||
"short": true
|
||||
},{
|
||||
"title": "field 2",
|
||||
"value": [],
|
||||
"short": false
|
||||
}]
|
||||
}]
|
||||
}`
|
||||
|
||||
response := CommandResponseFromJson(strings.NewReader(json))
|
||||
|
||||
if response == nil {
|
||||
t.Fatal("should've received non-nil CommandResponse")
|
||||
}
|
||||
|
||||
if response.ResponseType != "ephemeral" {
|
||||
t.Fatal("should've received correct response type")
|
||||
} else if response.Text != "response text" {
|
||||
t.Fatal("should've received correct response text")
|
||||
} else if response.Username != "response username" {
|
||||
t.Fatal("should've received correct response username")
|
||||
} else if response.IconURL != "response icon url" {
|
||||
t.Fatal("should've received correct response icon url")
|
||||
} else if response.GotoLocation != "response goto location" {
|
||||
t.Fatal("should've received correct response goto location")
|
||||
}
|
||||
|
||||
attachments := response.Attachments
|
||||
if len(attachments) != 2 {
|
||||
t.Fatal("should've received 2 attachments")
|
||||
} else if attachments[0].Text != "attachment 1 text" {
|
||||
t.Fatal("should've received correct first attachment text")
|
||||
} else if attachments[0].Pretext != "attachment 1 pretext" {
|
||||
t.Fatal("should've received correct first attachment pretext")
|
||||
} else if attachments[1].Text != "attachment 2 text" {
|
||||
t.Fatal("should've received correct second attachment text")
|
||||
}
|
||||
|
||||
fields := attachments[1].Fields
|
||||
if len(fields) != 2 {
|
||||
t.Fatal("should've received 2 fields")
|
||||
} else if fields[0].Value.(string) != "value 1" {
|
||||
t.Fatal("should've received correct first attachment value")
|
||||
} else if _, ok := fields[1].Value.(string); !ok {
|
||||
t.Fatal("should've received second attachment value parsed as a string")
|
||||
} else if fields[1].Value.(string) != "[]" {
|
||||
t.Fatal("should've received correct second attachment value")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@ type IncomingWebhook struct {
|
||||
}
|
||||
|
||||
type IncomingWebhookRequest struct {
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
ChannelName string `json:"channel"`
|
||||
Props StringInterface `json:"props"`
|
||||
Attachments interface{} `json:"attachments"`
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
ChannelName string `json:"channel"`
|
||||
Props StringInterface `json:"props"`
|
||||
Attachments []*SlackAttachment `json:"attachments"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (o *IncomingWebhook) ToJson() string {
|
||||
@@ -212,31 +212,15 @@ func expandAnnouncement(text string) string {
|
||||
func expandAnnouncements(i *IncomingWebhookRequest) {
|
||||
i.Text = expandAnnouncement(i.Text)
|
||||
|
||||
if i.Attachments != nil {
|
||||
attachments := i.Attachments.([]interface{})
|
||||
for _, attachment := range attachments {
|
||||
a := attachment.(map[string]interface{})
|
||||
for _, attachment := range i.Attachments {
|
||||
attachment.Pretext = expandAnnouncement(attachment.Pretext)
|
||||
attachment.Text = expandAnnouncement(attachment.Text)
|
||||
attachment.Title = expandAnnouncement(attachment.Title)
|
||||
|
||||
if a["pretext"] != nil {
|
||||
a["pretext"] = expandAnnouncement(a["pretext"].(string))
|
||||
}
|
||||
|
||||
if a["text"] != nil {
|
||||
a["text"] = expandAnnouncement(a["text"].(string))
|
||||
}
|
||||
|
||||
if a["title"] != nil {
|
||||
a["title"] = expandAnnouncement(a["title"].(string))
|
||||
}
|
||||
|
||||
if a["fields"] != nil {
|
||||
fields := a["fields"].([]interface{})
|
||||
for _, field := range fields {
|
||||
f := field.(map[string]interface{})
|
||||
if f["value"] != nil {
|
||||
f["value"] = expandAnnouncement(fmt.Sprintf("%v", f["value"]))
|
||||
}
|
||||
}
|
||||
for _, field := range attachment.Fields {
|
||||
if field.Value != nil {
|
||||
// Ensure the value is set to a string if it is set
|
||||
field.Value = expandAnnouncement(fmt.Sprintf("%v", field.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,21 +142,20 @@ func TestIncomingWebhookRequestFromJson_Announcements(t *testing.T) {
|
||||
t.Fatal("IncomingWebhookRequest should not be nil")
|
||||
}
|
||||
|
||||
attachments := iwr.Attachments.([]interface{})
|
||||
attachment := attachments[0].(map[string]interface{})
|
||||
if attachment["pretext"] != expected {
|
||||
t.Fatalf("Sample attachment pretext should be: %s, got: %s", expected, attachment["pretext"])
|
||||
attachment := iwr.Attachments[0]
|
||||
if attachment.Pretext != expected {
|
||||
t.Fatalf("Sample attachment pretext should be:%s, got: %s", expected, attachment.Pretext)
|
||||
}
|
||||
if attachment["text"] != expected {
|
||||
t.Fatalf("Sample attachment text should be: %s, got: %s", expected, attachment["text"])
|
||||
if attachment.Text != expected {
|
||||
t.Fatalf("Sample attachment text should be: %s, got: %s", expected, attachment.Text)
|
||||
}
|
||||
if attachment["title"] != expected {
|
||||
t.Fatalf("Sample attachment title should be: %s, got: %s", expected, attachment["title"])
|
||||
if attachment.Title != expected {
|
||||
t.Fatalf("Sample attachment title should be: %s, got: %s", expected, attachment.Title)
|
||||
}
|
||||
fields := attachment["fields"].([]interface{})
|
||||
field := fields[0].(map[string]interface{})
|
||||
if field["value"] != expected {
|
||||
t.Fatalf("Sample attachment field value should be: %s, got: %s", expected, field["value"])
|
||||
|
||||
field := attachment.Fields[0]
|
||||
if field.Value != expected {
|
||||
t.Fatalf("Sample attachment field value should be: %s, got: %s", expected, field.Value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,10 +223,10 @@ func TestIncomingWebhookRequestFromJson(t *testing.T) {
|
||||
if iwr.Text != expected {
|
||||
t.Fatalf("Sample %d text should be: %s, got: %s", i, expected, iwr.Text)
|
||||
}
|
||||
attachments := iwr.Attachments.([]interface{})
|
||||
attachment := attachments[0].(map[string]interface{})
|
||||
if attachment["text"] != expected {
|
||||
t.Fatalf("Sample %d attachment text should be: %s, got: %s", i, expected, attachment["text"])
|
||||
|
||||
attachment := iwr.Attachments[0]
|
||||
if attachment.Text != expected {
|
||||
t.Fatalf("Sample %d attachment text should be: %s, got: %s", i, expected, attachment.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
model/slack_attachment.go
Обычный файл
28
model/slack_attachment.go
Обычный файл
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
type SlackAttachment struct {
|
||||
Fallback string `json:"fallback"`
|
||||
Color string `json:"color"`
|
||||
Pretext string `json:"pretext"`
|
||||
AuthorName string `json:"author_name"`
|
||||
AuthorLink string `json:"author_link"`
|
||||
AuthorIcon string `json:"author_icon"`
|
||||
Title string `json:"title"`
|
||||
TitleLink string `json:"title_link"`
|
||||
Text string `json:"text"`
|
||||
Fields []*SlackAttachmentField `json:"fields"`
|
||||
ImageURL string `json:"image_url"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
Footer string `json:"footer"`
|
||||
FooterIcon string `json:"footer_icon"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
}
|
||||
|
||||
type SlackAttachmentField struct {
|
||||
Title string `json:"title"`
|
||||
Value interface{} `json:"value"`
|
||||
Short bool `json:"short"`
|
||||
}
|
||||
Ссылка в новой задаче
Block a user