Added additional validation for slack attachment format on server (#5680)

Этот коммит содержится в:
Harrison Healey
2017-03-08 04:15:33 -05:00
коммит произвёл George Goldberg
родитель f3a266ee5d
Коммит 5d62b3661b
6 изменённых файлов: 152 добавлений и 86 удалений

Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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"`
}