[MM-62427] Add message attachments validation (#30180)

* Add message attachments validation

* Add props validation

* Validate slack attachment fields

* Update tests and library usage

* Improve interactive dialog error for length checks

* Allow predefined colors for slack attachments

* Fix TestPostAction

* Use const for data source

* Add tests

* Cleanup unused props

* Add happy path tests

* lint fixes

* Add validation for PostActionOptions
Этот коммит содержится в:
Ben Schumacher
2025-03-20 12:53:50 +01:00
коммит произвёл GitHub
родитель 5609489e86
Коммит 9b5d8d52bf
47 изменённых файлов: 1402 добавлений и 355 удалений

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

@@ -39,7 +39,7 @@ func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
assert.NotEmpty(th.t, poir.TeamName)
assert.NotEmpty(th.t, poir.PostId)
assert.NotEmpty(th.t, poir.TriggerId)
assert.Equal(th.t, "button", poir.Type)
assert.Equal(th.t, model.PostActionTypeButton, poir.Type)
assert.Equal(th.t, "test-value", poir.Context["test-key"])
_, err = w.Write([]byte("{}"))
require.NoError(th.t, err)
@@ -118,7 +118,7 @@ func TestPostActionCookies(t *testing.T) {
CreateAt: model.GetMillis(),
UpdateAt: model.GetMillis(),
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Title: "some-title",
TitleLink: "https://some-url.com",

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

@@ -1420,7 +1420,7 @@ func TestUpdatePost(t *testing.T) {
ChannelId: channel.Id,
Message: "zz" + model.NewId() + " update post 3",
}
up4.AddProp("attachments", []model.SlackAttachment{
up4.AddProp(model.PostPropsAttachments, []model.SlackAttachment{
{
Text: "Hello World",
},
@@ -1772,12 +1772,12 @@ func TestPatchPost(t *testing.T) {
Text: "Hello World",
},
}
patch2.Props = &model.StringInterface{"attachments": attachments}
patch2.Props = &model.StringInterface{model.PostPropsAttachments: attachments}
var rpost2 *model.Post
rpost2, _, err = client.PatchPost(context.Background(), post.Id, patch2)
require.NoError(t, err)
assert.NotEmpty(t, rpost2.GetProp("attachments"))
assert.NotEmpty(t, rpost2.GetProp(model.PostPropsAttachments))
assert.NotEqual(t, rpost.EditAt, rpost2.EditAt)
})
@@ -4682,7 +4682,7 @@ func TestGetPostStripActionIntegrations(t *testing.T) {
ChannelId: th.BasicChannel.Id,
Message: "with slack attachment action",
}
post.AddProp("attachments", []*model.SlackAttachment{
post.AddProp(model.PostPropsAttachments, []*model.SlackAttachment{
{
Text: "Slack Attachment Text",
Fields: []*model.SlackAttachmentField{
@@ -4694,7 +4694,7 @@ func TestGetPostStripActionIntegrations(t *testing.T) {
},
Actions: []*model.PostAction{
{
Type: "button",
Type: model.PostActionTypeButton,
Name: "test-name",
Integration: &model.PostActionIntegration{
URL: "https://test.test/action",
@@ -4713,7 +4713,7 @@ func TestGetPostStripActionIntegrations(t *testing.T) {
actualPost, _, err := client.GetPost(context.Background(), rpost.Id, "")
require.NoError(t, err)
attachments, _ := actualPost.Props["attachments"].([]any)
attachments, _ := actualPost.Props[model.PostPropsAttachments].([]any)
require.Equal(t, 1, len(attachments))
att, _ := attachments[0].(map[string]any)
require.NotNil(t, att)

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

@@ -618,28 +618,28 @@ func (a *App) HandleCommandResponsePost(c request.CTX, command *model.Command, a
if *a.Config().ServiceSettings.EnablePostUsernameOverride {
if command.Username != "" {
post.AddProp("override_username", command.Username)
post.AddProp(model.PostPropsOverrideUsername, command.Username)
isBotPost = true
} else if response.Username != "" {
post.AddProp("override_username", response.Username)
post.AddProp(model.PostPropsOverrideUsername, response.Username)
isBotPost = true
}
}
if *a.Config().ServiceSettings.EnablePostIconOverride {
if command.IconURL != "" {
post.AddProp("override_icon_url", command.IconURL)
post.AddProp(model.PostPropsOverrideIconURL, command.IconURL)
isBotPost = true
} else if response.IconURL != "" {
post.AddProp("override_icon_url", response.IconURL)
post.AddProp(model.PostPropsOverrideIconURL, response.IconURL)
isBotPost = true
} else {
post.AddProp("override_icon_url", "")
post.AddProp(model.PostPropsOverrideIconURL, "")
}
}
if isBotPost {
post.AddProp("from_webhook", "true")
post.AddProp(model.PostPropsFromWebhook, "true")
}
// Process Slack text replacements if the response does not contain "skip_slack_parsing": true.

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

@@ -623,7 +623,7 @@ func TestExportPostWithProps(t *testing.T) {
ChannelId: dmChannel.Id,
Message: "aa" + model.NewId() + "a",
Props: map[string]any{
"attachments": attachments,
model.PostPropsAttachments: attachments,
},
UserId: th1.BasicUser.Id,
}
@@ -634,7 +634,7 @@ func TestExportPostWithProps(t *testing.T) {
ChannelId: gmChannel.Id,
Message: "dd" + model.NewId() + "a",
Props: map[string]any{
"attachments": attachments,
model.PostPropsAttachments: attachments,
},
UserId: th1.BasicUser.Id,
}
@@ -673,8 +673,8 @@ func TestExportPostWithProps(t *testing.T) {
assert.Len(t, posts, 2)
assert.ElementsMatch(t, gmMembers, *posts[0].ChannelMembers)
assert.ElementsMatch(t, dmMembers, *posts[1].ChannelMembers)
assert.Contains(t, posts[0].Props["attachments"].([]any)[0], "footer")
assert.Contains(t, posts[1].Props["attachments"].([]any)[0], "footer")
assert.Contains(t, posts[0].Props[model.PostPropsAttachments].([]any)[0], "footer")
assert.Contains(t, posts[1].Props[model.PostPropsAttachments].([]any)[0], "footer")
}
func TestExportUserCustomStatus(t *testing.T) {

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

@@ -1455,7 +1455,7 @@ func (a *App) importReplies(rctx request.CTX, data []imports.ReplyImportData, po
}
if len(postsForCreateList) > 0 {
postsCreated, _, err := a.Srv().Store().Post().SaveMultiple(postsForCreateList)
postsCreated, _, err := a.Srv().Store().Post().SaveMultiple(rctx, postsForCreateList)
if err != nil {
var appErr *model.AppError
var invErr *store.ErrInvalidInput
@@ -1478,7 +1478,7 @@ func (a *App) importReplies(rctx request.CTX, data []imports.ReplyImportData, po
}
}
if _, _, nErr := a.Srv().Store().Post().OverwriteMultiple(postsForOverwriteList); nErr != nil {
if _, _, nErr := a.Srv().Store().Post().OverwriteMultiple(rctx, postsForOverwriteList); nErr != nil {
return model.NewAppError("importReplies", "app.post.overwrite.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
@@ -1892,7 +1892,7 @@ func (a *App) importMultiplePostLines(rctx request.CTX, lines []imports.LineImpo
}
if len(postsForCreateList) > 0 {
_, idx, nErr := a.Srv().Store().Post().SaveMultiple(postsForCreateList)
_, idx, nErr := a.Srv().Store().Post().SaveMultiple(rctx, postsForCreateList)
if nErr != nil {
var appErr *model.AppError
var invErr *store.ErrInvalidInput
@@ -1946,7 +1946,7 @@ func (a *App) importMultiplePostLines(rctx request.CTX, lines []imports.LineImpo
}
}
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(postsForOverwriteList); err != nil {
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(rctx, postsForOverwriteList); err != nil {
if idx != -1 && idx < len(postsForOverwriteList) {
post := postsForOverwriteList[idx]
if lineNumber, ok := postsForOverwriteMap[getPostStrID(post)]; ok {
@@ -2411,7 +2411,7 @@ func (a *App) importMultipleDirectPostLines(rctx request.CTX, lines []imports.Li
}
if len(postsForCreateList) > 0 {
if _, idx, err := a.Srv().Store().Post().SaveMultiple(postsForCreateList); err != nil {
if _, idx, err := a.Srv().Store().Post().SaveMultiple(rctx, postsForCreateList); err != nil {
var appErr *model.AppError
var invErr *store.ErrInvalidInput
var retErr *model.AppError
@@ -2459,7 +2459,7 @@ func (a *App) importMultipleDirectPostLines(rctx request.CTX, lines []imports.Li
}
}
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(postsForOverwriteList); err != nil {
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(rctx, postsForOverwriteList); err != nil {
if idx != -1 && idx < len(postsForOverwriteList) {
post := postsForOverwriteList[idx]
if lineNumber, ok := postsForOverwriteMap[getPostStrID(post)]; ok {

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

@@ -42,16 +42,16 @@ func TestPostActionInvalidURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: ":test",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -61,7 +61,7 @@ func TestPostActionInvalidURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -90,11 +90,14 @@ func TestPostActionEmptyResponse(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -102,9 +105,6 @@ func TestPostActionEmptyResponse(t *testing.T) {
},
URL: ts.URL,
},
Name: "action",
Type: "some_type",
DataSource: "some_source",
},
},
},
@@ -115,7 +115,7 @@ func TestPostActionEmptyResponse(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
_, err = th.App.DoPostActionWithCookie(th.Context, post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "", nil)
@@ -134,11 +134,14 @@ func TestPostActionEmptyResponse(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -146,9 +149,6 @@ func TestPostActionEmptyResponse(t *testing.T) {
},
URL: ts.URL,
},
Name: "action",
Type: "some_type",
DataSource: "some_source",
},
},
},
@@ -159,7 +159,7 @@ func TestPostActionEmptyResponse(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
th.App.UpdateConfig(func(cfg *model.Config) {
@@ -209,23 +209,28 @@ func TestPostAction(t *testing.T) {
jsonErr := json.NewDecoder(r.Body).Decode(&request)
assert.NoError(t, jsonErr)
assert.Equal(t, request.UserId, th.BasicUser.Id)
assert.Equal(t, request.UserName, th.BasicUser.Username)
assert.Equal(t, request.ChannelId, channel.Id)
assert.Equal(t, request.ChannelName, channel.Name)
assert.Equal(t, th.BasicUser.Id, request.UserId)
assert.Equal(t, th.BasicUser.Username, request.UserName)
assert.Equal(t, channel.Id, request.ChannelId)
assert.Equal(t, channel.Name, request.ChannelName)
if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup {
assert.Empty(t, request.TeamId)
assert.Empty(t, request.TeamName)
} else {
assert.Equal(t, request.TeamId, th.BasicTeam.Id)
assert.Equal(t, request.TeamName, th.BasicTeam.Name)
assert.Equal(t, th.BasicTeam.Id, request.TeamId)
assert.Equal(t, th.BasicTeam.Name, request.TeamName)
}
assert.True(t, request.TriggerId != "")
if request.Type == model.PostActionTypeSelect {
assert.Equal(t, request.DataSource, "some_source")
assert.Equal(t, request.Context["selected_option"], "selected")
if selectedOption, ok := request.Context["selected_option"]; ok {
// If something was selected, confirm that the data source and selected option are present
assert.Equal(t, model.PostActionDataSourceUsers, request.DataSource)
assert.Equal(t, "selected", selectedOption)
} else {
assert.Empty(t, request.DataSource)
}
} else {
assert.Equal(t, request.DataSource, "")
assert.Equal(t, "", request.DataSource)
}
assert.Equal(t, "foo", request.Context["s"])
assert.EqualValues(t, 3, request.Context["n"])
@@ -239,11 +244,14 @@ func TestPostAction(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -251,9 +259,6 @@ func TestPostAction(t *testing.T) {
},
URL: ts.URL,
},
Name: "action",
Type: "some_type",
DataSource: "some_source",
},
},
},
@@ -264,7 +269,7 @@ func TestPostAction(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
@@ -276,11 +281,14 @@ func TestPostAction(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -288,9 +296,6 @@ func TestPostAction(t *testing.T) {
},
URL: ts.URL,
},
Name: "action",
Type: model.PostActionTypeSelect,
DataSource: "some_source",
},
},
},
@@ -301,7 +306,7 @@ func TestPostAction(t *testing.T) {
post2, err := th.App.CreatePostAsUser(th.Context, &menuPost, "", true)
require.Nil(t, err)
attachments2, ok := post2.GetProp("attachments").([]*model.SlackAttachment)
attachments2, ok := post2.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments2[0].Actions)
@@ -310,15 +315,15 @@ func TestPostAction(t *testing.T) {
clientTriggerID, err := th.App.DoPostActionWithCookie(th.Context, post.Id, "notavalidid", th.BasicUser.Id, "", nil)
require.NotNil(t, err)
assert.Equal(t, http.StatusNotFound, err.StatusCode)
assert.True(t, clientTriggerID == "")
assert.Len(t, clientTriggerID, 0)
clientTriggerID, err = th.App.DoPostActionWithCookie(th.Context, post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "", nil)
require.Nil(t, err)
assert.True(t, len(clientTriggerID) == 26)
assert.Len(t, clientTriggerID, 26)
clientTriggerID, err = th.App.DoPostActionWithCookie(th.Context, post2.Id, attachments2[0].Actions[0].Id, th.BasicUser.Id, "selected", nil)
require.Nil(t, err)
assert.True(t, len(clientTriggerID) == 26)
assert.Len(t, clientTriggerID, 26)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
@@ -334,11 +339,14 @@ func TestPostAction(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -346,9 +354,6 @@ func TestPostAction(t *testing.T) {
},
URL: ts.URL + "/plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
DataSource: "some_source",
},
},
},
@@ -359,7 +364,7 @@ func TestPostAction(t *testing.T) {
postplugin, err := th.App.CreatePostAsUser(th.Context, &interactivePostPlugin, "", true)
require.Nil(t, err)
attachmentsPlugin, ok := postplugin.GetProp("attachments").([]*model.SlackAttachment)
attachmentsPlugin, ok := postplugin.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
_, err = th.App.DoPostActionWithCookie(th.Context, postplugin.Id, attachmentsPlugin[0].Actions[0].Id, th.BasicUser.Id, "", nil)
@@ -382,11 +387,14 @@ func TestPostAction(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -394,9 +402,6 @@ func TestPostAction(t *testing.T) {
},
URL: "http://127.1.1.1/plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
DataSource: "some_source",
},
},
},
@@ -407,7 +412,7 @@ func TestPostAction(t *testing.T) {
postSiteURL, err := th.App.CreatePostAsUser(th.Context, &interactivePostSiteURL, "", true)
require.Nil(t, err)
attachmentsSiteURL, ok := postSiteURL.GetProp("attachments").([]*model.SlackAttachment)
attachmentsSiteURL, ok := postSiteURL.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
_, err = th.App.DoPostActionWithCookie(th.Context, postSiteURL.Id, attachmentsSiteURL[0].Actions[0].Id, th.BasicUser.Id, "", nil)
@@ -424,11 +429,14 @@ func TestPostAction(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -436,9 +444,6 @@ func TestPostAction(t *testing.T) {
},
URL: ts.URL + "/subpath/plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
DataSource: "some_source",
},
},
},
@@ -449,7 +454,7 @@ func TestPostAction(t *testing.T) {
postSubpath, err := th.App.CreatePostAsUser(th.Context, &interactivePostSubpath, "", true)
require.Nil(t, err)
attachmentsSubpath, ok := postSubpath.GetProp("attachments").([]*model.SlackAttachment)
attachmentsSubpath, ok := postSubpath.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
_, err = th.App.DoPostActionWithCookie(th.Context, postSubpath.Id, attachmentsSubpath[0].Actions[0].Id, th.BasicUser.Id, "", nil)
@@ -477,7 +482,7 @@ func TestPostActionProps(t *testing.T) {
"has_reactions": true,
"is_pinned": false,
"props": {
"from_webhook":true,
"from_webhook":"true",
"override_username":"new_override_user",
"override_icon_url":"new_override_icon",
"A":"AA"
@@ -496,11 +501,14 @@ func TestPostActionProps(t *testing.T) {
HasReactions: false,
IsPinned: true,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeSelect,
Name: "action",
DataSource: model.PostActionDataSourceUsers,
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"s": "foo",
@@ -508,27 +516,24 @@ func TestPostActionProps(t *testing.T) {
},
URL: ts.URL,
},
Name: "action",
Type: "some_type",
DataSource: "some_source",
},
},
},
},
"override_icon_url": "old_override_icon",
"from_webhook": false,
"B": "BB",
model.PostPropsOverrideIconURL: "old_override_icon",
model.PostPropsFromWebhook: "false",
"B": "BB",
},
}
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
clientTriggerId, err := th.App.DoPostActionWithCookie(th.Context, post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "", nil)
require.Nil(t, err)
assert.True(t, len(clientTriggerId) == 26)
assert.Len(t, clientTriggerId, 26)
newPost, nErr := th.App.Srv().Store().Post().GetSingle(th.Context, post.Id, false)
require.NoError(t, nErr)
@@ -536,10 +541,10 @@ func TestPostActionProps(t *testing.T) {
assert.True(t, newPost.IsPinned)
assert.False(t, newPost.HasReactions)
assert.Nil(t, newPost.GetProp("B"))
assert.Nil(t, newPost.GetProp("override_username"))
assert.Nil(t, newPost.GetProp(model.PostPropsOverrideUsername))
assert.Equal(t, "AA", newPost.GetProp("A"))
assert.Equal(t, "old_override_icon", newPost.GetProp("override_icon_url"))
assert.Equal(t, false, newPost.GetProp("from_webhook"))
assert.Equal(t, "old_override_icon", newPost.GetProp(model.PostPropsOverrideIconURL))
assert.Equal(t, "false", newPost.GetProp(model.PostPropsFromWebhook))
}
func TestSubmitInteractiveDialog(t *testing.T) {
@@ -688,16 +693,16 @@ func TestPostActionRelativeURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "/notaplugin/some/path",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -707,7 +712,7 @@ func TestPostActionRelativeURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -728,16 +733,16 @@ func TestPostActionRelativeURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "/plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -747,7 +752,7 @@ func TestPostActionRelativeURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -768,16 +773,16 @@ func TestPostActionRelativeURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "/plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -787,7 +792,7 @@ func TestPostActionRelativeURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -808,16 +813,16 @@ func TestPostActionRelativeURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "//plugins/myplugin///myaction",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -827,7 +832,7 @@ func TestPostActionRelativeURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -848,16 +853,16 @@ func TestPostActionRelativeURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -867,7 +872,7 @@ func TestPostActionRelativeURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -925,16 +930,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "/notaplugin/some/path",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -944,7 +949,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -965,16 +970,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "/plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -984,7 +989,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -1005,16 +1010,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "//plugins/myplugin///myaction",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -1024,7 +1029,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)
@@ -1045,16 +1050,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "hello",
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Name: "action",
Integration: &model.PostActionIntegration{
URL: "plugins/myplugin/myaction",
},
Name: "action",
Type: "some_type",
},
},
},
@@ -1064,7 +1069,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
require.Nil(t, err)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
require.True(t, ok)
require.NotEmpty(t, attachments[0].Actions)
require.NotEmpty(t, attachments[0].Actions[0].Id)

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

@@ -105,7 +105,7 @@ func TestIsKeywordMultibyte(t *testing.T) {
post := &model.Post{
Message: tc.Message,
Props: model.StringInterface{
"attachments": tc.Attachments,
model.PostPropsAttachments: tc.Attachments,
},
}

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

@@ -209,7 +209,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
for _, profile := range profileMap {
if (profile.NotifyProps[model.PushNotifyProp] == model.UserNotifyAll ||
channelMemberNotifyPropsMap[profile.Id][model.PushNotifyProp] == model.ChannelNotifyAll) &&
(post.UserId != profile.Id || post.GetProp("from_webhook") == "true") &&
(post.UserId != profile.Id || post.GetProp(model.PostPropsFromWebhook) == "true") &&
!post.IsSystemMessage() &&
!(a.IsCRTEnabledForUser(c, profile.Id) && post.RootId != "") {
allActivityPushUserIds = append(allActivityPushUserIds, profile.Id)
@@ -228,7 +228,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
var rootMentions *MentionResults
if parentPostList != nil {
rootPost := parentPostList.Posts[parentPostList.Order[0]]
if rootPost.GetProp("from_webhook") != "true" {
if rootPost.GetProp(model.PostPropsFromWebhook) != "true" {
if _, ok := profileMap[rootPost.UserId]; ok {
threadParticipants[rootPost.UserId] = true
}
@@ -358,7 +358,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
continue
}
if post.GetProp("from_webhook") != "true" && uid == post.UserId {
if post.GetProp(model.PostPropsFromWebhook) != "true" && uid == post.UserId {
continue
}
@@ -1038,7 +1038,7 @@ func (a *App) getExplicitMentionsAndKeywords(c request.CTX, post *model.Post, ch
var keywords MentionKeywords
if channel.Type == model.ChannelTypeDirect {
isWebhook := post.GetProp("from_webhook") == "true"
isWebhook := post.GetProp(model.PostPropsFromWebhook) == "true"
// A bot can post in a DM where it doesn't belong to.
// Therefore, we cannot "guess" who is the other user,
@@ -1119,7 +1119,7 @@ func (a *App) getExplicitMentionsAndKeywords(c request.CTX, post *model.Post, ch
}
// Prevent the user from mentioning themselves
if post.GetProp("from_webhook") != "true" {
if post.GetProp(model.PostPropsFromWebhook) != "true" {
mentions.removeMention(post.UserId)
}
}
@@ -1639,7 +1639,7 @@ func (n *PostNotification) GetSenderName(userNameFormat string, overridesAllowed
}
if overridesAllowed && n.Channel.Type != model.ChannelTypeDirect {
if value := n.Post.GetProps()["override_username"]; value != nil && n.Post.GetProp("from_webhook") == "true" {
if value := n.Post.GetProp(model.PostPropsOverrideUsername); value != nil && n.Post.GetProp(model.PostPropsFromWebhook) == "true" {
if s, ok := value.(string); ok {
return s
}

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

@@ -668,7 +668,7 @@ func doesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps m
}
if (notify == model.ChannelNotifyAll) &&
(post.UserId != user.Id || post.GetProp("from_webhook") == "true") {
(post.UserId != user.Id || post.GetProp(model.PostPropsFromWebhook) == "true") {
return ""
}
@@ -833,16 +833,16 @@ func (a *App) buildFullPushNotificationMessage(c request.CTX, contentsConfig str
}
msg.SenderName = senderName
if ou, ok := post.GetProp("override_username").(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
if ou, ok := post.GetProp(model.PostPropsOverrideUsername).(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
msg.OverrideUsername = ou
msg.SenderName = ou
}
if oi, ok := post.GetProp("override_icon_url").(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
if oi, ok := post.GetProp(model.PostPropsOverrideIconURL).(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
msg.OverrideIconURL = oi
}
if fw, ok := post.GetProp("from_webhook").(string); ok {
if fw, ok := post.GetProp(model.PostPropsFromWebhook).(string); ok {
msg.FromWebhook = fw
}

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

@@ -1617,7 +1617,7 @@ func TestPushNotificationAttachment(t *testing.T) {
post := &model.Post{
Message: originalMessage,
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
AuthorName: "testuser",
Text: "test attachment",

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

@@ -175,7 +175,7 @@ func TestSendNotifications(t *testing.T) {
UserId: user.Id,
ChannelId: th.BasicChannel.Id,
Message: "a message",
Props: model.StringInterface{"from_webhook": "true", "override_username": "a bot"},
Props: model.StringInterface{model.PostPropsFromWebhook: "true", model.PostPropsOverrideUsername: "a bot"},
}
rootPost, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true)
@@ -1428,7 +1428,7 @@ func TestGetExplicitMentions(t *testing.T) {
post := &model.Post{
Message: tc.Message,
Props: model.StringInterface{
"attachments": tc.Attachments,
model.PostPropsAttachments: tc.Attachments,
},
}
@@ -2208,7 +2208,7 @@ func TestGetMentionsEnabledFields(t *testing.T) {
post := &model.Post{
Message: "This is the message",
Props: model.StringInterface{
"attachments": attachments,
model.PostPropsAttachments: attachments,
},
}
expectedFields := []string{
@@ -2316,22 +2316,22 @@ func TestPostNotificationGetSenderName(t *testing.T) {
overriddenPost := &model.Post{
Props: model.StringInterface{
"override_username": "Overridden",
"from_webhook": "true",
model.PostPropsOverrideUsername: "Overridden",
model.PostPropsFromWebhook: "true",
},
}
overriddenPost2 := &model.Post{
Props: model.StringInterface{
"override_username": nil,
"from_webhook": "true",
model.PostPropsOverrideUsername: nil,
model.PostPropsFromWebhook: "true",
},
}
overriddenPost3 := &model.Post{
Props: model.StringInterface{
"override_username": 10,
"from_webhook": "true",
model.PostPropsOverrideUsername: 10,
model.PostPropsFromWebhook: "true",
},
}
@@ -2364,7 +2364,7 @@ func TestPostNotificationGetSenderName(t *testing.T) {
"overridden username": {
post: overriddenPost,
allowOverrides: true,
expected: overriddenPost.GetProp("override_username").(string),
expected: overriddenPost.GetProp(model.PostPropsOverrideUsername).(string),
},
"overridden username, direct channel": {
channel: &model.Channel{Type: model.ChannelTypeDirect},
@@ -2887,7 +2887,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) {
UserId: user.Id,
ChannelId: th.BasicChannel.Id,
Message: "a message",
Props: model.StringInterface{"from_webhook": "true", "override_username": "a bot"},
Props: model.StringInterface{model.PostPropsFromWebhook: "true", model.PostPropsOverrideUsername: "a bot"},
}
rootPost, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true)

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

@@ -756,7 +756,7 @@ func (api *PluginAPI) DeleteGroupSyncable(groupID string, syncableID string, syn
}
func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
post.AddProp("from_plugin", "true")
post.AddProp(model.PostPropsFromPlugin, "true")
post, appErr := api.app.CreatePostMissingChannel(api.ctx, post, true, true)
if post != nil {

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

@@ -1520,7 +1520,7 @@ func TestPluginCreatePostAddsFromPluginProp(t *testing.T) {
actualPost, err := api.GetPost(post.Id)
require.Nil(t, err)
assert.Equal(t, "true", actualPost.GetProp("from_plugin"))
assert.Equal(t, "true", actualPost.GetProp(model.PostPropsFromPlugin))
}
func TestPluginAPIGetConfig(t *testing.T) {

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

@@ -297,12 +297,12 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
}
// Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088
if attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment); ok {
if attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment); ok {
jsonAttachments, err := json.Marshal(attachments)
if err == nil {
attachmentsInterface := []any{}
err = json.Unmarshal(jsonAttachments, &attachmentsInterface)
post.AddProp("attachments", attachmentsInterface)
post.AddProp(model.PostPropsAttachments, attachmentsInterface)
}
if err != nil {
c.Logger().Warn("Could not convert post attachments to map interface.", mlog.Err(err))
@@ -524,9 +524,9 @@ func (a *App) FillInPostProps(c request.CTX, post *model.Post, channel *model.Ch
}
if len(channelMentionsProp) > 0 {
post.AddProp("channel_mentions", channelMentionsProp)
post.AddProp(model.PostPropsChannelMentions, channelMentionsProp)
} else if post.GetProps() != nil {
post.DelProp("channel_mentions")
post.DelProp(model.PostPropsChannelMentions)
}
matched := atMentionPattern.MatchString(post.Message)
@@ -2077,7 +2077,7 @@ func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]
func isPostMention(user *model.User, post *model.Post, keywords MentionKeywords, otherPosts map[string]*model.Post, mentionedByThread map[string]bool, checkForCommentMentions bool) bool {
// Prevent the user from mentioning themselves
if post.UserId == user.Id && post.GetProp("from_webhook") != "true" {
if post.UserId == user.Id && post.GetProp(model.PostPropsFromWebhook) != "true" {
return false
}

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

@@ -38,8 +38,6 @@ type linkMetadataCache struct {
const MaxMetadataImageSize = MaxOpenGraphResponseSize
const UnsafeLinksPostProp = "unsafe_links"
func (s *Server) initPostMetadata() {
// Dump any cached links if the proxy settings have changed so image URLs can be updated
s.platform.AddConfigListener(func(before, after *model.Config) {
@@ -183,7 +181,7 @@ func (a *App) getEmbedsAndImages(c request.CTX, post *model.Post, isNewPost bool
// Embeds and image dimensions
firstLink, images := a.getFirstLinkAndImages(c, post.Message)
if unsafeLinksProp := post.GetProp(UnsafeLinksPostProp); unsafeLinksProp != nil {
if unsafeLinksProp := post.GetProp(model.PostPropsUnsafeLinks); unsafeLinksProp != nil {
if prop, ok := unsafeLinksProp.(string); ok && prop == "true" {
images = []string{}
if !looksLikeAPermalink(firstLink, *a.Config().ServiceSettings.SiteURL) {
@@ -302,7 +300,7 @@ func (a *App) getEmojisAndReactionsForPost(c request.CTX, post *model.Post) ([]*
}
func (a *App) getEmbedForPost(c request.CTX, post *model.Post, firstLink string, isNewPost bool) (*model.PostEmbed, error) {
if _, ok := post.GetProps()["attachments"]; ok {
if _, ok := post.GetProps()[model.PostPropsAttachments]; ok {
return &model.PostEmbed{
Type: model.PostEmbedMessageAttachment,
}, nil

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

@@ -223,7 +223,7 @@ func TestPreparePostForClient(t *testing.T) {
ChannelId: th.BasicChannel.Id,
Message: ":" + emoji.Name + ": :taco:",
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: ":" + emoji.Name + ":",
},
@@ -267,7 +267,7 @@ func TestPreparePostForClient(t *testing.T) {
ChannelId: th.BasicChannel.Id,
Message: ":" + emoji3.Name + ": :taco:",
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: ":" + emoji4.Name + ":",
},
@@ -531,7 +531,7 @@ func TestPreparePostForClient(t *testing.T) {
ChannelId: th.BasicChannel.Id,
Message: `Bla bla bla: ` + fmt.Sprintf(tc.link, noAccessServer.URL),
}
prepost.AddProp(UnsafeLinksPostProp, "true")
prepost.AddProp(model.PostPropsUnsafeLinks, "true")
post, err := th.App.CreatePost(th.Context, prepost, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, err)
@@ -569,7 +569,7 @@ func TestPreparePostForClient(t *testing.T) {
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Props: map[string]any{
"attachments": []any{
model.PostPropsAttachments: []any{
map[string]any{
"text": "![icon](" + server.URL + "/test-image1.png)",
},
@@ -1039,7 +1039,7 @@ func TestGetEmbedForPost(t *testing.T) {
t.Run("should return a message attachment when the post has one", func(t *testing.T) {
embed, err := th.App.getEmbedForPost(th.Context, &model.Post{
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "test",
},
@@ -1117,7 +1117,7 @@ func TestGetEmbedForPost(t *testing.T) {
t.Run("should return an embedded message attachment", func(t *testing.T) {
embed, err := th.App.getEmbedForPost(th.Context, &model.Post{
Props: model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "test",
},
@@ -1532,7 +1532,7 @@ func TestGetEmojiNamesForPost(t *testing.T) {
Post: &model.Post{
Message: "this is a post",
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: ":emoji1:",
Pretext: ":emoji2:",
@@ -1560,7 +1560,7 @@ func TestGetEmojiNamesForPost(t *testing.T) {
Post: &model.Post{
Message: "this is :emoji1",
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: ":emoji2:",
Pretext: ":emoji2:",
@@ -1616,7 +1616,7 @@ func TestGetCustomEmojisForPost(t *testing.T) {
post := &model.Post{
Message: ":" + emojis[1].Name + ":",
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Pretext: ":" + emojis[2].Name + ":",
Text: ":" + emojis[3].Name + ":",
@@ -1642,7 +1642,7 @@ func TestGetCustomEmojisForPost(t *testing.T) {
post := &model.Post{
Message: ":secret: :" + emojis[0].Name + ":",
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: ":imaginary:",
},
@@ -1838,7 +1838,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "empty attachments",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{},
model.PostPropsAttachments: []*model.SlackAttachment{},
},
},
Expected: []string{},
@@ -1847,7 +1847,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "attachment with no fields that can contain images",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Title: "This is the title",
},
@@ -1860,7 +1860,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "images in text",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "![logo](https://example.com/logo) and ![icon](https://example.com/icon)",
},
@@ -1873,7 +1873,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "images in pretext",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Pretext: "![logo](https://example.com/logo1) and ![icon](https://example.com/icon1)",
},
@@ -1886,7 +1886,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "images in fields",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Fields: []*model.SlackAttachmentField{
{
@@ -1903,7 +1903,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "image in author_icon",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
AuthorIcon: "https://example.com/icon2",
},
@@ -1916,7 +1916,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "image in image_url",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
ImageURL: "https://example.com/image",
},
@@ -1929,7 +1929,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "image in thumb_url",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
ThumbURL: "https://example.com/image",
},
@@ -1942,7 +1942,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "image in footer_icon",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
FooterIcon: "https://example.com/image",
},
@@ -1955,7 +1955,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "images in multiple fields",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Fields: []*model.SlackAttachmentField{
{
@@ -1975,7 +1975,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "non-string field",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Fields: []*model.SlackAttachmentField{
{
@@ -1992,7 +1992,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "images in multiple locations",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "![text](https://example.com/text)",
Pretext: "![pretext](https://example.com/pretext)",
@@ -2014,7 +2014,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
Name: "multiple attachments",
Post: &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "![logo](https://example.com/logo)",
},

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

@@ -588,7 +588,7 @@ func TestPostChannelMentions(t *testing.T) {
"display_name": "Mention Test",
"team_name": th.BasicTeam.Name,
},
}, post.GetProp("channel_mentions"))
}, post.GetProp(model.PostPropsChannelMentions))
post.Message = fmt.Sprintf("goodbye, ~%v!", channelToMention2.Name)
result, err := th.App.UpdatePost(th.Context, post, nil)
@@ -598,12 +598,12 @@ func TestPostChannelMentions(t *testing.T) {
"display_name": "Mention Test2",
"team_name": th.BasicTeam.Name,
},
}, result.GetProp("channel_mentions"))
}, result.GetProp(model.PostPropsChannelMentions))
result.Message = "no more mentions!"
result, err = th.App.UpdatePost(th.Context, result, nil)
require.Nil(t, err)
assert.Nil(t, result.GetProp("channel_mentions"))
assert.Nil(t, result.GetProp(model.PostPropsChannelMentions))
}
func TestImageProxy(t *testing.T) {
@@ -1282,7 +1282,7 @@ func TestCreatePostAsUser(t *testing.T) {
Message: "test",
UserId: th.BasicUser.Id,
}
post.AddProp("from_webhook", "true")
post.AddProp(model.PostPropsFromWebhook, "true")
channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(context.Background(), th.BasicChannel.Id, th.BasicUser.Id)
require.NoError(t, err)
@@ -2502,7 +2502,7 @@ func TestCountMentionsFromPost(t *testing.T) {
ChannelId: channel.Id,
Message: fmt.Sprintf("@%s", user2.Username),
Props: map[string]any{
"from_webhook": "true",
model.PostPropsFromWebhook: "true",
},
}, channel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, err)

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

@@ -165,15 +165,15 @@ func TestHandleCommandResponsePost(t *testing.T) {
assert.Equal(t, resp.Type, post.Type)
assert.Equal(t, resp.Props, post.GetProps())
assert.Equal(t, resp.Text, post.Message)
assert.Nil(t, post.GetProp("override_icon_url"))
assert.Nil(t, post.GetProp("override_username"))
assert.Nil(t, post.GetProp("from_webhook"))
assert.Nil(t, post.GetProp(model.PostPropsOverrideIconURL))
assert.Nil(t, post.GetProp(model.PostPropsOverrideUsername))
assert.Nil(t, post.GetProp(model.PostPropsFromWebhook))
// Command is not built in, so it is a bot command.
builtIn = false
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Equal(t, "true", post.GetProp("from_webhook"))
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
builtIn = true
@@ -195,23 +195,23 @@ func TestHandleCommandResponsePost(t *testing.T) {
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Nil(t, post.GetProp("override_username"))
assert.Nil(t, post.GetProp(model.PostPropsOverrideUsername))
*th.App.Config().ServiceSettings.EnablePostUsernameOverride = true
// Override username config is turned on. Override username through command property.
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Equal(t, command.Username, post.GetProp("override_username"))
assert.Equal(t, "true", post.GetProp("from_webhook"))
assert.Equal(t, command.Username, post.GetProp(model.PostPropsOverrideUsername))
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
command.Username = ""
// Override username through response property.
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Equal(t, resp.Username, post.GetProp("override_username"))
assert.Equal(t, "true", post.GetProp("from_webhook"))
assert.Equal(t, resp.Username, post.GetProp(model.PostPropsOverrideUsername))
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
*th.App.Config().ServiceSettings.EnablePostUsernameOverride = false
@@ -222,23 +222,23 @@ func TestHandleCommandResponsePost(t *testing.T) {
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Nil(t, post.GetProp("override_icon_url"))
assert.Nil(t, post.GetProp(model.PostPropsOverrideIconURL))
*th.App.Config().ServiceSettings.EnablePostIconOverride = true
// Override icon url config is turned on. Override icon url through command property.
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Equal(t, command.IconURL, post.GetProp("override_icon_url"))
assert.Equal(t, "true", post.GetProp("from_webhook"))
assert.Equal(t, command.IconURL, post.GetProp(model.PostPropsOverrideIconURL))
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
command.IconURL = ""
// Override icon url through response property.
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Equal(t, resp.IconURL, post.GetProp("override_icon_url"))
assert.Equal(t, "true", post.GetProp("from_webhook"))
assert.Equal(t, resp.IconURL, post.GetProp(model.PostPropsOverrideIconURL))
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
// Test Slack text conversion.
resp.Text = "<!channel>"
@@ -246,7 +246,7 @@ func TestHandleCommandResponsePost(t *testing.T) {
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
assert.Nil(t, err)
assert.Equal(t, "@channel", post.Message)
assert.Equal(t, "true", post.GetProp("from_webhook"))
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
// Test Slack attachments text conversion.
resp.Attachments = []*model.SlackAttachment{
@@ -261,7 +261,7 @@ func TestHandleCommandResponsePost(t *testing.T) {
if assert.Len(t, post.Attachments(), 1) {
assert.Equal(t, "@here", post.Attachments()[0].Text)
}
assert.Equal(t, "true", post.GetProp("from_webhook"))
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
channel = th.createPrivateChannel(th.BasicTeam)
resp.ChannelId = channel.Id

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

@@ -163,7 +163,7 @@ func (a *App) TriggerWebhook(c request.CTX, payload *model.OutgoingWebhookPayloa
if len(webhookResp.Props) == 0 {
webhookResp.Props = make(model.StringInterface)
}
webhookResp.Props["webhook_display_name"] = hook.DisplayName
webhookResp.Props[model.PostPropsWebhookDisplayName] = hook.DisplayName
text := ""
if webhookResp.Text != nil {
@@ -172,7 +172,7 @@ func (a *App) TriggerWebhook(c request.CTX, payload *model.OutgoingWebhookPayloa
webhookResp.Attachments = a.ProcessSlackAttachments(webhookResp.Attachments)
// attachments is in here for slack compatibility
if len(webhookResp.Attachments) > 0 {
webhookResp.Props["attachments"] = webhookResp.Attachments
webhookResp.Props[model.PostPropsAttachments] = webhookResp.Attachments
}
if *a.Config().ServiceSettings.EnablePostUsernameOverride && hook.Username != "" && webhookResp.Username == "" {
webhookResp.Username = hook.Username
@@ -232,7 +232,7 @@ func splitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.
base.Message = ""
base.SetProps(make(map[string]any))
for k, v := range post.GetProps() {
if k != "attachments" {
if k != model.PostPropsAttachments {
base.AddProp(k, v)
}
}
@@ -259,7 +259,7 @@ func splitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.
split.Message = remainingText
splits = append(splits, split)
attachments, _ := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, _ := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
for _, attachment := range attachments {
newAttachment := *attachment
for {
@@ -268,8 +268,8 @@ func splitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.
for k, v := range lastSplit.GetProps() {
newProps[k] = v
}
origAttachments, _ := newProps["attachments"].([]*model.SlackAttachment)
newProps["attachments"] = append(origAttachments, &newAttachment)
origAttachments, _ := newProps[model.PostPropsAttachments].([]*model.SlackAttachment)
newProps[model.PostPropsAttachments] = append(origAttachments, &newAttachment)
newPropsString := model.StringInterfaceToJSON(newProps)
runeCount := utf8.RuneCountInString(newPropsString)
@@ -310,7 +310,7 @@ func (a *App) CreateWebhookPost(c request.CTX, userID string, channel *model.Cha
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
post := &model.Post{UserId: userID, ChannelId: channel.Id, Message: text, Type: postType, RootId: postRootId}
post.AddProp("from_webhook", "true")
post.AddProp(model.PostPropsFromWebhook, "true")
if priority != nil {
if priority.Priority == nil {
@@ -333,28 +333,33 @@ func (a *App) CreateWebhookPost(c request.CTX, userID string, channel *model.Cha
if *a.Config().ServiceSettings.EnablePostUsernameOverride {
if overrideUsername != "" {
post.AddProp("override_username", overrideUsername)
post.AddProp(model.PostPropsOverrideUsername, overrideUsername)
} else {
post.AddProp("override_username", model.DefaultWebhookUsername)
post.AddProp(model.PostPropsOverrideUsername, model.DefaultWebhookUsername)
}
}
if *a.Config().ServiceSettings.EnablePostIconOverride {
if overrideIconURL != "" {
post.AddProp("override_icon_url", overrideIconURL)
post.AddProp(model.PostPropsOverrideIconURL, overrideIconURL)
}
if overrideIconEmoji != "" {
post.AddProp("override_icon_emoji", overrideIconEmoji)
post.AddProp(model.PostPropsOverrideIconURL, overrideIconEmoji)
}
}
if len(props) > 0 {
for key, val := range props {
if key == "attachments" {
switch key {
case model.PostPropsAttachments:
if attachments, success := val.([]*model.SlackAttachment); success {
model.ParseSlackAttachment(post, attachments)
}
} else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" {
case model.PostPropsOverrideIconURL,
model.PostPropsOverrideUsername,
model.PostPropsFromWebhook:
// Do nothing
default:
post.AddProp(key, val)
}
}
@@ -765,13 +770,13 @@ func (a *App) HandleIncomingWebhook(c request.CTX, hookID string, req *model.Inc
req.Props = make(model.StringInterface)
}
req.Props["webhook_display_name"] = hook.DisplayName
req.Props[model.PostPropsWebhookDisplayName] = hook.DisplayName
text = a.ProcessSlackText(text)
req.Attachments = a.ProcessSlackAttachments(req.Attachments)
// attachments is in here for slack compatibility
if len(req.Attachments) > 0 {
req.Props["attachments"] = req.Attachments
req.Props[model.PostPropsAttachments] = req.Attachments
webhookType = model.PostTypeSlackAttachment
}

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

@@ -298,44 +298,44 @@ func TestCreateWebhookPost(t *testing.T) {
post, err := th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "",
model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "text",
},
},
"webhook_display_name": hook.DisplayName,
model.PostPropsWebhookDisplayName: hook.DisplayName,
},
model.PostTypeSlackAttachment,
"", nil)
require.Nil(t, err)
assert.Contains(t, post.GetProps(), "from_webhook", "missing from_webhook prop")
assert.Contains(t, post.GetProps(), "attachments", "missing attachments prop")
assert.Contains(t, post.GetProps(), "webhook_display_name", "missing webhook_display_name prop")
assert.Contains(t, post.GetProps(), model.PostPropsFromWebhook, "missing from_webhook prop")
assert.Contains(t, post.GetProps(), model.PostPropsAttachments, "missing attachments prop")
assert.Contains(t, post.GetProps(), model.PostPropsWebhookDisplayName, "missing webhook_display_name prop")
_, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "", nil, model.PostTypeSystemGeneric, "", nil)
require.NotNil(t, err, "Should have failed - bad post type")
expectedText := "`<>|<>|`"
post, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "text",
},
},
"webhook_display_name": hook.DisplayName,
model.PostPropsWebhookDisplayName: hook.DisplayName,
}, model.PostTypeSlackAttachment, "", nil)
require.Nil(t, err)
assert.Equal(t, expectedText, post.Message)
expectedText = "< | \n|\n>"
post, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "text",
},
},
"webhook_display_name": hook.DisplayName,
model.PostPropsWebhookDisplayName: hook.DisplayName,
}, model.PostTypeSlackAttachment, "", nil)
require.Nil(t, err)
assert.Equal(t, expectedText, post.Message)
@@ -358,12 +358,12 @@ Date: Thu Mar 1 19:46:48 2018 +0300
test | 3 +++
1 file changed, 3 insertions(+)`
post, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: "text",
},
},
"webhook_display_name": hook.DisplayName,
model.PostPropsWebhookDisplayName: hook.DisplayName,
}, model.PostTypeSlackAttachment, "", nil)
require.Nil(t, err)
assert.Equal(t, expectedText, post.Message)
@@ -415,7 +415,7 @@ func TestCreateWebhookPostWithPriority(t *testing.T) {
for _, conditions := range testConditions {
post, err := th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, "foo @"+th.BasicUser.Username, "user", "http://iconurl", "",
model.StringInterface{"webhook_display_name": hook.DisplayName},
model.StringInterface{model.PostPropsWebhookDisplayName: hook.DisplayName},
model.PostTypeSlackAttachment,
"",
&conditions,
@@ -424,7 +424,7 @@ func TestCreateWebhookPostWithPriority(t *testing.T) {
require.Nil(t, err)
assert.Equal(t, post.Message, "foo @"+th.BasicUser.Username)
assert.Contains(t, post.GetProps(), "from_webhook", "missing from_webhook prop")
assert.Contains(t, post.GetProps(), model.PostPropsFromWebhook, "missing from_webhook prop")
assert.Equal(t, *conditions.Priority, *post.GetPriority().Priority)
assert.Equal(t, *conditions.RequestedAck, *post.GetPriority().RequestedAck)
assert.Equal(t, *conditions.PersistentNotifications, *post.GetPriority().PersistentNotifications)
@@ -488,7 +488,7 @@ func TestSplitWebhookPost(t *testing.T) {
Post: &model.Post{
Message: strings.Repeat("本", maxPostSize*3/2),
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: strings.Repeat("本", 1000),
},
@@ -508,7 +508,7 @@ func TestSplitWebhookPost(t *testing.T) {
{
Message: strings.Repeat("本", maxPostSize/2),
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: strings.Repeat("本", 1000),
},
@@ -520,7 +520,7 @@ func TestSplitWebhookPost(t *testing.T) {
},
{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Text: strings.Repeat("本", model.PostPropsMaxUserRunes-1000),
},
@@ -549,7 +549,7 @@ func TestSplitWebhookPost(t *testing.T) {
for i, split := range splits {
if i < len(tc.Expected) {
assert.Equal(t, tc.Expected[i].Message, split.Message)
assert.Equal(t, tc.Expected[i].GetProp("attachments"), split.GetProp("attachments"))
assert.Equal(t, tc.Expected[i].GetProp(model.PostPropsAttachments), split.GetProp(model.PostPropsAttachments))
}
}
})
@@ -566,7 +566,7 @@ func makePost(message int, attachments []int) *model.Post {
}
sa = append(sa, attach)
}
props = map[string]any{"attachments": sa}
props = map[string]any{model.PostPropsAttachments: sa}
}
post := &model.Post{
Message: strings.Repeat("那", message),
@@ -628,7 +628,7 @@ func TestSplitWebhookPostAttachments(t *testing.T) {
for i, split := range splits {
if i < len(tc.expected) {
assert.Equal(t, tc.expected[i].Message, split.Message, i)
assert.Equal(t, tc.expected[i].GetProp("attachments"), split.GetProp("attachments"), i)
assert.Equal(t, tc.expected[i].GetProp(model.PostPropsAttachments), split.GetProp(model.PostPropsAttachments), i)
}
}
})
@@ -786,17 +786,17 @@ func TestTriggerOutGoingWebhookWithUsernameAndIconURL(t *testing.T) {
select {
case webhookPost := <-createdPost:
assert.Equal(t, webhookPost.Message, "sample response text from test server")
assert.Equal(t, webhookPost.GetProp("from_webhook"), "true")
assert.Equal(t, webhookPost.GetProp(model.PostPropsFromWebhook), "true")
if testCase.ExpectedIconURL != "" {
assert.Equal(t, webhookPost.GetProp("override_icon_url"), testCase.ExpectedIconURL)
assert.Equal(t, webhookPost.GetProp(model.PostPropsOverrideIconURL), testCase.ExpectedIconURL)
} else {
assert.Nil(t, webhookPost.GetProp("override_icon_url"))
assert.Nil(t, webhookPost.GetProp(model.PostPropsOverrideIconURL))
}
if testCase.ExpectedUsername != "" {
assert.Equal(t, webhookPost.GetProp("override_username"), testCase.ExpectedUsername)
assert.Equal(t, webhookPost.GetProp(model.PostPropsOverrideUsername), testCase.ExpectedUsername)
} else {
assert.Nil(t, webhookPost.GetProp("override_username"))
assert.Nil(t, webhookPost.GetProp(model.PostPropsOverrideUsername))
}
case <-time.After(5 * time.Second):
require.Fail(t, "Timeout, webhook response not created as post")

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

@@ -8067,11 +8067,11 @@ func (s *RetryLayerPostStore) Overwrite(rctx request.CTX, post *model.Post) (*mo
}
func (s *RetryLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) {
func (s *RetryLayerPostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
tries := 0
for {
result, resultVar1, err := s.PostStore.OverwriteMultiple(posts)
result, resultVar1, err := s.PostStore.OverwriteMultiple(rctx, posts)
if err == nil {
return result, resultVar1, nil
}
@@ -8235,11 +8235,11 @@ func (s *RetryLayerPostStore) Save(rctx request.CTX, post *model.Post) (*model.P
}
func (s *RetryLayerPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
func (s *RetryLayerPostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
tries := 0
for {
result, resultVar1, err := s.PostStore.SaveMultiple(posts)
result, resultVar1, err := s.PostStore.SaveMultiple(rctx, posts)
if err == nil {
return result, resultVar1, nil
}

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

@@ -147,7 +147,7 @@ func newSqlPostStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) s
}
}
func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
func (s *SqlPostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
channelNewPosts := make(map[string]int)
channelNewRootPosts := make(map[string]int)
maxDateNewPosts := make(map[string]int64)
@@ -160,9 +160,11 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
}
post.PreSave()
maxPostSize := s.GetMaxPostSize()
if err := post.IsValid(maxPostSize); err != nil {
return nil, idx, err
}
post.ValidateProps(rctx.Logger())
if currentChannelCount, ok := channelNewPosts[post.ChannelId]; !ok {
if post.IsJoinLeaveMessage() {
@@ -293,7 +295,7 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
}
func (s *SqlPostStore) Save(rctx request.CTX, post *model.Post) (*model.Post, error) {
posts, _, err := s.SaveMultiple([]*model.Post{post})
posts, _, err := s.SaveMultiple(rctx, []*model.Post{post})
if err != nil {
return nil, err
}
@@ -356,6 +358,7 @@ func (s *SqlPostStore) Update(rctx request.CTX, newPost *model.Post, oldPost *mo
if err := newPost.IsValid(maxPostSize); err != nil {
return nil, err
}
newPost.ValidateProps(rctx.Logger())
if _, err := s.GetMaster().NamedExec(`UPDATE Posts
SET CreateAt=:CreateAt,
@@ -409,14 +412,16 @@ func (s *SqlPostStore) Update(rctx request.CTX, newPost *model.Post, oldPost *mo
return newPost, nil
}
func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) (_ []*model.Post, _ int, err error) {
func (s *SqlPostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) (_ []*model.Post, _ int, err error) {
updateAt := model.GetMillis()
maxPostSize := s.GetMaxPostSize()
for idx, post := range posts {
post.UpdateAt = updateAt
if appErr := post.IsValid(maxPostSize); appErr != nil {
return nil, idx, appErr
}
post.ValidateProps(rctx.Logger())
}
tx, err := s.GetMaster().Beginx()
@@ -464,7 +469,7 @@ func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) (_ []*model.Post,
}
func (s *SqlPostStore) Overwrite(rctx request.CTX, post *model.Post) (*model.Post, error) {
posts, _, err := s.OverwriteMultiple([]*model.Post{post})
posts, _, err := s.OverwriteMultiple(rctx, []*model.Post{post})
if err != nil {
return nil, err
}

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

@@ -364,7 +364,7 @@ type ThreadStore interface {
}
type PostStore interface {
SaveMultiple(posts []*model.Post) ([]*model.Post, int, error)
SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error)
Save(rctx request.CTX, post *model.Post) (*model.Post, error)
Update(rctx request.CTX, newPost *model.Post, oldPost *model.Post) (*model.Post, error)
Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string, sanitizeOptions map[string]bool) (*model.PostList, error)
@@ -394,7 +394,7 @@ type PostStore interface {
InvalidateLastPostTimeCache(channelID string)
GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error)
Overwrite(rctx request.CTX, post *model.Post) (*model.Post, error)
OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error)
OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error)
GetPostsByIds(postIds []string) ([]*model.Post, error)
GetEditHistoryForPost(postID string) ([]*model.Post, error)
GetPostsBatchForIndexing(startTime int64, startPostID string, limit int) ([]*model.PostForIndexing, error)

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

@@ -1037,9 +1037,9 @@ func (_m *PostStore) Overwrite(rctx request.CTX, post *model.Post) (*model.Post,
return r0, r1
}
// OverwriteMultiple provides a mock function with given fields: posts
func (_m *PostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) {
ret := _m.Called(posts)
// OverwriteMultiple provides a mock function with given fields: rctx, posts
func (_m *PostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
ret := _m.Called(rctx, posts)
if len(ret) == 0 {
panic("no return value specified for OverwriteMultiple")
@@ -1048,25 +1048,25 @@ func (_m *PostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int,
var r0 []*model.Post
var r1 int
var r2 error
if rf, ok := ret.Get(0).(func([]*model.Post) ([]*model.Post, int, error)); ok {
return rf(posts)
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) ([]*model.Post, int, error)); ok {
return rf(rctx, posts)
}
if rf, ok := ret.Get(0).(func([]*model.Post) []*model.Post); ok {
r0 = rf(posts)
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) []*model.Post); ok {
r0 = rf(rctx, posts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Post)
}
}
if rf, ok := ret.Get(1).(func([]*model.Post) int); ok {
r1 = rf(posts)
if rf, ok := ret.Get(1).(func(request.CTX, []*model.Post) int); ok {
r1 = rf(rctx, posts)
} else {
r1 = ret.Get(1).(int)
}
if rf, ok := ret.Get(2).(func([]*model.Post) error); ok {
r2 = rf(posts)
if rf, ok := ret.Get(2).(func(request.CTX, []*model.Post) error); ok {
r2 = rf(rctx, posts)
} else {
r2 = ret.Error(2)
}
@@ -1239,9 +1239,9 @@ func (_m *PostStore) Save(rctx request.CTX, post *model.Post) (*model.Post, erro
return r0, r1
}
// SaveMultiple provides a mock function with given fields: posts
func (_m *PostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
ret := _m.Called(posts)
// SaveMultiple provides a mock function with given fields: rctx, posts
func (_m *PostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
ret := _m.Called(rctx, posts)
if len(ret) == 0 {
panic("no return value specified for SaveMultiple")
@@ -1250,25 +1250,25 @@ func (_m *PostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, erro
var r0 []*model.Post
var r1 int
var r2 error
if rf, ok := ret.Get(0).(func([]*model.Post) ([]*model.Post, int, error)); ok {
return rf(posts)
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) ([]*model.Post, int, error)); ok {
return rf(rctx, posts)
}
if rf, ok := ret.Get(0).(func([]*model.Post) []*model.Post); ok {
r0 = rf(posts)
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) []*model.Post); ok {
r0 = rf(rctx, posts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Post)
}
}
if rf, ok := ret.Get(1).(func([]*model.Post) int); ok {
r1 = rf(posts)
if rf, ok := ret.Get(1).(func(request.CTX, []*model.Post) int); ok {
r1 = rf(rctx, posts)
} else {
r1 = ret.Get(1).(int)
}
if rf, ok := ret.Get(2).(func([]*model.Post) error); ok {
r2 = rf(posts)
if rf, ok := ret.Get(2).(func(request.CTX, []*model.Post) error); ok {
r2 = rf(rctx, posts)
} else {
r2 = ret.Error(2)
}

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

@@ -140,7 +140,7 @@ func testPostAcknowledgementsStoreGetForPosts(t *testing.T, rctx request.CTX, ss
PersistentNotifications: model.NewPointer(false),
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2})
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2})
require.NoError(t, err)
require.Equal(t, -1, errIdx)

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

@@ -81,7 +81,7 @@ func testPostPersistentNotificationStoreGet(t *testing.T, rctx request.CTX, ss s
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3, &p4, &p5})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
@@ -156,7 +156,7 @@ func testPostPersistentNotificationStoreUpdateLastSentAt(t *testing.T, rctx requ
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1})
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
@@ -235,7 +235,7 @@ func testPostPersistentNotificationStoreDelete(t *testing.T, rctx request.CTX, s
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
@@ -321,7 +321,7 @@ func testPostPersistentNotificationStoreDelete(t *testing.T, rctx request.CTX, s
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3, &p4, &p5})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
@@ -425,7 +425,7 @@ func testPostPersistentNotificationStoreDelete(t *testing.T, rctx request.CTX, s
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3, &p4, &p5})
require.NoError(t, err)
require.Equal(t, -1, errIdx)

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

@@ -51,7 +51,7 @@ func testPostPriorityStoreGetForPost(t *testing.T, rctx request.CTX, ss store.St
p3.UserId = model.NewId()
p3.Message = NewTestID()
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3})
require.NoError(t, err)
require.Equal(t, -1, errIdx)

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

@@ -290,7 +290,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
p4.Message = NewTestID()
t.Run("Save correctly a new set of posts", func(t *testing.T) {
newPosts, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
for _, post := range newPosts {
@@ -358,7 +358,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
o4.UserId = model.NewId()
o4.Message = NewTestID()
newPosts, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&o1, &o2, &o3, &o4})
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&o1, &o2, &o3, &o4})
require.NoError(t, err, "couldn't save item")
require.Equal(t, -1, errIdx)
assert.Len(t, newPosts, 4)
@@ -369,7 +369,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
})
t.Run("Try to save mixed, already saved and not saved posts", func(t *testing.T) {
newPosts, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p4, &p3})
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p4, &p3})
require.Error(t, err)
require.Equal(t, 1, errIdx)
require.Nil(t, newPosts)
@@ -405,7 +405,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
replyPost.Message = NewTestID()
replyPost.RootId = rootPost.Id
_, _, err = ss.Post().SaveMultiple([]*model.Post{&rootPost, &replyPost})
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&rootPost, &replyPost})
require.NoError(t, err)
rrootPost, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
@@ -427,7 +427,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
// Ensure update does not occur in the same timestamp as creation
time.Sleep(time.Millisecond)
_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost2, &replyPost3})
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&replyPost2, &replyPost3})
require.NoError(t, err)
rrootPost2, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
@@ -460,7 +460,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
post3.UserId = model.NewId()
post3.Message = NewTestID()
_, _, err = ss.Post().SaveMultiple([]*model.Post{&post1, &post2, &post3})
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&post1, &post2, &post3})
require.NoError(t, err)
rchannel, err := ss.Channel().Get(channel.Id, false)
@@ -3772,7 +3772,7 @@ func testPostStoreOverwriteMultiple(t *testing.T, rctx request.CTX, ss store.Sto
o3a := ro3.Clone()
o3a.Message = ro3.Message + "WWWWWWW"
_, errIdx, err := ss.Post().OverwriteMultiple([]*model.Post{o1a, o2a, o3a})
_, errIdx, err := ss.Post().OverwriteMultiple(rctx, []*model.Post{o1a, o2a, o3a})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
@@ -3802,7 +3802,7 @@ func testPostStoreOverwriteMultiple(t *testing.T, rctx request.CTX, ss store.Sto
o5a.Filenames = []string{}
o5a.FileIds = []string{}
_, errIdx, err := ss.Post().OverwriteMultiple([]*model.Post{o4a, o5a})
_, errIdx, err := ss.Post().OverwriteMultiple(rctx, []*model.Post{o4a, o5a})
require.NoError(t, err)
require.Equal(t, -1, errIdx)

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

@@ -93,7 +93,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
o4.UserId = model.NewId()
o4.Message = NewTestID()
newPosts, errIdx, err3 := ss.Post().SaveMultiple([]*model.Post{&o2, &o3, &o4})
newPosts, errIdx, err3 := ss.Post().SaveMultiple(rctx, []*model.Post{&o2, &o3, &o4})
opts := model.GetPostsOptions{
SkipFetchThreads: true,
@@ -135,7 +135,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
o5.RootId = newPosts[0].Id
o5.Message = NewTestID()
_, _, err = ss.Post().SaveMultiple([]*model.Post{&o5})
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&o5})
require.NoError(t, err, "couldn't save item")
thread, err = ss.Thread().Get(newPosts[0].Id)
@@ -185,7 +185,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
replyPost.Message = NewTestID()
replyPost.RootId = rootPost.RootId
newPosts, _, err := ss.Post().SaveMultiple([]*model.Post{&rootPost, &replyPost})
newPosts, _, err := ss.Post().SaveMultiple(rctx, []*model.Post{&rootPost, &replyPost})
require.NoError(t, err)
thread1, err := ss.Thread().Get(newPosts[0].RootId)
@@ -207,7 +207,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
replyPost3.Message = NewTestID()
replyPost3.RootId = rootPost.Id
_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost2, &replyPost3})
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&replyPost2, &replyPost3})
require.NoError(t, err)
rrootPost2, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
@@ -295,7 +295,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
rootPost.UserId = model.NewId()
rootPost.Message = NewTestID()
newPosts1, _, err := ss.Post().SaveMultiple([]*model.Post{&rootPost})
newPosts1, _, err := ss.Post().SaveMultiple(rctx, []*model.Post{&rootPost})
require.NoError(t, err)
replyPost := model.Post{}
@@ -304,7 +304,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
replyPost.Message = NewTestID()
replyPost.RootId = newPosts1[0].Id
_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost})
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&replyPost})
require.NoError(t, err)
thread1, err := ss.Thread().Get(newPosts1[0].Id)

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

@@ -6433,10 +6433,10 @@ func (s *TimerLayerPostStore) Overwrite(rctx request.CTX, post *model.Post) (*mo
return result, err
}
func (s *TimerLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) {
func (s *TimerLayerPostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
start := time.Now()
result, resultVar1, err := s.PostStore.OverwriteMultiple(posts)
result, resultVar1, err := s.PostStore.OverwriteMultiple(rctx, posts)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
@@ -6561,10 +6561,10 @@ func (s *TimerLayerPostStore) Save(rctx request.CTX, post *model.Post) (*model.P
return result, err
}
func (s *TimerLayerPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
func (s *TimerLayerPostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
start := time.Now()
result, resultVar1, err := s.PostStore.SaveMultiple(posts)
result, resultVar1, err := s.PostStore.SaveMultiple(rctx, posts)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {

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

@@ -110,7 +110,7 @@ func ESPostFromPostForIndexing(post *model.PostForIndexing) *ESPost {
var searchAttachments []string
if attachments := post.GetProp("attachments"); attachments != nil {
if attachments := post.GetProp(model.PostPropsAttachments); attachments != nil {
attachmentsInterfaceArray, ok := attachments.([]any)
if ok {
for _, attachment := range attachmentsInterfaceArray {

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

@@ -351,12 +351,12 @@ func addPostToChannelExport(rctx request.CTX, channelExport *ChannelExport, post
if err != nil {
rctx.Logger().Warn("Failed to unmarshal post Props into JSON. Ignoring username override.", mlog.Err(err))
} else {
if overrideUsername, ok := postPropsLocal["override_username"]; ok {
if overrideUsername, ok := postPropsLocal[model.PostPropsOverrideUsername]; ok {
postUserName = overrideUsername.(string)
}
if postUserName == originalUsername {
if overrideUsername, ok := postPropsLocal["webhook_display_name"]; ok {
if overrideUsername, ok := postPropsLocal[model.PostPropsWebhookDisplayName]; ok {
postUserName = overrideUsername.(string)
}
}

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

@@ -392,9 +392,9 @@ func (si *SlackImporter) slackAddPosts(rctx request.CTX, teamId string, channel
}
props := make(model.StringInterface)
props["override_username"] = sPost.BotUsername
props[model.PostPropsOverrideUsername] = sPost.BotUsername
if len(sPost.Attachments) > 0 {
props["attachments"] = sPost.Attachments
props[model.PostPropsAttachments] = sPost.Attachments
}
post := &model.Post{
@@ -817,19 +817,19 @@ func (si *SlackImporter) oldImportIncomingWebhookPost(rctx request.CTX, post *mo
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
post.Message = linkWithTextRegex.ReplaceAllString(post.Message, "[${2}](${1})")
post.AddProp("from_webhook", "true")
post.AddProp(model.PostPropsFromWebhook, "true")
if _, ok := props["override_username"]; !ok {
post.AddProp("override_username", model.DefaultWebhookUsername)
if _, ok := props[model.PostPropsOverrideUsername]; !ok {
post.AddProp(model.PostPropsOverrideUsername, model.DefaultWebhookUsername)
}
if len(props) > 0 {
for key, val := range props {
if key == "attachments" {
if key == model.PostPropsAttachments {
if attachments, success := val.([]*model.SlackAttachment); success {
model.ParseSlackAttachment(post, attachments)
}
} else if key != "from_webhook" {
} else if key != model.PostPropsFromWebhook {
post.AddProp(key, val)
}
}

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

@@ -36,10 +36,11 @@ func TestClient4TrimTrailingSlash(t *testing.T) {
func TestClient4CreatePost(t *testing.T) {
post := &model.Post{
Props: map[string]any{
"attachments": []*model.SlackAttachment{
model.PostPropsAttachments: []*model.SlackAttachment{
{
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Integration: &model.PostActionIntegration{
Context: map[string]any{
"foo": "bar",
@@ -63,6 +64,7 @@ func TestClient4CreatePost(t *testing.T) {
{
Actions: []*model.PostAction{
{
Type: model.PostActionTypeButton,
Integration: &model.PostActionIntegration{
Context: map[string]any{
"foo": "bar",

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

@@ -17,6 +17,7 @@ import (
"math/big"
"net/http"
"reflect"
"slices"
"strconv"
"strings"
"time"
@@ -38,13 +39,18 @@ const (
DialogElementBoolMaxLength = 150
)
var PostActionRetainPropKeys = []string{"from_webhook", "override_username", "override_icon_url"}
var PostActionRetainPropKeys = []string{PostPropsFromWebhook, PostPropsOverrideUsername, PostPropsOverrideIconURL}
type DoPostActionRequest struct {
SelectedOption string `json:"selected_option,omitempty"`
Cookie string `json:"cookie,omitempty"`
}
const (
PostActionDataSourceUsers = "users"
PostActionDataSourceChannels = "channels"
)
type PostAction struct {
// A unique Action ID. If not set, generated automatically.
Id string `json:"id,omitempty"`
@@ -85,6 +91,73 @@ type PostAction struct {
Cookie string `json:"cookie,omitempty" db:"-"`
}
// IsValid validates the action and returns an error if it is invalid.
func (p *PostAction) IsValid() error {
var multiErr *multierror.Error
if p.Name == "" {
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have a name"))
}
if p.Style != "" {
validStyles := []string{"default", "primary", "success", "good", "warning", "danger"}
// If not a predefined style, check if it's a hex color
if !slices.Contains(validStyles, p.Style) && !hexColorRegex.MatchString(p.Style) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid style '%s' - must be one of [default, primary, success, good, warning, danger] or a hex color", p.Style))
}
}
switch p.Type {
case PostActionTypeButton:
if len(p.Options) > 0 {
multiErr = multierror.Append(multiErr, fmt.Errorf("button action must not have options"))
}
if p.DataSource != "" {
multiErr = multierror.Append(multiErr, fmt.Errorf("button action must not have a data source"))
}
case PostActionTypeSelect:
if p.DataSource != "" {
validSources := []string{PostActionDataSourceUsers, PostActionDataSourceChannels}
if !slices.Contains(validSources, p.DataSource) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid data_source '%s' for select action", p.DataSource))
}
if len(p.Options) > 0 {
multiErr = multierror.Append(multiErr, fmt.Errorf("select action cannot have both DataSource and Options set"))
}
} else {
if len(p.Options) == 0 {
multiErr = multierror.Append(multiErr, fmt.Errorf("select action must have either DataSource or Options set"))
} else {
for i, opt := range p.Options {
if opt == nil {
multiErr = multierror.Append(multiErr, fmt.Errorf("select action contains nil option"))
continue
}
if err := opt.IsValid(); err != nil {
multiErr = multierror.Append(multiErr, multierror.Prefix(err, fmt.Sprintf("option at index %d is invalid:", i)))
}
}
}
}
default:
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid action type: must be '%s' or '%s'", PostActionTypeButton, PostActionTypeSelect))
}
if p.Integration == nil {
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have integration settings"))
} else {
if p.Integration.URL == "" {
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have an integration URL"))
}
if !(strings.HasPrefix(p.Integration.URL, "/plugins/") || strings.HasPrefix(p.Integration.URL, "plugins/") || IsValidHTTPURL(p.Integration.URL)) {
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have an valid integration URL"))
}
}
return multiErr.ErrorOrNil()
}
func (p *PostAction) Equals(input *PostAction) bool {
if p.Id != input.Id {
return false
@@ -189,7 +262,22 @@ type PostActionOptions struct {
Value string `json:"value"`
}
func (o *PostActionOptions) IsValid() error {
var multiErr *multierror.Error
if o.Text == "" {
multiErr = multierror.Append(multiErr, fmt.Errorf("text is required"))
}
if o.Value == "" {
multiErr = multierror.Append(multiErr, fmt.Errorf("value is required"))
}
return multiErr.ErrorOrNil()
}
type PostActionIntegration struct {
// URL is the endpoint that the action will be sent to.
// It can be a relative path to a plugin.
URL string `json:"url,omitempty"`
Context map[string]any `json:"context,omitempty"`
}
@@ -474,24 +562,25 @@ func isDefaultInOptions(defaultValue string, options []*PostActionOptions) bool
return false
}
func checkMaxLength(fieldName string, field string, length int) error {
var valid bool
func checkMaxLength(fieldName string, field string, maxLength int) error {
// DisplayName and Name are required fields
if fieldName == "DisplayName" || fieldName == "Name" {
valid = len(field) > 0 && len(field) > length
} else {
valid = len(field) > length
if len(field) == 0 {
return errors.Errorf("%v cannot be empty", fieldName)
}
}
if valid {
return errors.Errorf("%v cannot be longer than %d characters", fieldName, length)
if len(field) > maxLength {
return errors.Errorf("%v cannot be longer than %d characters, got %d", fieldName, maxLength, len(field))
}
return nil
}
func (o *Post) StripActionIntegrations() {
attachments := o.Attachments()
if o.GetProp("attachments") != nil {
o.AddProp("attachments", attachments)
if o.GetProp(PostPropsAttachments) != nil {
o.AddProp(PostPropsAttachments, attachments)
}
for _, attachment := range attachments {
for _, action := range attachment.Actions {
@@ -512,10 +601,10 @@ func (o *Post) GetAction(id string) *PostAction {
}
func (o *Post) GenerateActionIds() {
if o.GetProp("attachments") != nil {
o.AddProp("attachments", o.Attachments())
if o.GetProp(PostPropsAttachments) != nil {
o.AddProp(PostPropsAttachments, o.Attachments())
}
if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
if attachments, ok := o.GetProp(PostPropsAttachments).([]*SlackAttachment); ok {
for _, attachment := range attachments {
for _, action := range attachment.Actions {
if action != nil && action.Id == "" {

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

@@ -16,6 +16,246 @@ import (
"github.com/stretchr/testify/require"
)
func TestPostAction_IsValid(t *testing.T) {
tests := map[string]struct {
action *PostAction
wantErr string
}{
"valid button action with http URL": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "",
},
"valid button action with http URL without Id": {
action: &PostAction{
Name: "Test Button",
Type: PostActionTypeButton,
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "",
},
"valid button action with plugin path": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Integration: &PostActionIntegration{
URL: "/plugins/myplugin/action",
},
},
wantErr: "",
},
"valid button action with relative plugin path": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Integration: &PostActionIntegration{
URL: "plugins/myplugin/action",
},
},
wantErr: "",
},
"invalid integration URL": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Integration: &PostActionIntegration{
URL: "invalid-url",
},
},
wantErr: "action must have an valid integration URL",
},
"valid select action with datasource": {
action: &PostAction{
Id: "validid",
Name: "Test Select",
Type: PostActionTypeSelect,
DataSource: PostActionDataSourceUsers,
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "",
},
"valid select action with options": {
action: &PostAction{
Id: "validid",
Name: "Test Select",
Type: PostActionTypeSelect,
Options: []*PostActionOptions{
{Text: "Opt1", Value: "opt1"},
},
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "",
},
"select action with nil option": {
action: &PostAction{
Id: "validid",
Name: "Test Select",
Type: PostActionTypeSelect,
Options: []*PostActionOptions{
nil,
{Text: "Opt1", Value: "opt1"},
},
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "select action contains nil option",
},
"missing name": {
action: &PostAction{
Id: "validid",
Type: PostActionTypeButton,
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "action must have a name",
},
"invalid style": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Style: "invalid",
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "invalid style 'invalid' - must be one of [default, primary, success, good, warning, danger] or a hex color",
},
"valid style": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Style: "primary",
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "",
},
"button with options": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Options: []*PostActionOptions{
{Text: "Opt1", Value: "opt1"},
},
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "button action must not have options",
},
"button with datasource": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
DataSource: PostActionDataSourceUsers,
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "button action must not have a data source",
},
"select without datasource or options": {
action: &PostAction{
Id: "validid",
Name: "Test Select",
Type: PostActionTypeSelect,
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "select action must have either DataSource or Options set",
},
"select with both datasource and options": {
action: &PostAction{
Id: "validid",
Name: "Test Select",
Type: PostActionTypeSelect,
DataSource: PostActionDataSourceUsers,
Options: []*PostActionOptions{
{Text: "Opt1", Value: "opt1"},
},
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "select action cannot have both DataSource and Options set",
},
"invalid datasource": {
action: &PostAction{
Id: "validid",
Name: "Test Select",
Type: PostActionTypeSelect,
DataSource: "invalid",
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "invalid data_source 'invalid' for select action",
},
"missing integration": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
},
wantErr: "action must have integration settings",
},
"missing integration URL": {
action: &PostAction{
Id: "validid",
Name: "Test Button",
Type: PostActionTypeButton,
Integration: &PostActionIntegration{},
},
wantErr: "action must have an integration URL",
},
"invalid type": {
action: &PostAction{
Id: "validid",
Name: "Test Action",
Type: "invalid",
Integration: &PostActionIntegration{
URL: "http://localhost:8065",
},
},
wantErr: "invalid action type: must be 'button' or 'select'",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
err := tc.action.IsValid()
if tc.wantErr == "" {
assert.NoError(t, err, name)
} else {
assert.ErrorContains(t, err, tc.wantErr, name)
}
})
}
}
func TestTriggerIdDecodeAndVerification(t *testing.T) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
@@ -182,6 +422,44 @@ func TestPostActionIntegrationEquals(t *testing.T) {
})
}
func TestPostActionOptions_IsValid(t *testing.T) {
tests := map[string]struct {
options *PostActionOptions
wantErr string
}{
"valid options": {
options: &PostActionOptions{
Text: "Option 1",
Value: "opt1",
},
wantErr: "",
},
"missing text": {
options: &PostActionOptions{
Value: "opt1",
},
wantErr: "text is required",
},
"missing value": {
options: &PostActionOptions{
Text: "Option 1",
},
wantErr: "value is required",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
err := tc.options.IsValid()
if tc.wantErr == "" {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, tc.wantErr)
}
})
}
}
func TestOpenDialogRequestIsValid(t *testing.T) {
getBaseOpenDialogRequest := func() OpenDialogRequest {
return OpenDialogRequest{

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

@@ -15,7 +15,9 @@ import (
"sync"
"unicode/utf8"
"github.com/hashicorp/go-multierror"
"github.com/mattermost/mattermost/server/public/shared/markdown"
"github.com/mattermost/mattermost/server/public/shared/mlog"
)
const (
@@ -72,14 +74,16 @@ const (
PostPropsFromBot = "from_bot"
PostPropsFromOAuthApp = "from_oauth_app"
PostPropsWebhookDisplayName = "webhook_display_name"
PostPropsAttachments = "attachments"
PostPropsFromPlugin = "from_plugin"
PostPropsMentionHighlightDisabled = "mentionHighlightDisabled"
PostPropsGroupHighlightDisabled = "disable_group_highlight"
PostPropsPreviewedPost = "previewed_post"
PostPropsForceNotification = "force_notification"
PostPropsChannelMentions = "channel_mentions"
PostPropsUnsafeLinks = "unsafe_links"
PostPriorityUrgent = "urgent"
PostPropsRequestedAck = "requested_ack"
PostPropsPersistentNotifications = "persistent_notifications"
PostPriorityUrgent = "urgent"
)
type Post struct {
@@ -657,6 +661,137 @@ func (o *Post) GetProp(key string) any {
return o.Props[key]
}
// ValidateProps checks all known props for validity.
// Currently, it logs warnings for invalid props rather than returning an error.
// In a future version, this will be updated to return errors for invalid props.
func (o *Post) ValidateProps(logger mlog.LoggerIFace) {
if err := o.propsIsValid(); err != nil {
logger.Warn(
"Invalid post props. In a future version this will result in an error. Please update your integration to be compliant.",
mlog.String("post_id", o.Id),
mlog.Err(err),
)
}
}
func (o *Post) propsIsValid() error {
var multiErr *multierror.Error
props := o.GetProps()
// Check basic props validity
if props == nil {
return nil
}
if props[PostPropsAddedUserId] != nil {
if addedUserID, ok := props[PostPropsAddedUserId].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("added_user_id prop must be a string"))
} else if !IsValidId(addedUserID) {
multiErr = multierror.Append(multiErr, fmt.Errorf("added_user_id prop must be a valid user ID"))
}
}
if props[PostPropsDeleteBy] != nil {
if deleteByID, ok := props[PostPropsDeleteBy].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("delete_by prop must be a string"))
} else if !IsValidId(deleteByID) {
multiErr = multierror.Append(multiErr, fmt.Errorf("delete_by prop must be a valid user ID"))
}
}
// Validate integration props
if props[PostPropsOverrideIconURL] != nil {
if iconURL, ok := props[PostPropsOverrideIconURL].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("override_icon_url prop must be a string"))
} else if iconURL == "" || !IsValidHTTPURL(iconURL) {
multiErr = multierror.Append(multiErr, fmt.Errorf("override_icon_url prop must be a valid URL"))
}
}
if props[PostPropsOverrideIconEmoji] != nil {
if _, ok := props[PostPropsOverrideIconEmoji].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("override_icon_emoji prop must be a string"))
}
}
if props[PostPropsOverrideUsername] != nil {
if _, ok := props[PostPropsOverrideUsername].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("override_username prop must be a string"))
}
}
if props[PostPropsFromWebhook] != nil {
if fromWebhook, ok := props[PostPropsFromWebhook].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_webhook prop must be a string"))
} else if fromWebhook != "true" {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_webhook prop must be \"true\""))
}
}
if props[PostPropsFromBot] != nil {
if fromBot, ok := props[PostPropsFromBot].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_bot prop must be a string"))
} else if fromBot != "true" {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_bot prop must be \"true\""))
}
}
if props[PostPropsFromOAuthApp] != nil {
if fromOAuthApp, ok := props[PostPropsFromOAuthApp].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_oauth_app prop must be a string"))
} else if fromOAuthApp != "true" {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_oauth_app prop must be \"true\""))
}
}
if props[PostPropsFromPlugin] != nil {
if fromPlugin, ok := props[PostPropsFromPlugin].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_plugin prop must be a string"))
} else if fromPlugin != "true" {
multiErr = multierror.Append(multiErr, fmt.Errorf("from_plugin prop must be \"true\""))
}
}
if props[PostPropsUnsafeLinks] != nil {
if unsafeLinks, ok := props[PostPropsUnsafeLinks].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("unsafe_links prop must be a string"))
} else if unsafeLinks != "true" {
multiErr = multierror.Append(multiErr, fmt.Errorf("unsafe_links prop must be \"true\""))
}
}
if props[PostPropsWebhookDisplayName] != nil {
if _, ok := props[PostPropsWebhookDisplayName].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("webhook_display_name prop must be a string"))
}
}
if props[PostPropsMentionHighlightDisabled] != nil {
if _, ok := props[PostPropsMentionHighlightDisabled].(bool); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("mention_highlight_disabled prop must be a boolean"))
}
}
if props[PostPropsGroupHighlightDisabled] != nil {
if _, ok := props[PostPropsGroupHighlightDisabled].(bool); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("disable_group_highlight prop must be a boolean"))
}
}
if props[PostPropsPreviewedPost] != nil {
if previewedPostID, ok := props[PostPropsPreviewedPost].(string); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("previewed_post prop must be a string"))
} else if !IsValidId(previewedPostID) {
multiErr = multierror.Append(multiErr, fmt.Errorf("previewed_post prop must be a valid post ID"))
}
}
if props[PostPropsForceNotification] != nil {
if _, ok := props[PostPropsForceNotification].(bool); !ok {
multiErr = multierror.Append(multiErr, fmt.Errorf("force_notification prop must be a boolean"))
}
}
for i, a := range o.Attachments() {
if err := a.IsValid(); err != nil {
multiErr = multierror.Append(multiErr, multierror.Prefix(err, fmt.Sprintf("message attachtment at index %d is invalid:", i)))
}
}
return multiErr.ErrorOrNil()
}
func (o *Post) IsSystemMessage() bool {
return len(o.Type) >= len(PostSystemMessagePrefix) && o.Type[:len(PostSystemMessagePrefix)] == PostSystemMessagePrefix
}
@@ -746,11 +881,11 @@ func findAtChannelMention(message string) (mention string, found bool) {
}
func (o *Post) Attachments() []*SlackAttachment {
if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
if attachments, ok := o.GetProp(PostPropsAttachments).([]*SlackAttachment); ok {
return attachments
}
var ret []*SlackAttachment
if attachments, ok := o.GetProp("attachments").([]any); ok {
if attachments, ok := o.GetProp(PostPropsAttachments).([]any); ok {
for _, attachment := range attachments {
if enc, err := json.Marshal(attachment); err == nil {
var decoded SlackAttachment
@@ -885,7 +1020,7 @@ func RewriteImageURLs(message string, f func(string) string) string {
func (o *Post) IsFromOAuthBot() bool {
props := o.GetProps()
return props["from_webhook"] == "true" && props["override_username"] != ""
return props[PostPropsFromWebhook] == "true" && props[PostPropsOverrideUsername] != ""
}
func (o *Post) ToNilIfInvalid() *Post {

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

@@ -140,7 +140,7 @@ func TestPostSanitizeProps(t *testing.T) {
Props: StringInterface{
PropsAddChannelMember: "no good",
PostPropsForceNotification: "no good",
"attachments": "good",
PostPropsAttachments: "good",
},
}
@@ -149,7 +149,7 @@ func TestPostSanitizeProps(t *testing.T) {
require.Nil(t, post3.GetProp(PropsAddChannelMember))
require.Nil(t, post3.GetProp(PostPropsForceNotification))
require.NotNil(t, post3.GetProp("attachments"))
require.NotNil(t, post3.GetProp(PostPropsAttachments))
}
func TestPost_ContainsIntegrationsReservedProps(t *testing.T) {
@@ -162,11 +162,11 @@ func TestPost_ContainsIntegrationsReservedProps(t *testing.T) {
post2 := &Post{
Message: "test",
Props: StringInterface{
"from_webhook": "true",
"webhook_display_name": "overridden_display_name",
"override_username": "overridden_username",
"override_icon_url": "a-custom-url",
"override_icon_emoji": ":custom_emoji_name:",
PostPropsFromWebhook: "true",
PostPropsWebhookDisplayName: "overridden_display_name",
PostPropsOverrideUsername: "overridden_username",
PostPropsOverrideIconURL: "a-custom-url",
PostPropsOverrideIconEmoji: ":custom_emoji_name:",
},
}
keys2 := post2.ContainsIntegrationsReservedProps()
@@ -176,7 +176,7 @@ func TestPost_ContainsIntegrationsReservedProps(t *testing.T) {
func TestPostPatch_ContainsIntegrationsReservedProps(t *testing.T) {
postPatch1 := &PostPatch{
Props: &StringInterface{
"from_webhook": "true",
PostPropsFromWebhook: "true",
},
}
keys1 := postPatch1.ContainsIntegrationsReservedProps()
@@ -405,8 +405,8 @@ func TestPost_AttachmentsEqual(t *testing.T) {
},
} {
t.Run(name, func(t *testing.T) {
post1.AddProp("attachments", tc.Attachments1)
post2.AddProp("attachments", tc.Attachments2)
post1.AddProp(PostPropsAttachments, tc.Attachments1)
post2.AddProp(PostPropsAttachments, tc.Attachments2)
assert.Equal(t, tc.Expected, post1.AttachmentsEqual(post2))
})
}
@@ -895,7 +895,7 @@ func TestPostPatchDisableMentionHighlights(t *testing.T) {
func TestPostAttachments(t *testing.T) {
p := &Post{
Props: map[string]any{
"attachments": []byte(`[{
PostPropsAttachments: []byte(`[{
"actions" : {null}
}]
`),
@@ -903,7 +903,7 @@ func TestPostAttachments(t *testing.T) {
}
t.Run("empty actions", func(t *testing.T) {
p.Props["attachments"] = []any{
p.Props[PostPropsAttachments] = []any{
map[string]any{"actions": []any{}},
}
attachments := p.Attachments()
@@ -911,7 +911,7 @@ func TestPostAttachments(t *testing.T) {
})
t.Run("a couple of actions", func(t *testing.T) {
p.Props["attachments"] = []any{
p.Props[PostPropsAttachments] = []any{
map[string]any{"actions": []any{
map[string]any{"id": "test1"}, map[string]any{"id": "test2"}},
},
@@ -924,7 +924,7 @@ func TestPostAttachments(t *testing.T) {
})
t.Run("should ignore null actions", func(t *testing.T) {
p.Props["attachments"] = []any{
p.Props[PostPropsAttachments] = []any{
map[string]any{"actions": []any{
map[string]any{"id": "test1"}, nil, map[string]any{"id": "test2"}, nil, nil},
},
@@ -937,7 +937,7 @@ func TestPostAttachments(t *testing.T) {
})
t.Run("nil fields", func(t *testing.T) {
p.Props["attachments"] = []any{
p.Props[PostPropsAttachments] = []any{
map[string]any{"fields": []any{
map[string]any{"value": ":emoji1:"},
nil,
@@ -995,3 +995,278 @@ func TestPostPriority(t *testing.T) {
p.Metadata.Priority.Priority = NewPointer(PostPriorityUrgent)
require.True(t, p.IsUrgent())
}
func TestPost_PropsIsValid(t *testing.T) {
tests := map[string]struct {
props StringInterface
wantErr string
}{
"valid empty props": {
props: nil,
wantErr: "",
},
"valid props": {
props: StringInterface{
"key": "value",
},
wantErr: "",
},
"valid added_user_id": {
props: StringInterface{
PostPropsAddedUserId: NewId(),
},
wantErr: "",
},
"valid delete_by": {
props: StringInterface{
PostPropsDeleteBy: NewId(),
},
wantErr: "",
},
"valid override_icon_url": {
props: StringInterface{
PostPropsOverrideIconURL: "https://example.com/icon.png",
},
wantErr: "",
},
"valid override_icon_emoji": {
props: StringInterface{
PostPropsOverrideIconEmoji: ":smile:",
},
wantErr: "",
},
"valid override_username": {
props: StringInterface{
PostPropsOverrideUsername: "testuser",
},
wantErr: "",
},
"valid from_webhook": {
props: StringInterface{
PostPropsFromWebhook: "true",
},
wantErr: "",
},
"valid from_bot": {
props: StringInterface{
PostPropsFromBot: "true",
},
wantErr: "",
},
"valid from_oauth_app": {
props: StringInterface{
PostPropsFromOAuthApp: "true",
},
wantErr: "",
},
"valid from_plugin": {
props: StringInterface{
PostPropsFromPlugin: "true",
},
wantErr: "",
},
"valid unsafe_links": {
props: StringInterface{
PostPropsUnsafeLinks: "true",
},
wantErr: "",
},
"valid webhook_display_name": {
props: StringInterface{
PostPropsWebhookDisplayName: "My Webhook",
},
wantErr: "",
},
"valid mention_highlight_disabled": {
props: StringInterface{
PostPropsMentionHighlightDisabled: true,
},
wantErr: "",
},
"valid disable_group_highlight": {
props: StringInterface{
PostPropsGroupHighlightDisabled: true,
},
wantErr: "",
},
"valid previewed_post": {
props: StringInterface{
PostPropsPreviewedPost: NewId(),
},
wantErr: "",
},
"valid force_notification": {
props: StringInterface{
PostPropsForceNotification: true,
},
wantErr: "",
},
"valid multiple props": {
props: StringInterface{
PostPropsFromWebhook: "true",
PostPropsOverrideUsername: "webhook-user",
PostPropsOverrideIconURL: "https://example.com/icon.png",
PostPropsWebhookDisplayName: "My Webhook",
PostPropsMentionHighlightDisabled: true,
},
wantErr: "",
},
"invalid added_user_id type": {
props: StringInterface{
PostPropsAddedUserId: 123,
},
wantErr: "added_user_id prop must be a string",
},
"invalid added_user_id value": {
props: StringInterface{
PostPropsAddedUserId: "invalid-id",
},
wantErr: "added_user_id prop must be a valid user ID",
},
"invalid delete_by type": {
props: StringInterface{
PostPropsDeleteBy: 123,
},
wantErr: "delete_by prop must be a string",
},
"invalid delete_by value": {
props: StringInterface{
PostPropsDeleteBy: "invalid-id",
},
wantErr: "delete_by prop must be a valid user ID",
},
"invalid override_icon_url type": {
props: StringInterface{
PostPropsOverrideIconURL: 123,
},
wantErr: "override_icon_url prop must be a string",
},
"invalid override_icon_url value": {
props: StringInterface{
PostPropsOverrideIconURL: "not-a-url",
},
wantErr: "override_icon_url prop must be a valid URL",
},
"invalid override_icon_emoji type": {
props: StringInterface{
PostPropsOverrideIconEmoji: 123,
},
wantErr: "override_icon_emoji prop must be a string",
},
"invalid override_username type": {
props: StringInterface{
PostPropsOverrideUsername: 123,
},
wantErr: "override_username prop must be a string",
},
"invalid from_webhook type": {
props: StringInterface{
PostPropsFromWebhook: 123,
},
wantErr: "from_webhook prop must be a string",
},
"invalid from_webhook value": {
props: StringInterface{
PostPropsFromWebhook: "false",
},
wantErr: "from_webhook prop must be \"true\"",
},
"invalid from_bot type": {
props: StringInterface{
PostPropsFromBot: 123,
},
wantErr: "from_bot prop must be a string",
},
"invalid from_bot value": {
props: StringInterface{
PostPropsFromBot: "false",
},
wantErr: "from_bot prop must be \"true\"",
},
"invalid from_oauth_app type": {
props: StringInterface{
PostPropsFromOAuthApp: 123,
},
wantErr: "from_oauth_app prop must be a string",
},
"invalid from_oauth_app value": {
props: StringInterface{
PostPropsFromOAuthApp: "false",
},
wantErr: "from_oauth_app prop must be \"true\"",
},
"invalid from_plugin type": {
props: StringInterface{
PostPropsFromPlugin: 123,
},
wantErr: "from_plugin prop must be a string",
},
"invalid from_plugin value": {
props: StringInterface{
PostPropsFromPlugin: "false",
},
wantErr: "from_plugin prop must be \"true\"",
},
"invalid unsafe_links type": {
props: StringInterface{
PostPropsUnsafeLinks: 123,
},
wantErr: "unsafe_links prop must be a string",
},
"invalid unsafe_links value": {
props: StringInterface{
PostPropsUnsafeLinks: "false",
},
wantErr: "unsafe_links prop must be \"true\"",
},
"invalid webhook_display_name type": {
props: StringInterface{
PostPropsWebhookDisplayName: 123,
},
wantErr: "webhook_display_name prop must be a string",
},
"invalid mention_highlight_disabled type": {
props: StringInterface{
PostPropsMentionHighlightDisabled: "true",
},
wantErr: "mention_highlight_disabled prop must be a boolean",
},
"invalid disable_group_highlight type": {
props: StringInterface{
PostPropsGroupHighlightDisabled: "true",
},
wantErr: "disable_group_highlight prop must be a boolean",
},
"invalid previewed_post type": {
props: StringInterface{
PostPropsPreviewedPost: 123,
},
wantErr: "previewed_post prop must be a string",
},
"invalid previewed_post value": {
props: StringInterface{
PostPropsPreviewedPost: "invalid-id",
},
wantErr: "previewed_post prop must be a valid post ID",
},
"invalid force_notification type": {
props: StringInterface{
PostPropsForceNotification: "true",
},
wantErr: "force_notification prop must be a boolean",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
p := &Post{}
p.SetProps(tc.props)
err := p.propsIsValid()
if tc.wantErr == "" {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, tc.wantErr)
}
})
}
}

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

@@ -6,9 +6,15 @@ package model
import (
"fmt"
"regexp"
"slices"
"github.com/hashicorp/go-multierror"
)
var linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
var (
linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
hexColorRegex = regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
)
type SlackAttachment struct {
Id int64 `json:"id"`
@@ -30,6 +36,78 @@ type SlackAttachment struct {
Actions []*PostAction `json:"actions,omitempty"`
}
func (s *SlackAttachment) IsValid() error {
var multiErr *multierror.Error
if s.Color != "" {
validStyles := []string{"good", "warning", "danger"}
// If not a predefined style, check if it's a hex color
if !slices.Contains(validStyles, s.Color) && !hexColorRegex.MatchString(s.Color) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid style '%s' - must be one of [good, warning, danger] or a hex color", s.Color))
}
}
if s.AuthorLink != "" {
if s.AuthorName == "" {
multiErr = multierror.Append(multiErr, fmt.Errorf("author link cannot be set without author name"))
}
if !IsValidHTTPURL(s.AuthorLink) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid author link URL"))
}
}
if s.AuthorIcon != "" && !IsValidHTTPURL(s.AuthorIcon) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid author icon URL"))
}
if s.TitleLink != "" {
if s.Title == "" {
multiErr = multierror.Append(multiErr, fmt.Errorf("title link cannot be set without title"))
}
if !IsValidHTTPURL(s.TitleLink) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid title link URL"))
}
}
for _, field := range s.Fields {
if err := field.IsValid(); err != nil {
multiErr = multierror.Append(multiErr, err)
}
}
if s.ImageURL != "" && !IsValidHTTPURL(s.ImageURL) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid image URL"))
}
if s.ThumbURL != "" && !IsValidHTTPURL(s.ThumbURL) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid thumb URL"))
}
if s.FooterIcon != "" && !IsValidHTTPURL(s.FooterIcon) {
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid footer icon URL"))
}
// Validate timestamp is either string or int64
if s.Timestamp != nil {
switch s.Timestamp.(type) {
case string, int64:
// Valid types
default:
multiErr = multierror.Append(multiErr, fmt.Errorf("timestamp must be either a string or int64"))
}
}
for i, action := range s.Actions {
if err := action.IsValid(); err != nil {
multiErr = multierror.Append(multiErr, multierror.Prefix(err, fmt.Sprintf("action at index %d is invalid:", i)))
}
}
return multiErr.ErrorOrNil()
}
func (s *SlackAttachment) Equals(input *SlackAttachment) bool {
// Direct comparison of simple types
@@ -120,6 +198,21 @@ type SlackAttachmentField struct {
Short SlackCompatibleBool `json:"short"`
}
func (s *SlackAttachmentField) IsValid() error {
var multiErr *multierror.Error
if s.Value != nil {
switch s.Value.(type) {
case string, int:
// Valid types
default:
multiErr = multierror.Append(multiErr, fmt.Errorf("value must be either a string or int"))
}
}
return multiErr.ErrorOrNil()
}
func (s *SlackAttachmentField) Equals(input *SlackAttachmentField) bool {
if s.Title != input.Title {
return false
@@ -188,7 +281,7 @@ func ParseSlackAttachment(post *Post, attachments []*SlackAttachment) {
}
postAttachments = append(postAttachments, attachment)
}
post.AddProp("attachments", postAttachments)
post.AddProp(PostPropsAttachments, postAttachments)
}
func ParseSlackLinksToMarkdown(text string) string {

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

@@ -9,6 +9,165 @@ import (
"github.com/stretchr/testify/assert"
)
func TestSlackAttachment_IsValid(t *testing.T) {
tests := map[string]struct {
attachment *SlackAttachment
wantErr string
}{
"valid attachment": {
attachment: &SlackAttachment{
Text: "This is a test",
},
wantErr: "",
},
"invalid color": {
attachment: &SlackAttachment{
Color: "invalid",
},
wantErr: "invalid style 'invalid' - must be one of [good, warning, danger] or a hex color",
},
"valid predefined color": {
attachment: &SlackAttachment{
Color: "good",
},
wantErr: "",
},
"valid warning color": {
attachment: &SlackAttachment{
Color: "warning",
},
wantErr: "",
},
"valid danger color": {
attachment: &SlackAttachment{
Color: "danger",
},
wantErr: "",
},
"valid hex color": {
attachment: &SlackAttachment{
Color: "#FF0000",
},
wantErr: "",
},
"author link without name": {
attachment: &SlackAttachment{
AuthorLink: "http://example.com",
},
wantErr: "author link cannot be set without author name",
},
"invalid author link": {
attachment: &SlackAttachment{
AuthorName: "Author",
AuthorLink: "invalid-url",
},
wantErr: "invalid author link URL",
},
"invalid author icon": {
attachment: &SlackAttachment{
AuthorIcon: "invalid-url",
},
wantErr: "invalid author icon URL",
},
"title link without title": {
attachment: &SlackAttachment{
TitleLink: "http://example.com",
},
wantErr: "title link cannot be set without title",
},
"invalid title link": {
attachment: &SlackAttachment{
Title: "Title",
TitleLink: "invalid-url",
},
wantErr: "invalid title link URL",
},
"invalid image URL": {
attachment: &SlackAttachment{
ImageURL: "invalid-url",
},
wantErr: "invalid image URL",
},
"invalid thumb URL": {
attachment: &SlackAttachment{
ThumbURL: "invalid-url",
},
wantErr: "invalid thumb URL",
},
"invalid footer icon": {
attachment: &SlackAttachment{
FooterIcon: "invalid-url",
},
wantErr: "invalid footer icon URL",
},
"invalid timestamp type": {
attachment: &SlackAttachment{
Timestamp: []string{"invalid"},
},
wantErr: "timestamp must be either a string or int64",
},
"valid timestamp string": {
attachment: &SlackAttachment{
Timestamp: "1234567890",
},
wantErr: "",
},
"valid timestamp int64": {
attachment: &SlackAttachment{
Timestamp: int64(1234567890),
},
wantErr: "",
},
"invalid action": {
attachment: &SlackAttachment{
Actions: []*PostAction{
{
Name: "", // Invalid - missing name
},
},
},
wantErr: "action must have a name",
},
"invalid field value type": {
attachment: &SlackAttachment{
Fields: []*SlackAttachmentField{
{
Title: "Title",
Value: []string{"invalid"},
},
},
},
wantErr: "value must be either a string or int",
},
"valid fields": {
attachment: &SlackAttachment{
Fields: []*SlackAttachmentField{
{
Title: "Title",
Value: "string value",
},
{
Title: "Number",
Value: 42,
},
},
},
wantErr: "",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
err := tc.attachment.IsValid()
if tc.wantErr == "" {
assert.NoError(t, err, name)
} else {
assert.ErrorContains(t, err, tc.wantErr, name)
}
})
}
}
func TestParseSlackAttachment(t *testing.T) {
t.Run("empty list", func(t *testing.T) {
post := &Post{}
@@ -19,7 +178,7 @@ func TestParseSlackAttachment(t *testing.T) {
expectedPost := &Post{
Type: PostTypeSlackAttachment,
Props: map[string]any{
"attachments": []*SlackAttachment{},
PostPropsAttachments: []*SlackAttachment{},
},
}
assert.Equal(t, expectedPost, post)
@@ -36,7 +195,7 @@ func TestParseSlackAttachment(t *testing.T) {
expectedPost := &Post{
Type: PostTypeSlackAttachment,
Props: map[string]any{
"attachments": []*SlackAttachment{},
PostPropsAttachments: []*SlackAttachment{},
},
}
assert.Equal(t, expectedPost, post)

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

@@ -106,7 +106,7 @@ func TestDMWithAttachments(t *testing.T) {
ChannelId: dmChannelID,
Type: model.PostTypeSlackAttachment,
Props: model.StringInterface{
"attachments": attachments,
model.PostPropsAttachments: attachments,
},
}

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

@@ -196,7 +196,7 @@ func (f *Flow) handle(
}
func (f *Flow) processButtonPostActions(post *model.Post) {
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
if !ok || len(attachments) == 0 {
return
}

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

@@ -145,7 +145,7 @@ func (s Step) render(f *Flow, done bool, selectedButton int) (*model.Post, bool,
buttons := processButtons(s.buttons, f.state.AppState)
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
if !ok || len(attachments) != 1 {
return nil, false, errors.New("expected 1 slack attachment")
}
@@ -224,6 +224,7 @@ func processDialog(in *model.Dialog, state State) model.Dialog {
func renderButton(b Button, stepName Name, i int, state State) *model.PostAction {
return &model.PostAction{
Type: model.PostActionTypeButton,
Name: formatState(b.Name, state),
Disabled: b.Disabled,
Style: string(b.Color),

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

@@ -75,6 +75,7 @@ func (s *boolSetting) GetSlackAttachments(userID, settingHandler string, disable
currentValueMessage = fmt.Sprintf("Current value: %s", currentTextValue)
actionTrue := model.PostAction{
Type: model.PostActionTypeButton,
Name: "Yes",
Integration: &model.PostActionIntegration{
URL: settingHandler,
@@ -86,6 +87,7 @@ func (s *boolSetting) GetSlackAttachments(userID, settingHandler string, disable
}
actionFalse := model.PostAction{
Type: model.PostActionTypeButton,
Name: "No",
Integration: &model.PostActionIntegration{
URL: settingHandler,

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

@@ -62,6 +62,7 @@ func (s *optionSetting) GetSlackAttachments(userID, settingHandler string, disab
currentValueMessage = fmt.Sprintf("Current value: %s", currentTextValue)
actionOptions := model.PostAction{
Type: model.PostActionTypeSelect,
Name: "Select an option:",
Integration: &model.PostActionIntegration{
URL: settingHandler + "?" + s.id + "=true",
@@ -69,7 +70,6 @@ func (s *optionSetting) GetSlackAttachments(userID, settingHandler string, disab
ContextIDKey: s.id,
},
},
Type: "select",
Options: stringsToOptions(s.options),
}

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

@@ -297,7 +297,7 @@ func (p *PostService) ShouldProcessMessage(post *model.Post, options ...ShouldPr
return false, nil
}
if !messageProcessOptions.AllowWebhook && post.GetProp("from_webhook") == "true" {
if !messageProcessOptions.AllowWebhook && post.GetProp(model.PostPropsFromWebhook) == "true" {
return false, nil
}

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

@@ -660,7 +660,7 @@ func TestShouldProcessMessage(t *testing.T) {
client := pluginapi.NewClient(api, &plugintest.Driver{})
shouldProcessMessage, err := client.Post.ShouldProcessMessage(
&model.Post{ChannelId: channelID, Props: model.StringInterface{"from_webhook": "true"}},
&model.Post{ChannelId: channelID, Props: model.StringInterface{model.PostPropsFromWebhook: "true"}},
pluginapi.AllowBots(),
)
@@ -677,7 +677,7 @@ func TestShouldProcessMessage(t *testing.T) {
client := pluginapi.NewClient(api, &plugintest.Driver{})
shouldProcessMessage, err := client.Post.ShouldProcessMessage(
&model.Post{ChannelId: channelID, Props: model.StringInterface{"from_webhook": "true"}},
&model.Post{ChannelId: channelID, Props: model.StringInterface{model.PostPropsFromWebhook: "true"}},
pluginapi.AllowBots(),
pluginapi.AllowWebhook(),
)
@@ -712,7 +712,7 @@ func TestShouldProcessMessage(t *testing.T) {
client := pluginapi.NewClient(api, &plugintest.Driver{})
shouldProcessMessage, err := client.Post.ShouldProcessMessage(
&model.Post{ChannelId: channelID, Props: model.StringInterface{"from_webhook": "false"}},
&model.Post{ChannelId: channelID, Props: model.StringInterface{model.PostPropsFromWebhook: "false"}},
pluginapi.AllowBots(),
)