From d655ab94a395ac8d7e265fd141f311b02db85fee Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Thu, 15 Jun 2023 08:23:56 -0400 Subject: [PATCH] Sanitize post for audit with preview links (#23745) * sanitize post for audit with preview links * add unit test --------- Co-authored-by: Mattermost Build --- server/channels/app/post_metadata_test.go | 52 +++++++++++++++++++++++ server/public/model/post.go | 7 ++- server/public/model/post_embed.go | 8 ++++ server/public/model/post_metadata.go | 20 +++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/server/channels/app/post_metadata_test.go b/server/channels/app/post_metadata_test.go index ff92c30be3..d9952bd8ed 100644 --- a/server/channels/app/post_metadata_test.go +++ b/server/channels/app/post_metadata_test.go @@ -2817,3 +2817,55 @@ func TestSanitizePostMetadataForUserAndChannel(t *testing.T) { actual = th.App.sanitizePostMetadataForUserAndChannel(th.Context, post, previewedPost, directChannel, guest.Id) assert.Nil(t, actual.Metadata.Embeds[0].Data) } + +func TestSanitizePostMetaDataForAudit(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.SiteURL = "http://mymattermost.com" + }) + + th.Context.Session().UserId = th.BasicUser.Id + + referencedPost, err := th.App.CreatePost(th.Context, &model.Post{ + UserId: th.BasicUser.Id, + ChannelId: th.BasicChannel.Id, + Message: "hello world", + }, th.BasicChannel, false, true) + require.Nil(t, err) + referencedPost.Metadata.Embeds = nil + + link := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) + + previewPost, err := th.App.CreatePost(th.Context, &model.Post{ + UserId: th.BasicUser.Id, + ChannelId: th.BasicChannel.Id, + Message: link, + }, th.BasicChannel, false, true) + require.Nil(t, err) + previewPost.Metadata.Embeds = nil + clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false) + firstEmbed := clientPost.Metadata.Embeds[0] + preview := firstEmbed.Data.(*model.PreviewPost) + require.Equal(t, referencedPost.Id, preview.PostID) + + // ensure the permalink metadata is sanitized for audit logging + m := clientPost.Auditable() + metaDataI, ok := m["metadata"] + require.True(t, ok) + metaData, ok := metaDataI.(map[string]any) + require.True(t, ok) + embedsI, ok := metaData["embeds"] + require.True(t, ok) + embeds, ok := embedsI.([]map[string]any) + require.True(t, ok) + for _, pe := range embeds { + // ensure all the PostEmbed maps only contain `type` and `url` + for k := range pe { + if k != "type" && k != "url" { + require.Fail(t, "PostEmbed should only contain 'type' and 'url fields'") + } + } + } +} diff --git a/server/public/model/post.go b/server/public/model/post.go index afeb54e72d..f33bb148a7 100644 --- a/server/public/model/post.go +++ b/server/public/model/post.go @@ -120,6 +120,11 @@ type Post struct { } func (o *Post) Auditable() map[string]interface{} { + var metaData map[string]any + if o.Metadata != nil { + metaData = o.Metadata.Auditable() + } + return map[string]interface{}{ "id": o.Id, "create_at": o.CreateAt, @@ -139,7 +144,7 @@ func (o *Post) Auditable() map[string]interface{} { "reply_count": o.ReplyCount, "last_reply_at": o.LastReplyAt, "is_following": o.IsFollowing, - "metadata": o.Metadata, + "metadata": metaData, } } diff --git a/server/public/model/post_embed.go b/server/public/model/post_embed.go index 6b14a34fe4..3081f16fef 100644 --- a/server/public/model/post_embed.go +++ b/server/public/model/post_embed.go @@ -23,3 +23,11 @@ type PostEmbed struct { // Any additional data for the embedded content. Only used for OpenGraph embeds. Data any `json:"data,omitempty"` } + +func (pe *PostEmbed) Auditable() map[string]any { + // filter out embedded content. + return map[string]any{ + "type": pe.Type, + "url": pe.URL, + } +} diff --git a/server/public/model/post_metadata.go b/server/public/model/post_metadata.go index ee49eb4f48..cbc9ded083 100644 --- a/server/public/model/post_metadata.go +++ b/server/public/model/post_metadata.go @@ -30,6 +30,26 @@ type PostMetadata struct { Acknowledgements []*PostAcknowledgement `json:"acknowledgements,omitempty"` } +func (pm *PostMetadata) Auditable() map[string]any { + embeds := make([]map[string]any, 0, len(pm.Embeds)) + for _, pe := range pm.Embeds { + embeds = append(embeds, pe.Auditable()) + } + if len(embeds) == 0 { + embeds = nil + } + + return map[string]any{ + "embeds": embeds, + "emojis": pm.Emojis, + "files": pm.Files, + "images": pm.Images, + "reactions": pm.Reactions, + "priority": pm.Priority, + "acknowledgements": pm.Acknowledgements, + } +} + type PostImage struct { Width int `json:"width"` Height int `json:"height"`