From e961b4cd0dfaa146b399695c4e34f10405974ef2 Mon Sep 17 00:00:00 2001 From: Nathan Lowe Date: Thu, 24 Jan 2019 10:36:10 -0500 Subject: [PATCH] MM-13826 Webhooks: Allow "true"/"false" for bool values in payload body (#10114) * Webhooks: Allow "true"/"false" for bool values in payload body Some slack integrations encode bool fields as "true"/"false", which was previously unsupported in mattermost due to how encoding/json works. This commit adds an aliased type for bool that implements json.Unmarshaler to maintain compatibility with Slack. * Add missing copyright header to added files --- model/slack_attachment.go | 6 ++-- model/slack_compatibility.go | 30 ++++++++++++++++ model/slack_compatibility_test.go | 57 +++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 model/slack_compatibility.go create mode 100644 model/slack_compatibility_test.go diff --git a/model/slack_attachment.go b/model/slack_attachment.go index 273def8767..17abddeb06 100644 --- a/model/slack_attachment.go +++ b/model/slack_attachment.go @@ -31,9 +31,9 @@ type SlackAttachment struct { } type SlackAttachmentField struct { - Title string `json:"title"` - Value interface{} `json:"value"` - Short bool `json:"short"` + Title string `json:"title"` + Value interface{} `json:"value"` + Short SlackCompatibleBool `json:"short"` } func StringifySlackFieldValue(a []*SlackAttachment) []*SlackAttachment { diff --git a/model/slack_compatibility.go b/model/slack_compatibility.go new file mode 100644 index 0000000000..25539063d9 --- /dev/null +++ b/model/slack_compatibility.go @@ -0,0 +1,30 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "fmt" + "strings" +) + +// SlackCompatibleBool is an alias for bool that implements json.Unmarshaler +type SlackCompatibleBool bool + +// UnmarshalJSON implements json.Unmarshaler +// +// Slack allows bool values to be represented as strings ("true"/"false") or +// literals (true/false). To maintain compatibility, we define an Unmarshaler +// that supports both. +func (b *SlackCompatibleBool) UnmarshalJSON(data []byte) error { + value := strings.ToLower(string(data)) + if value == "true" || value == `"true"` { + *b = true + } else if value == "false" || value == `"false"` { + *b = false + } else { + return fmt.Errorf("unmarshal: unable to convert %s to bool", data) + } + + return nil +} diff --git a/model/slack_compatibility_test.go b/model/slack_compatibility_test.go new file mode 100644 index 0000000000..c920ea0ed2 --- /dev/null +++ b/model/slack_compatibility_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSlackCompatibleBool_UnmarshalJSON_True(t *testing.T) { + cases := []struct { + name string + payload string + }{ + {name: "literal", payload: `{"title": "Foo", "value": "Bar", "short": true}`}, + {name: "stringLower", payload: `{"title": "Foo", "value": "Bar", "short": "true"}`}, + {name: "stringMixed", payload: `{"title": "Foo", "value": "Bar", "short": "True"}`}, + {name: "stringUpper", payload: `{"title": "Foo", "value": "Bar", "short": "TRUE"}`}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + field := &SlackAttachmentField{} + + err := json.Unmarshal([]byte(tt.payload), field) + + require.NoError(t, err) + require.True(t, bool(field.Short)) + }) + } +} + +func TestSlackCompatibleBool_UnmarshalJSON_False(t *testing.T) { + cases := []struct { + name string + payload string + }{ + {name: "literal", payload: `{"title": "Foo", "value": "Bar", "short": false}`}, + {name: "stringLower", payload: `{"title": "Foo", "value": "Bar", "short": "false"}`}, + {name: "stringMixed", payload: `{"title": "Foo", "value": "Bar", "short": "False"}`}, + {name: "stringUpper", payload: `{"title": "Foo", "value": "Bar", "short": "FALSE"}`}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + field := &SlackAttachmentField{} + + err := json.Unmarshal([]byte(tt.payload), field) + + require.NoError(t, err) + require.False(t, bool(field.Short)) + }) + } +}