From b5a816a657d6f33a96d374b04212685e2b0df77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Espino=20Garc=C3=ADa?= Date: Tue, 20 Jan 2026 10:38:27 +0100 Subject: [PATCH] Add audits for accessing posts without membership (#31266) * Add audits for accessing posts without membership * Fix tests * Use correct audit level * Address feedback * Add missing checks all over the app * Fix lint * Fix test * Fix tests * Fix enterprise test * Add missing test and docs * Fix merge * Fix lint * Add audit logs on the web socket hook for permalink posts * Fix lint * Fix merge conflicts * Handle all events with "non_channel_member_access" parameter * Fix lint and tests * Fix merge * Fix tests --- server/channels/api4/access_control.go | 14 +- server/channels/api4/channel.go | 138 +++--- server/channels/api4/channel_bookmark.go | 60 ++- server/channels/api4/command.go | 2 +- server/channels/api4/content_flagging.go | 12 +- server/channels/api4/drafts.go | 2 +- server/channels/api4/file.go | 58 ++- server/channels/api4/group.go | 8 +- server/channels/api4/integration_action.go | 8 +- server/channels/api4/post.go | 334 +++++++++++-- server/channels/api4/post_test.go | 59 ++- server/channels/api4/post_utils.go | 4 +- server/channels/api4/preference.go | 2 +- server/channels/api4/reaction.go | 4 +- server/channels/api4/report_test.go | 4 +- server/channels/api4/scheduled_post.go | 2 +- server/channels/api4/shared_channel.go | 2 +- .../api4/shared_channel_metadata_test.go | 10 +- server/channels/api4/system.go | 12 +- server/channels/api4/upload.go | 7 +- server/channels/api4/user.go | 52 +- server/channels/api4/user_test.go | 12 +- server/channels/api4/webhook.go | 36 +- server/channels/app/access_control.go | 4 +- server/channels/app/authorization.go | 101 +++- server/channels/app/authorization_test.go | 271 +++++++++- server/channels/app/auto_responder.go | 2 +- server/channels/app/auto_responder_test.go | 18 +- server/channels/app/bot.go | 4 +- server/channels/app/channel.go | 45 +- server/channels/app/channel_test.go | 58 +-- server/channels/app/command.go | 7 +- server/channels/app/content_flagging.go | 14 +- server/channels/app/content_flagging_test.go | 4 +- server/channels/app/export_test.go | 20 +- server/channels/app/file.go | 34 +- server/channels/app/file_test.go | 53 +- server/channels/app/helper_test.go | 8 +- server/channels/app/integration_action.go | 2 +- .../channels/app/integration_action_test.go | 36 +- server/channels/app/job.go | 2 +- server/channels/app/notification.go | 32 +- server/channels/app/notification_test.go | 54 +- server/channels/app/notify_admin.go | 2 +- server/channels/app/platform/helper_test.go | 14 +- .../channels/app/platform/mocks/SuiteIFace.go | 39 +- server/channels/app/platform/web_hub.go | 4 +- server/channels/app/plugin_api.go | 15 +- server/channels/app/plugin_hooks_test.go | 28 +- server/channels/app/plugin_test.go | 2 +- server/channels/app/post.go | 361 ++++++-------- .../app/post_acknowledgements_test.go | 12 +- server/channels/app/post_metadata.go | 44 +- server/channels/app/post_metadata_test.go | 166 ++----- server/channels/app/post_permission_utils.go | 2 +- .../app/post_persistent_notification_test.go | 6 +- server/channels/app/post_restore.go | 10 +- server/channels/app/post_restore_test.go | 14 +- server/channels/app/post_test.go | 463 +++++++++--------- server/channels/app/reaction_test.go | 8 +- server/channels/app/recap.go | 2 +- server/channels/app/report.go | 4 +- server/channels/app/scheduled_post_job.go | 4 +- server/channels/app/shared_channel_test.go | 2 +- .../channels/app/slashcommands/auto_posts.go | 2 +- .../slashcommands/command_channel_header.go | 4 +- .../slashcommands/command_channel_purpose.go | 4 +- .../slashcommands/command_channel_rename.go | 4 +- .../app/slashcommands/command_echo.go | 2 +- .../app/slashcommands/command_groupmsg.go | 2 +- .../app/slashcommands/command_invite.go | 4 +- .../app/slashcommands/command_join.go | 4 +- .../app/slashcommands/command_loadtest.go | 4 +- .../channels/app/slashcommands/command_msg.go | 2 +- .../app/slashcommands/command_remove.go | 4 +- .../channels/app/slashcommands/helper_test.go | 2 +- server/channels/app/team.go | 4 +- server/channels/app/team_test.go | 6 +- server/channels/app/user.go | 12 +- server/channels/app/user_test.go | 4 +- server/channels/app/web_broadcast_hooks.go | 13 +- server/channels/app/webhook.go | 9 +- server/channels/wsapi/user.go | 2 +- server/cmd/mmctl/commands/post_e2e_test.go | 4 +- .../sharedchannel/mock_AppIface_test.go | 56 ++- .../services/sharedchannel/permalink_test.go | 2 +- .../services/sharedchannel/service.go | 8 +- .../services/sharedchannel/sync_recv.go | 4 +- server/public/model/audit_events.go | 40 +- 89 files changed, 1903 insertions(+), 1112 deletions(-) diff --git a/server/channels/api4/access_control.go b/server/channels/api4/access_control.go index d0faf8c4a6f..2d5d861c560 100644 --- a/server/channels/api4/access_control.go +++ b/server/channels/api4/access_control.go @@ -64,7 +64,7 @@ func createAccessControlPolicy(c *Context, w http.ResponseWriter, r *http.Reques return } - hasChannelPermission := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, policy.ID, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, policy.ID, model.PermissionManageChannelAccessRules) if !hasChannelPermission { c.SetPermissionError(model.PermissionManageChannelAccessRules) return @@ -197,7 +197,7 @@ func checkExpression(c *Context, w http.ResponseWriter, r *http.Request) { } // SECURE: Check specific channel permission - hasChannelPermission := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) if !hasChannelPermission { c.SetPermissionError(model.PermissionManageChannelAccessRules) return @@ -245,7 +245,7 @@ func testExpression(c *Context, w http.ResponseWriter, r *http.Request) { } // SECURE: Check specific channel permission - hasChannelPermission := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) if !hasChannelPermission { c.SetPermissionError(model.PermissionManageChannelAccessRules) return @@ -321,7 +321,7 @@ func validateExpressionAgainstRequester(c *Context, w http.ResponseWriter, r *ht } // SECURE: Check specific channel permission - hasChannelPermission := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) if !hasChannelPermission { c.SetPermissionError(model.PermissionManageChannelAccessRules) return @@ -450,7 +450,7 @@ func setActiveStatus(c *Context, w http.ResponseWriter, r *http.Request) { hasManageSystemPermission := c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) if !hasManageSystemPermission { for _, entry := range list.Entries { - hasChannelPermission := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, entry.ID, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, entry.ID, model.PermissionManageChannelAccessRules) if !hasChannelPermission { c.SetPermissionError(model.PermissionManageChannelAccessRules) return @@ -661,7 +661,7 @@ func getFieldsAutocomplete(c *Context, w http.ResponseWriter, r *http.Request) { } // SECURE: Check specific channel permission - hasChannelPermission := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) if !hasChannelPermission { c.SetPermissionError(model.PermissionManageChannelAccessRules) return @@ -735,7 +735,7 @@ func convertToVisualAST(c *Context, w http.ResponseWriter, r *http.Request) { } // SECURE: Check specific channel permission - hasChannelPermission := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := c.App.HasPermissionToChannel(c.AppContext, c.AppContext.Session().UserId, channelId, model.PermissionManageChannelAccessRules) if !hasChannelPermission { c.SetPermissionError(model.PermissionManageChannelAccessRules) return diff --git a/server/channels/api4/channel.go b/server/channels/api4/channel.go index cadc997e262..7cbd9607025 100644 --- a/server/channels/api4/channel.go +++ b/server/channels/api4/channel.go @@ -166,13 +166,13 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { switch oldChannel.Type { case model.ChannelTypeOpen: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelProperties) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelProperties); !ok { c.SetPermissionError(model.PermissionManagePublicChannelProperties) return } case model.ChannelTypePrivate: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelProperties) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelProperties); !ok { c.SetPermissionError(model.PermissionManagePrivateChannelProperties) return } @@ -277,14 +277,18 @@ func updateChannelPrivacy(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.AddEventPriorState(channel) - if model.ChannelType(privacy) == model.ChannelTypeOpen && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionConvertPrivateChannelToPublic) { - c.SetPermissionError(model.PermissionConvertPrivateChannelToPublic) - return + if model.ChannelType(privacy) == model.ChannelTypeOpen { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionConvertPrivateChannelToPublic); !ok { + c.SetPermissionError(model.PermissionConvertPrivateChannelToPublic) + return + } } - if model.ChannelType(privacy) == model.ChannelTypePrivate && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionConvertPublicChannelToPrivate) { - c.SetPermissionError(model.PermissionConvertPublicChannelToPrivate) - return + if model.ChannelType(privacy) == model.ChannelTypePrivate { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionConvertPublicChannelToPrivate); !ok { + c.SetPermissionError(model.PermissionConvertPublicChannelToPrivate) + return + } } if channel.Name == model.DefaultChannelName && model.ChannelType(privacy) == model.ChannelTypePrivate { @@ -342,13 +346,13 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) { switch oldChannel.Type { case model.ChannelTypeOpen: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelProperties) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelProperties); !ok { c.SetPermissionError(model.PermissionManagePublicChannelProperties) return } case model.ChannelTypePrivate: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelProperties) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelProperties); !ok { c.SetPermissionError(model.PermissionManagePrivateChannelProperties) return } @@ -664,15 +668,15 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) { if !isContentReviewer { if channel.Type == model.ChannelTypeOpen { - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { - c.SetPermissionError(model.PermissionReadPublicChannel) - return - } - } else { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { - c.SetPermissionError(model.PermissionReadChannel) - return + if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { + c.SetPermissionError(model.PermissionReadChannel) + return + } } + } else if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { + c.SetPermissionError(model.PermissionReadChannel) + return } } @@ -698,7 +702,7 @@ func getChannelUnread(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -723,7 +727,7 @@ func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -813,7 +817,8 @@ func getPinnedPosts(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + var hasPermission, isMember bool + if hasPermission, isMember = c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel); !hasPermission { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -829,7 +834,7 @@ func getPinnedPosts(c *Context, w http.ResponseWriter, r *http.Request) { } clientPostList := c.App.PreparePostListForClient(c.AppContext, posts) - clientPostList, err = c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) + clientPostList, isMemberForAllPreviews, err := c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) if err != nil { c.Err = err return @@ -839,6 +844,14 @@ func getPinnedPosts(c *Context, w http.ResponseWriter, r *http.Request) { if err := clientPostList.EncodeJSON(w); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventUpdateChannelMemberRoles, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "channel_id", c.Params.ChannelId) + + if !isMember || !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } func getAllChannels(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1035,7 +1048,7 @@ func getPublicChannelsByIdsForTeam(c *Context, w http.ResponseWriter, r *http.Re if session := c.AppContext.Session(); session.IsGuest() { for _, channel := range channels { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *session, channel.Id, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *session, channel.Id, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -1387,14 +1400,18 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - if channel.Type == model.ChannelTypeOpen && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionDeletePublicChannel) { - c.SetPermissionError(model.PermissionDeletePublicChannel) - return + if channel.Type == model.ChannelTypeOpen { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionDeletePublicChannel); !ok { + c.SetPermissionError(model.PermissionDeletePublicChannel) + return + } } - if channel.Type == model.ChannelTypePrivate && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionDeletePrivateChannel) { - c.SetPermissionError(model.PermissionDeletePrivateChannel) - return + if channel.Type == model.ChannelTypePrivate { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionDeletePrivateChannel); !ok { + c.SetPermissionError(model.PermissionDeletePrivateChannel) + return + } } if c.Params.Permanent { @@ -1437,16 +1454,19 @@ func getChannelByName(c *Context, w http.ResponseWriter, r *http.Request) { } if channel.Type == model.ChannelTypeOpen { - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannel) { - c.SetPermissionError(model.PermissionReadPublicChannel) - return + if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannel); !ok { + c.SetPermissionError(model.PermissionReadPublicChannel) + return + } } } else { // allows team admins to access private channel - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionManageTeam) && - !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannel) { - c.Err = model.NewAppError("getChannelByName", "app.channel.get_by_name.missing.app_error", nil, "teamId="+channel.TeamId+", "+"name="+channel.Name+"", http.StatusNotFound) - return + if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionManageTeam) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannel); !ok { + c.Err = model.NewAppError("getChannelByName", "app.channel.get_by_name.missing.app_error", nil, "teamId="+channel.TeamId+", "+"name="+channel.Name+"", http.StatusNotFound) + return + } } } @@ -1474,7 +1494,7 @@ func getChannelByNameForTeamName(c *Context, w http.ResponseWriter, r *http.Requ return } - channelOk := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannel) + channelOk, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannel) if channel.Type == model.ChannelTypeOpen { teamOk := c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) if !teamOk && !channelOk { @@ -1506,7 +1526,7 @@ func getChannelMembers(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -1534,7 +1554,7 @@ func getChannelMembersTimezones(c *Context, w http.ResponseWriter, r *http.Reque return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -1565,7 +1585,7 @@ func getChannelMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -1593,7 +1613,7 @@ func getChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -1746,7 +1766,7 @@ func updateChannelMemberRoles(c *Context, w http.ResponseWriter, r *http.Request model.AddEventParameterToAuditRec(auditRec, "props", props) model.AddEventParameterToAuditRec(auditRec, "channel_id", c.Params.ChannelId) - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManageChannelRoles) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManageChannelRoles); !ok { c.SetPermissionError(model.PermissionManageChannelRoles) return } @@ -1778,7 +1798,7 @@ func updateChannelMemberSchemeRoles(c *Context, w http.ResponseWriter, r *http.R model.AddEventParameterToAuditRec(auditRec, "channel_id", c.Params.ChannelId) model.AddEventParameterAuditableToAuditRec(auditRec, "roles", &schemeRoles) - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManageChannelRoles) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManageChannelRoles); !ok { c.SetPermissionError(model.PermissionManageChannelRoles) return } @@ -1889,7 +1909,7 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { // Security check: if the user is a guest, they must have access to the channel // to view its members if c.AppContext.Session().IsGuest() { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if hasPermission, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !hasPermission { c.SetPermissionError(model.PermissionReadChannel) return } @@ -1917,13 +1937,13 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { if c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionJoinPublicChannels) { canAddSelf = true } - if c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePublicChannelMembers) { + if hasPermission, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePublicChannelMembers); hasPermission { canAddOthers = true } } if channel.Type == model.ChannelTypePrivate { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePrivateChannelMembers) { + if hasPermission, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePrivateChannelMembers); !hasPermission { c.SetPermissionError(model.PermissionManagePrivateChannelMembers) return } @@ -2086,14 +2106,18 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { } if c.Params.UserId != c.AppContext.Session().UserId { - if channel.Type == model.ChannelTypeOpen && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePublicChannelMembers) { - c.SetPermissionError(model.PermissionManagePublicChannelMembers) - return + if channel.Type == model.ChannelTypeOpen { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePublicChannelMembers); !ok { + c.SetPermissionError(model.PermissionManagePublicChannelMembers) + return + } } - if channel.Type == model.ChannelTypePrivate && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePrivateChannelMembers) { - c.SetPermissionError(model.PermissionManagePrivateChannelMembers) - return + if channel.Type == model.ChannelTypePrivate { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionManagePrivateChannelMembers); !ok { + c.SetPermissionError(model.PermissionManagePrivateChannelMembers) + return + } } } @@ -2235,7 +2259,7 @@ func channelMemberCountsByGroup(c *Context, w http.ResponseWriter, r *http.Reque return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -2457,7 +2481,7 @@ func getDirectOrGroupMessageMembersCommonTeams(c *Context, w http.ResponseWriter return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -2531,12 +2555,12 @@ func canEditChannelBanner(c *Context, originalChannel *model.Channel) { switch originalChannel.Type { case model.ChannelTypePrivate: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelBanner) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePrivateChannelBanner); !ok { c.SetPermissionError(model.PermissionManagePrivateChannelBanner) return } case model.ChannelTypeOpen: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelBanner) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManagePublicChannelBanner); !ok { c.SetPermissionError(model.PermissionManagePublicChannelBanner) return } @@ -2551,7 +2575,7 @@ func getChannelAccessControlAttributes(c *Context, w http.ResponseWriter, r *htt return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } diff --git a/server/channels/api4/channel_bookmark.go b/server/channels/api4/channel_bookmark.go index 50046c8e861..3572d00e3a8 100644 --- a/server/channels/api4/channel_bookmark.go +++ b/server/channels/api4/channel_bookmark.go @@ -59,13 +59,13 @@ func createChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) { switch channel.Type { case model.ChannelTypeOpen: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionAddBookmarkPublicChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionAddBookmarkPublicChannel); !ok { c.SetPermissionError(model.PermissionAddBookmarkPublicChannel) return } case model.ChannelTypePrivate: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionAddBookmarkPrivateChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionAddBookmarkPrivateChannel); !ok { c.SetPermissionError(model.PermissionAddBookmarkPrivateChannel) return } @@ -158,18 +158,23 @@ func updateChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) { return } + isMember := false switch channel.Type { case model.ChannelTypeOpen: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionEditBookmarkPublicChannel) { + ok, member := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionEditBookmarkPublicChannel) + if !ok { c.SetPermissionError(model.PermissionEditBookmarkPublicChannel) return } + isMember = member case model.ChannelTypePrivate: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionEditBookmarkPrivateChannel) { + ok, member := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionEditBookmarkPrivateChannel) + if !ok { c.SetPermissionError(model.PermissionEditBookmarkPrivateChannel) return } + isMember = member case model.ChannelTypeGroup, model.ChannelTypeDirect: // Any member of DM/GMs but guests can manage channel bookmarks @@ -178,6 +183,7 @@ func updateChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) { return } + isMember = true user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId) if gAppErr != nil { c.Err = gAppErr @@ -201,6 +207,10 @@ func updateChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) { return } + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() auditRec.AddEventResultState(updateChannelBookmarkResponse) auditRec.AddEventObjectType("updateChannelBookmarkResponse") @@ -250,19 +260,22 @@ func updateChannelBookmarkSortOrder(c *Context, w http.ResponseWriter, r *http.R return } + isMember := false switch channel.Type { case model.ChannelTypeOpen: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionOrderBookmarkPublicChannel) { + ok, member := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionOrderBookmarkPublicChannel) + if !ok { c.SetPermissionError(model.PermissionOrderBookmarkPublicChannel) return } - + isMember = member case model.ChannelTypePrivate: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionOrderBookmarkPrivateChannel) { + ok, member := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionOrderBookmarkPrivateChannel) + if !ok { c.SetPermissionError(model.PermissionOrderBookmarkPrivateChannel) return } - + isMember = member case model.ChannelTypeGroup, model.ChannelTypeDirect: // Any member of DM/GMs but guests can manage channel bookmarks if _, errGet := c.App.GetChannelMember(c.AppContext, channel.Id, c.AppContext.Session().UserId); errGet != nil { @@ -270,6 +283,7 @@ func updateChannelBookmarkSortOrder(c *Context, w http.ResponseWriter, r *http.R return } + isMember = true user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId) if gAppErr != nil { c.Err = gAppErr @@ -292,6 +306,10 @@ func updateChannelBookmarkSortOrder(c *Context, w http.ResponseWriter, r *http.R return } + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + for _, b := range bookmarks { if b.Id == c.Params.ChannelBookmarkId { auditRec.AddEventResultState(b) @@ -335,19 +353,22 @@ func deleteChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) { return } + isMember := false switch channel.Type { case model.ChannelTypeOpen: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionDeleteBookmarkPublicChannel) { + ok, member := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionDeleteBookmarkPublicChannel) + if !ok { c.SetPermissionError(model.PermissionDeleteBookmarkPublicChannel) return } - + isMember = member case model.ChannelTypePrivate: - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionDeleteBookmarkPrivateChannel) { + ok, member := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionDeleteBookmarkPrivateChannel) + if !ok { c.SetPermissionError(model.PermissionDeleteBookmarkPrivateChannel) return } - + isMember = member case model.ChannelTypeGroup, model.ChannelTypeDirect: // Any member of DM/GMs but guests can manage channel bookmarks if _, errGet := c.App.GetChannelMember(c.AppContext, channel.Id, c.AppContext.Session().UserId); errGet != nil { @@ -355,6 +376,7 @@ func deleteChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) { return } + isMember = true user, gAppErr := c.App.GetUser(c.AppContext.Session().UserId) if gAppErr != nil { c.Err = gAppErr @@ -390,6 +412,10 @@ func deleteChannelBookmark(c *Context, w http.ResponseWriter, r *http.Request) { return } + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() auditRec.AddEventResultState(bookmark) c.LogAudit("bookmark=" + bookmark.DisplayName) @@ -416,7 +442,8 @@ func listChannelBookmarksForChannel(c *Context, w http.ResponseWriter, r *http.R return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + hasPermission, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + if !hasPermission { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -427,6 +454,13 @@ func listChannelBookmarksForChannel(c *Context, w http.ResponseWriter, r *http.R return } + auditRec := c.MakeAuditRecord(model.AuditEventListChannelBookmarksForChannel, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "channel_id", c.Params.ChannelId) + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + if err := json.NewEncoder(w).Encode(bookmarks); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } diff --git a/server/channels/api4/command.go b/server/channels/api4/command.go index 81f18d2b666..e50830a1e4e 100644 --- a/server/channels/api4/command.go +++ b/server/channels/api4/command.go @@ -371,7 +371,7 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { model.AddEventParameterAuditableToAuditRec(auditRec, "command_args", &commandArgs) // Checks that user is a member of the specified channel, and that they have permission to create a post in it. - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), commandArgs.ChannelId, model.PermissionCreatePost) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), commandArgs.ChannelId, model.PermissionCreatePost); !ok { c.SetPermissionError(model.PermissionCreatePost) return } diff --git a/server/channels/api4/content_flagging.go b/server/channels/api4/content_flagging.go index 5540b6a82dd..47598fef666 100644 --- a/server/channels/api4/content_flagging.go +++ b/server/channels/api4/content_flagging.go @@ -169,7 +169,7 @@ func flagPost(c *Context, w http.ResponseWriter, r *http.Request) { model.AddEventParameterToAuditRec(auditRec, "postId", postId) model.AddEventParameterToAuditRec(auditRec, "userId", userId) - post, appErr := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false) + post, appErr, _ := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false) if appErr != nil { c.Err = appErr return @@ -341,7 +341,7 @@ func getFlaggedPost(c *Context, w http.ResponseWriter, r *http.Request) { } post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, &model.PreparePostForClientOpts{IncludePriority: true, RetainContent: true, IncludeDeleted: true}) - post, err := c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId) + post, isMemberForPreviews, err := c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId) if err != nil { c.Err = err return @@ -352,6 +352,14 @@ func getFlaggedPost(c *Context, w http.ResponseWriter, r *http.Request) { return } + if !isMemberForPreviews { + previewPost := post.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() } diff --git a/server/channels/api4/drafts.go b/server/channels/api4/drafts.go index a1a8d02f4e2..499e68bfe88 100644 --- a/server/channels/api4/drafts.go +++ b/server/channels/api4/drafts.go @@ -38,7 +38,7 @@ func upsertDraft(c *Context, w http.ResponseWriter, r *http.Request) { hasPermission := false - if c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), draft.ChannelId, model.PermissionCreatePost) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), draft.ChannelId, model.PermissionCreatePost); ok { hasPermission = true } else if channel, err := c.App.GetChannel(c.AppContext, draft.ChannelId); err == nil { // Temporary permission check method until advanced permissions, please do not copy diff --git a/server/channels/api4/file.go b/server/channels/api4/file.go index ed4fdd4909f..633a4112481 100644 --- a/server/channels/api4/file.go +++ b/server/channels/api4/file.go @@ -142,7 +142,7 @@ func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.F defer c.LogAuditRec(auditRec) model.AddEventParameterToAuditRec(auditRec, "channel_id", c.Params.ChannelId) - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionUploadFile) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return nil } @@ -316,7 +316,7 @@ NextPart: if c.Err != nil { return nil } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionUploadFile) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return nil } @@ -429,7 +429,7 @@ func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader, if c.Err != nil { return nil } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionUploadFile) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return nil } @@ -570,8 +570,8 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { model.AddEventParameterAuditableToAuditRec(auditRec, "file", fileInfo) + perm, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) if !isContentReviewer { - perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) if fileInfo.CreatorId == model.BookmarkFileOwner { if !perm { c.SetPermissionError(model.PermissionReadChannelContent) @@ -594,6 +594,10 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.Success() web.WriteFileResponse(fileInfo.Name, fileInfo.MimeType, fileInfo.Size, time.Unix(0, fileInfo.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) + + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { @@ -615,7 +619,7 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + perm, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) if info.CreatorId == model.BookmarkFileOwner { if !perm { c.SetPermissionError(model.PermissionReadChannelContent) @@ -640,6 +644,13 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { defer fileReader.Close() web.WriteFileResponse(info.Name, ThumbnailImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) + + auditRec := c.MakeAuditRecord(model.AuditEventGetFileThumbnail, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "file_id", c.Params.FileId) + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { @@ -669,7 +680,7 @@ func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + perm, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) if info.CreatorId == model.BookmarkFileOwner { if !perm { c.SetPermissionError(model.PermissionReadChannelContent) @@ -685,6 +696,10 @@ func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { return } + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + resp := make(map[string]string) link := c.App.GeneratePublicLink(c.GetSiteURLHeader(), info) resp["link"] = link @@ -715,7 +730,7 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + perm, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) if info.CreatorId == model.BookmarkFileOwner { if !perm { c.SetPermissionError(model.PermissionReadChannelContent) @@ -740,6 +755,13 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) { defer fileReader.Close() web.WriteFileResponse(info.Name, PreviewImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) + + auditRec := c.MakeAuditRecord(model.AuditEventGetFilePreview, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "file_id", c.Params.FileId) + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { @@ -760,7 +782,7 @@ func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + perm, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) if info.CreatorId == model.BookmarkFileOwner { if !perm { c.SetPermissionError(model.PermissionReadChannelContent) @@ -775,6 +797,14 @@ func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { if err := json.NewEncoder(w).Encode(info); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventGetFileInfo, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "file_id", c.Params.FileId) + + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { @@ -879,7 +909,7 @@ func searchFiles(c *Context, w http.ResponseWriter, r *http.Request, teamID stri startTime := time.Now() - results, err := c.App.SearchFilesInTeamForUser(c.AppContext, terms, c.AppContext.Session().UserId, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage) + results, allFilesHaveMembership, err := c.App.SearchFilesInTeamForUser(c.AppContext, terms, c.AppContext.Session().UserId, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage) elapsedTime := float64(time.Since(startTime)) / float64(time.Second) metrics := c.App.Metrics() @@ -897,6 +927,16 @@ func searchFiles(c *Context, w http.ResponseWriter, r *http.Request, teamID stri if err := json.NewEncoder(w).Encode(results); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventSearchFiles, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterAuditableToAuditRec(auditRec, "search_params", params) + + if !allFilesHaveMembership { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + + auditRec.Success() } func setInaccessibleFileHeader(w http.ResponseWriter, appErr *model.AppError) { diff --git a/server/channels/api4/group.go b/server/channels/api4/group.go index 5f679371e75..d6eb107990f 100644 --- a/server/channels/api4/group.go +++ b/server/channels/api4/group.go @@ -697,7 +697,7 @@ func verifyLinkUnlinkPermission(c *Context, syncableType model.GroupSyncableType permission = model.PermissionManagePublicChannelMembers } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), syncableID, permission) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), syncableID, permission); !ok { return model.MakePermissionError(c.AppContext.Session(), []*model.Permission{permission}) } } @@ -972,7 +972,7 @@ func getGroupsByChannelCommon(c *Context, r *http.Request) ([]byte, *model.AppEr } else { permission = model.PermissionReadPublicChannelGroups } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, permission) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, permission); !ok { return nil, model.MakePermissionError(c.AppContext.Session(), []*model.Permission{permission}) } @@ -1138,7 +1138,7 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) { } else { permission = model.PermissionManagePublicChannelMembers } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), NotAssociatedToChannelID, permission) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), NotAssociatedToChannelID, permission); !ok { c.SetPermissionError(permission) return } @@ -1157,7 +1157,7 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) { } else { permission = model.PermissionManagePublicChannelMembers } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), ChannelIDForMemberCount, permission) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), ChannelIDForMemberCount, permission); !ok { c.SetPermissionError(permission) return } diff --git a/server/channels/api4/integration_action.go b/server/channels/api4/integration_action.go index 53dfb6cffde..c0ab52462a1 100644 --- a/server/channels/api4/integration_action.go +++ b/server/channels/api4/integration_action.go @@ -67,12 +67,12 @@ func doPostAction(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + if ok, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } } else { - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.PostId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.PostId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -136,7 +136,7 @@ func submitDialog(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + if ok, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -189,7 +189,7 @@ func lookupDialog(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + if ok, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } diff --git a/server/channels/api4/post.go b/server/channels/api4/post.go index 827d362a64b..dcc8129aeee 100644 --- a/server/channels/api4/post.go +++ b/server/channels/api4/post.go @@ -69,7 +69,7 @@ func createPostChecks(where string, c *Context, post *model.Post) { } if len(post.FileIds) > 0 { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionUploadFile) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return } @@ -117,7 +117,7 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) { } } - rp, err := c.App.CreatePostAsUser(c.AppContext, c.App.PostWithProxyRemovedFromImageURLs(&post), c.AppContext.Session().Id, setOnlineBool) + rp, isMemberForPreviews, err := c.App.CreatePostAsUser(c.AppContext, c.App.PostWithProxyRemovedFromImageURLs(&post), c.AppContext.Session().Id, setOnlineBool) if err != nil { c.Err = err return @@ -126,6 +126,14 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.AddEventResultState(rp) auditRec.AddEventObjectType("post") + if !isMemberForPreviews { + previewPost := rp.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + if setOnlineBool { c.App.SetStatusOnline(c.AppContext.Session().UserId, false) } @@ -189,12 +197,13 @@ func createEphemeralPost(c *Context, w http.ResponseWriter, r *http.Request) { return } - rp := c.App.SendEphemeralPost(c.AppContext, ephRequest.UserID, c.App.PostWithProxyRemovedFromImageURLs(ephRequest.Post)) + // We prepare again the post here, so we can ignore the isMemberForPreviews return value from SendEphemeralPost + rp, _ := c.App.SendEphemeralPost(c.AppContext, ephRequest.UserID, c.App.PostWithProxyRemovedFromImageURLs(ephRequest.Post)) w.WriteHeader(http.StatusCreated) rp = model.AddPostActionCookies(rp, c.App.PostActionCookieSecret()) rp = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, rp, &model.PreparePostForClientOpts{IsNewPost: true, IncludePriority: true}) - rp, err := c.App.SanitizePostMetadataForUser(c.AppContext, rp, c.AppContext.Session().UserId) + rp, isMemberForPreviews, err := c.App.SanitizePostMetadataForUser(c.AppContext, rp, c.AppContext.Session().UserId) if err != nil { c.Err = err return @@ -202,6 +211,19 @@ func createEphemeralPost(c *Context, w http.ResponseWriter, r *http.Request) { if err := rp.EncodeJSON(w); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventCreateEphemeralPost, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "post_id", rp.Id) + + if !isMemberForPreviews { + previewPost := rp.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() } func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) { @@ -250,7 +272,8 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + hasPermission, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + if !hasPermission { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -301,7 +324,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) { // to ensure they only reference posts that are actually in the response c.App.AddCursorIdsForPostList(clientPostList, afterPost, beforePost, since, page, perPage, collapsedThreads) - clientPostList, err = c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) + clientPostList, isMemberForAllPreviews, err := c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) if err != nil { c.Err = err return @@ -310,6 +333,16 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) { if err := clientPostList.EncodeJSON(w); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventGetPostsForChannel, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "channel_id", channelId) + if !isMember || !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access_on_previews", true) + } + } } func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *http.Request) { @@ -330,7 +363,8 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht c.Err = err return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + hasPermission, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + if !hasPermission { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -371,7 +405,7 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht // to ensure they only reference posts that are actually in the response clientPostList.NextPostId = c.App.GetNextPostIdFromPostList(clientPostList, collapsedThreads) clientPostList.PrevPostId = c.App.GetPrevPostIdFromPostList(clientPostList, collapsedThreads) - clientPostList, err = c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) + clientPostList, isMemberForAllPreviews, err := c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) if err != nil { c.Err = err return @@ -383,6 +417,17 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht if err := clientPostList.EncodeJSON(w); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventGetPostsForChannelAroundLastUnread, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "channel_id", channelId) + + if !isMember || !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access_on_previews", true) + } + } } func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) { @@ -430,6 +475,7 @@ func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) pl := model.NewPostList() channelReadPermission := make(map[string]bool) + isMemberForAllPosts := true for _, post := range posts.Posts { allowed, ok := channelReadPermission[post.ChannelId] @@ -441,8 +487,11 @@ func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) if !ok { continue } - if c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + + hasPermission, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + if hasPermission { allowed = true + isMemberForAllPosts = isMemberForAllPosts && isMember } channelReadPermission[post.ChannelId] = allowed @@ -458,11 +507,23 @@ func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) pl.SortByCreateAt() clientPostList := c.App.PreparePostListForClient(c.AppContext, pl) - clientPostList, err = c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) + clientPostList, isMemberForAllPreviews, err := c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) if err != nil { c.Err = err return } + + auditRec := c.MakeAuditRecord(model.AuditEventGetFlaggedPosts, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "channel_id", channelId) + + if !isMemberForAllPosts || !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access_on_previews", true) + } + } + if err := clientPostList.EncodeJSON(w); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } @@ -481,7 +542,7 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) { return } - post, err := c.App.GetPostIfAuthorized(c.AppContext, c.Params.PostId, c.AppContext.Session(), includeDeleted) + post, err, isMember := c.App.GetPostIfAuthorized(c.AppContext, c.Params.PostId, c.AppContext.Session(), includeDeleted) if err != nil { c.Err = err @@ -494,7 +555,7 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) { } post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, &model.PreparePostForClientOpts{IncludePriority: true}) - post, err = c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId) + post, previewIsMember, err := c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId) if err != nil { c.Err = err return @@ -508,6 +569,20 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) { if err := post.EncodeJSON(w); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventGetPost, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "post_id", c.Params.PostId) + + if !isMember || !previewIsMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !previewIsMember { + previewPost := post.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + } + } } // getPostsByIds also sets a header to indicate, if posts were truncated as per the cloud plan's limit. @@ -547,16 +622,20 @@ func getPostsByIds(c *Context, w http.ResponseWriter, r *http.Request) { } var posts = []*model.Post{} + isMemberForAllPosts := true for _, post := range postsList { channel, ok := channelMap[post.ChannelId] if !ok { continue } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + hasPermission, isMemberForCurrentPost := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + if !hasPermission { continue } + isMemberForAllPosts = isMemberForAllPosts && isMemberForCurrentPost + post = c.App.PreparePostForClient(c.AppContext, post, &model.PreparePostForClientOpts{IncludePriority: true}) post.StripActionIntegrations() posts = append(posts, post) @@ -567,6 +646,14 @@ func getPostsByIds(c *Context, w http.ResponseWriter, r *http.Request) { if err := json.NewEncoder(w).Encode(posts); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventGetPostsByIds, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "post_ids", postIDs) + + if !isMemberForAllPosts { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } func getEditHistoryForPost(c *Context, w http.ResponseWriter, r *http.Request) { @@ -581,7 +668,8 @@ func getEditHistoryForPost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditPost) { + ok, isMember := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditPost) + if !ok { c.SetPermissionError(model.PermissionEditPost) return } @@ -597,6 +685,14 @@ func getEditHistoryForPost(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventGetEditHistoryForPost, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "post_id", c.Params.PostId) + + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + if err := json.NewEncoder(w).Encode(postsList); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } @@ -636,12 +732,12 @@ func deletePost(c *Context, w http.ResponseWriter, _ *http.Request) { auditRec.AddEventObjectType("post") if c.AppContext.Session().UserId == post.UserId { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionDeletePost) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionDeletePost); !ok { c.SetPermissionError(model.PermissionDeletePost) return } } else { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionDeleteOthersPosts) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionDeleteOthersPosts); !ok { c.SetPermissionError(model.PermissionDeleteOthersPosts) return } @@ -767,7 +863,8 @@ func getPostThread(c *Context, w http.ResponseWriter, r *http.Request) { return } - if _, err = c.App.GetPostIfAuthorized(c.AppContext, post.Id, c.AppContext.Session(), false); err != nil { + var isMember bool + if _, err, isMember = c.App.GetPostIfAuthorized(c.AppContext, post.Id, c.AppContext.Session(), false); err != nil { c.Err = err return } @@ -777,7 +874,7 @@ func getPostThread(c *Context, w http.ResponseWriter, r *http.Request) { } clientPostList := c.App.PreparePostListForClient(c.AppContext, list) - clientPostList, err = c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) + clientPostList, isMemberForAllPreviews, err := c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) if err != nil { c.Err = err return @@ -788,6 +885,17 @@ func getPostThread(c *Context, w http.ResponseWriter, r *http.Request) { if err := clientPostList.EncodeJSON(w); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventGetPostThread, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "post_id", c.Params.PostId) + + if !isMember || !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access_on_previews", true) + } + } } func searchPostsInTeam(c *Context, w http.ResponseWriter, r *http.Request) { @@ -852,7 +960,7 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request, teamId stri startTime := time.Now() - results, err := c.App.SearchPostsForUser(c.AppContext, terms, c.AppContext.Session().UserId, teamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage) + results, allPostHaveMembership, err := c.App.SearchPostsForUser(c.AppContext, terms, c.AppContext.Session().UserId, teamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage) elapsedTime := float64(time.Since(startTime)) / float64(time.Second) metrics := c.App.Metrics() @@ -867,12 +975,19 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request, teamId stri } clientPostList := c.App.PreparePostListForClient(c.AppContext, results.PostList) - clientPostList, err = c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) + clientPostList, isMemberForAllPreviews, err := c.App.SanitizePostListMetadataForUser(c.AppContext, clientPostList, c.AppContext.Session().UserId) if err != nil { c.Err = err return } + if !allPostHaveMembership || !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForAllPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access_on_previews", true) + } + } + results = model.MakePostSearchResults(clientPostList, results.Matches) model.AddEventParameterAuditableToAuditRec(auditRec, "search_results", results) auditRec.Success() @@ -916,7 +1031,8 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditPost) { + ok, isMember := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditPost) + if !ok { c.SetPermissionError(model.PermissionEditPost) return } @@ -937,7 +1053,8 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { } if c.AppContext.Session().UserId != originalPost.UserId { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditOthersPosts) { + // We don't need to check the member here, since we already checked it above + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionEditOthersPosts); !ok { c.SetPermissionError(model.PermissionEditOthersPosts) return } @@ -950,12 +1067,22 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - rpost, err := c.App.UpdatePost(c.AppContext, c.App.PostWithProxyRemovedFromImageURLs(&post), &model.UpdatePostOptions{SafeUpdate: false}) + rpost, isMemberForPreviews, err := c.App.UpdatePost(c.AppContext, c.App.PostWithProxyRemovedFromImageURLs(&post), &model.UpdatePostOptions{SafeUpdate: false}) if err != nil { c.Err = err return } + if !isMember || !isMemberForPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForPreviews { + previewPost := rpost.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + } + } + auditRec.Success() auditRec.AddEventResultState(rpost) @@ -988,7 +1115,7 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { } } - postPatchChecks(c, auditRec, post.Message) + isMember := postPatchChecks(c, auditRec, post.Message) if c.Err != nil { return } @@ -1006,12 +1133,16 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { } } - patchedPost, err := c.App.PatchPost(c.AppContext, c.Params.PostId, c.App.PostPatchWithProxyRemovedFromImageURLs(&post), nil) + patchedPost, isMemberForPReviews, err := c.App.PatchPost(c.AppContext, c.Params.PostId, c.App.PostPatchWithProxyRemovedFromImageURLs(&post), nil) if err != nil { c.Err = err return } + if !isMember || !isMemberForPReviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() auditRec.AddEventResultState(patchedPost) @@ -1020,11 +1151,11 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { } } -func postPatchChecks(c *Context, auditRec *model.AuditRecord, message *string) { +func postPatchChecks(c *Context, auditRec *model.AuditRecord, message *string) bool { originalPost, err := c.App.GetSinglePost(c.AppContext, c.Params.PostId, false) if err != nil { c.SetPermissionError(model.PermissionEditPost) - return + return false } auditRec.AddEventPriorState(originalPost) auditRec.AddEventObjectType("post") @@ -1037,15 +1168,18 @@ func postPatchChecks(c *Context, auditRec *model.AuditRecord, message *string) { permission = model.PermissionEditOthersPosts } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, permission) { + ok, isMember := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, permission) + if !ok { c.SetPermissionError(permission) - return + return false } if *c.App.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > originalPost.CreateAt+int64(*c.App.Config().ServiceSettings.PostEditTimeLimit*1000) && message != nil { c.Err = model.NewAppError("patchPost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) - return + return isMember } + + return isMember } func setPostUnread(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1061,7 +1195,7 @@ func setPostUnread(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(model.PermissionEditOtherUsers) return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.PostId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.PostId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -1086,7 +1220,7 @@ func setPostReminder(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(model.PermissionEditOtherUsers) return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.PostId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.PostId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -1129,7 +1263,8 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, isPinned bool) { c.Err = err return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + ok, isMember := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + if !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -1137,11 +1272,22 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, isPinned bool) { patch := &model.PostPatch{} patch.IsPinned = model.NewPointer(isPinned) - patchedPost, err := c.App.PatchPost(c.AppContext, c.Params.PostId, patch, nil) + patchedPost, isMemberForPreviews, err := c.App.PatchPost(c.AppContext, c.Params.PostId, patch, nil) if err != nil { c.Err = err return } + + if !isMember || !isMemberForPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForPreviews { + previewPost := patchedPost.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + } + } + auditRec.AddEventResultState(patchedPost) auditRec.Success() @@ -1173,7 +1319,7 @@ func acknowledgePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.PostId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.PostId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -1212,7 +1358,7 @@ func unacknowledgePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.PostId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.PostId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -1291,7 +1437,7 @@ func moveThread(c *Context, w http.ResponseWriter, r *http.Request) { return } - sourcePost, err := c.App.GetPostIfAuthorized(c.AppContext, c.Params.PostId, c.AppContext.Session(), false) + sourcePost, err, _ := c.App.GetPostIfAuthorized(c.AppContext, c.Params.PostId, c.AppContext.Session(), false) if err != nil { c.Err = err if err.Id == "app.post.cloud.get.app_error" { @@ -1318,7 +1464,8 @@ func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.PostId, model.PermissionReadChannelContent) { + ok, isMember := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.PostId) + if !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -1345,6 +1492,14 @@ func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventGetFileInfosForPost, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "post_id", c.Params.PostId) + + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + w.Header().Set("Cache-Control", "max-age=2592000, private") w.Header().Set(model.HeaderEtagServer, model.GetEtagForFileInfos(infos)) if _, err := w.Write(js); err != nil { @@ -1358,7 +1513,86 @@ func getPostInfo(c *Context, w http.ResponseWriter, r *http.Request) { return } - info, appErr := c.App.GetPostInfo(c.AppContext, c.Params.PostId) + userID := c.AppContext.Session().UserId + post, appErr := c.App.GetSinglePost(c.AppContext, c.Params.PostId, false) + if appErr != nil { + c.Err = appErr + return + } + + channel, appErr := c.App.GetChannel(c.AppContext, post.ChannelId) + if appErr != nil { + c.Err = appErr + return + } + + notFoundError := model.NewAppError("GetPostInfo", "app.post.get.app_error", nil, "", http.StatusNotFound) + + var team *model.Team + hasPermissionToAccessTeam := false + if channel.TeamId != "" { + team, appErr = c.App.GetTeam(channel.TeamId) + if appErr != nil { + c.Err = appErr + return + } + + var teamMember *model.TeamMember + teamMember, appErr = c.App.GetTeamMember(c.AppContext, channel.TeamId, userID) + if appErr != nil && appErr.StatusCode != http.StatusNotFound { + c.Err = appErr + return + } + + if appErr == nil { + if teamMember.DeleteAt == 0 { + hasPermissionToAccessTeam = true + } + } + + if !hasPermissionToAccessTeam { + if team.AllowOpenInvite { + hasPermissionToAccessTeam = c.App.HasPermissionToTeam(c.AppContext, userID, team.Id, model.PermissionJoinPublicTeams) + } else { + hasPermissionToAccessTeam = c.App.HasPermissionToTeam(c.AppContext, userID, team.Id, model.PermissionJoinPrivateTeams) + } + } + } else { + // This happens in case of DMs and GMs. + hasPermissionToAccessTeam = true + } + + if !hasPermissionToAccessTeam { + c.Err = notFoundError + return + } + + hasPermissionToAccessChannel := false + hasJoinedChannel := false + + _, channelMemberErr := c.App.GetChannelMember(c.AppContext, channel.Id, userID) + + if channelMemberErr == nil { + hasPermissionToAccessChannel = true + hasJoinedChannel = true + } + + if !hasPermissionToAccessChannel { + if channel.Type == model.ChannelTypeOpen { + hasPermissionToAccessChannel = true + } else if channel.Type == model.ChannelTypePrivate { + hasPermissionToAccessChannel, _ = c.App.HasPermissionToChannel(c.AppContext, userID, channel.Id, model.PermissionManagePrivateChannelMembers) + } else if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup { + hasPermissionToAccessChannel, _ = c.App.HasPermissionToReadChannel(c.AppContext, userID, channel) + } + } + + if !hasPermissionToAccessChannel { + c.Err = notFoundError + return + } + + info, appErr := c.App.GetPostInfo(c.AppContext, c.Params.PostId, channel, team, userID, hasJoinedChannel) if appErr != nil { c.Err = appErr return @@ -1405,17 +1639,27 @@ func restorePostVersion(c *Context, w http.ResponseWriter, r *http.Request) { return } - postPatchChecks(c, auditRec, &toRestorePost.Message) + isMember := postPatchChecks(c, auditRec, &toRestorePost.Message) if c.Err != nil { return } - updatedPost, appErr := c.App.RestorePostVersion(c.AppContext, c.AppContext.Session().UserId, c.Params.PostId, restoreVersionId) + updatedPost, isMemberForPreview, appErr := c.App.RestorePostVersion(c.AppContext, c.AppContext.Session().UserId, c.Params.PostId, restoreVersionId) if appErr != nil { c.Err = appErr return } + if !isMember || !isMemberForPreview { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + if !isMemberForPreview { + previewPost := updatedPost.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + } + } + auditRec.Success() auditRec.AddEventResultState(updatedPost) @@ -1495,7 +1739,7 @@ func revealPost(c *Context, w http.ResponseWriter, r *http.Request) { model.AddEventParameterToAuditRec(auditRec, "post_id", postId) model.AddEventParameterToAuditRec(auditRec, "user_id", userId) - post, err := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false) + post, err, isMember := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false) if err != nil { c.Err = err if err.Id == "app.post.cloud.get.app_error" { @@ -1528,6 +1772,10 @@ func revealPost(c *Context, w http.ResponseWriter, r *http.Request) { return } + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() auditRec.AddEventResultState(revealedPost) @@ -1552,7 +1800,7 @@ func burnPost(c *Context, w http.ResponseWriter, r *http.Request) { model.AddEventParameterToAuditRec(auditRec, "post_id", postId) model.AddEventParameterToAuditRec(auditRec, "user_id", userId) - post, err := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false) + post, err, _ := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false) if err != nil { c.Err = err if err.Id == "app.post.cloud.get.app_error" { diff --git a/server/channels/api4/post_test.go b/server/channels/api4/post_test.go index afb2acac375..23b65090ccf 100644 --- a/server/channels/api4/post_test.go +++ b/server/channels/api4/post_test.go @@ -353,6 +353,17 @@ func TestCreatePost(t *testing.T) { require.Nil(t, appErr) require.Zero(t, *createdPost.RemoteId) }) + t.Run("not logged in", func(t *testing.T) { + resp, err := client.Logout(context.Background()) + require.NoError(t, err) + CheckOKStatus(t, resp) + + post := basicPost() + rpost, resp, err := client.CreatePost(context.Background(), post) + require.Error(t, err) + CheckUnauthorizedStatus(t, resp) + assert.Nil(t, rpost) + }) } func TestCreatePostForPriority(t *testing.T) { @@ -1466,7 +1477,7 @@ func TestUpdatePost(t *testing.T) { fileIds[i] = fileResp.FileInfos[0].Id } - rpost, appErr := th.App.CreatePost(th.Context, &model.Post{ + rpost, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", @@ -1499,7 +1510,7 @@ func TestUpdatePost(t *testing.T) { t.Run("join/leave post", func(t *testing.T) { var rpost2 *model.Post - rpost2, appErr = th.App.CreatePost(th.Context, &model.Post{ + rpost2, _, appErr = th.App.CreatePost(th.Context, &model.Post{ ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", Type: model.PostTypeJoinLeave, @@ -1517,7 +1528,7 @@ func TestUpdatePost(t *testing.T) { CheckBadRequestStatus(t, resp) }) - rpost3, appErr := th.App.CreatePost(th.Context, &model.Post{ + rpost3, _, appErr := th.App.CreatePost(th.Context, &model.Post{ ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", UserId: th.BasicUser.Id, @@ -1549,7 +1560,7 @@ func TestUpdatePost(t *testing.T) { *cfg.ServiceSettings.PostEditTimeLimit = -1 }) - rpost4, appErr := th.App.CreatePost(th.Context, &model.Post{ + rpost4, _, appErr := th.App.CreatePost(th.Context, &model.Post{ ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", UserId: th.BasicUser.Id, @@ -1588,7 +1599,7 @@ func TestUpdatePost(t *testing.T) { }) t.Run("should prevent updating post with files when user lacks upload_file permission in target channel", func(t *testing.T) { - postWithoutFiles, appErr := th.App.CreatePost(th.Context, &model.Post{ + postWithoutFiles, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: channel.Id, Message: "Post without files", @@ -1618,7 +1629,7 @@ func TestUpdatePost(t *testing.T) { }) t.Run("should allow updating post with files when user has upload_file permission", func(t *testing.T) { - postWithoutFiles, appErr := th.App.CreatePost(th.Context, &model.Post{ + postWithoutFiles, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: channel.Id, Message: "Post without files", @@ -1685,7 +1696,7 @@ func TestUpdatePost(t *testing.T) { fileInfo := fileResponse.FileInfos[0] // create new post - post, appErr := th.App.CreatePost(th.Context, &model.Post{ + post, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", @@ -1721,7 +1732,7 @@ func TestUpdatePost(t *testing.T) { fileInfo := fileResponse.FileInfos[0] // create new post - post, appErr := th.App.CreatePost(th.Context, &model.Post{ + post, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", @@ -1759,7 +1770,7 @@ func TestUpdatePost(t *testing.T) { fileInfo := fileResponse.FileInfos[0] // create new post - post, appErr := th.App.CreatePost(th.Context, &model.Post{ + post, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", @@ -1805,7 +1816,7 @@ func TestUpdatePost(t *testing.T) { fileInfo2 := fileResponse2.FileInfos[0] // create new post - post, appErr := th.App.CreatePost(th.Context, &model.Post{ + post, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", @@ -4418,13 +4429,13 @@ func TestSetChannelUnread(t *testing.T) { t.Run("Unread on a direct channel in a thread", func(t *testing.T) { dc := th.CreateDmChannel(t, th.CreateUser(t)) - rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: u1.Id, CreateAt: now, ChannelId: dc.Id, Message: "root"}, dc, model.CreatePostFlags{}) + rootPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: u1.Id, CreateAt: now, ChannelId: dc.Id, Message: "root"}, dc, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 10, ChannelId: dc.Id, Message: "reply 1"}, dc, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 10, ChannelId: dc.Id, Message: "reply 1"}, dc, model.CreatePostFlags{}) require.Nil(t, appErr) - reply2, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 20, ChannelId: dc.Id, Message: "reply 2"}, dc, model.CreatePostFlags{}) + reply2, _, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 20, ChannelId: dc.Id, Message: "reply 2"}, dc, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 30, ChannelId: dc.Id, Message: "reply 3"}, dc, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 30, ChannelId: dc.Id, Message: "reply 3"}, dc, model.CreatePostFlags{}) require.Nil(t, appErr) // Ensure that post have been read @@ -4523,19 +4534,19 @@ func TestSetPostUnreadWithoutCollapsedThreads(t *testing.T) { // user1: a root post // user2: Another root mention @u1 user1Mention := " @" + th.BasicUser.Username - rootPost1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "first root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + rootPost1, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "first root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hello"}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hello"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - replyPost1, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + replyPost1, _, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another reply"}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another reply"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "a root post"}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "a root post"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) t.Run("Mark reply post as unread", func(t *testing.T) { @@ -4639,7 +4650,7 @@ func TestGetEditHistoryForPost(t *testing.T) { UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) time.Sleep(1 * time.Millisecond) @@ -4710,7 +4721,7 @@ func TestGetEditHistoryForPost(t *testing.T) { FileIds: []string{fileInfo1.Id, fileInfo2.Id}, } - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) require.Contains(t, createdPost.FileIds, fileInfo1.Id) require.Contains(t, createdPost.FileIds, fileInfo2.Id) @@ -4864,7 +4875,7 @@ func TestCreatePostNotificationsWithCRT(t *testing.T) { require.NoError(t, err) // post a reply on the thread - _, appErr := th.App.CreatePostAsUser(th.Context, tc.post, th.Context.Session().Id, false) + _, _, appErr := th.App.CreatePostAsUser(th.Context, tc.post, th.Context.Session().Id, false) require.Nil(t, appErr) var caught bool diff --git a/server/channels/api4/post_utils.go b/server/channels/api4/post_utils.go index 05c50a7d0a4..edc5ee720d4 100644 --- a/server/channels/api4/post_utils.go +++ b/server/channels/api4/post_utils.go @@ -10,7 +10,7 @@ import ( func userCreatePostPermissionCheckWithContext(c *Context, channelId string) { hasPermission := false - if c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionCreatePost) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionCreatePost); ok { hasPermission = true } else if channel, err := c.App.GetChannel(c.AppContext, channelId); err == nil { // Temporary permission check method until advanced permissions, please do not copy @@ -63,7 +63,7 @@ func checkUploadFilePermissionForNewFiles(c *Context, newFileIds []string, origi } if hasNewFiles { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionUploadFile) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), originalPost.ChannelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return } diff --git a/server/channels/api4/preference.go b/server/channels/api4/preference.go index fbe16433cea..0bd11fd9773 100644 --- a/server/channels/api4/preference.go +++ b/server/channels/api4/preference.go @@ -131,7 +131,7 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + if ok, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } diff --git a/server/channels/api4/reaction.go b/server/channels/api4/reaction.go index a9fb7c0e7d6..cb67d08a972 100644 --- a/server/channels/api4/reaction.go +++ b/server/channels/api4/reaction.go @@ -57,7 +57,7 @@ func getReactions(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.PostId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.PostId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -117,7 +117,7 @@ func getBulkReactions(c *Context, w http.ResponseWriter, r *http.Request) { return } for _, postId := range postIds { - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), postId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), postId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } diff --git a/server/channels/api4/report_test.go b/server/channels/api4/report_test.go index 41ac902f885..d7e47d798c3 100644 --- a/server/channels/api4/report_test.go +++ b/server/channels/api4/report_test.go @@ -205,7 +205,7 @@ func TestGetPostsForReporting(t *testing.T) { CreateAt: baseTime + (int64(i) * 1000), // 1 second apart UpdateAt: baseTime + (int64(i) * 1000), } - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) testPosts = append(testPosts, createdPost) } @@ -596,7 +596,7 @@ func TestGetPostsForReporting(t *testing.T) { CreateAt: baseTime + (int64(20+i) * 1000), // After all test posts UpdateAt: baseTime + (int64(20+i) * 1000), } - _, appErr := th.App.CreatePost(th.Context, systemPost, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr := th.App.CreatePost(th.Context, systemPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) } diff --git a/server/channels/api4/scheduled_post.go b/server/channels/api4/scheduled_post.go index b1a1d2601b3..84a9f0818cd 100644 --- a/server/channels/api4/scheduled_post.go +++ b/server/channels/api4/scheduled_post.go @@ -75,7 +75,7 @@ func createSchedulePost(c *Context, w http.ResponseWriter, r *http.Request) { model.AddEventParameterAuditableToAuditRec(auditRec, "scheduledPost", &scheduledPost) if len(scheduledPost.FileIds) > 0 { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), scheduledPost.ChannelId, model.PermissionUploadFile) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), scheduledPost.ChannelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return } diff --git a/server/channels/api4/shared_channel.go b/server/channels/api4/shared_channel.go index afb13444d14..130960b7038 100644 --- a/server/channels/api4/shared_channel.go +++ b/server/channels/api4/shared_channel.go @@ -261,7 +261,7 @@ func getSharedChannelRemotes(c *Context, w http.ResponseWriter, r *http.Request) return } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } diff --git a/server/channels/api4/shared_channel_metadata_test.go b/server/channels/api4/shared_channel_metadata_test.go index 8f374f53a5c..a1f43aaabd9 100644 --- a/server/channels/api4/shared_channel_metadata_test.go +++ b/server/channels/api4/shared_channel_metadata_test.go @@ -148,7 +148,7 @@ func TestSharedChannelPostMetadataSync(t *testing.T) { }) // Create a local post with priority metadata - originalPost, appErr := th.App.CreatePost(th.Context, &model.Post{ + originalPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: testChannel.Id, Message: "Test post with priority metadata @" + th.BasicUser2.Username, @@ -203,7 +203,7 @@ func TestSharedChannelPostMetadataSync(t *testing.T) { }) // Create post with acknowledgement request - originalPost, appErr := th.App.CreatePost(th.Context, &model.Post{ + originalPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: testChannel.Id, Message: "Test post requesting acknowledgements @" + th.BasicUser2.Username, @@ -273,7 +273,7 @@ func TestSharedChannelPostMetadataSync(t *testing.T) { }) // Create post with acknowledgement request - originalPost, appErr := th.App.CreatePost(th.Context, &model.Post{ + originalPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: testChannel.Id, Message: "Test post for ack count sync @" + th.BasicUser2.Username, @@ -369,7 +369,7 @@ func TestSharedChannelPostMetadataSync(t *testing.T) { }) // Create post with persistent notifications enabled - _, appErr := th.App.CreatePost(th.Context, &model.Post{ + _, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: testChannel.Id, Message: "Test post with persistent notifications @" + th.BasicUser2.Username, @@ -534,7 +534,7 @@ func TestSharedChannelPostMetadataSync(t *testing.T) { // STEP 1: Server A creates a post with acknowledgement request t.Log("=== STEP 1: Server A creates post with ack request ===") - originalPost, appErr := th.App.CreatePost(th.Context, &model.Post{ + originalPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: testChannel.Id, Message: "Cross-cluster ack test - please acknowledge", diff --git a/server/channels/api4/system.go b/server/channels/api4/system.go index 81cc85c12d8..fce0819847e 100644 --- a/server/channels/api4/system.go +++ b/server/channels/api4/system.go @@ -726,7 +726,9 @@ func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) { } // Return post data only when PostId is passed. if ack.PostId != "" && ack.NotificationType == model.PushTypeMessage { - if _, appErr := c.App.GetPostIfAuthorized(c.AppContext, ack.PostId, c.AppContext.Session(), false); appErr != nil { + var isMember bool + var appErr *model.AppError + if _, appErr, isMember = c.App.GetPostIfAuthorized(c.AppContext, ack.PostId, c.AppContext.Session(), false); appErr != nil { c.Err = appErr return } @@ -746,6 +748,14 @@ func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) { if err2 := json.NewEncoder(w).Encode(msg); err2 != nil { c.Logger.Warn("Error while writing response", mlog.Err(err2)) } + + auditRec := c.MakeAuditRecord(model.AuditEventNotificationAck, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "post_id", ack.PostId) + + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } return diff --git a/server/channels/api4/upload.go b/server/channels/api4/upload.go index 203314bfcb8..33055cd03df 100644 --- a/server/channels/api4/upload.go +++ b/server/channels/api4/upload.go @@ -56,7 +56,7 @@ func createUpload(c *Context, w http.ResponseWriter, r *http.Request) { return } } else { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), us.ChannelId, model.PermissionUploadFile) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), us.ChannelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return } @@ -142,7 +142,10 @@ func uploadData(c *Context, w http.ResponseWriter, r *http.Request) { return } } else { - if us.UserId != c.AppContext.Session().UserId || !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), us.ChannelId, model.PermissionUploadFile) { + if us.UserId != c.AppContext.Session().UserId { + c.SetPermissionError(model.PermissionUploadFile) + return + } else if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), us.ChannelId, model.PermissionUploadFile); !ok { c.SetPermissionError(model.PermissionUploadFile) return } diff --git a/server/channels/api4/user.go b/server/channels/api4/user.go index 68366c07df8..008c5034e6a 100644 --- a/server/channels/api4/user.go +++ b/server/channels/api4/user.go @@ -922,7 +922,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { profiles, appErr = c.App.GetUsersWithoutTeamPage(userGetOptions, c.IsSystemAdmin()) } else if notInChannelId != "" { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), notInChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), notInChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -964,7 +964,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { profiles, appErr = c.App.GetUsersInTeamPage(userGetOptions, c.IsSystemAdmin()) } } else if inChannelId != "" { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), inChannelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), inChannelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -1171,14 +1171,18 @@ func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) { } } - if props.InChannelId != "" && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), props.InChannelId, model.PermissionReadChannel) { - c.SetPermissionError(model.PermissionReadChannel) - return + if props.InChannelId != "" { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), props.InChannelId, model.PermissionReadChannel); !ok { + c.SetPermissionError(model.PermissionReadChannel) + return + } } - if props.NotInChannelId != "" && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), props.NotInChannelId, model.PermissionReadChannel) { - c.SetPermissionError(model.PermissionReadChannel) - return + if props.NotInChannelId != "" { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), props.NotInChannelId, model.PermissionReadChannel); !ok { + c.SetPermissionError(model.PermissionReadChannel) + return + } } if props.TeamId != "" && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), props.TeamId, model.PermissionViewTeam) { @@ -1264,7 +1268,7 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) { } if channelId != "" { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionReadChannel) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionReadChannel); !ok { c.SetPermissionError(model.PermissionReadChannel) return } @@ -3210,7 +3214,7 @@ func publishUserTyping(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.HasPermissionToChannel(c.AppContext, c.Params.UserId, typingRequest.ChannelId, model.PermissionCreatePost) { + if ok, _ := c.App.HasPermissionToChannel(c.AppContext, c.Params.UserId, typingRequest.ChannelId, model.PermissionCreatePost); !ok { c.SetPermissionError(model.PermissionCreatePost) return } @@ -3537,7 +3541,8 @@ func getThreadForUser(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(model.PermissionEditOtherUsers) return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.ThreadId, model.PermissionReadChannelContent) { + ok, isMember := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.ThreadId) + if !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -3559,6 +3564,14 @@ func getThreadForUser(c *Context, w http.ResponseWriter, r *http.Request) { if err := json.NewEncoder(w).Encode(thread); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } + + auditRec := c.MakeAuditRecord(model.AuditEventGetThreadForUser, model.AuditStatusSuccess) + defer c.LogAuditRec(auditRec) + model.AddEventParameterToAuditRec(auditRec, "thread_id", c.Params.ThreadId) + + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } } func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) { @@ -3654,11 +3667,16 @@ func updateReadStateThreadByUser(c *Context, w http.ResponseWriter, r *http.Requ c.SetPermissionError(model.PermissionEditOtherUsers) return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.ThreadId, model.PermissionReadChannelContent) { + ok, isMember := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.ThreadId) + if !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + thread, err := c.App.UpdateThreadReadForUser(c.AppContext, c.AppContext.Session().Id, c.Params.UserId, c.Params.TeamId, c.Params.ThreadId, c.Params.Timestamp) if err != nil { c.Err = err @@ -3690,10 +3708,14 @@ func setUnreadThreadByPostId(c *Context, w http.ResponseWriter, r *http.Request) return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.ThreadId, model.PermissionReadChannelContent) { + ok, isMember := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.ThreadId) + if !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } // We want to make sure the thread is followed when marking as unread // https://mattermost.atlassian.net/browse/MM-36430 @@ -3732,7 +3754,7 @@ func unfollowThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(model.PermissionEditOtherUsers) return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.ThreadId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.ThreadId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } @@ -3765,7 +3787,7 @@ func followThreadByUser(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), c.Params.ThreadId, model.PermissionReadChannelContent) { + if ok, _ := c.App.SessionHasPermissionToReadPost(c.AppContext, *c.AppContext.Session(), c.Params.ThreadId); !ok { c.SetPermissionError(model.PermissionReadChannelContent) return } diff --git a/server/channels/api4/user_test.go b/server/channels/api4/user_test.go index 50d82b09985..dc60db4e67c 100644 --- a/server/channels/api4/user_test.go +++ b/server/channels/api4/user_test.go @@ -7202,7 +7202,7 @@ func TestThreadSocketEvents(t *testing.T) { require.NoError(t, err) CheckCreatedStatus(t, resp) - replyPost, appErr := th.App.CreatePostAsUser(th.Context, &model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply @" + th.BasicUser.Username, UserId: th.BasicUser2.Id, RootId: rpost.Id}, th.Context.Session().Id, false) + replyPost, _, appErr := th.App.CreatePostAsUser(th.Context, &model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply @" + th.BasicUser.Username, UserId: th.BasicUser2.Id, RootId: rpost.Id}, th.Context.Session().Id, false) require.Nil(t, appErr) defer func() { err = th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, th.BasicUser.Id) @@ -7379,7 +7379,7 @@ func TestThreadSocketEvents(t *testing.T) { for _, tc := range testCases { // post a reply on the thread - _, appErr = th.App.CreatePostAsUser(th.Context, tc.post, th.Context.Session().Id, false) + _, _, appErr = th.App.CreatePostAsUser(th.Context, tc.post, th.Context.Session().Id, false) require.Nil(t, appErr) var caught bool @@ -7413,18 +7413,18 @@ func TestThreadSocketEvents(t *testing.T) { rpost2 := &model.Post{ChannelId: th.BasicChannel.Id, UserId: th.BasicUser2.Id, Message: "root post"} var appErr *model.AppError - rpost2, appErr = th.App.CreatePostAsUser(th.Context, rpost2, th.Context.Session().Id, false) + rpost2, _, appErr = th.App.CreatePostAsUser(th.Context, rpost2, th.Context.Session().Id, false) require.Nil(t, appErr) reply1 := &model.Post{ChannelId: th.BasicChannel.Id, UserId: th.BasicUser2.Id, Message: "reply 1", RootId: rpost2.Id} reply2 := &model.Post{ChannelId: th.BasicChannel.Id, UserId: th.BasicUser2.Id, Message: "reply 2", RootId: rpost2.Id} reply3 := &model.Post{ChannelId: th.BasicChannel.Id, UserId: th.BasicUser2.Id, Message: "mention @" + th.BasicUser.Username, RootId: rpost2.Id} - _, appErr = th.App.CreatePostAsUser(th.Context, reply1, th.Context.Session().Id, false) + _, _, appErr = th.App.CreatePostAsUser(th.Context, reply1, th.Context.Session().Id, false) require.Nil(t, appErr) - _, appErr = th.App.CreatePostAsUser(th.Context, reply2, th.Context.Session().Id, false) + _, _, appErr = th.App.CreatePostAsUser(th.Context, reply2, th.Context.Session().Id, false) require.Nil(t, appErr) - _, appErr = th.App.CreatePostAsUser(th.Context, reply3, th.Context.Session().Id, false) + _, _, appErr = th.App.CreatePostAsUser(th.Context, reply3, th.Context.Session().Id, false) require.Nil(t, appErr) count := 0 diff --git a/server/channels/api4/webhook.go b/server/channels/api4/webhook.go index 3b420efe454..3d85b64d0b2 100644 --- a/server/channels/api4/webhook.go +++ b/server/channels/api4/webhook.go @@ -50,7 +50,7 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { + if ok, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel); !ok { c.LogAudit("fail - bad channel permissions") c.SetPermissionError(model.PermissionReadChannelContent) return @@ -159,10 +159,12 @@ func updateIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } - if channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { - c.LogAudit("fail - bad channel permissions") - c.SetPermissionError(model.PermissionReadChannelContent) - return + if channel.Type != model.ChannelTypeOpen { + if ok, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel); !ok { + c.LogAudit("fail - bad channel permissions") + c.SetPermissionError(model.PermissionReadChannelContent) + return + } } if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionBypassIncomingWebhookChannelLock) { @@ -285,8 +287,14 @@ func getIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnIncomingWebhooks) || - (channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)) { + isPrivate := channel.Type != model.ChannelTypeOpen + restrictedChannel := false + if isPrivate { + hasChannelPermission, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + restrictedChannel = !hasChannelPermission + } + + if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnIncomingWebhooks) || restrictedChannel { c.LogAudit("fail - bad permissions") c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return @@ -339,8 +347,14 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.AddMeta("channel_name", channel.Name) auditRec.AddMeta("team_id", hook.TeamId) - if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnIncomingWebhooks) || - (channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)) { + isPrivate := channel.Type != model.ChannelTypeOpen + restrictedChannel := false + if isPrivate { + hasChannelPermission, _ := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) + restrictedChannel = !hasChannelPermission + } + + if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnIncomingWebhooks) || restrictedChannel { c.LogAudit("fail - bad permissions") c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return @@ -491,13 +505,13 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) { ) if channelID != "" { - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelID, model.PermissionManageOwnOutgoingWebhooks) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelID, model.PermissionManageOwnOutgoingWebhooks); !ok { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } // Remove userId as a filter if they have permission to manage others. - if c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelID, model.PermissionManageOthersOutgoingWebhooks) { + if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelID, model.PermissionManageOthersOutgoingWebhooks); ok { userID = "" } diff --git a/server/channels/app/access_control.go b/server/channels/app/access_control.go index 1475c4690ea..b17b0b9ca6a 100644 --- a/server/channels/app/access_control.go +++ b/server/channels/app/access_control.go @@ -347,7 +347,7 @@ func (a *App) ValidateChannelAccessControlPermission(rctx request.CTX, userID, c } // Check if user has channel admin permission for the specific channel - if !a.HasPermissionToChannel(rctx, userID, channelID, model.PermissionManageChannelAccessRules) { + if ok, _ := a.HasPermissionToChannel(rctx, userID, channelID, model.PermissionManageChannelAccessRules); !ok { return model.NewAppError("ValidateChannelAccessControlPermission", "app.pap.access_control.insufficient_channel_permissions", nil, "user_id="+userID+" channel_id="+channelID, http.StatusForbidden) } @@ -392,7 +392,7 @@ func (a *App) ValidateAccessControlPolicyPermissionWithOptions(rctx request.CTX, // For read-only operations, allow access to system policies if they're applied to the specific channel if opts.isReadOnly && policy.Type != model.AccessControlPolicyTypeChannel && opts.channelID != "" { // Check if user has access to the channel - if !a.HasPermissionToChannel(rctx, userID, opts.channelID, model.PermissionReadChannel) { + if ok, _ := a.HasPermissionToChannel(rctx, userID, opts.channelID, model.PermissionReadChannel); !ok { return model.NewAppError("ValidateAccessControlPolicyPermissionWithOptions", "app.pap.access_control.insufficient_permissions", nil, "user_id="+userID+" channel_id="+opts.channelID, http.StatusForbidden) } diff --git a/server/channels/app/authorization.go b/server/channels/app/authorization.go index 40197531857..bca762eb55a 100644 --- a/server/channels/app/authorization.go +++ b/server/channels/app/authorization.go @@ -90,39 +90,53 @@ func (a *App) SessionHasPermissionToTeams(rctx request.CTX, session model.Sessio return true } -func (a *App) SessionHasPermissionToChannel(rctx request.CTX, session model.Session, channelID string, permission *model.Permission) bool { +// SessionHasPermissionToChannel checks if the session has permission to the given channel. +// +// Returns: +// +// (hasPermission, isMember) +// +// hasPermission: true if the user has the specified permission for the channel, otherwise false. +// isMember: used for auditing access without membership. True if the user is a member of the channel, otherwise false. +func (a *App) SessionHasPermissionToChannel(rctx request.CTX, session model.Session, channelID string, permission *model.Permission) (hasPermission bool, isMember bool) { if channelID == "" { - return false + return false, false } channel, appErr := a.GetChannel(rctx, channelID) if appErr != nil && appErr.StatusCode == http.StatusNotFound { - return false + return false, false } else if appErr != nil { rctx.Logger().Warn("Failed to get channel", mlog.String("channel_id", channelID), mlog.Err(appErr)) - return false + return false, false } - if session.IsUnrestricted() || a.RolesGrantPermission(session.GetUserRoles(), model.PermissionManageSystem.Id) { - return true + if session.IsUnrestricted() { + return true, false } + isMember = false ids, err := a.Srv().Store().Channel().GetAllChannelMembersForUser(rctx, session.UserId, true, true) var channelRoles []string if err == nil { if roles, ok := ids[channelID]; ok { + isMember = true channelRoles = strings.Fields(roles) if a.RolesGrantPermission(channelRoles, permission.Id) { - return true + return true, isMember } } } + if a.RolesGrantPermission(session.GetUserRoles(), model.PermissionManageSystem.Id) { + return true, isMember + } + if channel.TeamId != "" { - return a.SessionHasPermissionToTeam(session, channel.TeamId, permission) + return a.SessionHasPermissionToTeam(session, channel.TeamId, permission), isMember } - return a.SessionHasPermissionTo(session, permission) + return a.SessionHasPermissionTo(session, permission), isMember } // SessionHasPermissionToChannels returns true only if user has access to all channels. @@ -210,6 +224,21 @@ func (a *App) SessionHasPermissionToChannelByPost(session model.Session, postID return a.SessionHasPermissionTo(session, permission) } +func (a *App) SessionHasPermissionToReadPost(rctx request.CTX, session model.Session, postID string) (hasPErmission bool, isMember bool) { + if postID == "" { + return false, false + } + + channel, err := a.Srv().Store().Channel().GetForPost(postID) + if err != nil { + // Original implementation (SessionHasPermissionToChannelByPost) still checks for + // general permissions even if the channel is not found, and some tests rely on this behavior. + return a.SessionHasPermissionTo(session, model.PermissionReadChannelContent), false + } + + return a.SessionHasPermissionToReadChannel(rctx, session, channel) +} + func (a *App) SessionHasPermissionToCategory(rctx request.CTX, session model.Session, userID, teamID, categoryId string) bool { if a.SessionHasPermissionTo(session, model.PermissionEditOtherUsers) { return true @@ -287,11 +316,21 @@ func (a *App) HasPermissionToTeam(rctx request.CTX, askingUserId string, teamID return a.HasPermissionTo(askingUserId, permission) } -func (a *App) HasPermissionToChannel(rctx request.CTX, askingUserId string, channelID string, permission *model.Permission) bool { +// HasPermissionToChannel determines if the specified user has the given permission on the provided channel. +// +// Returns: +// +// (hasPermission, isMember) +// +// hasPermission: true if the user has the specified permission for the channel, otherwise false. +// isMember: used for auditing access without membership. True if the user is a member of the channel, otherwise false. +func (a *App) HasPermissionToChannel(rctx request.CTX, askingUserId string, channelID string, permission *model.Permission) (hasPermission bool, isMember bool) { if channelID == "" || askingUserId == "" { - return false + return false, false } + isMember = false + // We call GetAllChannelMembersForUser instead of just getting // a single member from the DB, because it's cache backed // and this is a very frequent call. @@ -299,19 +338,20 @@ func (a *App) HasPermissionToChannel(rctx request.CTX, askingUserId string, chan var channelRoles []string if err == nil { if roles, ok := ids[channelID]; ok { + isMember = true channelRoles = strings.Fields(roles) if a.RolesGrantPermission(channelRoles, permission.Id) { - return true + return true, isMember } } } channel, appErr := a.GetChannel(rctx, channelID) if appErr == nil && channel.TeamId != "" { - return a.HasPermissionToTeam(rctx, askingUserId, channel.TeamId, permission) + return a.HasPermissionToTeam(rctx, askingUserId, channel.TeamId, permission), isMember } - return a.HasPermissionTo(askingUserId, permission) + return a.HasPermissionTo(askingUserId, permission), isMember } func (a *App) HasPermissionToChannelByPost(rctx request.CTX, askingUserId string, postID string, permission *model.Permission) bool { @@ -398,28 +438,45 @@ func (a *App) SessionHasPermissionToManageBot(rctx request.CTX, session model.Se return nil } -func (a *App) SessionHasPermissionToReadChannel(rctx request.CTX, session model.Session, channel *model.Channel) bool { +// SessionHasPermissionToReadChannel checks whether the given session has permission +// to read the specified channel. +// +// Returns: +// +// (hasPermission, isMember) +// +// hasPermission: true if the user has permission to read the channel, false otherwise +// isMember: used for auditing access without membership. True if the user is a member of the channel, false otherwise +func (a *App) SessionHasPermissionToReadChannel(rctx request.CTX, session model.Session, channel *model.Channel) (hasPermission bool, isMember bool) { if session.IsUnrestricted() { - return true + return true, false } return a.HasPermissionToReadChannel(rctx, session.UserId, channel) } -func (a *App) HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) bool { - if a.HasPermissionToChannel(rctx, userID, channel.Id, model.PermissionReadChannelContent) { - return true +// HasPermissionToReadChannel determines if the specified user has permission to read the given channel. +// +// Returns: +// +// (hasPermission, isMember) +// +// hasPermission: true if the user has permission to read the channel, false otherwise +// isMember: used for auditing access without membership. True if the user is a member of the channel, false otherwise +func (a *App) HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) (hasPermission bool, isMember bool) { + if ok, member := a.HasPermissionToChannel(rctx, userID, channel.Id, model.PermissionReadChannelContent); ok { + return true, member } if channel.Type == model.ChannelTypeOpen && !*a.Config().ComplianceSettings.Enable { - return a.HasPermissionToTeam(rctx, userID, channel.TeamId, model.PermissionReadPublicChannel) + return a.HasPermissionToTeam(rctx, userID, channel.TeamId, model.PermissionReadPublicChannel), false } - return false + return false, false } func (a *App) HasPermissionToChannelMemberCount(rctx request.CTX, userID string, channel *model.Channel) bool { - if a.HasPermissionToChannel(rctx, userID, channel.Id, model.PermissionReadChannelContent) { + if ok, _ := a.HasPermissionToChannel(rctx, userID, channel.Id, model.PermissionReadChannelContent); ok { return true } diff --git a/server/channels/app/authorization_test.go b/server/channels/app/authorization_test.go index c4109c10b6c..64214c254db 100644 --- a/server/channels/app/authorization_test.go +++ b/server/channels/app/authorization_test.go @@ -231,13 +231,28 @@ func TestSessionHasPermissionToChannel(t *testing.T) { } t.Run("basic user can access basic channel", func(t *testing.T) { - assert.True(t, th.App.SessionHasPermissionToChannel(th.Context, session, th.BasicChannel.Id, model.PermissionAddReaction)) + ok, isMember := th.App.SessionHasPermissionToChannel(th.Context, session, th.BasicChannel.Id, model.PermissionAddReaction) + assert.True(t, ok) + assert.True(t, isMember) }) t.Run("basic user can access archived channel", func(t *testing.T) { err := th.App.DeleteChannel(th.Context, th.BasicChannel, th.SystemAdminUser.Id) require.Nil(t, err) - assert.True(t, th.App.SessionHasPermissionToChannel(th.Context, session, th.BasicChannel.Id, model.PermissionReadChannel)) + ok, isMember := th.App.SessionHasPermissionToChannel(th.Context, session, th.BasicChannel.Id, model.PermissionReadChannel) + assert.True(t, ok) + assert.True(t, isMember) + }) + + t.Run("admin user can access channel if not a member", func(t *testing.T) { + adminSession := model.Session{ + UserId: th.SystemAdminUser.Id, + Roles: model.SystemAdminRoleId, + } + + ok, isMember := th.App.SessionHasPermissionToChannel(th.Context, adminSession, th.BasicChannel.Id, model.PermissionAddReaction) + assert.True(t, ok) + assert.False(t, isMember) }) t.Run("does not panic if fetching channel causes an error", func(t *testing.T) { @@ -268,7 +283,9 @@ func TestSessionHasPermissionToChannel(t *testing.T) { // If there's an error returned from the GetChannel call the code should continue to cascade and since there // are no session level permissions in this test case, the permission should be denied. - assert.False(t, th.App.SessionHasPermissionToChannel(th.Context, session, th.BasicUser.Id, model.PermissionAddReaction)) + ok, isMember := th.App.SessionHasPermissionToChannel(th.Context, session, th.BasicUser.Id, model.PermissionAddReaction) + assert.False(t, ok) + assert.False(t, isMember) }) } @@ -710,6 +727,7 @@ func TestHasPermissionToReadChannel(t *testing.T) { channelIsOpen bool canReadPublicChannel bool expected bool + isAdmin bool }{ { name: "Can read archived channels", @@ -765,10 +783,25 @@ func TestHasPermissionToReadChannel(t *testing.T) { canReadPublicChannel: true, expected: true, }, + { + name: "Can read private channels if it is a sysadmin and it is not member of the channel", + configComplianceEnabled: false, + channelDeleted: false, + canReadChannel: false, + channelIsOpen: false, + canReadPublicChannel: false, + expected: true, + isAdmin: true, + }, } for _, tc := range ttcc { t.Run(tc.name, func(t *testing.T) { + user := th.BasicUser2 + if tc.isAdmin { + user = th.SystemAdminUser + } + th.App.UpdateConfig(func(cfg *model.Config) { configComplianceEnabled := tc.configComplianceEnabled cfg.ComplianceSettings.Enable = &configComplianceEnabled @@ -786,7 +819,7 @@ func TestHasPermissionToReadChannel(t *testing.T) { channel = th.CreatePrivateChannel(t, team) } if tc.canReadChannel { - _, err := th.App.AddUserToChannel(th.Context, th.BasicUser2, channel, false) + _, err := th.App.AddUserToChannel(th.Context, user, channel, false) require.Nil(t, err) } @@ -797,8 +830,13 @@ func TestHasPermissionToReadChannel(t *testing.T) { require.Nil(t, err) } - result := th.App.HasPermissionToReadChannel(th.Context, th.BasicUser2.Id, channel) + result, isMember := th.App.HasPermissionToReadChannel(th.Context, user.Id, channel) require.Equal(t, tc.expected, result) + if result { + require.Equal(t, tc.canReadChannel, isMember) + } else { + require.Equal(t, false, isMember) + } }) } } @@ -888,3 +926,226 @@ func TestHasPermissionToChannelByPost(t *testing.T) { require.Equal(t, true, th.App.HasPermissionToChannelByPost(th.Context, th.SystemAdminUser.Id, post.Id, model.PermissionReadChannel)) }) } + +func TestHasPermissionToChannel(t *testing.T) { + mainHelper.Parallel(t) + th := Setup(t).InitBasic(t) + + channel := th.CreateChannel(t, th.BasicTeam) + _, appErr := th.App.AddUserToChannel(th.Context, th.BasicUser, channel, false) + assert.Nil(t, appErr) + + archivedChannel := th.CreateChannel(t, th.BasicTeam) + _, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, archivedChannel, false) + assert.Nil(t, appErr) + appErr = th.App.DeleteChannel(th.Context, archivedChannel, th.SystemAdminUser.Id) + assert.Nil(t, appErr) + + t.Run("read channel", func(t *testing.T) { + ok, isMember := th.App.HasPermissionToChannel(th.Context, th.BasicUser.Id, channel.Id, model.PermissionReadChannel) + assert.True(t, ok) + assert.True(t, isMember) + + ok, isMember = th.App.HasPermissionToChannel(th.Context, th.BasicUser2.Id, channel.Id, model.PermissionReadChannel) + assert.False(t, ok) + assert.False(t, isMember) + }) + + t.Run("read archived channel", func(t *testing.T) { + ok, isMember := th.App.HasPermissionToChannel(th.Context, th.BasicUser.Id, archivedChannel.Id, model.PermissionReadChannel) + assert.True(t, ok) + assert.True(t, isMember) + + ok, isMember = th.App.HasPermissionToChannel(th.Context, th.BasicUser2.Id, archivedChannel.Id, model.PermissionReadChannel) + assert.False(t, ok) + assert.False(t, isMember) + }) + + t.Run("read public channel", func(t *testing.T) { + ok, isMember := th.App.HasPermissionToChannel(th.Context, th.BasicUser.Id, channel.Id, model.PermissionReadPublicChannel) + assert.True(t, ok) + assert.True(t, isMember) + + ok, isMember = th.App.HasPermissionToChannel(th.Context, th.BasicUser2.Id, channel.Id, model.PermissionReadPublicChannel) + assert.True(t, ok) + assert.False(t, isMember) + }) + + t.Run("read channel - user is admin", func(t *testing.T) { + ok, isMember := th.App.HasPermissionToChannel(th.Context, th.SystemAdminUser.Id, channel.Id, model.PermissionReadChannel) + assert.True(t, ok) + assert.False(t, isMember) + }) +} + +func TestSessionHasPermissionToReadChannel(t *testing.T) { + mainHelper.Parallel(t) + th := Setup(t).InitBasic(t) + + channel := th.CreateChannel(t, th.BasicTeam) + _, appErr := th.App.AddUserToChannel(th.Context, th.BasicUser, channel, false) + assert.Nil(t, appErr) + + archivedChannel := th.CreateChannel(t, th.BasicTeam) + _, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, archivedChannel, false) + assert.Nil(t, appErr) + appErr = th.App.DeleteChannel(th.Context, archivedChannel, th.SystemAdminUser.Id) + assert.Nil(t, appErr) + + t.Run("basic user can read channel", func(t *testing.T) { + session := model.Session{ + UserId: th.BasicUser.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadChannel(th.Context, session, channel) + assert.True(t, ok) + assert.True(t, isMember) + }) + + t.Run("basic user cannot read channel if not a member and not public", func(t *testing.T) { + privateChannel := th.CreatePrivateChannel(t, th.BasicTeam) + session := model.Session{ + UserId: th.BasicUser2.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadChannel(th.Context, session, privateChannel) + assert.False(t, ok) + assert.False(t, isMember) + }) + + t.Run("basic user can read archived channel if member", func(t *testing.T) { + session := model.Session{ + UserId: th.BasicUser.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadChannel(th.Context, session, archivedChannel) + assert.True(t, ok) + assert.True(t, isMember) + }) + + t.Run("non-member can read public channel", func(t *testing.T) { + session := model.Session{ + UserId: th.BasicUser2.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadChannel(th.Context, session, channel) + assert.True(t, ok) + assert.False(t, isMember) + }) + + t.Run("admin can read any channel", func(t *testing.T) { + session := model.Session{ + UserId: th.SystemAdminUser.Id, + Roles: model.SystemAdminRoleId, + } + ok, isMember := th.App.SessionHasPermissionToReadChannel(th.Context, session, channel) + assert.True(t, ok) + assert.False(t, isMember) + }) +} + +func TestSessionHasPermissionToReadPost(t *testing.T) { + mainHelper.Parallel(t) + th := Setup(t).InitBasic(t) + + // Create a post in a public channel, ensure basic user can read it. + post, _, err := th.App.CreatePost(th.Context, &model.Post{ + UserId: th.BasicUser.Id, + ChannelId: th.BasicChannel.Id, + Message: "hello world", + }, th.BasicChannel, model.CreatePostFlags{}) + require.Nil(t, err) + + t.Run("basic user can read their post", func(t *testing.T) { + session := model.Session{ + UserId: th.BasicUser.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadPost(th.Context, session, post.Id) + assert.True(t, ok) + assert.True(t, isMember) + }) + + t.Run("other member in channel can read post", func(t *testing.T) { + // Add BasicUser2 to channel + _, aerr := th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel, false) + require.Nil(t, aerr) + session := model.Session{ + UserId: th.BasicUser2.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadPost(th.Context, session, post.Id) + assert.True(t, ok) + assert.True(t, isMember) + }) + + t.Run("non-member can read post in public channel", func(t *testing.T) { + // Remove BasicUser2 from channel + aerr := th.App.removeUserFromChannel(th.Context, th.BasicUser2.Id, th.SystemAdminUser.Id, th.BasicChannel) + assert.Nil(t, aerr) + session := model.Session{ + UserId: th.BasicUser2.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadPost(th.Context, session, post.Id) + assert.True(t, ok) + assert.False(t, isMember) + }) + + t.Run("non-member cannot read post in private channel", func(t *testing.T) { + privateChan := th.CreatePrivateChannel(t, th.BasicTeam) + privatePost, _, err := th.App.CreatePost(th.Context, &model.Post{ + UserId: th.BasicUser.Id, + ChannelId: privateChan.Id, + Message: "private message", + }, privateChan, model.CreatePostFlags{}) + require.Nil(t, err) + + session := model.Session{ + UserId: th.BasicUser2.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadPost(th.Context, session, privatePost.Id) + assert.False(t, ok) + assert.False(t, isMember) + }) + + t.Run("admin can read post even if not a channel member", func(t *testing.T) { + privateChan := th.CreatePrivateChannel(t, th.BasicTeam) + privatePost, _, err := th.App.CreatePost(th.Context, &model.Post{ + UserId: th.BasicUser.Id, + ChannelId: privateChan.Id, + Message: "private admin", + }, privateChan, model.CreatePostFlags{}) + require.Nil(t, err) + + session := model.Session{ + UserId: th.SystemAdminUser.Id, + Roles: model.SystemAdminRoleId, + } + ok, isMember := th.App.SessionHasPermissionToReadPost(th.Context, session, privatePost.Id) + assert.True(t, ok) + assert.False(t, isMember) + }) + + t.Run("returns false for empty postID", func(t *testing.T) { + session := model.Session{ + UserId: th.BasicUser.Id, + } + ok, isMember := th.App.SessionHasPermissionToReadPost(th.Context, session, "") + assert.False(t, ok) + assert.False(t, isMember) + }) + + t.Run("returns permission based on system level if postID is missing", func(t *testing.T) { + // To simulate a missing post, use a postID that doesn't exist + session := model.Session{ + UserId: th.SystemAdminUser.Id, + Roles: model.SystemAdminRoleId, + } + + ok, isMember := th.App.SessionHasPermissionToReadPost(th.Context, session, model.NewId()) + assert.True(t, ok) + assert.False(t, isMember) + + // Basic user, should be false + session = model.Session{ + UserId: th.BasicUser2.Id, + } + ok, isMember = th.App.SessionHasPermissionToReadPost(th.Context, session, model.NewId()) + assert.False(t, ok) + assert.False(t, isMember) + }) +} diff --git a/server/channels/app/auto_responder.go b/server/channels/app/auto_responder.go index 8c6c42a0ddc..fd7be6fd29d 100644 --- a/server/channels/app/auto_responder.go +++ b/server/channels/app/auto_responder.go @@ -77,7 +77,7 @@ func (a *App) SendAutoResponse(rctx request.CTX, channel *model.Channel, receive UserId: receiver.Id, } - if _, err := a.CreatePost(rctx, autoResponderPost, channel, model.CreatePostFlags{}); err != nil { + if _, _, err := a.CreatePost(rctx, autoResponderPost, channel, model.CreatePostFlags{}); err != nil { return false, err } diff --git a/server/channels/app/auto_responder_test.go b/server/channels/app/auto_responder_test.go index 79ac4477412..471dcaef54f 100644 --- a/server/channels/app/auto_responder_test.go +++ b/server/channels/app/auto_responder_test.go @@ -107,7 +107,7 @@ func TestSendAutoResponseIfNecessary(t *testing.T) { channel := th.CreateDmChannel(t, receiver) - savedPost, _ := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: channel.Id, Message: NewTestId(), UserId: th.BasicUser.Id, @@ -137,7 +137,7 @@ func TestSendAutoResponseIfNecessary(t *testing.T) { channel := th.CreateDmChannel(t, receiver) - savedPost, _ := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: channel.Id, Message: NewTestId(), UserId: th.BasicUser.Id, @@ -154,7 +154,7 @@ func TestSendAutoResponseIfNecessary(t *testing.T) { t.Run("should not send auto response for non-DM channel", func(t *testing.T) { th := Setup(t).InitBasic(t) - savedPost, _ := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: th.BasicChannel.Id, Message: NewTestId(), UserId: th.BasicUser.Id, @@ -194,7 +194,7 @@ func TestSendAutoResponseIfNecessary(t *testing.T) { botUser, err := th.App.GetUser(bot.UserId) assert.Nil(t, err) - savedPost, _ := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: channel.Id, Message: NewTestId(), UserId: botUser.Id, @@ -229,7 +229,7 @@ func TestSendAutoResponseIfNecessary(t *testing.T) { // which needs to be cleaned up. require.NoError(t, th.GetSqlStore().Post().PermanentDeleteByUser(th.Context, th.BasicUser.Id)) - savedPost, err := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, err := th.App.CreatePost(th.Context, &model.Post{ ChannelId: channel.Id, Message: patch.NotifyProps["auto_responder_message"], UserId: receiver.Id, @@ -266,7 +266,7 @@ func TestSendAutoResponseSuccess(t *testing.T) { userUpdated1, err := th.App.PatchUser(th.Context, user.Id, patch, true) require.Nil(t, err) - savedPost, _ := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", UserId: th.BasicUser.Id, @@ -310,7 +310,7 @@ func TestSendAutoResponseSuccessOnThread(t *testing.T) { userUpdated1, err := th.App.PatchUser(th.Context, user.Id, patch, true) require.Nil(t, err) - parentPost, _ := th.App.CreatePost(th.Context, &model.Post{ + parentPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", UserId: th.BasicUser.Id, @@ -318,7 +318,7 @@ func TestSendAutoResponseSuccessOnThread(t *testing.T) { th.BasicChannel, model.CreatePostFlags{SetOnline: true}) - savedPost, _ := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", UserId: th.BasicUser.Id, @@ -363,7 +363,7 @@ func TestSendAutoResponseFailure(t *testing.T) { userUpdated1, err := th.App.PatchUser(th.Context, user.Id, patch, true) require.Nil(t, err) - savedPost, _ := th.App.CreatePost(th.Context, &model.Post{ + savedPost, _, _ := th.App.CreatePost(th.Context, &model.Post{ ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", UserId: th.BasicUser.Id, diff --git a/server/channels/app/bot.go b/server/channels/app/bot.go index 42357ef140a..09c036a6904 100644 --- a/server/channels/app/bot.go +++ b/server/channels/app/bot.go @@ -157,7 +157,7 @@ func (a *App) CreateBot(rctx request.CTX, bot *model.Bot) (*model.Bot, *model.Ap Message: T("api.bot.teams_channels.add_message_mobile"), } - if _, err := a.CreatePostAsUser(rctx, botAddPost, rctx.Session().Id, true); err != nil { + if _, _, err := a.CreatePostAsUser(rctx, botAddPost, rctx.Session().Id, true); err != nil { return nil, err } } @@ -599,7 +599,7 @@ func (a *App) notifySysadminsBotOwnerDeactivated(rctx request.CTX, userID string Type: model.PostTypeSystemGeneric, } - _, appErr = a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}) if appErr != nil { return appErr } diff --git a/server/channels/app/channel.go b/server/channels/app/channel.go index 885c90cfa9e..e50a30f5147 100644 --- a/server/channels/app/channel.go +++ b/server/channels/app/channel.go @@ -851,7 +851,7 @@ func (a *App) postChannelPrivacyMessage(rctx request.CTX, user *model.User, chan }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postChannelPrivacyMessage", "api.channel.post_channel_privacy_message.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -906,7 +906,7 @@ func (a *App) RestoreChannel(rctx request.CTX, channel *model.Channel, userID st }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { rctx.Logger().Warn("Failed to post unarchive message", mlog.Err(err)) } } else { @@ -927,7 +927,7 @@ func (a *App) RestoreChannel(rctx request.CTX, channel *model.Channel, userID st }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { rctx.Logger().Error("Failed to post unarchive message", mlog.Err(err)) } }) @@ -1593,7 +1593,7 @@ func (a *App) DeleteChannel(rctx request.CTX, channel *model.Channel, userID str }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { rctx.Logger().Warn("Failed to post archive message", mlog.Err(err)) } } else { @@ -1611,7 +1611,7 @@ func (a *App) DeleteChannel(rctx request.CTX, channel *model.Channel, userID str }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { rctx.Logger().Warn("Failed to post archive message", mlog.Err(err)) } } @@ -1926,7 +1926,7 @@ func (a *App) PostUpdateChannelHeaderMessage(rctx request.CTX, userID string, ch }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("", "api.channel.post_update_channel_header_message_and_forget.post.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -1959,7 +1959,7 @@ func (a *App) PostUpdateChannelPurposeMessage(rctx request.CTX, userID string, c "new_purpose": newChannelPurpose, }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("", "app.channel.post_update_channel_purpose_message.post.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -1986,7 +1986,7 @@ func (a *App) PostUpdateChannelDisplayNameMessage(rctx request.CTX, userID strin }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("PostUpdateChannelDisplayNameMessage", "api.channel.post_update_channel_displayname_message_and_forget.create_post.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -2502,7 +2502,7 @@ func (a *App) postJoinChannelMessage(rctx request.CTX, user *model.User, channel }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postJoinChannelMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -2520,7 +2520,7 @@ func (a *App) postJoinTeamMessage(rctx request.CTX, user *model.User, channel *m }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postJoinTeamMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -2603,7 +2603,7 @@ func (a *App) postLeaveChannelMessage(rctx request.CTX, user *model.User, channe }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postLeaveChannelMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -2632,7 +2632,7 @@ func (a *App) PostAddToChannelMessage(rctx request.CTX, user *model.User, addedU }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postAddToChannelMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -2654,7 +2654,7 @@ func (a *App) postAddToTeamMessage(rctx request.CTX, user *model.User, addedUser }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postAddToTeamMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -2686,7 +2686,7 @@ func (a *App) postRemoveFromChannelMessage(rctx request.CTX, removerUserId strin }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postRemoveFromChannelMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -2890,9 +2890,16 @@ func (a *App) ValidateUserPermissionsOnChannels(rctx request.CTX, userId string, continue } - if channel.Type == model.ChannelTypePrivate && a.HasPermissionToChannel(rctx, userId, channelId, model.PermissionManagePrivateChannelMembers) { - allowedChannelIds = append(allowedChannelIds, channelId) - } else if channel.Type == model.ChannelTypeOpen && a.HasPermissionToChannel(rctx, userId, channelId, model.PermissionManagePublicChannelMembers) { + allowedPrivate := false + if channel.Type == model.ChannelTypePrivate { + allowedPrivate, _ = a.HasPermissionToChannel(rctx, userId, channelId, model.PermissionManagePrivateChannelMembers) + } + allowedPublic := false + if channel.Type == model.ChannelTypeOpen { + allowedPublic, _ = a.HasPermissionToChannel(rctx, userId, channelId, model.PermissionManagePublicChannelMembers) + } + + if allowedPrivate || allowedPublic { allowedChannelIds = append(allowedChannelIds, channelId) } else { rctx.Logger().Info("Invite users to team - no permission to add members to that channel. UserId: " + userId + " ChannelId: " + channelId) @@ -3439,7 +3446,7 @@ func (a *App) postChannelMoveMessage(rctx request.CTX, user *model.User, channel }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postChannelMoveMessage", "api.team.move_channel.post.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -3962,7 +3969,7 @@ func (a *App) postMessageForConvertGroupMessageToChannel(rctx request.CTX, chann return appErr } - if _, appErr := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); appErr != nil { + if _, _, appErr := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); appErr != nil { rctx.Logger().Error("Failed to create post for notifying about GM converted to private channel", mlog.Err(appErr)) return model.NewAppError( diff --git a/server/channels/app/channel_test.go b/server/channels/app/channel_test.go index ec616ca8515..baf7ba9b3d1 100644 --- a/server/channels/app/channel_test.go +++ b/server/channels/app/channel_test.go @@ -264,7 +264,7 @@ func TestMoveChannel(t *testing.T) { ChannelId: channel.Id, Message: "test", } - post, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{}) + post, _, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // Post a reply to the thread @@ -274,7 +274,7 @@ func TestMoveChannel(t *testing.T) { RootId: post.Id, Message: "reply", } - _, appErr = th.App.CreatePost(th.Context, reply, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, reply, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // Check that the thread count before move @@ -758,7 +758,7 @@ func TestUsersAndPostsCreateActivityInChannel(t *testing.T) { Message: "root post", UserId: th.BasicUser.Id, } - _, err = th.App.CreatePost(th.Context, post, channel1, model.CreatePostFlags{}) + _, _, err = th.App.CreatePost(th.Context, post, channel1, model.CreatePostFlags{}) require.Nil(t, err, "Failed to create post.") _, err = th.App.AddUserToChannel(th.Context, user, channel2, false) @@ -785,7 +785,7 @@ func TestUsersAndPostsCreateActivityInChannel(t *testing.T) { } err = th.App.RemoveUserFromChannel(th.Context, user4.Id, user4.Id, channel4) require.Nil(t, err, "Failed to create post.") - _, err = th.App.CreatePost(th.Context, post2, channel5, model.CreatePostFlags{}) + _, _, err = th.App.CreatePost(th.Context, post2, channel5, model.CreatePostFlags{}) require.Nil(t, err, "Failed to create post.") _, err = th.App.AddUserToChannel(th.Context, user, channel6, false) require.Nil(t, err, "Failed to add user to channel.") @@ -830,7 +830,7 @@ func TestLeaveDefaultChannel(t *testing.T) { Message: "root post", UserId: th.BasicUser.Id, } - rpost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) reply := &model.Post{ @@ -839,7 +839,7 @@ func TestLeaveDefaultChannel(t *testing.T) { UserId: th.BasicUser.Id, RootId: rpost.Id, } - _, appErr = th.App.CreatePost(th.Context, reply, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, reply, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) threads, appErr := th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, townSquare.TeamId, model.GetUserThreadsOpts{}) @@ -868,7 +868,7 @@ func TestLeaveChannel(t *testing.T) { UserId: th.BasicUser.Id, } - rpost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) reply := &model.Post{ @@ -877,7 +877,7 @@ func TestLeaveChannel(t *testing.T) { UserId: th.BasicUser.Id, RootId: rpost.Id, } - _, appErr = th.App.CreatePost(th.Context, reply, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, reply, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) return rpost @@ -1851,7 +1851,7 @@ func TestMarkChannelAsUnreadFromPost(t *testing.T) { _, appErr := th.App.AddUserToChannel(th.Context, u2, c2, false) require.Nil(t, appErr) - p4, appErr := th.App.CreatePost(th.Context, &model.Post{ + p4, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: u2.Id, ChannelId: c2.Id, Message: "@" + u1.Username, @@ -1859,7 +1859,7 @@ func TestMarkChannelAsUnreadFromPost(t *testing.T) { require.Nil(t, appErr) th.CreatePost(t, c2) - _, appErr = th.App.CreatePost(th.Context, &model.Post{ + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{ UserId: u2.Id, ChannelId: c2.Id, RootId: p4.Id, @@ -1887,7 +1887,7 @@ func TestMarkChannelAsUnreadFromPost(t *testing.T) { th.CreatePost(t, dc) th.CreatePost(t, dc) - _, appErr := th.App.CreatePost(th.Context, &model.Post{ChannelId: dc.Id, UserId: th.BasicUser.Id, Message: "testReply", RootId: dm1.Id}, dc, model.CreatePostFlags{}) + _, _, appErr := th.App.CreatePost(th.Context, &model.Post{ChannelId: dc.Id, UserId: th.BasicUser.Id, Message: "testReply", RootId: dm1.Id}, dc, model.CreatePostFlags{}) assert.Nil(t, appErr) response, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, dm1.Id, u2.Id, true) @@ -2535,11 +2535,13 @@ func TestPatchChannelModerationsForChannel(t *testing.T) { _, appErr := th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), addCreatePosts) require.Nil(t, appErr) - require.True(t, th.App.SessionHasPermissionToChannel(th.Context, mockSession, channel.Id, model.PermissionCreatePost)) + ok, _ := th.App.SessionHasPermissionToChannel(th.Context, mockSession, channel.Id, model.PermissionCreatePost) + require.True(t, ok) _, appErr = th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), removeCreatePosts) require.Nil(t, appErr) - require.False(t, th.App.SessionHasPermissionToChannel(th.Context, mockSession, channel.Id, model.PermissionCreatePost)) + ok, _ = th.App.SessionHasPermissionToChannel(th.Context, mockSession, channel.Id, model.PermissionCreatePost) + require.False(t, ok) }) } @@ -2649,7 +2651,7 @@ func TestViewChannelCollapsedThreadsTurnedOff(t *testing.T) { Message: "root post @" + u1.Username, UserId: u2.Id, } - rpost1, appErr := th.App.CreatePost(th.Context, post1, c1, model.CreatePostFlags{SetOnline: true}) + rpost1, _, appErr := th.App.CreatePost(th.Context, post1, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // mention the user in a reply post @@ -2659,7 +2661,7 @@ func TestViewChannelCollapsedThreadsTurnedOff(t *testing.T) { UserId: u2.Id, RootId: rpost1.Id, } - _, appErr = th.App.CreatePost(th.Context, post2, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, post2, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // Check we have unread mention in the thread @@ -2726,19 +2728,19 @@ func TestMarkChannelAsUnreadFromPostCollapsedThreadsTurnedOff(t *testing.T) { // user1: a root post // user2: Another root mention @u1 user1Mention := " @" + th.BasicUser.Username - rootPost1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "first root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + rootPost1, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "first root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hello"}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hello"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - replyPost1, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + replyPost1, _, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another reply"}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another reply"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "a root post"}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "a root post"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) t.Run("Mark reply post as unread", func(t *testing.T) { @@ -2796,17 +2798,17 @@ func TestMarkUnreadCRTOffUpdatesThreads(t *testing.T) { appErr := th.App.PermanentDeleteUser(th.Context, user3) require.Nil(t, appErr) }() - rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "root post"}, th.BasicChannel, model.CreatePostFlags{}) + rootPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "root post"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - r1, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 1"}, th.BasicChannel, model.CreatePostFlags{}) + r1, _, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 1"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 2 @" + user3.Username}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 2 @" + user3.Username}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 3"}, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 3"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) editedPost := r1.Clone() editedPost.Message += " edited" - _, appErr = th.App.UpdatePost(th.Context, editedPost, &model.UpdatePostOptions{SafeUpdate: false}) + _, _, appErr = th.App.UpdatePost(th.Context, editedPost, &model.UpdatePostOptions{SafeUpdate: false}) require.Nil(t, appErr) th.LinkUserToTeam(t, user3, th.BasicTeam) @@ -3411,7 +3413,7 @@ func TestGetChannelFileCount(t *testing.T) { Message: "This is a test post", UserId: th.BasicUser.Id, } - post, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{}) + post, _, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{}) require.Nil(t, appErr) fileInfo1 := &model.FileInfo{ diff --git a/server/channels/app/command.go b/server/channels/app/command.go index ab0f4e025a1..e7768d1b590 100644 --- a/server/channels/app/command.go +++ b/server/channels/app/command.go @@ -71,7 +71,12 @@ func (a *App) CreateCommandPost(rctx request.CTX, post *model.Post, teamID strin } if response.ResponseType == model.CommandResponseTypeInChannel { - return a.CreatePostMissingChannel(rctx, post, true, true) + // The post is only used for tests, so even if there are membership issues, we won't send the post to the client. + createdPost, _, appErr := a.CreatePostMissingChannel(rctx, post, true, true) + if appErr != nil { + return nil, appErr + } + return createdPost, nil } if (response.ResponseType == "" || response.ResponseType == model.CommandResponseTypeEphemeral) && (response.Text != "" || response.Attachments != nil) { diff --git a/server/channels/app/content_flagging.go b/server/channels/app/content_flagging.go index b231ba5f398..9a8ea111ae2 100644 --- a/server/channels/app/content_flagging.go +++ b/server/channels/app/content_flagging.go @@ -352,7 +352,7 @@ func (a *App) createContentReviewPost(rctx request.CTX, flaggedPostId, teamId, r ChannelId: channel.Id, } post.AddProp(POST_PROP_KEY_FLAGGED_POST_ID, flaggedPostId) - createdPost, appErr := a.CreatePost(rctx, post, channel, model.CreatePostFlags{}) + createdPost, _, appErr := a.CreatePost(rctx, post, channel, model.CreatePostFlags{}) if appErr != nil { rctx.Logger().Error("Failed to create content review post in one of the channels", mlog.Err(appErr), mlog.String("channel_id", channel.Id), mlog.String("team_id", teamId)) continue // Don't stop processing other channels if one fails @@ -1118,7 +1118,12 @@ func (a *App) postContentReviewBotMessage(rctx request.CTX, flaggedPost *model.P ChannelId: dmChannel.Id, } - return a.CreatePost(rctx, post, dmChannel, model.CreatePostFlags{}) + // We can ignore the membership since the post itself is does not have a permalink + createdPost, _, appErr := a.CreatePost(rctx, post, dmChannel, model.CreatePostFlags{}) + if appErr != nil { + return nil, appErr + } + return createdPost, nil } func (a *App) postMessageToReporter(rctx request.CTX, contentFlaggingGroupId string, flaggedPost *model.Post, messageTemplate string) (*model.Post, *model.AppError) { @@ -1172,7 +1177,8 @@ func (a *App) postReviewerMessage(rctx request.CTX, message, contentFlaggingGrou RootId: postId, } - createdPost, appErr := a.CreatePost(rctx, post, channel, model.CreatePostFlags{}) + // We can ignore the membership since the post itself is does not have a permalink + createdPost, _, appErr := a.CreatePost(rctx, post, channel, model.CreatePostFlags{}) if appErr != nil { rctx.Logger().Error("Failed to create assign reviewer post in one of the channels", mlog.Err(appErr), mlog.String("channel_id", channel.Id), mlog.String("post_id", postId)) continue @@ -1250,7 +1256,7 @@ func (a *App) sendFlagPostNotification(rctx request.CTX, flaggedPost *model.Post ChannelId: dmChannel.Id, } - _, appErr = a.CreatePost(rctx, post, dmChannel, model.CreatePostFlags{}) + _, _, appErr = a.CreatePost(rctx, post, dmChannel, model.CreatePostFlags{}) return appErr } diff --git a/server/channels/app/content_flagging_test.go b/server/channels/app/content_flagging_test.go index 26fecc6bc5a..0e60e4fb184 100644 --- a/server/channels/app/content_flagging_test.go +++ b/server/channels/app/content_flagging_test.go @@ -2446,7 +2446,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) { // Update post to include file IDs post.FileIds = []string{fileInfo1.Id, fileInfo2.Id} - _, appErr := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{}) + _, _, appErr := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{}) require.Nil(t, appErr) // Flag the post @@ -2487,7 +2487,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) { editedPost.Message = "Edited message" editedPost.EditAt = model.GetMillis() - _, appErr := th.App.UpdatePost(th.Context, editedPost, &model.UpdatePostOptions{}) + _, _, appErr := th.App.UpdatePost(th.Context, editedPost, &model.UpdatePostOptions{}) require.Nil(t, appErr) // Flag the post diff --git a/server/channels/app/export_test.go b/server/channels/app/export_test.go index 383f6278649..8a4eb70af10 100644 --- a/server/channels/app/export_test.go +++ b/server/channels/app/export_test.go @@ -541,7 +541,7 @@ func TestExportDMandGMPost(t *testing.T) { Message: "aa" + model.NewId() + "a", UserId: th1.BasicUser.Id, } - _, appErr := th1.App.CreatePost(th1.Context, p1, dmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr := th1.App.CreatePost(th1.Context, p1, dmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) p2 := &model.Post{ @@ -549,7 +549,7 @@ func TestExportDMandGMPost(t *testing.T) { Message: "bb" + model.NewId() + "a", UserId: th1.BasicUser.Id, } - _, appErr = th1.App.CreatePost(th1.Context, p2, dmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th1.App.CreatePost(th1.Context, p2, dmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // GM posts @@ -558,7 +558,7 @@ func TestExportDMandGMPost(t *testing.T) { Message: "cc" + model.NewId() + "a", UserId: th1.BasicUser.Id, } - _, appErr = th1.App.CreatePost(th1.Context, p3, gmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th1.App.CreatePost(th1.Context, p3, gmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) p4 := &model.Post{ @@ -566,7 +566,7 @@ func TestExportDMandGMPost(t *testing.T) { Message: "dd" + model.NewId() + "a", UserId: th1.BasicUser.Id, } - _, appErr = th1.App.CreatePost(th1.Context, p4, gmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th1.App.CreatePost(th1.Context, p4, gmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) posts, err := th1.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false) @@ -628,7 +628,7 @@ func TestExportPostWithProps(t *testing.T) { }, UserId: th1.BasicUser.Id, } - _, appErr := th1.App.CreatePost(th1.Context, p1, dmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr := th1.App.CreatePost(th1.Context, p1, dmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) p2 := &model.Post{ @@ -639,7 +639,7 @@ func TestExportPostWithProps(t *testing.T) { }, UserId: th1.BasicUser.Id, } - _, appErr = th1.App.CreatePost(th1.Context, p2, gmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th1.App.CreatePost(th1.Context, p2, gmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) posts, err := th1.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false) @@ -1047,7 +1047,7 @@ func TestBuildPostReplies(t *testing.T) { fileIDs = append(fileIDs, info.Id) } - post, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, RootId: rootID, FileIds: fileIDs}, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, RootId: rootID, FileIds: fileIDs}, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) return post @@ -1597,7 +1597,7 @@ func TestExportDeactivatedUserDMs(t *testing.T) { Message: initialMessage, UserId: th1.BasicUser.Id, } - initialPostCreated, appErr := th1.App.CreatePost(th1.Context, initialPost, dmChannel, model.CreatePostFlags{SetOnline: true}) + initialPostCreated, _, appErr := th1.App.CreatePost(th1.Context, initialPost, dmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // 2. Have user2 reply with TWO types of replies: @@ -1610,7 +1610,7 @@ func TestExportDeactivatedUserDMs(t *testing.T) { UserId: user2.Id, RootId: initialPostCreated.Id, // This makes it a threaded reply } - _, appErr = th1.App.CreatePost(th1.Context, threadedReply, dmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th1.App.CreatePost(th1.Context, threadedReply, dmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // 2b. User2 sends a standalone reply (NOT in a thread) @@ -1621,7 +1621,7 @@ func TestExportDeactivatedUserDMs(t *testing.T) { UserId: user2.Id, // No RootId, making it a standalone message, not a thread reply } - _, appErr = th1.App.CreatePost(th1.Context, nonThreadedReply, dmChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th1.App.CreatePost(th1.Context, nonThreadedReply, dmChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // 3. Now deactivate user2 diff --git a/server/channels/app/file.go b/server/channels/app/file.go index eeeb1089309..db07d36b170 100644 --- a/server/channels/app/file.go +++ b/server/channels/app/file.go @@ -1441,11 +1441,11 @@ func populateZipfile(w *zip.Writer, fileDatas []model.FileData) error { return nil } -func (a *App) SearchFilesInTeamForUser(rctx request.CTX, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.FileInfoList, *model.AppError) { +func (a *App) SearchFilesInTeamForUser(rctx request.CTX, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.FileInfoList, bool, *model.AppError) { paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset) if !*a.Config().ServiceSettings.EnableFileSearch { - return nil, model.NewAppError("SearchFilesInTeamForUser", "store.sql_file_info.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented) + return nil, false, model.NewAppError("SearchFilesInTeamForUser", "store.sql_file_info.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented) } finalParamsList := []*model.SearchParams{} @@ -1469,7 +1469,7 @@ func (a *App) SearchFilesInTeamForUser(rctx request.CTX, terms string, userId st // If the processed search params are empty, return empty search results. if len(finalParamsList) == 0 { - return model.NewFileInfoList(), nil + return model.NewFileInfoList(), true, nil } fileInfoSearchResults, nErr := a.Srv().Store().FileInfo().Search(rctx, finalParamsList, userId, teamId, page, perPage) @@ -1477,26 +1477,27 @@ func (a *App) SearchFilesInTeamForUser(rctx request.CTX, terms string, userId st var appErr *model.AppError switch { case errors.As(nErr, &appErr): - return nil, appErr + return nil, false, appErr default: - return nil, model.NewAppError("SearchFilesInTeamForUser", "app.post.search.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + return nil, false, model.NewAppError("SearchFilesInTeamForUser", "app.post.search.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } if appErr := a.filterInaccessibleFiles(fileInfoSearchResults, filterFileOptions{assumeSortedCreatedAt: true}); appErr != nil { - return nil, appErr + return nil, false, appErr } - if appErr := a.FilterFilesByChannelPermissions(rctx, fileInfoSearchResults, userId); appErr != nil { - return nil, appErr + allFilesHaveMembership, appErr := a.FilterFilesByChannelPermissions(rctx, fileInfoSearchResults, userId) + if appErr != nil { + return nil, false, appErr } - return fileInfoSearchResults, nil + return fileInfoSearchResults, allFilesHaveMembership, nil } -func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model.FileInfoList, userID string) *model.AppError { +func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model.FileInfoList, userID string) (bool, *model.AppError) { if fileList == nil || fileList.FileInfos == nil || len(fileList.FileInfos) == 0 { - return nil + return true, nil // On an empty file list, we consider all files as having membership } channels := make(map[string]*model.Channel) @@ -1510,7 +1511,7 @@ func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model. channelIDs := slices.Collect(maps.Keys(channels)) channelList, err := a.GetChannels(rctx, channelIDs) if err != nil && err.StatusCode != http.StatusNotFound { - return err + return false, err } for _, channel := range channelList { channels[channel.Id] = channel @@ -1520,6 +1521,7 @@ func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model. channelReadPermission := make(map[string]bool) filteredFiles := make(map[string]*model.FileInfo) filteredOrder := []string{} + allFilesHaveMembership := true for _, fileID := range fileList.Order { fileInfo, ok := fileList.FileInfos[fileID] @@ -1530,10 +1532,14 @@ func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model. if _, ok := channelReadPermission[fileInfo.ChannelId]; !ok { channel := channels[fileInfo.ChannelId] allowed := false + isMember := true if channel != nil { - allowed = a.HasPermissionToReadChannel(rctx, userID, channel) + allowed, isMember = a.HasPermissionToReadChannel(rctx, userID, channel) } channelReadPermission[fileInfo.ChannelId] = allowed + if allowed { + allFilesHaveMembership = allFilesHaveMembership && isMember + } } if channelReadPermission[fileInfo.ChannelId] { @@ -1545,7 +1551,7 @@ func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model. fileList.FileInfos = filteredFiles fileList.Order = filteredOrder - return nil + return allFilesHaveMembership, nil } func (a *App) ExtractContentFromFileInfo(rctx request.CTX, fileInfo *model.FileInfo) error { diff --git a/server/channels/app/file_test.go b/server/channels/app/file_test.go index b5c33141ef6..0f52a8911c9 100644 --- a/server/channels/app/file_test.go +++ b/server/channels/app/file_test.go @@ -291,13 +291,13 @@ func TestMigrateFilenamesToFileInfos(t *testing.T) { fpath := fmt.Sprintf("/teams/%v/channels/%v/users/%v/%v/test.png", th.BasicTeam.Id, th.BasicChannel.Id, th.BasicUser.Id, fileID) _, err := th.App.WriteFile(file, fpath) require.Nil(t, err) - rpost, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Filenames: []string{fmt.Sprintf("/%v/%v/%v/test.png", th.BasicChannel.Id, th.BasicUser.Id, fileID)}}, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Filenames: []string{fmt.Sprintf("/%v/%v/%v/test.png", th.BasicChannel.Id, th.BasicUser.Id, fileID)}}, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) infos = th.App.MigrateFilenamesToFileInfos(th.Context, rpost) assert.Equal(t, 1, len(infos)) - rpost, err = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Filenames: []string{fmt.Sprintf("/%v/%v/%v/../../test.png", th.BasicChannel.Id, th.BasicUser.Id, fileID)}}, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Filenames: []string{fmt.Sprintf("/%v/%v/%v/../../test.png", th.BasicChannel.Id, th.BasicUser.Id, fileID)}}, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) infos = th.App.MigrateFilenamesToFileInfos(th.Context, rpost) @@ -511,7 +511,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) { page := 0 - results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allFilesHaveMembership, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) require.Nil(t, err) require.NotNil(t, results) @@ -524,6 +524,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) { fileInfos[1].Id, fileInfos[0].Id, }, results.Order) + require.True(t, allFilesHaveMembership) }) t.Run("should not return later pages of fileInfos from database", func(t *testing.T) { @@ -531,11 +532,12 @@ func TestSearchFilesInTeamForUser(t *testing.T) { page := 1 - results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allFilesHaveMembership, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) require.Nil(t, err) require.NotNil(t, results) assert.Equal(t, []string{}, results.Order) + require.True(t, allFilesHaveMembership) }) t.Run("should return first page of fileInfos from ElasticSearch", func(t *testing.T) { @@ -560,11 +562,12 @@ func TestSearchFilesInTeamForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allFilesHaveMembership, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) require.Nil(t, err) require.NotNil(t, results) assert.Equal(t, resultsPage, results.Order) + require.True(t, allFilesHaveMembership) es.AssertExpectations(t) }) @@ -587,11 +590,12 @@ func TestSearchFilesInTeamForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allFilesHaveMembership, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) require.Nil(t, err) require.NotNil(t, results) assert.Equal(t, resultsPage, results.Order) + require.True(t, allFilesHaveMembership) es.AssertExpectations(t) }) @@ -611,7 +615,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allFilesHaveMembership, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) require.Nil(t, err) require.NotNil(t, results) @@ -624,6 +628,7 @@ func TestSearchFilesInTeamForUser(t *testing.T) { fileInfos[1].Id, fileInfos[0].Id, }, results.Order) + require.True(t, allFilesHaveMembership) es.AssertExpectations(t) }) @@ -643,10 +648,11 @@ func TestSearchFilesInTeamForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allFilesHaveMembership, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) require.Nil(t, err) assert.Equal(t, []string{}, results.Order) + require.True(t, allFilesHaveMembership) es.AssertExpectations(t) }) } @@ -769,16 +775,18 @@ func TestSetFileSearchableContent(t *testing.T) { }) require.NoError(t, err) - result, appErr := th.App.SearchFilesInTeamForUser(th.Context, "searchable", th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, 0, 60) + result, allFilesHaveMembership, appErr := th.App.SearchFilesInTeamForUser(th.Context, "searchable", th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, 0, 60) require.Nil(t, appErr) assert.Equal(t, 0, len(result.Order)) + require.True(t, allFilesHaveMembership) appErr = th.App.SetFileSearchableContent(th.Context, fileInfo.Id, "searchable") require.Nil(t, appErr) - result, appErr = th.App.SearchFilesInTeamForUser(th.Context, "searchable", th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, 0, 60) + result, allFilesHaveMembership, appErr = th.App.SearchFilesInTeamForUser(th.Context, "searchable", th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, 0, 60) require.Nil(t, appErr) assert.Equal(t, 1, len(result.Order)) + require.True(t, allFilesHaveMembership) } func TestPermanentDeleteFilesByPost(t *testing.T) { @@ -805,7 +813,7 @@ func TestPermanentDeleteFilesByPost(t *testing.T) { FileIds: []string{info1.Id}, } - post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) assert.Nil(t, err) err = th.App.PermanentDeleteFilesByPost(th.Context, post.Id) @@ -829,7 +837,7 @@ func TestPermanentDeleteFilesByPost(t *testing.T) { CreateAt: 0, } - post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) assert.Nil(t, err) err = th.App.PermanentDeleteFilesByPost(th.Context, post.Id) @@ -872,10 +880,11 @@ func TestFilterFilesByChannelPermissions(t *testing.T) { fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id} // BasicUser should have access to all files - appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) + allFilesHaveMembership, appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, fileList.FileInfos, 3) require.Len(t, fileList.Order, 3) + require.True(t, allFilesHaveMembership) }) t.Run("should filter files when guest has read_channel_content permission", func(t *testing.T) { @@ -885,10 +894,11 @@ func TestFilterFilesByChannelPermissions(t *testing.T) { fileList.FileInfos[fileInfo3.Id] = fileInfo3 fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id} - appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id) + allFilesHaveMembership, appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id) require.Nil(t, appErr) require.Len(t, fileList.FileInfos, 3) require.Len(t, fileList.Order, 3) + require.True(t, allFilesHaveMembership) }) t.Run("should filter files when guest does not have read_channel_content permission", func(t *testing.T) { @@ -923,22 +933,25 @@ func TestFilterFilesByChannelPermissions(t *testing.T) { fileList.FileInfos[fileInfo3.Id] = fileInfo3 fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id} - appErr = th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id) + allFilesHaveMembership, appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id) require.Nil(t, appErr) require.Len(t, fileList.FileInfos, 0) require.Len(t, fileList.Order, 0) + require.True(t, allFilesHaveMembership) }) t.Run("should handle empty file list", func(t *testing.T) { fileList := model.NewFileInfoList() - appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) + allFilesHaveMembership, appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, fileList.FileInfos, 0) require.Len(t, fileList.Order, 0) + require.True(t, allFilesHaveMembership) }) t.Run("should handle nil file list", func(t *testing.T) { - appErr := th.App.FilterFilesByChannelPermissions(th.Context, nil, th.BasicUser.Id) + allFilesHaveMembership, appErr := th.App.FilterFilesByChannelPermissions(th.Context, nil, th.BasicUser.Id) + require.True(t, allFilesHaveMembership) require.Nil(t, appErr) }) @@ -952,10 +965,11 @@ func TestFilterFilesByChannelPermissions(t *testing.T) { fileList.FileInfos[fileWithoutChannel.Id] = fileWithoutChannel fileList.Order = []string{fileWithoutChannel.Id} - appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) + allFilesHaveMembership, appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, fileList.FileInfos, 0) require.Len(t, fileList.Order, 0) + require.True(t, allFilesHaveMembership) }) t.Run("should handle files from non-existent channels", func(t *testing.T) { @@ -968,9 +982,10 @@ func TestFilterFilesByChannelPermissions(t *testing.T) { fileList.FileInfos[fileWithInvalidChannel.Id] = fileWithInvalidChannel fileList.Order = []string{fileWithInvalidChannel.Id} - appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) + allFilesHaveMembership, appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, fileList.FileInfos, 0) require.Len(t, fileList.Order, 0) + require.True(t, allFilesHaveMembership) }) } diff --git a/server/channels/app/helper_test.go b/server/channels/app/helper_test.go index 6094b85248d..f0ec945d4a2 100644 --- a/server/channels/app/helper_test.go +++ b/server/channels/app/helper_test.go @@ -488,7 +488,7 @@ func (th *TestHelper) CreatePost(tb testing.TB, channel *model.Channel, postOpti option(post) } - post, err := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(tb, err) return post } @@ -501,7 +501,7 @@ func (th *TestHelper) CreateMessagePost(tb testing.TB, channel *model.Channel, m CreateAt: model.GetMillis() - 10000, } - post, err := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(tb, err) return post } @@ -518,7 +518,7 @@ func (th *TestHelper) CreatePostReply(tb testing.TB, root *model.Post) *model.Po ch, err := th.App.GetChannel(th.Context, root.ChannelId) require.Nil(tb, err) - post, err = th.App.CreatePost(th.Context, post, ch, model.CreatePostFlags{SetOnline: true}) + post, _, err = th.App.CreatePost(th.Context, post, ch, model.CreatePostFlags{SetOnline: true}) require.Nil(tb, err) return post } @@ -775,7 +775,7 @@ func (th *TestHelper) PostPatch(tb testing.TB, post *model.Post, message string, optionFunc(postPatch) } - updatedPost, appErr := th.App.PatchPost(th.Context, post.Id, postPatch, nil) + updatedPost, _, appErr := th.App.PatchPost(th.Context, post.Id, postPatch, nil) require.Nil(tb, appErr) return updatedPost diff --git a/server/channels/app/integration_action.go b/server/channels/app/integration_action.go index 5b509261cbf..b72fa94a636 100644 --- a/server/channels/app/integration_action.go +++ b/server/channels/app/integration_action.go @@ -280,7 +280,7 @@ func (a *App) DoPostActionWithCookie(rctx request.CTX, postID, actionId, userID, response.Update.IsPinned = originalIsPinned response.Update.HasReactions = originalHasReactions - if _, appErr = a.UpdatePost(rctx, response.Update, &model.UpdatePostOptions{SafeUpdate: false}); appErr != nil { + if _, _, appErr = a.UpdatePost(rctx, response.Update, &model.UpdatePostOptions{SafeUpdate: false}); appErr != nil { return "", appErr } } diff --git a/server/channels/app/integration_action_test.go b/server/channels/app/integration_action_test.go index e22dedf2972..a55b6876e97 100644 --- a/server/channels/app/integration_action_test.go +++ b/server/channels/app/integration_action_test.go @@ -60,7 +60,7 @@ func TestPostActionInvalidURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -113,7 +113,7 @@ func TestPostActionEmptyResponse(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) @@ -157,7 +157,7 @@ func TestPostActionEmptyResponse(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) @@ -267,7 +267,7 @@ func TestPostAction(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) @@ -304,7 +304,7 @@ func TestPostAction(t *testing.T) { }, } - post2, err := th.App.CreatePostAsUser(th.Context, &menuPost, "", true) + post2, _, err := th.App.CreatePostAsUser(th.Context, &menuPost, "", true) require.Nil(t, err) attachments2, ok := post2.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) @@ -362,7 +362,7 @@ func TestPostAction(t *testing.T) { }, } - postplugin, err := th.App.CreatePostAsUser(th.Context, &interactivePostPlugin, "", true) + postplugin, _, err := th.App.CreatePostAsUser(th.Context, &interactivePostPlugin, "", true) require.Nil(t, err) attachmentsPlugin, ok := postplugin.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) @@ -410,7 +410,7 @@ func TestPostAction(t *testing.T) { }, } - postSiteURL, err := th.App.CreatePostAsUser(th.Context, &interactivePostSiteURL, "", true) + postSiteURL, _, err := th.App.CreatePostAsUser(th.Context, &interactivePostSiteURL, "", true) require.Nil(t, err) attachmentsSiteURL, ok := postSiteURL.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) @@ -452,7 +452,7 @@ func TestPostAction(t *testing.T) { }, } - postSubpath, err := th.App.CreatePostAsUser(th.Context, &interactivePostSubpath, "", true) + postSubpath, _, err := th.App.CreatePostAsUser(th.Context, &interactivePostSubpath, "", true) require.Nil(t, err) attachmentsSubpath, ok := postSubpath.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) @@ -527,7 +527,7 @@ func TestPostActionProps(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -711,7 +711,7 @@ func TestPostActionRelativeURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -751,7 +751,7 @@ func TestPostActionRelativeURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -791,7 +791,7 @@ func TestPostActionRelativeURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -831,7 +831,7 @@ func TestPostActionRelativeURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -871,7 +871,7 @@ func TestPostActionRelativeURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -948,7 +948,7 @@ func TestPostActionRelativePluginURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -988,7 +988,7 @@ func TestPostActionRelativePluginURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -1028,7 +1028,7 @@ func TestPostActionRelativePluginURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) @@ -1068,7 +1068,7 @@ func TestPostActionRelativePluginURL(t *testing.T) { }, } - post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) + post, _, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true) require.Nil(t, err) attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment) require.True(t, ok) diff --git a/server/channels/app/job.go b/server/channels/app/job.go index abbe2b38e6b..82816c14ef0 100644 --- a/server/channels/app/job.go +++ b/server/channels/app/job.go @@ -172,7 +172,7 @@ func (a *App) SessionHasPermissionToCreateJob(session model.Session, job *model. channelID := a.getChannelIDFromJobData(job.Data) if channelID != "" { // SECURE: Check specific channel permission - hasChannelPermission := a.HasPermissionToChannel(request.EmptyContext(a.Srv().Log()), session.UserId, channelID, model.PermissionManageChannelAccessRules) + hasChannelPermission, _ := a.HasPermissionToChannel(request.EmptyContext(a.Srv().Log()), session.UserId, channelID, model.PermissionManageChannelAccessRules) if hasChannelPermission { return true, model.PermissionManageChannelAccessRules } diff --git a/server/channels/app/notification.go b/server/channels/app/notification.go index ebd8de19721..f194b20ab6e 100644 --- a/server/channels/app/notification.go +++ b/server/channels/app/notification.go @@ -844,7 +844,7 @@ func (a *App) SendNotifications(rctx request.CTX, post *model.Post, team *model. a.sanitizeProfiles(userThread.Participants, false) userThread.Post.SanitizeProps() - sanitizedPost, err := a.SanitizePostMetadataForUser(rctx, userThread.Post, uid) + sanitizedPost, isMemberForPreview, err := a.SanitizePostMetadataForUser(rctx, userThread.Post, uid) if err != nil { a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonParseError, model.NotificationNoPlatform) rctx.Logger().LogM(mlog.MlvlNotificationError, "Failed to sanitize metadata", @@ -868,6 +868,18 @@ func (a *App) SendNotifications(rctx request.CTX, post *model.Post, team *model. message.Add("previous_unread_mentions", previousUnreadMentions) message.Add("previous_unread_replies", previousUnreadReplies) + auditRec := a.MakeAuditRecord(rctx, model.AuditEventWebsocketPost, model.AuditStatusSuccess) + defer a.LogAuditRec(rctx, auditRec, nil) + model.AddEventParameterToAuditRec(auditRec, "post_id", userThread.Post.Id) + if !isMemberForPreview { + previewPost := userThread.Post.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() + a.Publish(message) } } @@ -1000,7 +1012,7 @@ func (a *App) RemoveNotifications(rctx request.CTX, post *model.Post, channel *m a.sanitizeProfiles(userThread.Participants, false) userThread.Post.SanitizeProps() - sanitizedPost, err1 := a.SanitizePostMetadataForUser(rctx, userThread.Post, userID) + sanitizedPost, isMemberForPreview, err1 := a.SanitizePostMetadataForUser(rctx, userThread.Post, userID) if err1 != nil { return err1 } @@ -1011,6 +1023,18 @@ func (a *App) RemoveNotifications(rctx request.CTX, post *model.Post, channel *m rctx.Logger().Warn("Failed to encode thread to JSON") } + auditRec := a.MakeAuditRecord(rctx, model.AuditEventWebsocketPost, model.AuditStatusSuccess) + defer a.LogAuditRec(rctx, auditRec, nil) + model.AddEventParameterToAuditRec(auditRec, "post_id", userThread.Post.Id) + if !isMemberForPreview { + previewPost := userThread.Post.GetPreviewPost() + if previewPost != nil { + model.AddEventParameterToAuditRec(auditRec, "preview_post_id", previewPost.Post.Id) + } + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() + message := model.NewWebSocketEvent(model.WebsocketEventThreadUpdated, team.Id, "", userID, nil, "") message.Add("thread", string(payload)) message.Add("previous_unread_mentions", previousUnreadMentions) @@ -1443,7 +1467,7 @@ func getMentionsEnabledFields(post *model.Post) model.StringArray { // allowChannelMentions returns whether or not the channel mentions are allowed for the given post. func (a *App) allowChannelMentions(rctx request.CTX, post *model.Post, numProfiles int) bool { - if !a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseChannelMentions) { + if ok, _ := a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseChannelMentions); !ok { return false } @@ -1464,7 +1488,7 @@ func (a *App) allowGroupMentions(rctx request.CTX, post *model.Post) bool { return false } - if !a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseGroupMentions) { + if ok, _ := a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseGroupMentions); !ok { return false } diff --git a/server/channels/app/notification_test.go b/server/channels/app/notification_test.go index 2a89ef3e4c9..d8232fafe0c 100644 --- a/server/channels/app/notification_test.go +++ b/server/channels/app/notification_test.go @@ -44,7 +44,7 @@ func TestSendNotifications(t *testing.T) { _, appErr := th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel, false) require.Nil(t, appErr) - post1, createPostErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ + post1, _, createPostErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "@" + th.BasicUser2.Username, @@ -75,7 +75,7 @@ func TestSendNotifications(t *testing.T) { Message: fmt.Sprintf("hello @%s group", *group.Name), CreateAt: model.GetMillis() - 10000, } - groupMentionPost, createPostErr := th.App.CreatePost(th.Context, groupMentionPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + groupMentionPost, _, createPostErr := th.App.CreatePost(th.Context, groupMentionPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, createPostErr) mentions, err := th.App.SendNotifications(th.Context, groupMentionPost, th.BasicTeam, th.BasicChannel, th.BasicUser, nil, true) @@ -95,7 +95,7 @@ func TestSendNotifications(t *testing.T) { dm, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id) require.Nil(t, appErr) - post2, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ + post2, _, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: dm.Id, Message: "dm message", @@ -111,7 +111,7 @@ func TestSendNotifications(t *testing.T) { appErr = th.App.Srv().InvalidateAllCaches() require.Nil(t, appErr) - post3, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ + post3, _, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: dm.Id, Message: "dm message", @@ -136,7 +136,7 @@ func TestSendNotifications(t *testing.T) { } channel := th.CreateGroupChannel(t, users[0], users[1]) - post2, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ + post2, _, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ UserId: users[0].Id, ChannelId: channel.Id, Message: "gm message", @@ -152,7 +152,7 @@ func TestSendNotifications(t *testing.T) { appErr = th.App.Srv().InvalidateAllCaches() require.Nil(t, appErr) - post3, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ + post3, _, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{ UserId: users[0].Id, ChannelId: channel.Id, Message: "gm message", @@ -178,7 +178,7 @@ func TestSendNotifications(t *testing.T) { Props: model.StringInterface{model.PostPropsFromWebhook: "true", model.PostPropsOverrideUsername: "a bot"}, } - rootPost, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true) + rootPost, _, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true) require.Nil(t, appErr) childPost := &model.Post{ @@ -187,7 +187,7 @@ func TestSendNotifications(t *testing.T) { RootId: rootPost.Id, Message: "a reply", } - childPost, appErr = th.App.CreatePostMissingChannel(th.Context, childPost, false, true) + childPost, _, appErr = th.App.CreatePostMissingChannel(th.Context, childPost, false, true) require.Nil(t, appErr) postList := model.PostList{ @@ -359,7 +359,7 @@ func TestSendNotifications_MentionsFollowers(t *testing.T) { } // Use CreatePost instead of SendNotifications here since we need that to set up some threads state - _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) received1 := <-messages1 @@ -576,7 +576,7 @@ func TestSendNotificationsWithManyUsers(t *testing.T) { users = append(users, user) } - _, appErr1 := th.App.CreatePostMissingChannel(th.Context, &model.Post{ + _, _, appErr1 := th.App.CreatePostMissingChannel(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "@channel", @@ -596,7 +596,7 @@ func TestSendNotificationsWithManyUsers(t *testing.T) { } }) - _, appErr1 = th.App.CreatePostMissingChannel(th.Context, &model.Post{ + _, _, appErr1 = th.App.CreatePostMissingChannel(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "@channel", @@ -2843,7 +2843,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { Message: "root post by user1", UserId: u1.Id, } - rpost, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) + rpost, _, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) replyPost1 := &model.Post{ @@ -2852,7 +2852,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { UserId: u2.Id, RootId: rpost.Id, } - _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) replyPost2 := &model.Post{ @@ -2861,7 +2861,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { UserId: u1.Id, RootId: rpost.Id, } - _, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) threadMembership, appErr := th.App.GetThreadMembershipForUser(u2.Id, rpost.Id) @@ -2892,7 +2892,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { Props: model.StringInterface{model.PostPropsFromWebhook: "true", model.PostPropsOverrideUsername: "a bot"}, } - rootPost, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true) + rootPost, _, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true) require.Nil(t, appErr) childPost := &model.Post{ @@ -2901,7 +2901,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { RootId: rootPost.Id, Message: "a reply", } - childPost, appErr = th.App.CreatePostMissingChannel(th.Context, childPost, false, true) + childPost, _, appErr = th.App.CreatePostMissingChannel(th.Context, childPost, false, true) require.Nil(t, appErr) postList := model.PostList{ @@ -2936,7 +2936,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { Message: "root post by user1", UserId: u1.Id, } - rpost, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) + rpost, _, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // Remove user1 from the channel @@ -2949,7 +2949,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { UserId: u2.Id, RootId: rpost.Id, } - _, appErr = th.App.CreatePost(th.Context, replyPost, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, replyPost, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // Ensure user1 is not auto-following the thread @@ -2981,7 +2981,7 @@ func TestChannelAutoFollowThreads(t *testing.T) { Message: "root post by user3", UserId: u3.Id, } - rpost, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) + rpost, _, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) replyPost1 := &model.Post{ @@ -2990,7 +2990,7 @@ func TestChannelAutoFollowThreads(t *testing.T) { UserId: u1.Id, RootId: rpost.Id, } - _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // user-2 starts auto-following thread @@ -3012,7 +3012,7 @@ func TestChannelAutoFollowThreads(t *testing.T) { UserId: u1.Id, RootId: rpost.Id, } - _, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) // Do NOT start auto-following thread, once "un-followed" @@ -3043,7 +3043,7 @@ func TestRemoveNotifications(t *testing.T) { Message: "root post by user1", UserId: u1.Id, } - rootPost, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) + rootPost, _, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) replyPost1 := &model.Post{ @@ -3052,7 +3052,7 @@ func TestRemoveNotifications(t *testing.T) { UserId: u2.Id, RootId: rootPost.Id, } - _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) replyPost2 := &model.Post{ @@ -3061,7 +3061,7 @@ func TestRemoveNotifications(t *testing.T) { UserId: u1.Id, RootId: rootPost.Id, } - replyPost2, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) + replyPost2, _, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) _, appErr = th.App.DeletePost(th.Context, replyPost2.Id, u1.Id) @@ -3100,7 +3100,7 @@ func TestRemoveNotifications(t *testing.T) { Message: "root post by user1", UserId: u1.Id, } - rootPost, appErr = th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) + rootPost, _, appErr = th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) replyPost1 := &model.Post{ @@ -3109,7 +3109,7 @@ func TestRemoveNotifications(t *testing.T) { UserId: u2.Id, RootId: rootPost.Id, } - _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, replyPost1, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) replyPost2 := &model.Post{ @@ -3118,7 +3118,7 @@ func TestRemoveNotifications(t *testing.T) { UserId: u1.Id, RootId: rootPost.Id, } - replyPost2, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) + replyPost2, _, appErr = th.App.CreatePost(th.Context, replyPost2, c1, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) _, appErr = th.App.DeletePost(th.Context, replyPost2.Id, u1.Id) diff --git a/server/channels/app/notify_admin.go b/server/channels/app/notify_admin.go index 016214b778a..bf33b1936f3 100644 --- a/server/channels/app/notify_admin.go +++ b/server/channels/app/notify_admin.go @@ -164,7 +164,7 @@ func (a *App) upgradePlanAdminNotifyPost(rctx request.CTX, workspaceName string, props["trial"] = trial post.SetProps(props) - _, appErr = a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}) if appErr != nil { rctx.Logger().Warn("Error creating post", mlog.Err(appErr)) diff --git a/server/channels/app/platform/helper_test.go b/server/channels/app/platform/helper_test.go index 47691ee3382..19c4c2dbf62 100644 --- a/server/channels/app/platform/helper_test.go +++ b/server/channels/app/platform/helper_test.go @@ -56,14 +56,24 @@ func (ms *mockSuite) UserCanSeeOtherUser(rctx request.CTX, userID string, otherU return true, nil } -func (ms *mockSuite) HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) bool { - return true +func (ms *mockSuite) HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) (bool, bool) { + return true, true } func (ms *mockSuite) MFARequired(rctx request.CTX) *model.AppError { return nil } +func (ms *mockSuite) MakeAuditRecord(rctx request.CTX, event string, initialStatus string) *model.AuditRecord { + return &model.AuditRecord{ + Status: initialStatus, + EventName: event, + } +} + +func (ms *mockSuite) LogAuditRec(rctx request.CTX, auditRec *model.AuditRecord, err error) { +} + func setupDBStore(tb testing.TB) (store.Store, *model.SqlSettings) { var dbStore store.Store var dbSettings *model.SqlSettings diff --git a/server/channels/app/platform/mocks/SuiteIFace.go b/server/channels/app/platform/mocks/SuiteIFace.go index 7161bef253c..52198075aa1 100644 --- a/server/channels/app/platform/mocks/SuiteIFace.go +++ b/server/channels/app/platform/mocks/SuiteIFace.go @@ -49,7 +49,7 @@ func (_m *SuiteIFace) GetSession(token string) (*model.Session, *model.AppError) } // HasPermissionToReadChannel provides a mock function with given fields: rctx, userID, channel -func (_m *SuiteIFace) HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) bool { +func (_m *SuiteIFace) HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) (bool, bool) { ret := _m.Called(rctx, userID, channel) if len(ret) == 0 { @@ -57,13 +57,28 @@ func (_m *SuiteIFace) HasPermissionToReadChannel(rctx request.CTX, userID string } var r0 bool + var r1 bool + if rf, ok := ret.Get(0).(func(request.CTX, string, *model.Channel) (bool, bool)); ok { + return rf(rctx, userID, channel) + } if rf, ok := ret.Get(0).(func(request.CTX, string, *model.Channel) bool); ok { r0 = rf(rctx, userID, channel) } else { r0 = ret.Get(0).(bool) } - return r0 + if rf, ok := ret.Get(1).(func(request.CTX, string, *model.Channel) bool); ok { + r1 = rf(rctx, userID, channel) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// LogAuditRec provides a mock function with given fields: rctx, auditRec, err +func (_m *SuiteIFace) LogAuditRec(rctx request.CTX, auditRec *model.AuditRecord, err error) { + _m.Called(rctx, auditRec, err) } // MFARequired provides a mock function with given fields: rctx @@ -86,6 +101,26 @@ func (_m *SuiteIFace) MFARequired(rctx request.CTX) *model.AppError { return r0 } +// MakeAuditRecord provides a mock function with given fields: rctx, event, initialStatus +func (_m *SuiteIFace) MakeAuditRecord(rctx request.CTX, event string, initialStatus string) *model.AuditRecord { + ret := _m.Called(rctx, event, initialStatus) + + if len(ret) == 0 { + panic("no return value specified for MakeAuditRecord") + } + + var r0 *model.AuditRecord + if rf, ok := ret.Get(0).(func(request.CTX, string, string) *model.AuditRecord); ok { + r0 = rf(rctx, event, initialStatus) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AuditRecord) + } + } + + return r0 +} + // RolesGrantPermission provides a mock function with given fields: roleNames, permissionId func (_m *SuiteIFace) RolesGrantPermission(roleNames []string, permissionId string) bool { ret := _m.Called(roleNames, permissionId) diff --git a/server/channels/app/platform/web_hub.go b/server/channels/app/platform/web_hub.go index 18fb4da9a5e..502903d265a 100644 --- a/server/channels/app/platform/web_hub.go +++ b/server/channels/app/platform/web_hub.go @@ -28,9 +28,11 @@ const ( type SuiteIFace interface { GetSession(token string) (*model.Session, *model.AppError) RolesGrantPermission(roleNames []string, permissionId string) bool - HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) bool + HasPermissionToReadChannel(rctx request.CTX, userID string, channel *model.Channel) (bool, bool) UserCanSeeOtherUser(rctx request.CTX, userID string, otherUserId string) (bool, *model.AppError) MFARequired(rctx request.CTX) *model.AppError + MakeAuditRecord(rctx request.CTX, event string, initialStatus string) *model.AuditRecord + LogAuditRec(rctx request.CTX, auditRec *model.AuditRecord, err error) } type webConnActivityMessage struct { diff --git a/server/channels/app/plugin_api.go b/server/channels/app/plugin_api.go index 61d169e0fc7..fbaa15c0041 100644 --- a/server/channels/app/plugin_api.go +++ b/server/channels/app/plugin_api.go @@ -591,7 +591,7 @@ func (api *PluginAPI) SearchPostsInTeamForUser(teamID string, userID string, sea includeDeletedChannels = *searchParams.IncludeDeletedChannels } - results, appErr := api.app.SearchPostsForUser(api.ctx, terms, userID, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage) + results, _, appErr := api.app.SearchPostsForUser(api.ctx, terms, userID, teamID, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage) if results != nil { results = results.ForPlugin() } @@ -773,7 +773,7 @@ func (api *PluginAPI) DeleteGroupSyncable(groupID string, syncableID string, syn func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) { post.AddProp(model.PostPropsFromPlugin, "true") - post, appErr := api.app.CreatePostMissingChannel(api.ctx, post, true, true) + post, _, appErr := api.app.CreatePostMissingChannel(api.ctx, post, true, true) if post != nil { post = post.ForPlugin() } @@ -793,11 +793,13 @@ func (api *PluginAPI) GetReactions(postID string) ([]*model.Reaction, *model.App } func (api *PluginAPI) SendEphemeralPost(userID string, post *model.Post) *model.Post { - return api.app.SendEphemeralPost(api.ctx, userID, post).ForPlugin() + newPost, _ := api.app.SendEphemeralPost(api.ctx, userID, post) + return newPost.ForPlugin() } func (api *PluginAPI) UpdateEphemeralPost(userID string, post *model.Post) *model.Post { - return api.app.UpdateEphemeralPost(api.ctx, userID, post).ForPlugin() + newPost, _ := api.app.UpdateEphemeralPost(api.ctx, userID, post) + return newPost.ForPlugin() } func (api *PluginAPI) DeleteEphemeralPost(userID, postID string) { @@ -858,7 +860,7 @@ func (api *PluginAPI) GetPostsForChannel(channelID string, page, perPage int) (* } func (api *PluginAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError) { - post, appErr := api.app.UpdatePost(api.ctx, post, &model.UpdatePostOptions{SafeUpdate: false}) + post, _, appErr := api.app.UpdatePost(api.ctx, post, &model.UpdatePostOptions{SafeUpdate: false}) if post != nil { post = post.ForPlugin() } @@ -1104,7 +1106,8 @@ func (api *PluginAPI) HasPermissionToTeam(userID, teamID string, permission *mod } func (api *PluginAPI) HasPermissionToChannel(userID, channelID string, permission *model.Permission) bool { - return api.app.HasPermissionToChannel(api.ctx, userID, channelID, permission) + ok, _ := api.app.HasPermissionToChannel(api.ctx, userID, channelID, permission) + return ok } func (api *PluginAPI) RolesGrantPermission(roleNames []string, permissionId string) bool { diff --git a/server/channels/app/plugin_hooks_test.go b/server/channels/app/plugin_hooks_test.go index f8ee129b59b..6c6fca87adf 100644 --- a/server/channels/app/plugin_hooks_test.go +++ b/server/channels/app/plugin_hooks_test.go @@ -111,7 +111,7 @@ func TestHookMessageWillBePosted(t *testing.T) { Message: "message_", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) if assert.NotNil(t, err) { assert.Equal(t, "Post rejected by plugin. rejected", err.Message) } @@ -152,7 +152,7 @@ func TestHookMessageWillBePosted(t *testing.T) { Message: "message_", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) if assert.NotNil(t, err) { assert.Equal(t, "Post rejected by plugin. rejected", err.Message) } @@ -192,7 +192,7 @@ func TestHookMessageWillBePosted(t *testing.T) { Message: "message", CreateAt: model.GetMillis() - 10000, } - post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, "message", post.Message) @@ -236,7 +236,7 @@ func TestHookMessageWillBePosted(t *testing.T) { Message: "message", CreateAt: model.GetMillis() - 10000, } - post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, "message_fromplugin", post.Message) @@ -302,7 +302,7 @@ func TestHookMessageWillBePosted(t *testing.T) { Message: "message", CreateAt: model.GetMillis() - 10000, } - post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, "prefix_message_suffix", post.Message) }) @@ -347,7 +347,7 @@ func TestHookMessageHasBeenPosted(t *testing.T) { Message: "message", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) } @@ -387,11 +387,11 @@ func TestHookMessageWillBeUpdated(t *testing.T) { Message: "message_", CreateAt: model.GetMillis() - 10000, } - post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, "message_", post.Message) post.Message = post.Message + "edited_" - post, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + post, _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err) assert.Equal(t, "message_edited_fromplugin", post.Message) } @@ -436,11 +436,11 @@ func TestHookMessageHasBeenUpdated(t *testing.T) { Message: "message_", CreateAt: model.GetMillis() - 10000, } - post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, "message_", post.Message) post.Message = post.Message + "edited" - _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + _, _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err) } @@ -483,7 +483,7 @@ func TestHookMessageHasBeenDeleted(t *testing.T) { Message: "message", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) _, err = th.App.DeletePost(th.Context, post.Id, th.BasicUser.Id) require.Nil(t, err) @@ -1062,7 +1062,7 @@ func TestHookContext(t *testing.T) { Message: "not this", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(ctx, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(ctx, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) } @@ -1766,7 +1766,7 @@ func TestHookMessagesWillBeConsumed(t *testing.T) { Message: "message", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(th.Context, newPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(th.Context, newPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) post, err := th.App.GetSinglePost(th.Context, newPost.Id, true) @@ -1789,7 +1789,7 @@ func TestHookMessagesWillBeConsumed(t *testing.T) { Message: "message", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(th.Context, newPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(th.Context, newPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) post, err := th.App.GetSinglePost(th.Context, newPost.Id, true) diff --git a/server/channels/app/plugin_test.go b/server/channels/app/plugin_test.go index 49c8f95a1a6..d1e1913b56d 100644 --- a/server/channels/app/plugin_test.go +++ b/server/channels/app/plugin_test.go @@ -639,7 +639,7 @@ func TestPluginPanicLogs(t *testing.T) { Message: "message_", CreateAt: model.GetMillis() - 10000, } - _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) assert.Nil(t, err) th.TestLogger.Flush() diff --git a/server/channels/app/post.go b/server/channels/app/post.go index e7f49931f49..98f0c6d37be 100644 --- a/server/channels/app/post.go +++ b/server/channels/app/post.go @@ -38,41 +38,41 @@ const ( var atMentionPattern = regexp.MustCompile(`\B@`) -func (a *App) CreatePostAsUser(rctx request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) { +func (a *App) CreatePostAsUser(rctx request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, bool, *model.AppError) { // Check that channel has not been deleted channel, errCh := a.Srv().Store().Channel().Get(post.ChannelId, true) if errCh != nil { err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]any{"Name": "post.channel_id"}, "", http.StatusBadRequest).Wrap(errCh) - return nil, err + return nil, false, err } if strings.HasPrefix(post.Type, model.PostSystemMessagePrefix) { err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]any{"Name": "post.type"}, "", http.StatusBadRequest) - return nil, err + return nil, false, err } if channel.DeleteAt != 0 { err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) - return nil, err + return nil, false, err } restrictDM, err := a.CheckIfChannelIsRestrictedDM(rctx, channel) if err != nil { - return nil, err + return nil, false, err } if restrictDM { - return nil, model.NewAppError("createPost", "api.post.create_post.can_not_post_in_restricted_dm.error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("createPost", "api.post.create_post.can_not_post_in_restricted_dm.error", nil, "", http.StatusBadRequest) } - rp, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{TriggerWebhooks: true, SetOnline: setOnline}) + rp, isMemberForPreviews, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{TriggerWebhooks: true, SetOnline: setOnline}) if err != nil { if err.Id == "api.post.create_post.root_id.app_error" || err.Id == "api.post.create_post.channel_root_id.app_error" { err.StatusCode = http.StatusBadRequest } - return nil, err + return nil, false, err } // Update the Channel LastViewAt only if: @@ -94,19 +94,19 @@ func (a *App) CreatePostAsUser(rctx request.CTX, post *model.Post, currentSessio } } - return rp, nil + return rp, isMemberForPreviews, nil } -func (a *App) CreatePostMissingChannel(rctx request.CTX, post *model.Post, triggerWebhooks bool, setOnline bool) (*model.Post, *model.AppError) { +func (a *App) CreatePostMissingChannel(rctx request.CTX, post *model.Post, triggerWebhooks bool, setOnline bool) (*model.Post, bool, *model.AppError) { channel, err := a.Srv().Store().Channel().Get(post.ChannelId, true) if err != nil { errCtx := map[string]any{"channel_id": post.ChannelId} var nfErr *store.ErrNotFound switch { case errors.As(err, &nfErr): - return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.existing.app_error", errCtx, "", http.StatusNotFound).Wrap(err) + return nil, false, model.NewAppError("CreatePostMissingChannel", "app.channel.get.existing.app_error", errCtx, "", http.StatusNotFound).Wrap(err) default: - return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.find.app_error", errCtx, "", http.StatusInternalServerError).Wrap(err) + return nil, false, model.NewAppError("CreatePostMissingChannel", "app.channel.get.find.app_error", errCtx, "", http.StatusInternalServerError).Wrap(err) } } @@ -147,7 +147,7 @@ func (a *App) deduplicateCreatePost(rctx request.CTX, post *model.Post) (foundPo // If the other thread finished creating the post, return the created post back to the // client, making the API call feel idempotent. - actualPost, err := a.GetPostIfAuthorized(rctx, postID, rctx.Session(), false) + actualPost, err, _ := a.GetPostIfAuthorized(rctx, postID, rctx.Session(), false) if err != nil && err.StatusCode == http.StatusForbidden { rctx.Logger().Warn("Ignoring pending_post_id for which the user is unauthorized", mlog.String("pending_post_id", post.PendingPostId), mlog.String("post_id", postID), mlog.Err(err)) return nil, nil @@ -160,17 +160,25 @@ func (a *App) deduplicateCreatePost(rctx request.CTX, post *model.Post) (foundPo return actualPost, nil } -func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (savedPost *model.Post, err *model.AppError) { +func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (savedPost *model.Post, isMemberForPreviews bool, err *model.AppError) { if !a.Config().FeatureFlags.EnableSharedChannelsDMs && channel.IsShared() && (channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup) { - return nil, model.NewAppError("CreatePost", "app.post.create_post.shared_dm_or_gm.app_error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("CreatePost", "app.post.create_post.shared_dm_or_gm.app_error", nil, "", http.StatusBadRequest) } foundPost, err := a.deduplicateCreatePost(rctx, post) if err != nil { - return nil, err + return nil, false, err } if foundPost != nil { - return foundPost, nil + isMemberForPreviews = true + if previewPost := foundPost.GetPreviewPost(); previewPost != nil { + var member *model.ChannelMember + member, err = a.GetChannelMember(rctx, previewPost.Post.ChannelId, rctx.Session().UserId) + if err != nil || member == nil { + isMemberForPreviews = false + } + } + return foundPost, isMemberForPreviews, nil } // If we get this far, we've recorded the client-provided pending post id to the cache. @@ -203,7 +211,7 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan return nil }) if err != nil { - return nil, model.NewAppError("CreatePost", "api.post.post_priority.persistent_notification_validation_error.request_error", nil, "", http.StatusInternalServerError).Wrap(err) + return nil, false, model.NewAppError("CreatePost", "api.post.post_priority.persistent_notification_validation_error.request_error", nil, "", http.StatusInternalServerError).Wrap(err) } } @@ -224,9 +232,9 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan var nfErr *store.ErrNotFound switch { case errors.As(nErr, &nfErr): - return nil, model.NewAppError("CreatePost", MissingAccountError, nil, "", http.StatusNotFound).Wrap(nErr) + return nil, false, model.NewAppError("CreatePost", MissingAccountError, nil, "", http.StatusNotFound).Wrap(nErr) default: - return nil, model.NewAppError("CreatePost", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + return nil, false, model.NewAppError("CreatePost", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } @@ -243,16 +251,18 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan } var ephemeralPost *model.Post - if post.Type == "" && !a.HasPermissionToChannel(rctx, user.Id, channel.Id, model.PermissionUseChannelMentions) { - mention := post.DisableMentionHighlights() - if mention != "" { - T := i18n.GetUserTranslations(user.Locale) - ephemeralPost = &model.Post{ - UserId: user.Id, - RootId: post.RootId, - ChannelId: channel.Id, - Message: T("model.post.channel_notifications_disabled_in_channel.message", model.StringInterface{"ChannelName": channel.Name, "Mention": mention}), - Props: model.StringInterface{model.PostPropsMentionHighlightDisabled: true}, + if post.Type == "" { + if hasPermission, _ := a.HasPermissionToChannel(rctx, user.Id, channel.Id, model.PermissionUseChannelMentions); !hasPermission { + mention := post.DisableMentionHighlights() + if mention != "" { + T := i18n.GetUserTranslations(user.Locale) + ephemeralPost = &model.Post{ + UserId: user.Id, + RootId: post.RootId, + ChannelId: channel.Id, + Message: T("model.post.channel_notifications_disabled_in_channel.message", model.StringInterface{"ChannelName": channel.Name, "Mention": mention}), + Props: model.StringInterface{model.PostPropsMentionHighlightDisabled: true}, + } } } } @@ -262,27 +272,27 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan if pchan != nil { result := <-pchan if result.NErr != nil { - return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest).Wrap(result.NErr) + return nil, false, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest).Wrap(result.NErr) } parentPostList = result.Data if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) { - return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) + return nil, false, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) } rootPost := parentPostList.Posts[post.RootId] if rootPost.RootId != "" { - return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) } if rootPost.Type == model.PostTypeBurnOnRead { - return nil, model.NewAppError("createPost", "api.post.create_post.burn_on_read.app_error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("createPost", "api.post.create_post.burn_on_read.app_error", nil, "", http.StatusBadRequest) } } post.Hashtags, _ = model.ParseHashtags(post.Message) if err = a.FillInPostProps(rctx, post, channel); err != nil { - return nil, err + return nil, false, err } // Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088 @@ -329,7 +339,7 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan }, plugin.MessageWillBePostedID) if rejectionError != nil { - return nil, rejectionError + return nil, false, rejectionError } } @@ -353,18 +363,18 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan var invErr *store.ErrInvalidInput switch { case errors.As(nErr, &appErr): - return nil, appErr + return nil, false, appErr case errors.As(nErr, &invErr): - return nil, model.NewAppError("CreatePost", "app.post.save.existing.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) + return nil, false, model.NewAppError("CreatePost", "app.post.save.existing.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) default: - return nil, model.NewAppError("CreatePost", "app.post.save.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + return nil, false, model.NewAppError("CreatePost", "app.post.save.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } // Update the mapping from pending post id to the actual post id, for any clients that // might be duplicating requests. if appErr := a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, rpost.Id, pendingPostIDsCacheTTL); appErr != nil { - return nil, model.NewAppError("CreatePost", "api.post.deduplicate_create_post.cache_error", nil, "", http.StatusInternalServerError).Wrap(appErr) + return nil, false, model.NewAppError("CreatePost", "api.post.deduplicate_create_post.cache_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } if a.Metrics() != nil { @@ -416,7 +426,7 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan mlog.String("reason", model.NotificationReasonResolvePersistentNotificationError), mlog.Err(appErr), ) - return nil, appErr + return nil, false, appErr } } @@ -440,12 +450,12 @@ func (a *App) CreatePost(rctx request.CTX, post *model.Post, channel *model.Chan a.SendEphemeralPost(rctx, post.UserId, ephemeralPost) } - rpost, err = a.SanitizePostMetadataForUser(rctx, rpost, rctx.Session().UserId) + rpost, isMemberForPreviews, err = a.SanitizePostMetadataForUser(rctx, rpost, rctx.Session().UserId) if err != nil { - return nil, err + return nil, false, err } - return rpost, nil + return rpost, isMemberForPreviews, nil } func (a *App) addPostPreviewProp(rctx request.CTX, post *model.Post) (*model.Post, error) { @@ -509,15 +519,17 @@ func (a *App) FillInPostProps(rctx request.CTX, post *model.Post, channel *model } for _, mentioned := range mentionedChannels { - if mentioned.Type == model.ChannelTypeOpen && a.HasPermissionToReadChannel(rctx, post.UserId, mentioned) { - team, err := a.Srv().Store().Team().Get(mentioned.TeamId) - if err != nil { - rctx.Logger().Warn("Failed to get team of the channel mention", mlog.String("team_id", channel.TeamId), mlog.String("channel_id", channel.Id), mlog.Err(err)) - continue - } - channelMentionsProp[mentioned.Name] = map[string]any{ - "display_name": mentioned.DisplayName, - "team_name": team.Name, + if mentioned.Type == model.ChannelTypeOpen { + if ok, _ := a.HasPermissionToReadChannel(rctx, post.UserId, mentioned); ok { + team, err := a.Srv().Store().Team().Get(mentioned.TeamId) + if err != nil { + rctx.Logger().Warn("Failed to get team of the channel mention", mlog.String("team_id", channel.TeamId), mlog.String("channel_id", channel.Id), mlog.Err(err)) + continue + } + channelMentionsProp[mentioned.Name] = map[string]any{ + "display_name": mentioned.DisplayName, + "team_name": team.Name, + } } } } @@ -530,7 +542,12 @@ func (a *App) FillInPostProps(rctx request.CTX, post *model.Post, channel *model } matched := atMentionPattern.MatchString(post.Message) - if a.Srv().License() != nil && *a.Srv().License().Features.LDAPGroups && matched && !a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseGroupMentions) { + shouldAddProp := false + if a.Srv().License() != nil && *a.Srv().License().Features.LDAPGroups && matched { + hasPermission, _ := a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseGroupMentions) + shouldAddProp = !hasPermission + } + if shouldAddProp { post.AddProp(model.PostPropsGroupHighlightDisabled, true) } @@ -630,7 +647,7 @@ func (a *App) handlePostEvents(rctx request.CTX, post *model.Post, user *model.U return nil } -func (a *App) SendEphemeralPost(rctx request.CTX, userID string, post *model.Post) *model.Post { +func (a *App) SendEphemeralPost(rctx request.CTX, userID string, post *model.Post) (*model.Post, bool) { post.Type = model.PostTypeEphemeral // fill in fields which haven't been specified which have sensible defaults @@ -649,7 +666,7 @@ func (a *App) SendEphemeralPost(rctx request.CTX, userID string, post *model.Pos post = a.PreparePostForClientWithEmbedsAndImages(rctx, post, &model.PreparePostForClientOpts{IsNewPost: true, IncludePriority: true}) post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) - sanitizedPost, appErr := a.SanitizePostMetadataForUser(rctx, post, userID) + sanitizedPost, isMemberForPreviews, appErr := a.SanitizePostMetadataForUser(rctx, post, userID) if appErr != nil { rctx.Logger().Error("Failed to sanitize post metadata for user", mlog.String("user_id", userID), mlog.Err(appErr)) @@ -667,10 +684,10 @@ func (a *App) SendEphemeralPost(rctx request.CTX, userID string, post *model.Pos message.Add("post", postJSON) a.Publish(message) - return post + return post, isMemberForPreviews } -func (a *App) UpdateEphemeralPost(rctx request.CTX, userID string, post *model.Post) *model.Post { +func (a *App) UpdateEphemeralPost(rctx request.CTX, userID string, post *model.Post) (*model.Post, bool) { post.Type = model.PostTypeEphemeral post.UpdateAt = model.GetMillis() @@ -683,7 +700,7 @@ func (a *App) UpdateEphemeralPost(rctx request.CTX, userID string, post *model.P post = a.PreparePostForClientWithEmbedsAndImages(rctx, post, &model.PreparePostForClientOpts{IsNewPost: true, IncludePriority: true}) post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) - sanitizedPost, appErr := a.SanitizePostMetadataForUser(rctx, post, userID) + sanitizedPost, isMemberForPreviews, appErr := a.SanitizePostMetadataForUser(rctx, post, userID) if appErr != nil { rctx.Logger().Error("Failed to sanitize post metadata for user", mlog.String("user_id", userID), mlog.Err(appErr)) @@ -701,7 +718,7 @@ func (a *App) UpdateEphemeralPost(rctx request.CTX, userID string, post *model.P message.Add("post", postJSON) a.Publish(message) - return post + return post, isMemberForPreviews } func (a *App) DeleteEphemeralPost(rctx request.CTX, userID, postID string) { @@ -722,7 +739,7 @@ func (a *App) DeleteEphemeralPost(rctx request.CTX, userID, postID string) { a.Publish(message) } -func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, *model.AppError) { +func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, bool, *model.AppError) { if updatePostOptions == nil { updatePostOptions = model.DefaultUpdatePostOptions() } @@ -735,11 +752,11 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda var invErr *store.ErrInvalidInput switch { case errors.As(nErr, &invErr): - return nil, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) + return nil, false, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, "", http.StatusBadRequest).Wrap(nErr) case errors.As(nErr, &nfErr): - return nil, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, "", http.StatusNotFound).Wrap(nErr) + return nil, false, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, "", http.StatusNotFound).Wrap(nErr) default: - return nil, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + return nil, false, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } oldPost := postLists.Posts[receivedUpdatedPost.Id] @@ -747,40 +764,40 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda var appErr *model.AppError if oldPost == nil { appErr = model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+receivedUpdatedPost.Id, http.StatusBadRequest) - return nil, appErr + return nil, false, appErr } if oldPost.DeleteAt != 0 { appErr = model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]any{"PostId": receivedUpdatedPost.Id}, "", http.StatusBadRequest) - return nil, appErr + return nil, false, appErr } if oldPost.Type == model.PostTypeBurnOnRead { - return nil, model.NewAppError("UpdatePost", "api.post.update_post.burn_on_read.app_error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("UpdatePost", "api.post.update_post.burn_on_read.app_error", nil, "", http.StatusBadRequest) } if oldPost.IsSystemMessage() { appErr = model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+receivedUpdatedPost.Id, http.StatusBadRequest) - return nil, appErr + return nil, false, appErr } channel, appErr := a.GetChannel(rctx, oldPost.ChannelId) if appErr != nil { - return nil, appErr + return nil, false, appErr } if channel.DeleteAt != 0 { - return nil, model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) } restrictDM, err := a.CheckIfChannelIsRestrictedDM(rctx, channel) if err != nil { - return nil, err + return nil, false, err } if restrictDM { err := model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_restricted_dm.error", nil, "", http.StatusBadRequest) - return nil, err + return nil, false, err } newPost := oldPost.Clone() @@ -799,7 +816,7 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda var fileIds []string fileIds, appErr = a.processPostFileChanges(rctx, receivedUpdatedPost, oldPost, updatePostOptions) if appErr != nil { - return nil, appErr + return nil, false, appErr } newPost.FileIds = fileIds } @@ -810,7 +827,7 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda } if appErr = a.FillInPostProps(rctx, newPost, nil); appErr != nil { - return nil, appErr + return nil, false, appErr } if receivedUpdatedPost.IsRemote() { @@ -825,7 +842,7 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda return newPost != nil }, plugin.MessageWillBeUpdatedID) if newPost == nil { - return nil, model.NewAppError("UpdatePost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("UpdatePost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) } } @@ -842,9 +859,9 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda if nErr != nil { switch { case errors.As(nErr, &appErr): - return nil, appErr + return nil, false, appErr default: - return nil, model.NewAppError("UpdatePost", "app.post.update.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + return nil, false, model.NewAppError("UpdatePost", "app.post.update.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } } @@ -868,20 +885,20 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda rpost, nErr = a.addPostPreviewProp(rctx, rpost) if nErr != nil { - return nil, model.NewAppError("UpdatePost", "app.post.update.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + return nil, false, model.NewAppError("UpdatePost", "app.post.update.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) } message := model.NewWebSocketEvent(model.WebsocketEventPostEdited, "", rpost.ChannelId, "", nil, "") appErr = a.publishWebsocketEventForPost(rctx, rpost, message) if appErr != nil { - return nil, appErr + return nil, false, appErr } a.invalidateCacheForChannelPosts(rpost.ChannelId) userID := rctx.Session().UserId - sanitizedPost, appErr := a.SanitizePostMetadataForUser(rctx, rpost, userID) + sanitizedPost, isMemberForPreviews, appErr := a.SanitizePostMetadataForUser(rctx, rpost, userID) if appErr != nil { mlog.Error("Failed to sanitize post metadata for user", mlog.String("user_id", userID), mlog.Err(appErr)) @@ -892,7 +909,7 @@ func (a *App) UpdatePost(rctx request.CTX, receivedUpdatedPost *model.Post, upda } rpost = sanitizedPost - return rpost, nil + return rpost, isMemberForPreviews, nil } func (a *App) publishWebsocketEventForPost(rctx request.CTX, post *model.Post, message *model.WebSocketEvent) *model.AppError { @@ -1016,7 +1033,9 @@ func (a *App) setupBroadcastHookForPermalink(rctx request.CTX, post *model.Post, // In case the user does have permission to read, we set the metadata back. // Note that this is the return value to the post creator, and has nothing to do // with the content of the websocket broadcast to that user or any other. - if a.HasPermissionToReadChannel(rctx, post.UserId, permalinkPreviewedChannel) { + // We also don't check the membership for the previewed post, since + // the broadcast handler will create the audit events if needed. + if ok, _ := a.HasPermissionToReadChannel(rctx, post.UserId, permalinkPreviewedChannel); ok { post.AddProp(model.PostPropsPreviewedPost, previewProp) post.Metadata.Embeds = append(post.Metadata.Embeds, &model.PostEmbed{Type: model.PostEmbedPermalink, Data: permalinkPreviewedPost}) } @@ -1041,53 +1060,53 @@ func (a *App) processBroadcastHookForBurnOnRead(rctx request.CTX, postJSON strin return nil } -func (a *App) PatchPost(rctx request.CTX, postID string, patch *model.PostPatch, patchPostOptions *model.UpdatePostOptions) (*model.Post, *model.AppError) { +func (a *App) PatchPost(rctx request.CTX, postID string, patch *model.PostPatch, patchPostOptions *model.UpdatePostOptions) (*model.Post, bool, *model.AppError) { if patchPostOptions == nil { patchPostOptions = model.DefaultUpdatePostOptions() } post, err := a.GetSinglePost(rctx, postID, false) if err != nil { - return nil, err + return nil, false, err } // only allow to update the pinned status of burn-on-read posts if the status is different if post.Type == model.PostTypeBurnOnRead { - return nil, model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_burn_on_read_post.error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_burn_on_read_post.error", nil, "", http.StatusBadRequest) } channel, err := a.GetChannel(rctx, post.ChannelId) if err != nil { - return nil, err + return nil, false, err } if channel.DeleteAt != 0 { err = model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) - return nil, err + return nil, false, err } restrictDM, err := a.CheckIfChannelIsRestrictedDM(rctx, channel) if err != nil { - return nil, err + return nil, false, err } if restrictDM { - return nil, model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_restricted_dm.error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_restricted_dm.error", nil, "", http.StatusBadRequest) } - if !a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseChannelMentions) { + if ok, _ := a.HasPermissionToChannel(rctx, post.UserId, post.ChannelId, model.PermissionUseChannelMentions); !ok { patch.DisableMentionHighlights() } post.Patch(patch) patchPostOptions.SafeUpdate = false - updatedPost, err := a.UpdatePost(rctx, post, patchPostOptions) + updatedPost, isMemberForPreviews, err := a.UpdatePost(rctx, post, patchPostOptions) if err != nil { - return nil, err + return nil, false, err } - return updatedPost, nil + return updatedPost, isMemberForPreviews, nil } func (a *App) GetPostsPage(rctx request.CTX, options model.GetPostsOptions) (*model.PostList, *model.AppError) { @@ -1888,12 +1907,12 @@ func (a *App) SearchPostsInTeam(teamID string, paramsList []*model.SearchParams) }) } -func (a *App) SearchPostsForUser(rctx request.CTX, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) { +func (a *App) SearchPostsForUser(rctx request.CTX, terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, bool, *model.AppError) { var postSearchResults *model.PostSearchResults paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset) if !*a.Config().ServiceSettings.EnablePostSearch { - return nil, model.NewAppError("SearchPostsForUser", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamID, userID), http.StatusNotImplemented) + return nil, false, model.NewAppError("SearchPostsForUser", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamID, userID), http.StatusNotImplemented) } finalParamsList := []*model.SearchParams{} @@ -1920,7 +1939,7 @@ func (a *App) SearchPostsForUser(rctx request.CTX, terms string, userID string, // If the processed search params are empty, return empty search results. if len(finalParamsList) == 0 { - return model.MakePostSearchResults(model.NewPostList(), nil), nil + return model.MakePostSearchResults(model.NewPostList(), nil), true, nil } postSearchResults, err := a.Srv().Store().Post().SearchPostsForUser(rctx, finalParamsList, userID, teamID, page, perPage) @@ -1928,30 +1947,31 @@ func (a *App) SearchPostsForUser(rctx request.CTX, terms string, userID string, var appErr *model.AppError switch { case errors.As(err, &appErr): - return nil, appErr + return nil, false, appErr default: - return nil, model.NewAppError("SearchPostsForUser", "app.post.search.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + return nil, false, model.NewAppError("SearchPostsForUser", "app.post.search.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } if appErr := a.filterInaccessiblePosts(postSearchResults.PostList, filterPostOptions{assumeSortedCreatedAt: true}); appErr != nil { - return nil, appErr + return nil, false, appErr } - if appErr := a.FilterPostsByChannelPermissions(rctx, postSearchResults.PostList, userID); appErr != nil { - return nil, appErr + allPostHaveMembership, appErr := a.FilterPostsByChannelPermissions(rctx, postSearchResults.PostList, userID) + if appErr != nil { + return nil, false, appErr } if appErr := a.filterBurnOnReadPosts(postSearchResults.PostList); appErr != nil { - return nil, appErr + return nil, false, appErr } - return postSearchResults, nil + return postSearchResults, allPostHaveMembership, nil } -func (a *App) FilterPostsByChannelPermissions(rctx request.CTX, postList *model.PostList, userID string) *model.AppError { +func (a *App) FilterPostsByChannelPermissions(rctx request.CTX, postList *model.PostList, userID string) (bool, *model.AppError) { if postList == nil || postList.Posts == nil || len(postList.Posts) == 0 { - return nil + return true, nil // On an empty post list, we consider all posts as having membership } channels := make(map[string]*model.Channel) @@ -1965,7 +1985,7 @@ func (a *App) FilterPostsByChannelPermissions(rctx request.CTX, postList *model. channelIDs := slices.Collect(maps.Keys(channels)) channelList, err := a.GetChannels(rctx, channelIDs) if err != nil && err.StatusCode != http.StatusNotFound { - return err + return false, err } for _, channel := range channelList { channels[channel.Id] = channel @@ -1975,6 +1995,7 @@ func (a *App) FilterPostsByChannelPermissions(rctx request.CTX, postList *model. channelReadPermission := make(map[string]bool) filteredPosts := make(map[string]*model.Post) filteredOrder := []string{} + allPostHaveMembership := true for _, postID := range postList.Order { post, ok := postList.Posts[postID] @@ -1985,10 +2006,14 @@ func (a *App) FilterPostsByChannelPermissions(rctx request.CTX, postList *model. if _, ok := channelReadPermission[post.ChannelId]; !ok { channel := channels[post.ChannelId] allowed := false + isMember := true if channel != nil { - allowed = a.HasPermissionToReadChannel(rctx, userID, channel) + allowed, isMember = a.HasPermissionToReadChannel(rctx, userID, channel) } channelReadPermission[post.ChannelId] = allowed + if allowed { + allPostHaveMembership = allPostHaveMembership && isMember + } } if channelReadPermission[post.ChannelId] { @@ -2000,7 +2025,7 @@ func (a *App) FilterPostsByChannelPermissions(rctx request.CTX, postList *model. postList.Posts = filteredPosts postList.Order = filteredOrder - return nil + return allPostHaveMembership, nil } func (a *App) GetFileInfosForPostWithMigration(rctx request.CTX, postID string, includeDeleted bool) ([]*model.FileInfo, *model.AppError) { @@ -2353,28 +2378,29 @@ func (a *App) GetThreadMembershipsForUser(userID, teamID string) ([]*model.Threa return a.Srv().Store().Thread().GetMembershipsForUser(userID, teamID) } -func (a *App) GetPostIfAuthorized(rctx request.CTX, postID string, session *model.Session, includeDeleted bool) (*model.Post, *model.AppError) { +func (a *App) GetPostIfAuthorized(rctx request.CTX, postID string, session *model.Session, includeDeleted bool) (*model.Post, *model.AppError, bool) { post, err := a.GetSinglePost(rctx, postID, includeDeleted) if err != nil { - return nil, err + return nil, err, false } channel, err := a.GetChannel(rctx, post.ChannelId) if err != nil { - return nil, err + return nil, err, false } - if !a.SessionHasPermissionToReadChannel(rctx, *session, channel) { + ok, isMember := a.SessionHasPermissionToReadChannel(rctx, *session, channel) + if !ok { if channel.Type == model.ChannelTypeOpen && !*a.Config().ComplianceSettings.Enable { if !a.SessionHasPermissionToTeam(*session, channel.TeamId, model.PermissionReadPublicChannel) { - return nil, model.MakePermissionError(session, []*model.Permission{model.PermissionReadPublicChannel}) + return nil, model.MakePermissionError(session, []*model.Permission{model.PermissionReadPublicChannel}), false } } else { - return nil, model.MakePermissionError(session, []*model.Permission{model.PermissionReadChannelContent}) + return nil, model.MakePermissionError(session, []*model.Permission{model.PermissionReadChannelContent}), false } } - return post, nil + return post, nil, isMember } // GetPostsByIds response bool value indicates, if the post is inaccessible due to cloud plan's limit. @@ -2557,89 +2583,19 @@ func (a *App) CheckPostReminders(rctx request.CTX) { }, } - if _, err := a.CreatePost(request.EmptyContext(a.Log()), dm, ch, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(request.EmptyContext(a.Log()), dm, ch, model.CreatePostFlags{SetOnline: true}); err != nil { rctx.Logger().Error("Failed to post reminder message", mlog.Err(err)) } } } } -func (a *App) GetPostInfo(rctx request.CTX, postID string) (*model.PostInfo, *model.AppError) { - userID := rctx.Session().UserId - post, appErr := a.GetSinglePost(rctx, postID, false) - if appErr != nil { - return nil, appErr - } - - channel, appErr := a.GetChannel(rctx, post.ChannelId) - if appErr != nil { - return nil, appErr - } - - notFoundError := model.NewAppError("GetPostInfo", "app.post.get.app_error", nil, "", http.StatusNotFound) - - var team *model.Team - hasPermissionToAccessTeam := false - if channel.TeamId != "" { - team, appErr = a.GetTeam(channel.TeamId) - if appErr != nil { - return nil, appErr - } - - teamMember, appErr := a.GetTeamMember(rctx, channel.TeamId, userID) - if appErr != nil && appErr.StatusCode != http.StatusNotFound { - return nil, appErr - } - - if appErr == nil { - if teamMember.DeleteAt == 0 { - hasPermissionToAccessTeam = true - } - } - - if !hasPermissionToAccessTeam { - if team.AllowOpenInvite { - hasPermissionToAccessTeam = a.HasPermissionToTeam(rctx, userID, team.Id, model.PermissionJoinPublicTeams) - } else { - hasPermissionToAccessTeam = a.HasPermissionToTeam(rctx, userID, team.Id, model.PermissionJoinPrivateTeams) - } - } - } else { - // This happens in case of DMs and GMs. - hasPermissionToAccessTeam = true - } - - if !hasPermissionToAccessTeam { - return nil, notFoundError - } - - hasPermissionToAccessChannel := false - - _, channelMemberErr := a.GetChannelMember(rctx, channel.Id, userID) - - if channelMemberErr == nil { - hasPermissionToAccessChannel = true - } - - if !hasPermissionToAccessChannel { - if channel.Type == model.ChannelTypeOpen { - hasPermissionToAccessChannel = true - } else if channel.Type == model.ChannelTypePrivate { - hasPermissionToAccessChannel = a.HasPermissionToChannel(rctx, userID, channel.Id, model.PermissionManagePrivateChannelMembers) - } else if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup { - hasPermissionToAccessChannel = a.HasPermissionToChannel(rctx, userID, channel.Id, model.PermissionReadChannelContent) - } - } - - if !hasPermissionToAccessChannel { - return nil, notFoundError - } - +func (a *App) GetPostInfo(rctx request.CTX, postID string, channel *model.Channel, team *model.Team, userID string, hasJoinedChannel bool) (*model.PostInfo, *model.AppError) { info := model.PostInfo{ ChannelId: channel.Id, ChannelType: channel.Type, ChannelDisplayName: channel.DisplayName, - HasJoinedChannel: channelMemberErr == nil, + HasJoinedChannel: hasJoinedChannel, } if team != nil { teamMember, teamMemberErr := a.GetTeamMember(rctx, team.Id, userID) @@ -2744,7 +2700,7 @@ func (a *App) ValidateMoveOrCopy(rctx request.CTX, wpl *model.WranglerPostList, return nil } -func (a *App) CopyWranglerPostlist(rctx request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, *model.AppError) { +func (a *App) CopyWranglerPostlist(rctx request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, bool, *model.AppError) { var appErr *model.AppError var newRootPost *model.Post @@ -2764,15 +2720,15 @@ func (a *App) CopyWranglerPostlist(rctx request.CTX, wpl *model.WranglerPostList for _, fileID := range post.FileIds { oldFileInfo, appErr = a.GetFileInfo(rctx, fileID) if appErr != nil { - return nil, appErr + return nil, false, appErr } fileBytes, appErr = a.GetFile(rctx, fileID) if appErr != nil { - return nil, appErr + return nil, false, appErr } newFileInfo, appErr = a.UploadFile(rctx, fileBytes, targetChannel.Id, oldFileInfo.Name) if appErr != nil { - return nil, appErr + return nil, false, appErr } newFileIDs = append(newFileIDs, newFileInfo.Id) @@ -2782,6 +2738,8 @@ func (a *App) CopyWranglerPostlist(rctx request.CTX, wpl *model.WranglerPostList } } + var isMemberForPreviews bool + for i, post := range wpl.Posts { var reactions []*model.Reaction @@ -2797,16 +2755,16 @@ func (a *App) CopyWranglerPostlist(rctx request.CTX, wpl *model.WranglerPostList newPost.ChannelId = targetChannel.Id if i == 0 { - newPost, appErr = a.CreatePost(rctx, newPost, targetChannel, model.CreatePostFlags{}) + newPost, isMemberForPreviews, appErr = a.CreatePost(rctx, newPost, targetChannel, model.CreatePostFlags{}) if appErr != nil { - return nil, appErr + return nil, false, appErr } newRootPost = newPost.Clone() } else { newPost.RootId = newRootPost.Id - newPost, appErr = a.CreatePost(rctx, newPost, targetChannel, model.CreatePostFlags{}) + newPost, _, appErr = a.CreatePost(rctx, newPost, targetChannel, model.CreatePostFlags{}) if appErr != nil { - return nil, appErr + return nil, false, appErr } } @@ -2820,7 +2778,7 @@ func (a *App) CopyWranglerPostlist(rctx request.CTX, wpl *model.WranglerPostList } } - return newRootPost, nil + return newRootPost, isMemberForPreviews, nil } func (a *App) MoveThread(rctx request.CTX, postID string, sourceChannelID, channelID string, user *model.User) *model.AppError { @@ -2866,7 +2824,7 @@ func (a *App) MoveThread(rctx request.CTX, postID string, sourceChannelID, chann // To simulate the move, we first copy the original messages(s) to the // new channel and later delete the original messages(s). - newRootPost, appErr := a.CopyWranglerPostlist(rctx, wpl, targetChannel) + newRootPost, _, appErr := a.CopyWranglerPostlist(rctx, wpl, targetChannel) if appErr != nil { return appErr } @@ -2879,7 +2837,7 @@ func (a *App) MoveThread(rctx request.CTX, postID string, sourceChannelID, chann ephemeralPostProps := model.StringInterface{ "TranslationID": "app.post.move_thread.from_another_channel", } - _, appErr = a.CreatePost(rctx, &model.Post{ + _, _, appErr = a.CreatePost(rctx, &model.Post{ UserId: user.Id, Type: model.PostTypeWrangler, RootId: newRootPost.Id, @@ -2927,7 +2885,7 @@ func (a *App) MoveThread(rctx request.CTX, postID string, sourceChannelID, chann ephemeralPostProps["NumMessages"] = wpl.NumPosts() - _, appErr = a.CreatePost(rctx, &model.Post{ + _, _, appErr = a.CreatePost(rctx, &model.Post{ UserId: user.Id, Type: model.PostTypeWrangler, ChannelId: originalChannel.Id, @@ -3063,7 +3021,8 @@ func (a *App) SendTestMessage(rctx request.CTX, userID string) (*model.Post, *mo UserId: bot.UserId, } - post, err = a.CreatePost(rctx, post, channel, model.CreatePostFlags{ForceNotification: true}) + // We don't check the preview membership because the test message does not send a link to a different post. + post, _, err = a.CreatePost(rctx, post, channel, model.CreatePostFlags{ForceNotification: true}) if err != nil { return nil, model.NewAppError("SendTestMessage", "app.notifications.send_test_message.errors.create_post", nil, "", http.StatusInternalServerError).Wrap(err) } diff --git a/server/channels/app/post_acknowledgements_test.go b/server/channels/app/post_acknowledgements_test.go index b40b00cc240..09e781c0015 100644 --- a/server/channels/app/post_acknowledgements_test.go +++ b/server/channels/app/post_acknowledgements_test.go @@ -23,7 +23,7 @@ func testSaveAcknowledgementForPost(t *testing.T) { th := Setup(t).InitBasic(t) t.Run("save acknowledgment for post should save acknowledgement", func(t *testing.T) { - post, err := th.App.CreatePostAsUser(th.Context, &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -40,7 +40,7 @@ func testSaveAcknowledgementForPost(t *testing.T) { }) t.Run("saving acknowledgment should update the post's update_at", func(t *testing.T) { - post, err := th.App.CreatePostAsUser(th.Context, &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -64,7 +64,7 @@ func testDeleteAcknowledgementForPost(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic(t) - post, err1 := th.App.CreatePostAsUser(th.Context, &model.Post{ + post, _, err1 := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, CreateAt: model.GetMillis(), @@ -137,7 +137,7 @@ func testGetAcknowledgementsForPostList(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic(t) - p1, err := th.App.CreatePostAsUser(th.Context, &model.Post{ + p1, _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, CreateAt: model.GetMillis(), @@ -145,7 +145,7 @@ func testGetAcknowledgementsForPostList(t *testing.T) { }, "", true) require.Nil(t, err) - p2, err := th.App.CreatePostAsUser(th.Context, &model.Post{ + p2, _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, CreateAt: model.GetMillis(), @@ -153,7 +153,7 @@ func testGetAcknowledgementsForPostList(t *testing.T) { }, "", true) require.Nil(t, err) - p3, err := th.App.CreatePostAsUser(th.Context, &model.Post{ + p3, _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, CreateAt: model.GetMillis(), diff --git a/server/channels/app/post_metadata.go b/server/channels/app/post_metadata.go index ef2cfc4a7e2..a882230f98e 100644 --- a/server/channels/app/post_metadata.go +++ b/server/channels/app/post_metadata.go @@ -247,50 +247,48 @@ func removeEmbeddedPostsFromMetadata(post *model.Post) { post.Metadata.Embeds = newEmbeds } -func (a *App) sanitizePostMetadataForUserAndChannel(rctx request.CTX, post *model.Post, previewedPost *model.PreviewPost, previewedChannel *model.Channel, userID string) *model.Post { - if post.Metadata == nil || len(post.Metadata.Embeds) == 0 || previewedPost == nil { - return post - } - - if previewedChannel != nil && !a.HasPermissionToReadChannel(rctx, userID, previewedChannel) { - removePermalinkMetadataFromPost(post) - } - - return post -} - -func (a *App) SanitizePostMetadataForUser(rctx request.CTX, post *model.Post, userID string) (*model.Post, *model.AppError) { +func (a *App) SanitizePostMetadataForUser(rctx request.CTX, post *model.Post, userID string) (*model.Post, bool, *model.AppError) { if post.Metadata == nil || len(post.Metadata.Embeds) == 0 { - return post, nil + return post, true, nil } previewPost := post.GetPreviewPost() if previewPost == nil { - return post, nil + return post, true, nil } previewedChannel, err := a.GetChannel(rctx, previewPost.Post.ChannelId) if err != nil { - return nil, err + return nil, false, err } - if previewedChannel != nil && !a.HasPermissionToReadChannel(rctx, userID, previewedChannel) { - removePermalinkMetadataFromPost(post) + isMember := true + + if previewedChannel != nil { + var hasPermission bool + hasPermission, isMember = a.HasPermissionToReadChannel(rctx, userID, previewedChannel) + if !hasPermission { + removePermalinkMetadataFromPost(post) + // Since we remove the permalink metadata, we return true + isMember = true + } } - return post, nil + return post, isMember, nil } -func (a *App) SanitizePostListMetadataForUser(rctx request.CTX, postList *model.PostList, userID string) (*model.PostList, *model.AppError) { +func (a *App) SanitizePostListMetadataForUser(rctx request.CTX, postList *model.PostList, userID string) (*model.PostList, bool, *model.AppError) { clonedPostList := postList.Clone() + allPreviewsHaveMembership := true for postID, post := range clonedPostList.Posts { - sanitizedPost, err := a.SanitizePostMetadataForUser(rctx, post, userID) + sanitizedPost, isMember, err := a.SanitizePostMetadataForUser(rctx, post, userID) if err != nil { - return nil, err + return nil, false, err } clonedPostList.Posts[postID] = sanitizedPost + allPreviewsHaveMembership = allPreviewsHaveMembership && isMember } - return clonedPostList, nil + return clonedPostList, allPreviewsHaveMembership, nil } func (a *App) getFileMetadataForPost(rctx request.CTX, post *model.Post, fromMaster, includeDeleted bool) ([]*model.FileInfo, int64, *model.AppError) { diff --git a/server/channels/app/post_metadata_test.go b/server/channels/app/post_metadata_test.go index 5d35ef264cf..41680ba279e 100644 --- a/server/channels/app/post_metadata_test.go +++ b/server/channels/app/post_metadata_test.go @@ -189,7 +189,7 @@ func TestPreparePostForClient(t *testing.T) { fileInfo.ChannelId = th.BasicChannel.Id require.Nil(t, err) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, FileIds: []string{fileInfo.Id}, @@ -216,7 +216,7 @@ func TestPreparePostForClient(t *testing.T) { emoji := th.CreateEmoji(t) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: ":" + emoji.Name + ": :taco:", @@ -259,7 +259,7 @@ func TestPreparePostForClient(t *testing.T) { emoji3 := th.CreateEmoji(t) emoji4 := th.CreateEmoji(t) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: ":" + emoji3.Name + ": :taco:", @@ -299,7 +299,7 @@ func TestPreparePostForClient(t *testing.T) { *cfg.ServiceSettings.EnablePostIconOverride = override }) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "Test", @@ -355,7 +355,7 @@ func TestPreparePostForClient(t *testing.T) { t.Run("markdown image dimensions", func(t *testing.T) { th := setup(t) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: fmt.Sprintf("This is ![our logo](%s/test-image2.png) and ![our icon](%s/test-image1.png)", server.URL, server.URL), @@ -383,7 +383,7 @@ func TestPreparePostForClient(t *testing.T) { t.Run("post props has invalid fields", func(t *testing.T) { th := setup(t) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "some post", @@ -413,7 +413,7 @@ func TestPreparePostForClient(t *testing.T) { t.Run("image embed", func(t *testing.T) { th := setup(t) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: `This is our logo: ` + server.URL + `/test-image2.png @@ -448,7 +448,7 @@ func TestPreparePostForClient(t *testing.T) { t.Run("opengraph embed", func(t *testing.T) { th := setup(t) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: `This is our web page: ` + server.URL, @@ -522,7 +522,7 @@ func TestPreparePostForClient(t *testing.T) { } prepost.AddProp(model.PostPropsUnsafeLinks, "true") - post, err := th.App.CreatePost(th.Context, prepost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, prepost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) clientPost := th.App.PreparePostForClient(th.Context, post, &model.PreparePostForClientOpts{}) @@ -538,7 +538,7 @@ func TestPreparePostForClient(t *testing.T) { Message: `Bla bla bla: ` + fmt.Sprintf(tc.link, server.URL), } - post, err := th.App.CreatePost(th.Context, prepost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err := th.App.CreatePost(th.Context, prepost, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) clientPost := th.App.PreparePostForClient(th.Context, post, &model.PreparePostForClientOpts{}) @@ -553,7 +553,7 @@ func TestPreparePostForClient(t *testing.T) { t.Run("message attachment embed", func(t *testing.T) { th := setup(t) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Props: map[string]any{ @@ -593,7 +593,7 @@ func TestPreparePostForClient(t *testing.T) { fileInfo, err := th.App.DoUploadFile(th.Context, time.Now(), th.BasicTeam.Id, th.BasicChannel.Id, th.BasicUser.Id, "test.txt", []byte("test"), true) require.Nil(t, err) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ Message: "test", FileIds: []string{fileInfo.Id}, UserId: th.BasicUser.Id, @@ -627,7 +627,7 @@ func TestPreparePostForClient(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, &model.Post{ + referencedPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "hello world", @@ -637,7 +637,7 @@ func TestPreparePostForClient(t *testing.T) { link := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) - previewPost, err := th.App.CreatePost(th.Context, &model.Post{ + previewPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: link, @@ -684,7 +684,7 @@ func TestPreparePostForClient(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.Description, func(t *testing.T) { - referencedPost, err := th.App.CreatePost(th.Context, &model.Post{ + referencedPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: testCase.Channel.Id, Message: "hello world", @@ -694,7 +694,7 @@ func TestPreparePostForClient(t *testing.T) { link := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) - previewPost, err := th.App.CreatePost(th.Context, &model.Post{ + previewPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: link, @@ -721,7 +721,7 @@ func TestPreparePostForClient(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, &model.Post{ + referencedPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: `This is our logo: ` + server.URL + `/test-image2.png`, @@ -731,7 +731,7 @@ func TestPreparePostForClient(t *testing.T) { link := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) - previewPost, err := th.App.CreatePost(th.Context, &model.Post{ + previewPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: link, @@ -757,7 +757,7 @@ func TestPreparePostForClient(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - nestedPermalinkPost, err := th.App.CreatePost(th.Context, &model.Post{ + nestedPermalinkPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: `This is our logo: ` + server.URL + `/test-image2.png`, @@ -767,7 +767,7 @@ func TestPreparePostForClient(t *testing.T) { nestedLink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, nestedPermalinkPost.Id) - referencedPost, err := th.App.CreatePost(th.Context, &model.Post{ + referencedPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: nestedLink, @@ -777,7 +777,7 @@ func TestPreparePostForClient(t *testing.T) { link := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) - previewPost, err := th.App.CreatePost(th.Context, &model.Post{ + previewPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: link, @@ -803,7 +803,7 @@ func TestPreparePostForClient(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, &model.Post{ + referencedPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "hello world", @@ -812,7 +812,7 @@ func TestPreparePostForClient(t *testing.T) { link := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) - previewPost, err := th.App.CreatePost(th.Context, &model.Post{ + previewPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: link, @@ -927,7 +927,7 @@ func testProxyOpenGraphImage(t *testing.T, th *TestHelper, shouldProxy bool) { serverURL = server.URL defer server.Close() - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: `This is our web page: ` + server.URL, @@ -2909,112 +2909,6 @@ func TestContainsPermalink(t *testing.T) { } } -func TestSanitizePostMetadataForUserAndChannel(t *testing.T) { - mainHelper.Parallel(t) - th := Setup(t).InitBasic(t) - - enableLinkPreviews := *th.App.Config().ServiceSettings.EnableLinkPreviews - siteURL := *th.App.Config().ServiceSettings.SiteURL - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - cfg.ServiceSettings.EnableLinkPreviews = &enableLinkPreviews - cfg.ServiceSettings.SiteURL = &siteURL - }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.EnableLinkPreviews = true - *cfg.ServiceSettings.SiteURL = "http://mymattermost.com" - }) - - t.Run("should not preview for users with no access to the channel", func(t *testing.T) { - directChannel, err := th.App.createDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id) - assert.Nil(t, err) - - userID := model.NewId() - post := &model.Post{ - Id: userID, - Metadata: &model.PostMetadata{ - Embeds: []*model.PostEmbed{ - { - Type: model.PostEmbedPermalink, - Data: &model.PreviewPost{ - PostID: "permalink_post_id", - Post: &model.Post{ - Id: "permalink_post_id", - Message: "permalink post message", - ChannelId: directChannel.Id, - }, - }, - }, - }, - }, - } - - previewedPost := model.NewPreviewPost(post, th.BasicTeam, directChannel) - - actual := th.App.sanitizePostMetadataForUserAndChannel(th.Context, post, previewedPost, directChannel, th.BasicUser2.Id) - assert.NotNil(t, actual.Metadata.Embeds[0].Data) - - guestID := model.NewId() - guest := &model.User{ - Email: "success+" + guestID + "@simulator.amazonses.com", - Username: "un_" + guestID, - Nickname: "nn_" + guestID, - Password: "Password1", - EmailVerified: true, - } - guest, appErr := th.App.CreateGuest(th.Context, guest) - require.Nil(t, appErr) - - actual = th.App.sanitizePostMetadataForUserAndChannel(th.Context, post, previewedPost, directChannel, guest.Id) - assert.Len(t, actual.Metadata.Embeds, 0) - }) - - t.Run("channel previews always work for archived channels", func(t *testing.T) { - publicChannel, err := th.App.CreateChannel(th.Context, &model.Channel{ - Name: model.NewId(), - Type: model.ChannelTypeOpen, - TeamId: th.BasicTeam.Id, - CreatorId: th.SystemAdminUser.Id, - }, true) - - require.Nil(t, err) - require.NotEmpty(t, publicChannel.Id) - - err = th.App.DeleteChannel(th.Context, publicChannel, th.SystemAdminUser.Id) - require.Nil(t, err) - - publicChannel, err = th.App.GetChannel(th.Context, publicChannel.Id) - require.Nil(t, err) - require.NotEmpty(t, publicChannel.Id) - require.NotEqual(t, 0, publicChannel.DeleteAt) - - post := &model.Post{ - Id: th.BasicUser.Id, - Metadata: &model.PostMetadata{ - Embeds: []*model.PostEmbed{ - { - Type: model.PostEmbedPermalink, - Data: &model.PreviewPost{ - PostID: "permalink_post_id", - Post: &model.Post{ - Id: "permalink_post_id", - Message: "permalink post message", - ChannelId: publicChannel.Id, - }, - }, - }, - }, - }, - } - - previewedPost := model.NewPreviewPost(post, th.BasicTeam, publicChannel) - - actual := th.App.sanitizePostMetadataForUserAndChannel(th.Context, post, previewedPost, publicChannel, th.BasicUser.Id) - assert.NotNil(t, actual.Metadata.Embeds[0].Data) - }) -} - func TestSanitizePostMetaDataForAudit(t *testing.T) { mainHelper.Parallel(t) th := Setup(t).InitBasic(t) @@ -3025,7 +2919,7 @@ func TestSanitizePostMetaDataForAudit(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, &model.Post{ + referencedPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "hello world", @@ -3035,7 +2929,7 @@ func TestSanitizePostMetaDataForAudit(t *testing.T) { link := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) - previewPost, err := th.App.CreatePost(th.Context, &model.Post{ + previewPost, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: link, @@ -3127,15 +3021,16 @@ func TestSanitizePostMetadataForUser(t *testing.T) { }, } - sanitizedPost, err := th.App.SanitizePostMetadataForUser(th.Context, post, th.BasicUser.Id) + sanitizedPost, isMemberForPreviews, err := th.App.SanitizePostMetadataForUser(th.Context, post, th.BasicUser.Id) require.Nil(t, err) require.NotNil(t, sanitizedPost) require.Equal(t, 1, len(sanitizedPost.Metadata.Embeds)) require.Equal(t, model.PostEmbedLink, sanitizedPost.Metadata.Embeds[0].Type) + require.True(t, isMemberForPreviews) }) - t.Run("should remove embeds for archived channels if the config does not allow it", func(t *testing.T) { + t.Run("should not remove embeds for archived channels", func(t *testing.T) { publicChannel, err := th.App.CreateChannel(th.Context, &model.Channel{ Name: model.NewId(), Type: model.ChannelTypeOpen, @@ -3178,12 +3073,13 @@ func TestSanitizePostMetadataForUser(t *testing.T) { }, } - sanitizedPost, err := th.App.SanitizePostMetadataForUser(th.Context, post, th.BasicUser.Id) + sanitizedPost, isMemberForPreviews, err := th.App.SanitizePostMetadataForUser(th.Context, post, th.BasicUser.Id) require.Nil(t, err) require.NotNil(t, sanitizedPost) require.Equal(t, 2, len(sanitizedPost.Metadata.Embeds)) require.Equal(t, model.PostEmbedPermalink, sanitizedPost.Metadata.Embeds[0].Type) + require.False(t, isMemberForPreviews) }) } diff --git a/server/channels/app/post_permission_utils.go b/server/channels/app/post_permission_utils.go index 31f6b2fce06..6bc5c5084cf 100644 --- a/server/channels/app/post_permission_utils.go +++ b/server/channels/app/post_permission_utils.go @@ -99,7 +99,7 @@ func postHardenedModeCheck(hardenedModeEnabled, isIntegration bool, props model. func userCreatePostPermissionCheckWithApp(rctx request.CTX, a *App, userId, channelId string) *model.AppError { hasPermission := false - if a.HasPermissionToChannel(rctx, userId, channelId, model.PermissionCreatePost) { + if ok, _ := a.HasPermissionToChannel(rctx, userId, channelId, model.PermissionCreatePost); ok { hasPermission = true } else if channel, err := a.GetChannel(rctx, channelId); err == nil { // Temporary permission check method until advanced permissions, please do not copy diff --git a/server/channels/app/post_persistent_notification_test.go b/server/channels/app/post_persistent_notification_test.go index e21522ffba8..e60dcab3bfb 100644 --- a/server/channels/app/post_persistent_notification_test.go +++ b/server/channels/app/post_persistent_notification_test.go @@ -212,7 +212,7 @@ func TestSendPersistentNotifications(t *testing.T) { }, }, } - _, appErr = th.App.CreatePost(th.Context, p1, th.BasicChannel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, p1, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) err := th.App.SendPersistentNotifications() @@ -256,7 +256,7 @@ func TestSendPersistentNotificationsBotSender(t *testing.T) { // Simulate old timestamp so persistent notifications are sent right away CreateAt: time.Now().Add(-5 * time.Minute).UnixMilli(), } - post, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + post, _, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) assert.EventuallyWithT(t, func(c *assert.CollectT) { @@ -307,7 +307,7 @@ func TestSendPersistentNotificationsBotSenderNotInChannel(t *testing.T) { }, CreateAt: time.Now().Add(-5 * time.Minute).UnixMilli(), } - post, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) assert.EventuallyWithT(t, func(c *assert.CollectT) { diff --git a/server/channels/app/post_restore.go b/server/channels/app/post_restore.go index 3d699897144..678bcbeca5e 100644 --- a/server/channels/app/post_restore.go +++ b/server/channels/app/post_restore.go @@ -13,7 +13,7 @@ import ( "github.com/mattermost/mattermost/server/public/shared/request" ) -func (a *App) RestorePostVersion(rctx request.CTX, userID, postID, restoreVersionID string) (*model.Post, *model.AppError) { +func (a *App) RestorePostVersion(rctx request.CTX, userID, postID, restoreVersionID string) (*model.Post, bool, *model.AppError) { toRestorePostVersion, err := a.Srv().Store().Post().GetSingle(rctx, restoreVersionID, true) if err != nil { var statusCode int @@ -25,24 +25,24 @@ func (a *App) RestorePostVersion(rctx request.CTX, userID, postID, restoreVersio statusCode = http.StatusInternalServerError } - return nil, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.get_single.app_error", nil, err.Error(), statusCode) + return nil, false, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.get_single.app_error", nil, err.Error(), statusCode) } // restoreVersionID needs to be an old version of postID // this is only a safeguard and this should never happen in practice. if toRestorePostVersion.OriginalId != postID { - return nil, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.not_an_history_item.app_error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.not_an_history_item.app_error", nil, "", http.StatusBadRequest) } // the user needs to be the author of the post // this is only a safeguard and this should never happen in practice. if toRestorePostVersion.UserId != userID { - return nil, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.not_allowed.app_error", nil, "", http.StatusForbidden) + return nil, false, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.not_allowed.app_error", nil, "", http.StatusForbidden) } // the old version of post needs to be a deleted post if toRestorePostVersion.DeleteAt == 0 { - return nil, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.not_valid_post_history_item.app_error", nil, "", http.StatusBadRequest) + return nil, false, model.NewAppError("RestorePostVersion", "app.post.restore_post_version.not_valid_post_history_item.app_error", nil, "", http.StatusBadRequest) } postPatch := &model.PostPatch{ diff --git a/server/channels/app/post_restore_test.go b/server/channels/app/post_restore_test.go index 627660fb2a8..f7454c05093 100644 --- a/server/channels/app/post_restore_test.go +++ b/server/channels/app/post_restore_test.go @@ -33,9 +33,10 @@ func TestRestorePostVersion(t *testing.T) { require.Equal(t, "new message 2", editHistory[0].Message) // now we'll restore a post version - restoredPost, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, editHistory[0].Id) + restoredPost, isMemberForPreview, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, editHistory[0].Id) require.Nil(t, appErr) require.Equal(t, "new message 2", restoredPost.Message) + require.True(t, isMemberForPreview) // verify from database fetchedPost, err = th.App.Srv().Store().Post().GetSingle(th.Context, post.Id, true) @@ -84,10 +85,11 @@ func TestRestorePostVersion(t *testing.T) { require.Equal(t, 1, len(editHistory[1].FileIds)) // now we'll restore a post version - restoredPost, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, editHistory[1].Id) + restoredPost, isMemberForPreview, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, editHistory[1].Id) require.Nil(t, appErr) require.Equal(t, "original message", restoredPost.Message) require.Equal(t, 1, len(restoredPost.FileIds)) + require.True(t, isMemberForPreview) // verify from database fetchedPost, err = th.App.Srv().Store().Post().GetSingle(th.Context, post.Id, true) @@ -124,7 +126,7 @@ func TestRestorePostVersion(t *testing.T) { // now we'll restore a post version otherPost := th.CreatePost(t, th.BasicChannel) - restoredPost, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, otherPost.Id) + restoredPost, _, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, otherPost.Id) require.NotNil(t, appErr) require.Equal(t, http.StatusBadRequest, appErr.StatusCode) require.Equal(t, "app.post.restore_post_version.not_an_history_item.app_error", appErr.Id) @@ -137,7 +139,7 @@ func TestRestorePostVersion(t *testing.T) { }) t.Run("should return an error if the post does not exist", func(t *testing.T) { - restoredPost, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, model.NewId(), model.NewId()) + restoredPost, _, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, model.NewId(), model.NewId()) require.NotNil(t, appErr) require.Equal(t, http.StatusNotFound, appErr.StatusCode) require.Equal(t, "app.post.restore_post_version.get_single.app_error", appErr.Id) @@ -149,7 +151,7 @@ func TestRestorePostVersion(t *testing.T) { // now we'll restore a post version invalidRestorePostIUd := model.NewId() - restoredPost, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, invalidRestorePostIUd) + restoredPost, _, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, invalidRestorePostIUd) require.NotNil(t, appErr) require.Equal(t, http.StatusNotFound, appErr.StatusCode) require.Equal(t, "app.post.restore_post_version.get_single.app_error", appErr.Id) @@ -171,7 +173,7 @@ func TestRestorePostVersion(t *testing.T) { require.Equal(t, "other post original message", otherPostEditHistory[0].Message) // we'll specify post's ID and other post's version ID, his should fail - restoredPost, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, otherPostEditHistory[0].Id) + restoredPost, _, appErr := th.App.RestorePostVersion(th.Context, th.BasicUser.Id, post.Id, otherPostEditHistory[0].Id) require.NotNil(t, appErr) require.Equal(t, "app.post.restore_post_version.not_an_history_item.app_error", appErr.Id) require.Equal(t, http.StatusBadRequest, appErr.StatusCode) diff --git a/server/channels/app/post_test.go b/server/channels/app/post_test.go index 40535a9b809..493ccf179c3 100644 --- a/server/channels/app/post_test.go +++ b/server/channels/app/post_test.go @@ -49,7 +49,7 @@ func TestCreatePostDeduplicate(t *testing.T) { pendingPostId := makePendingPostId(th.BasicUser) - post, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -58,7 +58,7 @@ func TestCreatePostDeduplicate(t *testing.T) { require.Nil(t, err) require.Equal(t, "message", post.Message) - duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + duplicatePost, _, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -105,7 +105,7 @@ func TestCreatePostDeduplicate(t *testing.T) { pendingPostId := makePendingPostId(th.BasicUser) - post, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -115,7 +115,7 @@ func TestCreatePostDeduplicate(t *testing.T) { require.Equal(t, "Post rejected by plugin. rejected", err.Id) require.Nil(t, post) - duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + duplicatePost, _, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -171,7 +171,7 @@ func TestCreatePostDeduplicate(t *testing.T) { go func() { defer wg.Done() var appErr *model.AppError - post, appErr = th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + post, _, appErr = th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "plugin delayed", @@ -185,7 +185,7 @@ func TestCreatePostDeduplicate(t *testing.T) { time.Sleep(2 * time.Second) // Try creating a duplicate post - duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + duplicatePost, _, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "plugin delayed", @@ -214,7 +214,7 @@ func TestCreatePostDeduplicate(t *testing.T) { pendingPostId := makePendingPostId(th.BasicUser) - post, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -225,7 +225,7 @@ func TestCreatePostDeduplicate(t *testing.T) { time.Sleep(pendingPostIDsCacheTTL) - duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ + duplicatePost, _, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -254,7 +254,7 @@ func TestCreatePostDeduplicate(t *testing.T) { privateChannel := th.CreatePrivateChannel(t, th.BasicTeam) th.AddUserToChannel(t, th.BasicUser, privateChannel) - post, err := th.App.CreatePostAsUser(th.Context.WithSession(sessionBasicUser), &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context.WithSession(sessionBasicUser), &model.Post{ UserId: th.BasicUser.Id, ChannelId: privateChannel.Id, Message: "message", @@ -263,7 +263,7 @@ func TestCreatePostDeduplicate(t *testing.T) { require.Nil(t, err) require.Equal(t, "message", post.Message) - postAsDifferentUser, err := th.App.CreatePostAsUser(th.Context.WithSession(sessionBasicUser2), &model.Post{ + postAsDifferentUser, _, err := th.App.CreatePostAsUser(th.Context.WithSession(sessionBasicUser2), &model.Post{ UserId: th.BasicUser2.Id, ChannelId: th.BasicChannel.Id, Message: "message2", @@ -364,18 +364,19 @@ func TestUpdatePostEditAt(t *testing.T) { post := th.BasicPost.Clone() post.IsPinned = true - saved, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + saved, isMemberForPreviews, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err) assert.Equal(t, saved.EditAt, post.EditAt, "shouldn't have updated post.EditAt when pinning post") + assert.True(t, isMemberForPreviews) post = saved.Clone() time.Sleep(time.Millisecond * 100) post.Message = model.NewId() - saved, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + saved, isMemberForPreviews, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err) assert.NotEqual(t, saved.EditAt, post.EditAt, "should have updated post.EditAt when updating post message") - + assert.True(t, isMemberForPreviews) time.Sleep(time.Millisecond * 200) } @@ -390,7 +391,7 @@ func TestUpdatePostTimeLimit(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.PostEditTimeLimit = -1 }) - _, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + _, _, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err) th.App.UpdateConfig(func(cfg *model.Config) { @@ -398,14 +399,14 @@ func TestUpdatePostTimeLimit(t *testing.T) { }) post.Message = model.NewId() - _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + _, _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err, "should allow you to edit the post") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.PostEditTimeLimit = 1 }) post.Message = model.NewId() - _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + _, _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err, "should allow you to edit an old post because the time check is applied above in the call hierarchy") th.App.UpdateConfig(func(cfg *model.Config) { @@ -422,7 +423,7 @@ func TestUpdatePostInArchivedChannel(t *testing.T) { appErr := th.App.DeleteChannel(th.Context, archivedChannel, "") require.Nil(t, appErr) - _, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + _, _, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.NotNil(t, err) require.Equal(t, "api.post.update_post.can_not_update_post_in_deleted.error", err.Id) } @@ -452,7 +453,7 @@ func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) { CreateAt: 0, } - _, err = th.App.CreatePostAsUser(th.Context, &replyPost, "", true) + _, _, err = th.App.CreatePostAsUser(th.Context, &replyPost, "", true) require.Nil(t, err) } @@ -473,7 +474,7 @@ func TestPostAttachPostToChildPost(t *testing.T) { CreateAt: 0, } - res1, err := th.App.CreatePostAsUser(th.Context, &replyPost1, "", true) + res1, _, err := th.App.CreatePostAsUser(th.Context, &replyPost1, "", true) require.Nil(t, err) replyPost2 := model.Post{ @@ -485,7 +486,7 @@ func TestPostAttachPostToChildPost(t *testing.T) { CreateAt: 0, } - _, err = th.App.CreatePostAsUser(th.Context, &replyPost2, "", true) + _, _, err = th.App.CreatePostAsUser(th.Context, &replyPost2, "", true) assert.Equalf(t, err.StatusCode, http.StatusBadRequest, "Expected BadRequest error, got %v", err) replyPost3 := model.Post{ @@ -497,7 +498,7 @@ func TestPostAttachPostToChildPost(t *testing.T) { CreateAt: 0, } - _, err = th.App.CreatePostAsUser(th.Context, &replyPost3, "", true) + _, _, err = th.App.CreatePostAsUser(th.Context, &replyPost3, "", true) assert.Nil(t, err) } @@ -559,7 +560,7 @@ func TestUpdatePostPluginHooks(t *testing.T) { }, true, th.App, th.Context) pendingPostId := makePendingPostId(th.BasicUser) - post, err := th.App.CreatePostAsUser(th.Context, &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -568,7 +569,7 @@ func TestUpdatePostPluginHooks(t *testing.T) { require.Nil(t, err) post.Message = "new message" - updatedPost, err := th.App.UpdatePost(th.Context, post, nil) + updatedPost, _, err := th.App.UpdatePost(th.Context, post, nil) require.Nil(t, updatedPost) require.NotNil(t, err) require.Equal(t, "Post rejected by plugin. rejected", err.Id) @@ -626,7 +627,7 @@ func TestUpdatePostPluginHooks(t *testing.T) { }, true, th.App, th.Context) pendingPostId := makePendingPostId(th.BasicUser) - post, err := th.App.CreatePostAsUser(th.Context, &model.Post{ + post, _, err := th.App.CreatePostAsUser(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: "message", @@ -635,7 +636,8 @@ func TestUpdatePostPluginHooks(t *testing.T) { require.Nil(t, err) post.Message = "new message" - updatedPost, err := th.App.UpdatePost(th.Context, post, nil) + updatedPost, isMemberForPreviews, err := th.App.UpdatePost(th.Context, post, nil) + require.True(t, isMemberForPreviews) require.Nil(t, err) require.NotNil(t, updatedPost) require.Equal(t, "2 new message 1", updatedPost.Message) @@ -683,7 +685,7 @@ func TestPostChannelMentions(t *testing.T) { CreateAt: 0, } - post, err = th.App.CreatePostAsUser(th.Context, post, "", true) + post, _, err = th.App.CreatePostAsUser(th.Context, post, "", true) require.Nil(t, err) assert.Equal(t, map[string]any{ "mention-test": map[string]any{ @@ -693,7 +695,8 @@ func TestPostChannelMentions(t *testing.T) { }, post.GetProp(model.PostPropsChannelMentions)) post.Message = fmt.Sprintf("goodbye, ~%v!", channelToMention2.Name) - result, err := th.App.UpdatePost(th.Context, post, nil) + result, isMemberForPreviews, err := th.App.UpdatePost(th.Context, post, nil) + require.True(t, isMemberForPreviews) require.Nil(t, err) assert.Equal(t, map[string]any{ "mention-test2": map[string]any{ @@ -703,7 +706,8 @@ func TestPostChannelMentions(t *testing.T) { }, result.GetProp(model.PostPropsChannelMentions)) result.Message = "no more mentions!" - result, err = th.App.UpdatePost(th.Context, result, nil) + result, isMemberForPreviews, err = th.App.UpdatePost(th.Context, result, nil) + require.True(t, isMemberForPreviews) require.Nil(t, err) assert.Nil(t, result.GetProp(model.PostPropsChannelMentions)) } @@ -860,7 +864,7 @@ func TestDeletePostWithFileAttachments(t *testing.T) { FileIds: []string{info1.Id}, } - post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) assert.Nil(t, err) // Delete the post. @@ -914,7 +918,7 @@ func TestDeletePostWithRestrictedDM(t *testing.T) { ChannelId: dmChannel.Id, Message: "test post", } - post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) + post, _, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) require.Nil(t, err) // Try to delete the post @@ -969,7 +973,7 @@ func TestCreatePost(t *testing.T) { UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, "![image]("+proxiedImageURL+")", rpost.Message) }) @@ -986,7 +990,7 @@ func TestCreatePost(t *testing.T) { Message: "This post does not have mentions", UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, postWithNoMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, postWithNoMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, rpost.GetProps(), model.StringInterface{}) @@ -995,7 +999,7 @@ func TestCreatePost(t *testing.T) { Message: "This post has @here mention @all", UserId: th.BasicUser.Id, } - rpost, err = th.App.CreatePost(th.Context, postWithMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err = th.App.CreatePost(th.Context, postWithMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, rpost.GetProps(), model.StringInterface{}) }) @@ -1009,7 +1013,7 @@ func TestCreatePost(t *testing.T) { Message: "This post does not have mentions", UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, postWithNoMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, postWithNoMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, rpost.GetProps(), model.StringInterface{}) @@ -1018,7 +1022,7 @@ func TestCreatePost(t *testing.T) { Message: "This post has @here mention @all", UserId: th.BasicUser.Id, } - rpost, err = th.App.CreatePost(th.Context, postWithMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err = th.App.CreatePost(th.Context, postWithMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.Equal(t, rpost.GetProp(model.PostPropsMentionHighlightDisabled), true) @@ -1045,7 +1049,7 @@ func TestCreatePost(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) + referencedPost, _, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) @@ -1057,7 +1061,7 @@ func TestCreatePost(t *testing.T) { UserId: th.BasicUser.Id, } - previewPost, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{}) + previewPost, _, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{}) require.Nil(t, err) assert.Equal(t, previewPost.GetProps(), model.StringInterface{"previewed_post": referencedPost.Id}) @@ -1074,7 +1078,7 @@ func TestCreatePost(t *testing.T) { Message: "hello world", UserId: th.BasicUser.Id, } - referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) + referencedPost, _, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) th.App.UpdateConfig(func(cfg *model.Config) { @@ -1090,7 +1094,7 @@ func TestCreatePost(t *testing.T) { UserId: th.BasicUser.Id, } - previewPost, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{}) + previewPost, _, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{}) require.Nil(t, err) sqlStore := th.GetSqlStore() @@ -1146,7 +1150,7 @@ func TestCreatePost(t *testing.T) { Message: "hello world", UserId: testCase.Author, } - referencedPost, err = th.App.CreatePost(th.Context, referencedPost, testCase.Channel, model.CreatePostFlags{}) + referencedPost, _, err = th.App.CreatePost(th.Context, referencedPost, testCase.Channel, model.CreatePostFlags{}) require.Nil(t, err) permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) @@ -1156,7 +1160,7 @@ func TestCreatePost(t *testing.T) { UserId: th.BasicUser.Id, } - previewPost, err = th.App.CreatePost(th.Context, previewPost, th.BasicChannel, model.CreatePostFlags{}) + previewPost, _, err = th.App.CreatePost(th.Context, previewPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) require.Len(t, previewPost.Metadata.Embeds, testCase.Length) @@ -1198,7 +1202,7 @@ func TestCreatePost(t *testing.T) { Message: "hello world", UserId: user1.Id, } - createdPost, appErr := th.App.CreatePost(th.Context, newPost, dm, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, newPost, dm, model.CreatePostFlags{}) require.NotNil(t, appErr) require.Nil(t, createdPost) }) @@ -1238,7 +1242,7 @@ func TestCreatePost(t *testing.T) { Message: "hello world", UserId: user1.Id, } - createdPost, appErr := th.App.CreatePost(th.Context, newPost, gm, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, newPost, gm, model.CreatePostFlags{}) require.NotNil(t, appErr) require.Nil(t, createdPost) }) @@ -1260,7 +1264,7 @@ func TestCreatePost(t *testing.T) { Message: "hello world", UserId: th.BasicUser.Id, } - referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) + referencedPost, _, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) th.App.UpdateConfig(func(cfg *model.Config) { @@ -1276,7 +1280,7 @@ func TestCreatePost(t *testing.T) { UserId: th.BasicUser.Id, } - previewPost, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{}) + previewPost, _, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{}) require.Nil(t, err) n := 1000 @@ -1286,7 +1290,7 @@ func TestCreatePost(t *testing.T) { go func() { defer wg.Done() post := previewPost.Clone() - _, appErr := th.App.UpdatePost(th.Context, post, nil) + _, _, appErr := th.App.UpdatePost(th.Context, post, nil) require.Nil(t, appErr) }() } @@ -1306,7 +1310,7 @@ func TestCreatePost(t *testing.T) { UserId: th.BasicUser.Id, } postToCreate.AddProp(model.PostPropsForceNotification, model.NewId()) - createdPost, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) require.Empty(t, createdPost.GetProp(model.PostPropsForceNotification)) }) @@ -1322,7 +1326,7 @@ func TestCreatePost(t *testing.T) { Message: "hello world", UserId: th.BasicUser.Id, } - createdPost, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{ForceNotification: true}) + createdPost, _, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{ForceNotification: true}) require.Nil(t, err) require.NotEmpty(t, createdPost.GetProp(model.PostPropsForceNotification)) }) @@ -1343,7 +1347,7 @@ func TestCreatePost(t *testing.T) { FileIds: []string{model.NewId()}, } - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) require.Empty(t, createdPost.FileIds) }) @@ -1374,7 +1378,7 @@ func TestPatchPost(t *testing.T) { UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.NotEqual(t, "![image]("+proxiedImageURL+")", rpost.Message) @@ -1382,7 +1386,7 @@ func TestPatchPost(t *testing.T) { Message: model.NewPointer("![image](" + imageURL + ")"), } - rpost, err = th.App.PatchPost(th.Context, rpost.Id, patch, nil) + rpost, _, err = th.App.PatchPost(th.Context, rpost.Id, patch, nil) require.Nil(t, err) assert.Equal(t, "![image]("+proxiedImageURL+")", rpost.Message) }) @@ -1399,19 +1403,19 @@ func TestPatchPost(t *testing.T) { UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) t.Run("Does not set prop when user has USE_CHANNEL_MENTIONS", func(t *testing.T) { patchWithNoMention := &model.PostPatch{Message: model.NewPointer("This patch has no channel mention")} - rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithNoMention, nil) + rpost, _, err = th.App.PatchPost(th.Context, rpost.Id, patchWithNoMention, nil) require.Nil(t, err) assert.Equal(t, rpost.GetProps(), model.StringInterface{}) patchWithMention := &model.PostPatch{Message: model.NewPointer("This patch has a mention now @here")} - rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithMention, nil) + rpost, _, err = th.App.PatchPost(th.Context, rpost.Id, patchWithMention, nil) require.Nil(t, err) assert.Equal(t, rpost.GetProps(), model.StringInterface{}) }) @@ -1421,13 +1425,13 @@ func TestPatchPost(t *testing.T) { th.RemovePermissionFromRole(t, model.PermissionUseChannelMentions.Id, model.ChannelAdminRoleId) patchWithNoMention := &model.PostPatch{Message: model.NewPointer("This patch still does not have a mention")} - rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithNoMention, nil) + rpost, _, err = th.App.PatchPost(th.Context, rpost.Id, patchWithNoMention, nil) require.Nil(t, err) assert.Equal(t, rpost.GetProps(), model.StringInterface{}) patchWithMention := &model.PostPatch{Message: model.NewPointer("This patch has a mention now @here")} - rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithMention, nil) + rpost, _, err = th.App.PatchPost(th.Context, rpost.Id, patchWithMention, nil) require.Nil(t, err) assert.Equal(t, rpost.GetProp(model.PostPropsMentionHighlightDisabled), true) @@ -1473,14 +1477,14 @@ func TestPatchPost(t *testing.T) { ChannelId: dmChannel.Id, Message: "test post", } - post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) + post, _, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) require.Nil(t, err) // Try to patch the post patch := &model.PostPatch{ Message: model.NewPointer("updated message"), } - _, appErr := th.App.PatchPost(th.Context, post.Id, patch, model.DefaultUpdatePostOptions()) + _, _, appErr := th.App.PatchPost(th.Context, post.Id, patch, model.DefaultUpdatePostOptions()) require.NotNil(t, appErr) require.Equal(t, "api.post.patch_post.can_not_update_post_in_restricted_dm.error", appErr.Id) require.Equal(t, http.StatusBadRequest, appErr.StatusCode) @@ -1508,7 +1512,7 @@ func TestCreatePostAsUser(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Millisecond) - _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) + _, _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) require.Nil(t, appErr) channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id) @@ -1532,7 +1536,7 @@ func TestCreatePostAsUser(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Millisecond) - _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) + _, _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) require.Nil(t, appErr) channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id) @@ -1563,7 +1567,7 @@ func TestCreatePostAsUser(t *testing.T) { require.NoError(t, err) time.Sleep(1 * time.Millisecond) - _, appErr = th.App.CreatePostAsUser(th.Context, post, "", true) + _, _, appErr = th.App.CreatePostAsUser(th.Context, post, "", true) require.Nil(t, appErr) channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id) @@ -1589,7 +1593,7 @@ func TestCreatePostAsUser(t *testing.T) { UserId: bot.UserId, } - _, appErr = th.App.CreatePostAsUser(th.Context, post, "", true) + _, _, appErr = th.App.CreatePostAsUser(th.Context, post, "", true) require.Nil(t, appErr) require.NoError(t, th.TestLogger.Flush()) @@ -1610,7 +1614,7 @@ func TestCreatePostAsUser(t *testing.T) { Message: "test", UserId: th.BasicUser2.Id, } - rootPost, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) + rootPost, _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) require.Nil(t, appErr) channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id) @@ -1623,7 +1627,7 @@ func TestCreatePostAsUser(t *testing.T) { UserId: th.BasicUser.Id, RootId: rootPost.Id, } - _, appErr = th.App.CreatePostAsUser(th.Context, replyPost, "", true) + _, _, appErr = th.App.CreatePostAsUser(th.Context, replyPost, "", true) require.Nil(t, appErr) channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id) @@ -1646,7 +1650,7 @@ func TestCreatePostAsUser(t *testing.T) { Message: "test", UserId: th.BasicUser2.Id, } - rootPost, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) + rootPost, _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) require.Nil(t, appErr) channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id) @@ -1659,7 +1663,7 @@ func TestCreatePostAsUser(t *testing.T) { UserId: th.BasicUser.Id, RootId: rootPost.Id, } - _, appErr = th.App.CreatePostAsUser(th.Context, replyPost, "", true) + _, _, appErr = th.App.CreatePostAsUser(th.Context, replyPost, "", true) require.Nil(t, appErr) channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id) @@ -1705,7 +1709,7 @@ func TestCreatePostAsUser(t *testing.T) { Message: "test post", } - _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) + _, _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true) require.NotNil(t, appErr) require.Equal(t, "api.post.create_post.can_not_post_in_restricted_dm.error", appErr.Id) require.Equal(t, http.StatusBadRequest, appErr.StatusCode) @@ -1726,7 +1730,7 @@ func TestPatchPostInArchivedChannel(t *testing.T) { appErr := th.App.DeleteChannel(th.Context, archivedChannel, "") require.Nil(t, appErr) - _, err := th.App.PatchPost(th.Context, post.Id, &model.PostPatch{IsPinned: model.NewPointer(true)}, nil) + _, _, err := th.App.PatchPost(th.Context, post.Id, &model.PostPatch{IsPinned: model.NewPointer(true)}, nil) require.NotNil(t, err) require.Equal(t, "api.post.patch_post.can_not_update_post_in_deleted.error", err.Id) } @@ -1751,7 +1755,7 @@ func TestUpdateEphemeralPost(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) + referencedPost, _, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) @@ -1762,7 +1766,7 @@ func TestUpdateEphemeralPost(t *testing.T) { UserId: th.BasicUser.Id, } - testPost = th.App.UpdateEphemeralPost(th.Context, th.BasicUser.Id, testPost) + testPost, _ = th.App.UpdateEphemeralPost(th.Context, th.BasicUser.Id, testPost) require.NotNil(t, testPost.Metadata) require.Len(t, testPost.Metadata.Embeds, 1) require.Equal(t, model.PostEmbedPermalink, testPost.Metadata.Embeds[0].Type) @@ -1788,7 +1792,7 @@ func TestUpdateEphemeralPost(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) + referencedPost, _, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) @@ -1799,7 +1803,7 @@ func TestUpdateEphemeralPost(t *testing.T) { UserId: th.BasicUser2.Id, } - testPost = th.App.UpdateEphemeralPost(th.Context, th.BasicUser2.Id, testPost) + testPost, _ = th.App.UpdateEphemeralPost(th.Context, th.BasicUser2.Id, testPost) require.Nil(t, testPost.Metadata.Embeds) }) } @@ -1829,14 +1833,15 @@ func TestUpdatePost(t *testing.T) { UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) assert.NotEqual(t, "![image]("+proxiedImageURL+")", rpost.Message) post.Id = rpost.Id post.Message = "![image](" + imageURL + ")" - rpost, err = th.App.UpdatePost(th.Context, post, nil) + rpost, isMemberForPreviews, err := th.App.UpdatePost(th.Context, post, nil) + require.True(t, isMemberForPreviews) require.Nil(t, err) assert.Equal(t, "![image]("+proxiedImageURL+")", rpost.Message) }) @@ -1859,7 +1864,7 @@ func TestUpdatePost(t *testing.T) { th.Context.Session().UserId = th.BasicUser.Id - referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) + referencedPost, _, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) @@ -1871,12 +1876,13 @@ func TestUpdatePost(t *testing.T) { UserId: th.BasicUser.Id, } - testPost, err = th.App.CreatePost(th.Context, testPost, channelForTestPost, model.CreatePostFlags{}) + testPost, _, err = th.App.CreatePost(th.Context, testPost, channelForTestPost, model.CreatePostFlags{}) require.Nil(t, err) assert.Equal(t, model.StringInterface{}, testPost.GetProps()) testPost.Message = permalink - testPost, err = th.App.UpdatePost(th.Context, testPost, nil) + testPost, isMemberForPreviews, err := th.App.UpdatePost(th.Context, testPost, nil) + require.True(t, isMemberForPreviews) require.Nil(t, err) assert.Equal(t, model.StringInterface{model.PostPropsPreviewedPost: referencedPost.Id}, testPost.GetProps()) }) @@ -1925,19 +1931,20 @@ func TestUpdatePost(t *testing.T) { Message: "hello world", UserId: testCase.Author, } - _, err = th.App.CreatePost(th.Context, referencedPost, testCase.Channel, model.CreatePostFlags{}) + _, _, err = th.App.CreatePost(th.Context, referencedPost, testCase.Channel, model.CreatePostFlags{}) require.Nil(t, err) previewPost := &model.Post{ ChannelId: th.BasicChannel.Id, UserId: th.BasicUser.Id, } - previewPost, err = th.App.CreatePost(th.Context, previewPost, th.BasicChannel, model.CreatePostFlags{}) + previewPost, _, err = th.App.CreatePost(th.Context, previewPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, err) permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id) previewPost.Message = permalink - previewPost, err = th.App.UpdatePost(th.Context, previewPost, nil) + previewPost, isMemberForPreviews, err := th.App.UpdatePost(th.Context, previewPost, nil) + require.True(t, isMemberForPreviews) require.Nil(t, err) require.Len(t, previewPost.Metadata.Embeds, testCase.Length) @@ -1982,12 +1989,12 @@ func TestUpdatePost(t *testing.T) { ChannelId: dmChannel.Id, Message: "test post", } - post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) + post, _, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) require.Nil(t, err) // Try to update the post post.Message = "updated message" - _, appErr := th.App.UpdatePost(th.Context, post, model.DefaultUpdatePostOptions()) + _, _, appErr := th.App.UpdatePost(th.Context, post, model.DefaultUpdatePostOptions()) require.NotNil(t, appErr) require.Equal(t, "api.post.update_post.can_not_update_post_in_restricted_dm.error", appErr.Id) require.Equal(t, http.StatusBadRequest, appErr.StatusCode) @@ -2009,7 +2016,7 @@ func TestSearchPostsForUser(t *testing.T) { posts := make([]*model.Post, 7) for i := 0; i < cap(posts); i++ { - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Message: searchTerm, @@ -2042,7 +2049,7 @@ func TestSearchPostsForUser(t *testing.T) { page := 0 - results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allPostHaveMembership, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, []string{ @@ -2054,6 +2061,7 @@ func TestSearchPostsForUser(t *testing.T) { posts[1].Id, posts[0].Id, }, results.Order) + assert.True(t, allPostHaveMembership) }) t.Run("should not return later pages of posts from database", func(t *testing.T) { @@ -2062,10 +2070,11 @@ func TestSearchPostsForUser(t *testing.T) { page := 1 - results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allPostHaveMembership, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, []string{}, results.Order) + assert.True(t, allPostHaveMembership) }) t.Run("should return first page of posts from ElasticSearch", func(t *testing.T) { @@ -2091,10 +2100,11 @@ func TestSearchPostsForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allPostHaveMembership, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, resultsPage, results.Order) + assert.True(t, allPostHaveMembership) es.AssertExpectations(t) }) @@ -2118,10 +2128,11 @@ func TestSearchPostsForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allPostHaveMembership, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, resultsPage, results.Order) + assert.True(t, allPostHaveMembership) es.AssertExpectations(t) }) @@ -2142,7 +2153,7 @@ func TestSearchPostsForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allPostHaveMembership, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, []string{ @@ -2154,6 +2165,7 @@ func TestSearchPostsForUser(t *testing.T) { posts[1].Id, posts[0].Id, }, results.Order) + assert.True(t, allPostHaveMembership) es.AssertExpectations(t) }) @@ -2174,10 +2186,11 @@ func TestSearchPostsForUser(t *testing.T) { th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil }() - results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + results, allPostHaveMembership, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, []string{}, results.Order) + assert.True(t, allPostHaveMembership) es.AssertExpectations(t) }) @@ -2189,12 +2202,12 @@ func TestSearchPostsForUser(t *testing.T) { searchQueryWithPrefix := fmt.Sprintf("in:~%s %s", th.BasicChannel.Name, searchTerm) - resultsWithPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + resultsWithPrefix, _, err := th.App.SearchPostsForUser(th.Context, searchQueryWithPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Greater(t, len(resultsWithPrefix.PostList.Posts), 0, "searching using a tilde in front of a channel should return results") searchQueryWithoutPrefix := fmt.Sprintf("in:%s %s", th.BasicChannel.Name, searchTerm) - resultsWithoutPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithoutPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + resultsWithoutPrefix, _, err := th.App.SearchPostsForUser(th.Context, searchQueryWithoutPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, len(resultsWithPrefix.Posts), len(resultsWithoutPrefix.Posts), "searching using a tilde in front of a channel should return the same number of results") for k, v := range resultsWithPrefix.Posts { @@ -2210,12 +2223,12 @@ func TestSearchPostsForUser(t *testing.T) { searchQueryWithPrefix := fmt.Sprintf("from:@%s %s", th.BasicUser.Username, searchTerm) - resultsWithPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + resultsWithPrefix, _, err := th.App.SearchPostsForUser(th.Context, searchQueryWithPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Greater(t, len(resultsWithPrefix.PostList.Posts), 0, "searching using a 'at' symbol in front of a channel should return results") searchQueryWithoutPrefix := fmt.Sprintf("from:@%s %s", th.BasicUser.Username, searchTerm) - resultsWithoutPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithoutPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) + resultsWithoutPrefix, _, err := th.App.SearchPostsForUser(th.Context, searchQueryWithoutPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage) assert.Nil(t, err) assert.Equal(t, len(resultsWithPrefix.Posts), len(resultsWithoutPrefix.Posts), "searching using an 'at' symbol in front of a channel should return the same number of results") for k, v := range resultsWithPrefix.Posts { @@ -2236,19 +2249,19 @@ func TestCountMentionsFromPost(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, user2, channel) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test2", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test3", @@ -2273,19 +2286,19 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.MentionKeysNotifyProp] = "apple" - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test2", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "apple", @@ -2312,19 +2325,19 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.ChannelMentionsNotifyProp] = "true" - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "@channel", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "@all", @@ -2351,19 +2364,19 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.ChannelMentionsNotifyProp] = "false" - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "@channel", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "@all", @@ -2393,19 +2406,19 @@ func TestCountMentionsFromPost(t *testing.T) { }, channel.Id, user2.Id) require.Nil(t, err) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "@channel", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "@all", @@ -2430,33 +2443,33 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyRoot - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: post1.Id, Message: "test2", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - post3, err := th.App.CreatePost(th.Context, &model.Post{ + post3, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test3", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, RootId: post3.Id, Message: "test4", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: post3.Id, @@ -2484,33 +2497,33 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: post1.Id, Message: "test2", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - post3, err := th.App.CreatePost(th.Context, &model.Post{ + post3, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test3", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, RootId: post3.Id, Message: "test4", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: post3.Id, @@ -2536,7 +2549,7 @@ func TestCountMentionsFromPost(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, user2, channel) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test", @@ -2546,7 +2559,7 @@ func TestCountMentionsFromPost(t *testing.T) { }, }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test2", @@ -2556,7 +2569,7 @@ func TestCountMentionsFromPost(t *testing.T) { }, }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test3", @@ -2585,14 +2598,14 @@ func TestCountMentionsFromPost(t *testing.T) { channel, err := th.App.createDirectChannel(th.Context, user1.Id, user2.Id) require.Nil(t, err) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test2", @@ -2621,21 +2634,21 @@ func TestCountMentionsFromPost(t *testing.T) { channel, err := th.App.createGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id}, user1.Id) require.Nil(t, err) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test2", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user3.Id, ChannelId: channel.Id, Message: "test3", @@ -2663,19 +2676,19 @@ func TestCountMentionsFromPost(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, user2, channel) - _, err := th.App.CreatePost(th.Context, &model.Post{ + _, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - post2, err := th.App.CreatePost(th.Context, &model.Post{ + post2, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test2", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), @@ -2700,13 +2713,13 @@ func TestCountMentionsFromPost(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, user2, channel) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), @@ -2733,26 +2746,26 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test1", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, RootId: post1.Id, Message: "test2", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - post3, err := th.App.CreatePost(th.Context, &model.Post{ + post3, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test3", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: post1.Id, @@ -2787,13 +2800,13 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test1", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, RootId: post1.Id, @@ -2803,13 +2816,13 @@ func TestCountMentionsFromPost(t *testing.T) { time.Sleep(time.Millisecond * 2) - post3, err := th.App.CreatePost(th.Context, &model.Post{ + post3, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test3", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: post1.Id, @@ -2843,19 +2856,19 @@ func TestCountMentionsFromPost(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, user2, channel) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test1", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), @@ -2885,7 +2898,7 @@ func TestCountMentionsFromPost(t *testing.T) { numPosts := 215 - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), @@ -2893,7 +2906,7 @@ func TestCountMentionsFromPost(t *testing.T) { require.Nil(t, err) for i := 0; i < numPosts-1; i++ { - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), @@ -2925,7 +2938,7 @@ func TestCountMentionsFromPost(t *testing.T) { user2.NotifyProps[model.MentionKeysNotifyProp] = "apple" - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), @@ -2937,14 +2950,14 @@ func TestCountMentionsFromPost(t *testing.T) { }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: fmt.Sprintf("@%s", user2.Username), }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "apple", @@ -2977,7 +2990,7 @@ func TestFillInPostProps(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "test123123 @group1 @group2 blah blah blah", @@ -3009,7 +3022,7 @@ func TestFillInPostProps(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, guest, channel) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: guest.Id, ChannelId: channel.Id, Message: "test123123 @group1 @group2 blah blah blah", @@ -3043,7 +3056,7 @@ func TestFillInPostProps(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, guest, channel) - post1, err := th.App.CreatePost(th.Context, &model.Post{ + post1, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: guest.Id, ChannelId: channel.Id, Message: "test123123 @group1 @group2 blah blah blah", @@ -3175,7 +3188,7 @@ func TestFillInPostProps(t *testing.T) { dmChannelBetweenUser1AndUser2 := th.CreateDmChannel(t, user2) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: dmChannelBetweenUser1AndUser2.Id, Message: "Testing out i should not be able to mention channel2 from team2? ~" + channel2.Name, @@ -3200,7 +3213,7 @@ func TestFillInPostProps(t *testing.T) { dmChannel := th.CreateDmChannel(t, user2) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: dmChannel.Id, Message: "Check out ~" + channel.Name, @@ -3233,14 +3246,14 @@ func TestThreadMembership(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam) th.AddUserToChannel(t, user2, channel) - postRoot, err := th.App.CreatePost(th.Context, &model.Post{ + postRoot, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "root post", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: postRoot.Id, @@ -3257,14 +3270,14 @@ func TestThreadMembership(t *testing.T) { require.NoError(t, err2) require.Len(t, memberships, 1) - post2, err := th.App.CreatePost(th.Context, &model.Post{ + post2, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, Message: "second post", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) - _, err = th.App.CreatePost(th.Context, &model.Post{ + _, _, err = th.App.CreatePost(th.Context, &model.Post{ UserId: user2.Id, ChannelId: channel.Id, RootId: post2.Id, @@ -3302,9 +3315,9 @@ func TestFollowThreadSkipsParticipants(t *testing.T) { appErr = th.App.JoinChannel(th.Context, channel, sysadmin.Id) require.Nil(t, appErr) - p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + sysadmin.Username}, channel, model.CreatePostFlags{}) + p1, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + sysadmin.Username}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) threadMembership, appErr := th.App.GetThreadMembershipForUser(user.Id, p1.Id) @@ -3313,7 +3326,7 @@ func TestFollowThreadSkipsParticipants(t *testing.T) { require.Nil(t, appErr) require.Len(t, thread.Participants, 1) // length should be 1, the original poster, since sysadmin was just mentioned but didn't post - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: sysadmin.Id, ChannelId: channel.Id, Message: "sysadmin reply"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: sysadmin.Id, ChannelId: channel.Id, Message: "sysadmin reply"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) threadMembership, appErr = th.App.GetThreadMembershipForUser(user.Id, p1.Id) @@ -3364,12 +3377,12 @@ func TestAutofollowBasedOnRootPost(t *testing.T) { require.Nil(t, appErr) appErr = th.App.JoinChannel(th.Context, channel, user2.Id) require.Nil(t, appErr) - p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) + p1, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) m, err := th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id) require.NoError(t, err) require.Len(t, m, 0) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) m, err = th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id) require.NoError(t, err) @@ -3392,9 +3405,9 @@ func TestViewChannelShouldNotUpdateThreads(t *testing.T) { require.Nil(t, appErr) appErr = th.App.JoinChannel(th.Context, channel, user2.Id) require.Nil(t, appErr) - p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) + p1, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) m, err := th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id) require.NoError(t, err) @@ -3429,14 +3442,14 @@ func TestCollapsedThreadFetch(t *testing.T) { require.Nil(t, appErr) }() - postRoot, appErr := th.App.CreatePost(th.Context, &model.Post{ + postRoot, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "root post", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{ + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: postRoot.Id, @@ -3472,7 +3485,7 @@ func TestCollapsedThreadFetch(t *testing.T) { require.Nil(t, appErr) }() - postRoot, err := th.App.CreatePost(th.Context, &model.Post{ + postRoot, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "root post", @@ -3491,7 +3504,7 @@ func TestCollapsedThreadFetch(t *testing.T) { require.NotPanics(t, func() { // We're only testing that this doesn't panic, not checking the error // #nosec G104 - purposely not checking error as we're in a NotPanics block - _, _ = th.App.CreatePost(th.Context, &model.Post{ + _, _, _ = th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, RootId: postRoot.Id, @@ -3527,14 +3540,14 @@ func TestCollapsedThreadFetch(t *testing.T) { th.LinkUserToTeam(t, user3, th.BasicTeam) th.AddUserToChannel(t, user3, channel) - postRoot, appErr := th.App.CreatePost(th.Context, &model.Post{ + postRoot, _, appErr := th.App.CreatePost(th.Context, &model.Post{ UserId: user1.Id, ChannelId: channel.Id, Message: "root post", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{ + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{ UserId: user3.Id, ChannelId: channel.Id, RootId: postRoot.Id, @@ -3598,7 +3611,7 @@ func TestSharedChannelSyncForPostActions(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam, WithShared(true)) - _, err := th.App.CreatePost(th.Context, &model.Post{ + _, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user.Id, ChannelId: channel.Id, Message: "Hello folks", @@ -3621,14 +3634,14 @@ func TestSharedChannelSyncForPostActions(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam, WithShared(true)) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user.Id, ChannelId: channel.Id, Message: "Hello folks", }, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err, "Creating a post should not error") - _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) + _, _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true}) require.Nil(t, err, "Updating a post should not error") require.Len(t, sharedChannelService.channelNotifications, 2) @@ -3648,7 +3661,7 @@ func TestSharedChannelSyncForPostActions(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam, WithShared(true)) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user.Id, ChannelId: channel.Id, Message: "Hello folks", @@ -3682,11 +3695,11 @@ func TestAutofollowOnPostingAfterUnfollow(t *testing.T) { require.Nil(t, appErr) appErr = th.App.JoinChannel(th.Context, channel, user2.Id) require.Nil(t, appErr) - p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) + p1, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user2.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user2.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "reply"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "reply"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // unfollow thread @@ -3697,7 +3710,7 @@ func TestAutofollowOnPostingAfterUnfollow(t *testing.T) { require.NoError(t, err) require.False(t, m.Following) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "another reply"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "another reply"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // User should be following thread after posting in it, even after previously @@ -3713,7 +3726,7 @@ func TestGetPostIfAuthorized(t *testing.T) { t.Run("Private channel", func(t *testing.T) { privateChannel := th.CreatePrivateChannel(t, th.BasicTeam) - post, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: privateChannel.Id, Message: "Hello"}, privateChannel, model.CreatePostFlags{}) + post, _, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: privateChannel.Id, Message: "Hello"}, privateChannel, model.CreatePostFlags{}) require.Nil(t, err) require.NotNil(t, post) @@ -3726,17 +3739,17 @@ func TestGetPostIfAuthorized(t *testing.T) { require.NotNil(t, session2) // User is not authorized to get post - _, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false) + _, err, _ = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false) require.NotNil(t, err) // User is authorized to get post - _, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false) + _, err, _ = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false) require.Nil(t, err) }) t.Run("Public channel", func(t *testing.T) { publicChannel := th.CreateChannel(t, th.BasicTeam) - post, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: publicChannel.Id, Message: "Hello"}, publicChannel, model.CreatePostFlags{}) + post, _, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: publicChannel.Id, Message: "Hello"}, publicChannel, model.CreatePostFlags{}) require.Nil(t, err) require.NotNil(t, post) @@ -3749,11 +3762,11 @@ func TestGetPostIfAuthorized(t *testing.T) { require.NotNil(t, session2) // User is authorized to get post - _, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false) + _, err, _ = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false) require.Nil(t, err) // User is authorized to get post - _, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false) + _, err, _ = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false) require.Nil(t, err) th.App.UpdateConfig(func(c *model.Config) { @@ -3762,11 +3775,11 @@ func TestGetPostIfAuthorized(t *testing.T) { }) // User is not authorized to get post - _, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false) + _, err, _ = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false) require.NotNil(t, err) // User is authorized to get post - _, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false) + _, err, _ = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false) require.Nil(t, err) }) } @@ -3787,9 +3800,9 @@ func TestShouldNotRefollowOnOthersReply(t *testing.T) { require.Nil(t, appErr) appErr = th.App.JoinChannel(th.Context, channel, user2.Id) require.Nil(t, appErr) - p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) + p1, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user2.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user2.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // User2 unfollows thread @@ -3801,7 +3814,7 @@ func TestShouldNotRefollowOnOthersReply(t *testing.T) { require.False(t, m.Following) // user posts in the thread - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "another reply"}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "another reply"}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // User2 should still not be following the thread because they manually @@ -3811,7 +3824,7 @@ func TestShouldNotRefollowOnOthersReply(t *testing.T) { require.False(t, m.Following) // user posts in the thread mentioning user2 - _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "reply with mention @" + user2.Username}, channel, model.CreatePostFlags{}) + _, _, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "reply with mention @" + user2.Username}, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // User2 should now be following the thread because they were explicitly mentioned @@ -3942,14 +3955,14 @@ func TestGetEditHistoryForPost(t *testing.T) { UserId: th.BasicUser.Id, } - rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rpost, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) // update the post message patch := &model.PostPatch{ Message: model.NewPointer("new message edited"), } - _, err1 := th.App.PatchPost(th.Context, rpost.Id, patch, nil) + _, _, err1 := th.App.PatchPost(th.Context, rpost.Id, patch, nil) require.Nil(t, err1) // update the post message again @@ -3957,7 +3970,7 @@ func TestGetEditHistoryForPost(t *testing.T) { Message: model.NewPointer("new message edited again"), } - _, err2 := th.App.PatchPost(th.Context, rpost.Id, patch, nil) + _, _, err2 := th.App.PatchPost(th.Context, rpost.Id, patch, nil) require.Nil(t, err2) t.Run("should return the edit history", func(t *testing.T) { @@ -3987,25 +4000,25 @@ func TestGetEditHistoryForPost(t *testing.T) { FileIds: model.StringArray{fileInfo.Id}, } - _, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) patch := &model.PostPatch{ Message: model.NewPointer("new message edited"), } - _, appErr := th.App.PatchPost(th.Context, post.Id, patch, nil) + _, _, appErr := th.App.PatchPost(th.Context, post.Id, patch, nil) require.Nil(t, appErr) patch = &model.PostPatch{ Message: model.NewPointer("new message edited 2"), } - _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) + _, _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) require.Nil(t, appErr) patch = &model.PostPatch{ Message: model.NewPointer("new message edited 3"), } - _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) + _, _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) require.Nil(t, appErr) edits, err := th.App.GetEditHistoryForPost(post.Id) @@ -4033,25 +4046,25 @@ func TestGetEditHistoryForPost(t *testing.T) { FileIds: model.StringArray{fileInfo.Id}, } - _, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + _, _, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) patch := &model.PostPatch{ Message: model.NewPointer("new message edited"), } - _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) + _, _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) require.Nil(t, appErr) patch = &model.PostPatch{ Message: model.NewPointer("new message edited 2"), } - _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) + _, _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) require.Nil(t, appErr) patch = &model.PostPatch{ Message: model.NewPointer("new message edited 3"), } - _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) + _, _, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil) require.Nil(t, appErr) // now delete the file info, and it should still be include in edit history metadata @@ -4087,7 +4100,7 @@ func TestCopyWranglerPostlist(t *testing.T) { UserId: th.BasicUser.Id, FileIds: []string{fileInfo.Id}, } - rootPost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + rootPost, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, err) // Add a reaction to the post @@ -4111,7 +4124,7 @@ func TestCopyWranglerPostlist(t *testing.T) { Posts: []*model.Post{rootPost}, FileAttachmentCount: 1, } - newRootPost, err := th.App.CopyWranglerPostlist(th.Context, wpl, targetChannel) + newRootPost, _, err := th.App.CopyWranglerPostlist(th.Context, wpl, targetChannel) require.Nil(t, err) // Check that the new post has the same message and file attachment @@ -4278,7 +4291,7 @@ func TestPermanentDeletePost(t *testing.T) { FileIds: []string{info1.Id}, } - post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) assert.Nil(t, err) // Delete the post. @@ -4317,7 +4330,7 @@ func TestPermanentDeletePost(t *testing.T) { FileIds: []string{info1.Id}, } - post, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) assert.Nil(t, appErr) infos, err := th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, true, false) @@ -4372,7 +4385,7 @@ func TestPermanentDeletePost(t *testing.T) { } post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000)) - post, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) + post, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true}) require.Nil(t, appErr) require.Equal(t, model.PostTypeBurnOnRead, post.Type) @@ -4569,10 +4582,11 @@ func TestFilterPostsByChannelPermissions(t *testing.T) { postList.Posts[post3.Id] = post3 postList.Order = []string{post1.Id, post2.Id, post3.Id} - appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) + allPostHaveMembership, appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, postList.Posts, 3) require.Len(t, postList.Order, 3) + require.True(t, allPostHaveMembership) }) t.Run("should filter posts when guest has read_channel_content permission", func(t *testing.T) { @@ -4582,10 +4596,11 @@ func TestFilterPostsByChannelPermissions(t *testing.T) { postList.Posts[post3.Id] = post3 postList.Order = []string{post1.Id, post2.Id, post3.Id} - appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, guestUser.Id) + allPostHaveMembership, appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, guestUser.Id) require.Nil(t, appErr) require.Len(t, postList.Posts, 3) require.Len(t, postList.Order, 3) + require.True(t, allPostHaveMembership) }) t.Run("should filter posts when guest does not have read_channel_content permission", func(t *testing.T) { @@ -4620,22 +4635,24 @@ func TestFilterPostsByChannelPermissions(t *testing.T) { postList.Posts[post3.Id] = post3 postList.Order = []string{post1.Id, post2.Id, post3.Id} - appErr = th.App.FilterPostsByChannelPermissions(th.Context, postList, guestUser.Id) + allPostHaveMembership, appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, guestUser.Id) require.Nil(t, appErr) require.Len(t, postList.Posts, 0) require.Len(t, postList.Order, 0) + require.True(t, allPostHaveMembership) }) t.Run("should handle empty post list", func(t *testing.T) { postList := model.NewPostList() - appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) + allPostHaveMembership, appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, postList.Posts, 0) require.Len(t, postList.Order, 0) + require.True(t, allPostHaveMembership) }) t.Run("should handle nil post list", func(t *testing.T) { - appErr := th.App.FilterPostsByChannelPermissions(th.Context, nil, th.BasicUser.Id) + _, appErr := th.App.FilterPostsByChannelPermissions(th.Context, nil, th.BasicUser.Id) require.Nil(t, appErr) }) @@ -4649,10 +4666,11 @@ func TestFilterPostsByChannelPermissions(t *testing.T) { postList.Posts[postWithoutChannel.Id] = postWithoutChannel postList.Order = []string{postWithoutChannel.Id} - appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) + allPostHaveMembership, appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, postList.Posts, 0) require.Len(t, postList.Order, 0) + require.True(t, allPostHaveMembership) }) t.Run("should handle posts from non-existent channels", func(t *testing.T) { @@ -4665,10 +4683,11 @@ func TestFilterPostsByChannelPermissions(t *testing.T) { postList.Posts[postWithInvalidChannel.Id] = postWithInvalidChannel postList.Order = []string{postWithInvalidChannel.Id} - appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) + allPostHaveMembership, appErr := th.App.FilterPostsByChannelPermissions(th.Context, postList, th.BasicUser.Id) require.Nil(t, appErr) require.Len(t, postList.Posts, 0) require.Len(t, postList.Order, 0) + require.True(t, allPostHaveMembership) }) } @@ -4690,7 +4709,7 @@ func TestRevealPost(t *testing.T) { } post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000)) - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) require.NotNil(t, createdPost) return createdPost @@ -4703,7 +4722,7 @@ func TestRevealPost(t *testing.T) { UserId: th.BasicUser.Id, Message: "regular message", } - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) require.NotNil(t, createdPost) return createdPost @@ -4736,7 +4755,7 @@ func TestRevealPost(t *testing.T) { } // First save the post normally (which will add expire_at automatically) - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) // Now manually remove the expire_at prop to test missing prop scenario @@ -4871,7 +4890,7 @@ func TestRevealPost(t *testing.T) { } post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000)) - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) require.NotNil(t, createdPost) @@ -4912,7 +4931,7 @@ func TestBurnPost(t *testing.T) { } post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000)) - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) require.NotNil(t, createdPost) return createdPost @@ -4925,7 +4944,7 @@ func TestBurnPost(t *testing.T) { UserId: th.BasicUser.Id, Message: "regular message", } - createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) require.NotNil(t, createdPost) return createdPost @@ -5029,7 +5048,7 @@ func TestGetFlaggedPostsWithExpiredBurnOnRead(t *testing.T) { } borPost.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(10*1000)) // 10 seconds - createdPost, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) require.NotNil(t, createdPost) @@ -5084,7 +5103,7 @@ func TestGetFlaggedPostsWithExpiredBurnOnRead(t *testing.T) { } borPost.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(10*1000)) - createdPost, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) // User2 reveals and flags the post @@ -5126,7 +5145,7 @@ func TestGetFlaggedPostsWithExpiredBurnOnRead(t *testing.T) { } borPost.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(10*1000)) - createdPost, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) // User2 reveals and flags the post @@ -5168,7 +5187,7 @@ func TestGetFlaggedPostsWithExpiredBurnOnRead(t *testing.T) { } borPost.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(3600*1000)) // 1 hour - createdPost, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) + createdPost, _, appErr := th.App.CreatePost(th.Context, borPost, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) // User2 reveals and flags the post diff --git a/server/channels/app/reaction_test.go b/server/channels/app/reaction_test.go index d9a26325546..9f729a561a0 100644 --- a/server/channels/app/reaction_test.go +++ b/server/channels/app/reaction_test.go @@ -131,7 +131,7 @@ func TestSaveReactionForPost(t *testing.T) { ChannelId: dmChannel.Id, Message: "test post", } - post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) + post, _, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) require.Nil(t, err) reaction := &model.Reaction{ @@ -191,7 +191,7 @@ func TestDeleteReactionForPostWithRestrictedDM(t *testing.T) { ChannelId: dmChannel.Id, Message: "test post", } - post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) + post, _, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{}) require.Nil(t, err) reaction := &model.Reaction{ @@ -226,7 +226,7 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam, WithShared(true)) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user.Id, ChannelId: channel.Id, Message: "Hello folks", @@ -258,7 +258,7 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) { channel := th.CreateChannel(t, th.BasicTeam, WithShared(true)) - post, err := th.App.CreatePost(th.Context, &model.Post{ + post, _, err := th.App.CreatePost(th.Context, &model.Post{ UserId: user.Id, ChannelId: channel.Id, Message: "Hello folks", diff --git a/server/channels/app/recap.go b/server/channels/app/recap.go index 52b3ec5fac6..743dc576c0c 100644 --- a/server/channels/app/recap.go +++ b/server/channels/app/recap.go @@ -17,7 +17,7 @@ func (a *App) CreateRecap(rctx request.CTX, title string, channelIDs []string, a // Validate user is member of all channels for _, channelID := range channelIDs { - if !a.HasPermissionToChannel(rctx, userID, channelID, model.PermissionReadChannel) { + if ok, _ := a.HasPermissionToChannel(rctx, userID, channelID, model.PermissionReadChannel); !ok { return nil, model.NewAppError("CreateRecap", "app.recap.permission_denied", nil, "", http.StatusForbidden) } } diff --git a/server/channels/app/report.go b/server/channels/app/report.go index aab2f71afac..b91c45df964 100644 --- a/server/channels/app/report.go +++ b/server/channels/app/report.go @@ -138,7 +138,7 @@ func (a *App) SendReportToUser(rctx request.CTX, job *model.Job, format string) FileIds: []string{fileInfo.Id}, } - _, err = a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}) + _, _, err = a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}) return err } @@ -252,7 +252,7 @@ func (a *App) StartUsersBatchExport(rctx request.CTX, ro *model.UserReportOption UserId: systemBot.UserId, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { rctx.Logger().Error("Failed to post batch export message", mlog.Err(err)) } }) diff --git a/server/channels/app/scheduled_post_job.go b/server/channels/app/scheduled_post_job.go index aba737775d5..f1316b52cfa 100644 --- a/server/channels/app/scheduled_post_job.go +++ b/server/channels/app/scheduled_post_job.go @@ -190,7 +190,7 @@ func (a *App) postScheduledPost(rctx request.CTX, scheduledPost *model.Scheduled return scheduledPost, err } - _, appErr = a.CreatePost(rctx.WithContext(context.WithValue(rctx.Context(), model.PostContextKeyIsScheduledPost, true)), post, channel, model.CreatePostFlags{ + _, _, appErr = a.CreatePost(rctx.WithContext(context.WithValue(rctx.Context(), model.PostContextKeyIsScheduledPost, true)), post, channel, model.CreatePostFlags{ TriggerWebhooks: true, SetOnline: false, }) @@ -452,7 +452,7 @@ func (a *App) notifyUser(rctx request.CTX, userId string, userFailedMessages []* UserId: systemBot.UserId, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { rctx.Logger().Error("Failed to post notification about failed scheduled messages", mlog.Err(err)) } } diff --git a/server/channels/app/shared_channel_test.go b/server/channels/app/shared_channel_test.go index ce8c552c27c..40a482b83ee 100644 --- a/server/channels/app/shared_channel_test.go +++ b/server/channels/app/shared_channel_test.go @@ -365,7 +365,7 @@ func TestApp_RemoteUnsharing(t *testing.T) { UserId: th.BasicUser.Id, Message: "Test message after remote 1 unshare", } - _, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{}) + _, _, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{}) require.Nil(t, appErr) // Get post count after creating the test post but before "remote-initiated unshare" diff --git a/server/channels/app/slashcommands/auto_posts.go b/server/channels/app/slashcommands/auto_posts.go index 4030b6bb428..5230ae9d208 100644 --- a/server/channels/app/slashcommands/auto_posts.go +++ b/server/channels/app/slashcommands/auto_posts.go @@ -114,7 +114,7 @@ func (cfg *AutoPostCreator) CreateRandomPostNested(rctx request.CTX, rootID stri post.UserId = cfg.UsersToPostFrom[i] } } - rpost, err := cfg.a.CreatePostMissingChannel(rctx, post, true, true) + rpost, _, err := cfg.a.CreatePostMissingChannel(rctx, post, true, true) if err != nil { return nil, err } diff --git a/server/channels/app/slashcommands/command_channel_header.go b/server/channels/app/slashcommands/command_channel_header.go index f59ba4b5975..a3733670606 100644 --- a/server/channels/app/slashcommands/command_channel_header.go +++ b/server/channels/app/slashcommands/command_channel_header.go @@ -46,7 +46,7 @@ func (*HeaderProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Comma switch channel.Type { case model.ChannelTypeOpen: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelProperties) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelProperties); !ok { return &model.CommandResponse{ Text: args.T("api.command_channel_header.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, @@ -54,7 +54,7 @@ func (*HeaderProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Comma } case model.ChannelTypePrivate: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelProperties) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelProperties); !ok { return &model.CommandResponse{ Text: args.T("api.command_channel_header.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, diff --git a/server/channels/app/slashcommands/command_channel_purpose.go b/server/channels/app/slashcommands/command_channel_purpose.go index acd629857c9..fbc6ee21b74 100644 --- a/server/channels/app/slashcommands/command_channel_purpose.go +++ b/server/channels/app/slashcommands/command_channel_purpose.go @@ -46,14 +46,14 @@ func (*PurposeProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Comm switch channel.Type { case model.ChannelTypeOpen: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelProperties) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelProperties); !ok { return &model.CommandResponse{ Text: args.T("api.command_channel_purpose.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, } } case model.ChannelTypePrivate: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelProperties) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelProperties); !ok { return &model.CommandResponse{ Text: args.T("api.command_channel_purpose.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, diff --git a/server/channels/app/slashcommands/command_channel_rename.go b/server/channels/app/slashcommands/command_channel_rename.go index ddb0e6f6797..4ee333df610 100644 --- a/server/channels/app/slashcommands/command_channel_rename.go +++ b/server/channels/app/slashcommands/command_channel_rename.go @@ -49,14 +49,14 @@ func (*RenameProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Comma switch channel.Type { case model.ChannelTypeOpen: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelProperties) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelProperties); !ok { return &model.CommandResponse{ Text: args.T("api.command_channel_rename.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, } } case model.ChannelTypePrivate: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelProperties) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelProperties); !ok { return &model.CommandResponse{ Text: args.T("api.command_channel_rename.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, diff --git a/server/channels/app/slashcommands/command_echo.go b/server/channels/app/slashcommands/command_echo.go index 7276615c6ea..bba5d961a25 100644 --- a/server/channels/app/slashcommands/command_echo.go +++ b/server/channels/app/slashcommands/command_echo.go @@ -89,7 +89,7 @@ func (*EchoProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Command time.Sleep(time.Duration(delay) * time.Second) - if _, err := a.CreatePostMissingChannel(rctx, post, true, true); err != nil { + if _, _, err := a.CreatePostMissingChannel(rctx, post, true, true); err != nil { rctx.Logger().Error("Unable to create /echo post.", mlog.Err(err)) } }) diff --git a/server/channels/app/slashcommands/command_groupmsg.go b/server/channels/app/slashcommands/command_groupmsg.go index e589d36efd7..ddfe207a3b8 100644 --- a/server/channels/app/slashcommands/command_groupmsg.go +++ b/server/channels/app/slashcommands/command_groupmsg.go @@ -126,7 +126,7 @@ func (*groupmsgProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Com post.Message = parsedMessage post.ChannelId = groupChannel.Id post.UserId = args.UserId - if _, err := a.CreatePostMissingChannel(rctx, post, true, true); err != nil { + if _, _, err := a.CreatePostMissingChannel(rctx, post, true, true); err != nil { return &model.CommandResponse{Text: args.T("api.command_groupmsg.fail.app_error"), ResponseType: model.CommandResponseTypeEphemeral} } } diff --git a/server/channels/app/slashcommands/command_invite.go b/server/channels/app/slashcommands/command_invite.go index 4b12f4598b4..645f8b96ac4 100644 --- a/server/channels/app/slashcommands/command_invite.go +++ b/server/channels/app/slashcommands/command_invite.go @@ -266,7 +266,7 @@ func (i *InviteProvider) checkPermissions(a *app.App, rctx request.CTX, args *mo for _, targetChannel := range targetChannels { switch targetChannel.Type { case model.ChannelTypeOpen: - if !a.HasPermissionToChannel(rctx, args.UserId, targetChannel.Id, model.PermissionManagePublicChannelMembers) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, targetChannel.Id, model.PermissionManagePublicChannelMembers); !ok { *resps = append(*resps, args.T("api.command_invite.permission.app_error", map[string]any{ "User": targetUser.Username, "Channel": targetChannel.Name, @@ -274,7 +274,7 @@ func (i *InviteProvider) checkPermissions(a *app.App, rctx request.CTX, args *mo continue } case model.ChannelTypePrivate: - if !a.HasPermissionToChannel(rctx, args.UserId, targetChannel.Id, model.PermissionManagePrivateChannelMembers) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, targetChannel.Id, model.PermissionManagePrivateChannelMembers); !ok { if _, err = a.GetChannelMember(rctx, targetChannel.Id, args.UserId); err == nil { // User doing the inviting is a member of the channel. *resps = append(*resps, args.T("api.command_invite.permission.app_error", map[string]any{ diff --git a/server/channels/app/slashcommands/command_join.go b/server/channels/app/slashcommands/command_join.go index b3a14b2a716..79c5a0b44df 100644 --- a/server/channels/app/slashcommands/command_join.go +++ b/server/channels/app/slashcommands/command_join.go @@ -55,11 +55,11 @@ func (*JoinProvider) DoCommand(a *app.App, rctx request.CTX, args *model.Command switch channel.Type { case model.ChannelTypeOpen: - if !a.HasPermissionToChannel(rctx, args.UserId, channel.Id, model.PermissionJoinPublicChannels) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, channel.Id, model.PermissionJoinPublicChannels); !ok { return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.CommandResponseTypeEphemeral} } case model.ChannelTypePrivate: - if !a.HasPermissionToChannel(rctx, args.UserId, channel.Id, model.PermissionReadChannel) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, channel.Id, model.PermissionReadChannel); !ok { return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.CommandResponseTypeEphemeral} } default: diff --git a/server/channels/app/slashcommands/command_loadtest.go b/server/channels/app/slashcommands/command_loadtest.go index ae87ea971a1..bddaab3aa5a 100644 --- a/server/channels/app/slashcommands/command_loadtest.go +++ b/server/channels/app/slashcommands/command_loadtest.go @@ -673,7 +673,7 @@ func (*LoadTestProvider) URLCommand(a *app.App, rctx request.CTX, args *model.Co post.ChannelId = args.ChannelId post.UserId = args.UserId - if _, err := a.CreatePostMissingChannel(rctx, post, false, true); err != nil { + if _, _, err := a.CreatePostMissingChannel(rctx, post, false, true); err != nil { return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.CommandResponseTypeEphemeral}, err } } @@ -722,7 +722,7 @@ func (*LoadTestProvider) JSONCommand(a *app.App, rctx request.CTX, args *model.C post.Message = message } - if _, err := a.CreatePostMissingChannel(rctx, &post, false, true); err != nil { + if _, _, err := a.CreatePostMissingChannel(rctx, &post, false, true); err != nil { return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.CommandResponseTypeEphemeral}, err } diff --git a/server/channels/app/slashcommands/command_msg.go b/server/channels/app/slashcommands/command_msg.go index c6b80828f4f..0739cbbad4b 100644 --- a/server/channels/app/slashcommands/command_msg.go +++ b/server/channels/app/slashcommands/command_msg.go @@ -100,7 +100,7 @@ func (*msgProvider) DoCommand(a *app.App, rctx request.CTX, args *model.CommandA post.Message = parsedMessage post.ChannelId = targetChannelID post.UserId = args.UserId - if _, err = a.CreatePostMissingChannel(rctx, post, true, true); err != nil { + if _, _, err = a.CreatePostMissingChannel(rctx, post, true, true); err != nil { return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.CommandResponseTypeEphemeral} } } diff --git a/server/channels/app/slashcommands/command_remove.go b/server/channels/app/slashcommands/command_remove.go index a22433aed49..4f4443ccf9b 100644 --- a/server/channels/app/slashcommands/command_remove.go +++ b/server/channels/app/slashcommands/command_remove.go @@ -75,14 +75,14 @@ func doCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message st switch channel.Type { case model.ChannelTypeOpen: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelMembers) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePublicChannelMembers); !ok { return &model.CommandResponse{ Text: args.T("api.command_remove.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, } } case model.ChannelTypePrivate: - if !a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelMembers) { + if ok, _ := a.HasPermissionToChannel(rctx, args.UserId, args.ChannelId, model.PermissionManagePrivateChannelMembers); !ok { return &model.CommandResponse{ Text: args.T("api.command_remove.permission.app_error"), ResponseType: model.CommandResponseTypeEphemeral, diff --git a/server/channels/app/slashcommands/helper_test.go b/server/channels/app/slashcommands/helper_test.go index 0359d4aac91..d386b9a3a0c 100644 --- a/server/channels/app/slashcommands/helper_test.go +++ b/server/channels/app/slashcommands/helper_test.go @@ -353,7 +353,7 @@ func (th *TestHelper) createPost(tb testing.TB, channel *model.Channel) *model.P CreateAt: model.GetMillis() - 10000, } - post, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{SetOnline: true}) + post, _, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{SetOnline: true}) require.Nil(tb, appErr) return post } diff --git a/server/channels/app/team.go b/server/channels/app/team.go index fffe6c42e20..2fd5e6562fd 100644 --- a/server/channels/app/team.go +++ b/server/channels/app/team.go @@ -1313,7 +1313,7 @@ func (a *App) postLeaveTeamMessage(rctx request.CTX, user *model.User, channel * }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postRemoveFromChannelMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -1331,7 +1331,7 @@ func (a *App) postRemoveFromTeamMessage(rctx request.CTX, user *model.User, chan }, } - if _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { + if _, _, err := a.CreatePost(rctx, post, channel, model.CreatePostFlags{SetOnline: true}); err != nil { return model.NewAppError("postRemoveFromTeamMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, "", http.StatusInternalServerError).Wrap(err) } diff --git a/server/channels/app/team_test.go b/server/channels/app/team_test.go index a3c242a1bc7..3a9f826ebdb 100644 --- a/server/channels/app/team_test.go +++ b/server/channels/app/team_test.go @@ -1265,12 +1265,14 @@ func TestAppUpdateTeamScheme(t *testing.T) { }, } // ensure user can update channel properties before applying the scheme - require.True(t, th.App.SessionHasPermissionToChannel(th.Context, session, channel.Id, model.PermissionManagePublicChannelProperties)) + ok, _ := th.App.SessionHasPermissionToChannel(th.Context, session, channel.Id, model.PermissionManagePublicChannelProperties) + require.True(t, ok) // apply the team scheme team2.SchemeId = &team2Scheme.Id _, appErr = th.App.UpdateTeamScheme(team2) require.Nil(t, appErr) - require.False(t, th.App.SessionHasPermissionToChannel(th.Context, session, channel.Id, model.PermissionManagePublicChannelProperties)) + ok, _ = th.App.SessionHasPermissionToChannel(th.Context, session, channel.Id, model.PermissionManagePublicChannelProperties) + require.False(t, ok) } func TestGetTeamMembers(t *testing.T) { diff --git a/server/channels/app/user.go b/server/channels/app/user.go index bdf09828b13..3fc77da2c9b 100644 --- a/server/channels/app/user.go +++ b/server/channels/app/user.go @@ -3036,7 +3036,7 @@ func (a *App) UpdateThreadFollowForUserFromChannelAdd(rctx request.CTX, userID, } a.sanitizeProfiles(userThread.Participants, false) userThread.Post.SanitizeProps() - sanitizedPost, appErr := a.SanitizePostMetadataForUser(rctx, userThread.Post, userID) + sanitizedPost, isMemberForPreviews, appErr := a.SanitizePostMetadataForUser(rctx, userThread.Post, userID) if appErr != nil { return appErr } @@ -3050,6 +3050,16 @@ func (a *App) UpdateThreadFollowForUserFromChannelAdd(rctx request.CTX, userID, message.Add("previous_unread_replies", int64(0)) message.Add("previous_unread_mentions", int64(0)) + auditRec := a.MakeAuditRecord(rctx, model.AuditEventWebsocketPost, model.AuditStatusSuccess) + defer a.LogAuditRec(rctx, auditRec, nil) + model.AddEventParameterToAuditRec(auditRec, "post_id", userThread.Post.Id) + model.AddEventParameterToAuditRec(auditRec, "user_id", userID) + model.AddEventParameterToAuditRec(auditRec, "source", "UpdateThreadFollowForUserFromChannelAdd") + if !isMemberForPreviews { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + auditRec.Success() + a.Publish(message) return nil } diff --git a/server/channels/app/user_test.go b/server/channels/app/user_test.go index 7b3fe1b7440..9a6520d93eb 100644 --- a/server/channels/app/user_test.go +++ b/server/channels/app/user_test.go @@ -2287,9 +2287,9 @@ func TestUpdateThreadReadForUser(t *testing.T) { *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn }) - rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, model.CreatePostFlags{}) + rootPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) - replyPost, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, model.CreatePostFlags{}) + replyPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, model.CreatePostFlags{}) require.Nil(t, appErr) threads, appErr := th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{}) require.Nil(t, appErr) diff --git a/server/channels/app/web_broadcast_hooks.go b/server/channels/app/web_broadcast_hooks.go index 34ce537b592..fdf635091b0 100644 --- a/server/channels/app/web_broadcast_hooks.go +++ b/server/channels/app/web_broadcast_hooks.go @@ -165,7 +165,8 @@ func (h *permalinkBroadcastHook) Process(msg *platform.HookedWebSocketEvent, web } rctx := request.EmptyContext(webConn.Platform.Log()) - if !webConn.Suite.HasPermissionToReadChannel(rctx, webConn.UserId, previewChannel) { + ok, isMember := webConn.Suite.HasPermissionToReadChannel(rctx, webConn.UserId, previewChannel) + if !ok { // Do nothing. // In this case, the sanitized post is already attached to the ws event. return nil @@ -178,6 +179,16 @@ func (h *permalinkBroadcastHook) Process(msg *platform.HookedWebSocketEvent, web } msg.Add("post", postJSON) + auditRec := webConn.Suite.MakeAuditRecord(rctx, model.AuditEventWebsocketPost, model.AuditStatusSuccess) + defer webConn.Suite.LogAuditRec(rctx, auditRec, nil) + model.AddEventParameterToAuditRec(auditRec, "channel_id", previewChannel.Id) + model.AddEventParameterToAuditRec(auditRec, "user_id", webConn.UserId) + model.AddEventParameterToAuditRec(auditRec, "source", "permalinkBroadcastHook") + + if !isMember { + model.AddEventParameterToAuditRec(auditRec, "non_channel_member_access", true) + } + return nil } diff --git a/server/channels/app/webhook.go b/server/channels/app/webhook.go index 333c8de475a..0ad7df18e8e 100644 --- a/server/channels/app/webhook.go +++ b/server/channels/app/webhook.go @@ -371,7 +371,7 @@ func (a *App) CreateWebhookPost(rctx request.CTX, userID string, channel *model. } for _, split := range splits { - if _, err = a.CreatePost(rctx, split, channel, model.CreatePostFlags{}); err != nil { + if _, _, err := a.CreatePost(rctx, split, channel, model.CreatePostFlags{}); err != nil { return nil, model.NewAppError("CreateWebhookPost", "api.post.create_webhook_post.creating.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } @@ -847,7 +847,12 @@ func (a *App) HandleIncomingWebhook(rctx request.CTX, hookID string, req *model. return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.user.app_error", map[string]any{"user": hook.UserId}, "", http.StatusForbidden).Wrap(resultU.NErr) } - if channel.Type != model.ChannelTypeOpen && !a.HasPermissionToChannel(rctx, hook.UserId, channel.Id, model.PermissionReadChannelContent) { + restrictedChannel := false + if channel.Type != model.ChannelTypeOpen { + hasPermission, _ := a.HasPermissionToChannel(rctx, hook.UserId, channel.Id, model.PermissionReadChannelContent) + restrictedChannel = !hasPermission + } + if restrictedChannel { return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.permissions.app_error", map[string]any{"user": hook.UserId, "channel": channel.Id}, "", http.StatusForbidden) } diff --git a/server/channels/wsapi/user.go b/server/channels/wsapi/user.go index 9761fec0b40..2087064a16c 100644 --- a/server/channels/wsapi/user.go +++ b/server/channels/wsapi/user.go @@ -27,7 +27,7 @@ func (api *API) userTyping(req *model.WebSocketRequest) (map[string]any, *model. return nil, NewInvalidWebSocketParamError(req.Action, "channel_id") } - if !api.App.SessionHasPermissionToChannel(request.EmptyContext(api.App.Log()), req.Session, channelId, model.PermissionCreatePost) { + if hasPermission, _ := api.App.SessionHasPermissionToChannel(request.EmptyContext(api.App.Log()), req.Session, channelId, model.PermissionCreatePost); !hasPermission { return nil, NewInvalidWebSocketParamError(req.Action, "channel_id") } diff --git a/server/cmd/mmctl/commands/post_e2e_test.go b/server/cmd/mmctl/commands/post_e2e_test.go index a738499e245..44fd6821ebc 100644 --- a/server/cmd/mmctl/commands/post_e2e_test.go +++ b/server/cmd/mmctl/commands/post_e2e_test.go @@ -22,10 +22,10 @@ func (s *MmctlE2ETestSuite) TestPostListCmd() { channel, err := s.th.App.CreateChannel(s.th.Context, &model.Channel{Name: channelName, DisplayName: channelDisplayName, Type: model.ChannelTypePrivate, TeamId: s.th.BasicTeam.Id}, false) s.Require().Nil(err) - post1, err := s.th.App.CreatePost(s.th.Context, &model.Post{Message: model.NewRandomString(15), UserId: s.th.BasicUser.Id, ChannelId: channel.Id}, channel, model.CreatePostFlags{}) + post1, _, err := s.th.App.CreatePost(s.th.Context, &model.Post{Message: model.NewRandomString(15), UserId: s.th.BasicUser.Id, ChannelId: channel.Id}, channel, model.CreatePostFlags{}) s.Require().Nil(err) - post2, err := s.th.App.CreatePost(s.th.Context, &model.Post{Message: model.NewRandomString(15), UserId: s.th.BasicUser.Id, ChannelId: channel.Id}, channel, model.CreatePostFlags{}) + post2, _, err := s.th.App.CreatePost(s.th.Context, &model.Post{Message: model.NewRandomString(15), UserId: s.th.BasicUser.Id, ChannelId: channel.Id}, channel, model.CreatePostFlags{}) s.Require().Nil(err) return channelName, post1, post2 diff --git a/server/platform/services/sharedchannel/mock_AppIface_test.go b/server/platform/services/sharedchannel/mock_AppIface_test.go index 13e3d194b2e..91d665080d3 100644 --- a/server/platform/services/sharedchannel/mock_AppIface_test.go +++ b/server/platform/services/sharedchannel/mock_AppIface_test.go @@ -142,7 +142,7 @@ func (_m *MockAppIface) CreateGroupChannel(rctx request.CTX, userIDs []string, c } // CreatePost provides a mock function with given fields: rctx, post, channel, flags -func (_m *MockAppIface) CreatePost(rctx request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (*model.Post, *model.AppError) { +func (_m *MockAppIface) CreatePost(rctx request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (*model.Post, bool, *model.AppError) { ret := _m.Called(rctx, post, channel, flags) if len(ret) == 0 { @@ -150,8 +150,9 @@ func (_m *MockAppIface) CreatePost(rctx request.CTX, post *model.Post, channel * } var r0 *model.Post - var r1 *model.AppError - if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, *model.Channel, model.CreatePostFlags) (*model.Post, *model.AppError)); ok { + var r1 bool + var r2 *model.AppError + if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, *model.Channel, model.CreatePostFlags) (*model.Post, bool, *model.AppError)); ok { return rf(rctx, post, channel, flags) } if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, *model.Channel, model.CreatePostFlags) *model.Post); ok { @@ -162,15 +163,21 @@ func (_m *MockAppIface) CreatePost(rctx request.CTX, post *model.Post, channel * } } - if rf, ok := ret.Get(1).(func(request.CTX, *model.Post, *model.Channel, model.CreatePostFlags) *model.AppError); ok { + if rf, ok := ret.Get(1).(func(request.CTX, *model.Post, *model.Channel, model.CreatePostFlags) bool); ok { r1 = rf(rctx, post, channel, flags) } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(*model.AppError) + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(request.CTX, *model.Post, *model.Channel, model.CreatePostFlags) *model.AppError); ok { + r2 = rf(rctx, post, channel, flags) + } else { + if ret.Get(2) != nil { + r2 = ret.Get(2).(*model.AppError) } } - return r0, r1 + return r0, r1, r2 } // CreateUploadSession provides a mock function with given fields: rctx, us @@ -707,7 +714,7 @@ func (_m *MockAppIface) SaveReactionForPost(rctx request.CTX, reaction *model.Re } // SendEphemeralPost provides a mock function with given fields: rctx, userId, post -func (_m *MockAppIface) SendEphemeralPost(rctx request.CTX, userId string, post *model.Post) *model.Post { +func (_m *MockAppIface) SendEphemeralPost(rctx request.CTX, userId string, post *model.Post) (*model.Post, bool) { ret := _m.Called(rctx, userId, post) if len(ret) == 0 { @@ -715,6 +722,10 @@ func (_m *MockAppIface) SendEphemeralPost(rctx request.CTX, userId string, post } var r0 *model.Post + var r1 bool + if rf, ok := ret.Get(0).(func(request.CTX, string, *model.Post) (*model.Post, bool)); ok { + return rf(rctx, userId, post) + } if rf, ok := ret.Get(0).(func(request.CTX, string, *model.Post) *model.Post); ok { r0 = rf(rctx, userId, post) } else { @@ -723,11 +734,17 @@ func (_m *MockAppIface) SendEphemeralPost(rctx request.CTX, userId string, post } } - return r0 + if rf, ok := ret.Get(1).(func(request.CTX, string, *model.Post) bool); ok { + r1 = rf(rctx, userId, post) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 } // UpdatePost provides a mock function with given fields: rctx, post, updatePostOptions -func (_m *MockAppIface) UpdatePost(rctx request.CTX, post *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, *model.AppError) { +func (_m *MockAppIface) UpdatePost(rctx request.CTX, post *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, bool, *model.AppError) { ret := _m.Called(rctx, post, updatePostOptions) if len(ret) == 0 { @@ -735,8 +752,9 @@ func (_m *MockAppIface) UpdatePost(rctx request.CTX, post *model.Post, updatePos } var r0 *model.Post - var r1 *model.AppError - if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, *model.UpdatePostOptions) (*model.Post, *model.AppError)); ok { + var r1 bool + var r2 *model.AppError + if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, *model.UpdatePostOptions) (*model.Post, bool, *model.AppError)); ok { return rf(rctx, post, updatePostOptions) } if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, *model.UpdatePostOptions) *model.Post); ok { @@ -747,15 +765,21 @@ func (_m *MockAppIface) UpdatePost(rctx request.CTX, post *model.Post, updatePos } } - if rf, ok := ret.Get(1).(func(request.CTX, *model.Post, *model.UpdatePostOptions) *model.AppError); ok { + if rf, ok := ret.Get(1).(func(request.CTX, *model.Post, *model.UpdatePostOptions) bool); ok { r1 = rf(rctx, post, updatePostOptions) } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(*model.AppError) + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(request.CTX, *model.Post, *model.UpdatePostOptions) *model.AppError); ok { + r2 = rf(rctx, post, updatePostOptions) + } else { + if ret.Get(2) != nil { + r2 = ret.Get(2).(*model.AppError) } } - return r0, r1 + return r0, r1, r2 } // UserCanSeeOtherUser provides a mock function with given fields: rctx, userID, otherUserId diff --git a/server/platform/services/sharedchannel/permalink_test.go b/server/platform/services/sharedchannel/permalink_test.go index 940b8df0d66..a31d2ed16c5 100644 --- a/server/platform/services/sharedchannel/permalink_test.go +++ b/server/platform/services/sharedchannel/permalink_test.go @@ -38,7 +38,7 @@ func TestProcessPermalinkToRemote(t *testing.T) { mockServer.On("Log").Return(logger) mockApp := scs.app.(*MockAppIface) - mockApp.On("SendEphemeralPost", mock.Anything, "user", mock.AnythingOfType("*model.Post")).Return(&model.Post{}).Times(1) + mockApp.On("SendEphemeralPost", mock.Anything, "user", mock.AnythingOfType("*model.Post")).Return(&model.Post{}, true).Times(1) defer mockApp.AssertExpectations(t) t.Run("same channel", func(t *testing.T) { diff --git a/server/platform/services/sharedchannel/service.go b/server/platform/services/sharedchannel/service.go index daf07f167ec..8d5afca8092 100644 --- a/server/platform/services/sharedchannel/service.go +++ b/server/platform/services/sharedchannel/service.go @@ -54,7 +54,7 @@ type PlatformIface interface { } type AppIface interface { - SendEphemeralPost(rctx request.CTX, userId string, post *model.Post) *model.Post + SendEphemeralPost(rctx request.CTX, userId string, post *model.Post) (*model.Post, bool) CreateChannelWithUser(rctx request.CTX, channel *model.Channel, userId string) (*model.Channel, *model.AppError) GetOrCreateDirectChannel(rctx request.CTX, userId, otherUserId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError) CreateGroupChannel(rctx request.CTX, userIDs []string, creatorId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError) @@ -63,8 +63,8 @@ type AppIface interface { AddUserToTeamByTeamId(rctx request.CTX, teamId string, user *model.User) *model.AppError RemoveUserFromChannel(rctx request.CTX, userID string, removerUserId string, channel *model.Channel) *model.AppError PermanentDeleteChannel(rctx request.CTX, channel *model.Channel) *model.AppError - CreatePost(rctx request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (savedPost *model.Post, err *model.AppError) - UpdatePost(rctx request.CTX, post *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, *model.AppError) + CreatePost(rctx request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (savedPost *model.Post, isMemberForPreviews bool, err *model.AppError) + UpdatePost(rctx request.CTX, post *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, bool, *model.AppError) DeletePost(rctx request.CTX, postID, deleteByID string) (*model.Post, *model.AppError) SaveReactionForPost(rctx request.CTX, reaction *model.Reaction) (*model.Reaction, *model.AppError) DeleteReactionForPost(rctx request.CTX, reaction *model.Reaction) *model.AppError @@ -315,7 +315,7 @@ func (scs *Service) postUnshareNotification(channelID string, creatorID string, } logger := scs.server.Log() - _, appErr := scs.app.CreatePost(request.EmptyContext(logger), post, channel, model.CreatePostFlags{}) + _, _, appErr := scs.app.CreatePost(request.EmptyContext(logger), post, channel, model.CreatePostFlags{}) if appErr != nil { scs.server.Log().Log( diff --git a/server/platform/services/sharedchannel/sync_recv.go b/server/platform/services/sharedchannel/sync_recv.go index ab30b0ac8c1..60dddad1fa9 100644 --- a/server/platform/services/sharedchannel/sync_recv.go +++ b/server/platform/services/sharedchannel/sync_recv.go @@ -493,7 +493,7 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe scs.transformMentionsOnReceive(rctx, post, targetChannel, rc, mentionTransforms) - rpost, appErr = scs.app.CreatePost(rctx, post, targetChannel, model.CreatePostFlags{TriggerWebhooks: true, SetOnline: true}) + rpost, _, appErr = scs.app.CreatePost(rctx, post, targetChannel, model.CreatePostFlags{TriggerWebhooks: true, SetOnline: true}) if appErr == nil { scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Created sync post", mlog.String("post_id", post.Id), @@ -527,7 +527,7 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe } // First update the basic post - rpost, appErr = scs.app.UpdatePost(rctx, post, nil) + rpost, _, appErr = scs.app.UpdatePost(rctx, post, nil) if appErr != nil { rerr := errors.New(appErr.Error()) return nil, rerr diff --git a/server/public/model/audit_events.go b/server/public/model/audit_events.go index c26ba5182c5..0d28b80241c 100644 --- a/server/public/model/audit_events.go +++ b/server/public/model/audit_events.go @@ -44,6 +44,7 @@ const ( AuditEventDeleteChannelBookmark = "deleteChannelBookmark" // delete bookmark AuditEventUpdateChannelBookmark = "updateChannelBookmark" // update bookmark AuditEventUpdateChannelBookmarkSortOrder = "updateChannelBookmarkSortOrder" // update display order of bookmarks + AuditEventListChannelBookmarksForChannel = "listChannelBookmarksForChannel" // list bookmarks for channel ) // Channel Categories @@ -63,6 +64,7 @@ const ( AuditEventCreateDirectChannel = "createDirectChannel" // create direct message channel between two users AuditEventCreateGroupChannel = "createGroupChannel" // create group message channel with multiple users AuditEventDeleteChannel = "deleteChannel" // delete channel + AuditEventGetPinnedPosts = "getPinnedPosts" // get pinned posts AuditEventLocalAddChannelMember = "localAddChannelMember" // add channel member locally AuditEventLocalCreateChannel = "localCreateChannel" // create channel locally AuditEventLocalDeleteChannel = "localDeleteChannel" // delete channel locally @@ -156,6 +158,11 @@ const ( AuditEventUploadFileMultipart = "uploadFileMultipart" // upload file using multipart form data AuditEventUploadFileMultipartLegacy = "uploadFileMultipartLegacy" // upload file using legacy multipart method AuditEventUploadFileSimple = "uploadFileSimple" // upload file using simple direct upload method + AuditEventGetFileThumbnail = "getFileThumbnail" // get file thumbnail + AuditEventGetFileInfosForPost = "getFileInfosForPost" // get file infos for post + AuditEventGetFileInfo = "getFileInfo" // get file info + AuditEventGetFilePreview = "getFilePreview" // get file preview + AuditEventSearchFiles = "searchFiles" // search for files ) // Groups @@ -244,17 +251,28 @@ const ( // Posts const ( - AuditEventCreatePost = "createPost" // create post - AuditEventDeletePost = "deletePost" // delete post - AuditEventLocalDeletePost = "localDeletePost" // delete post locally - AuditEventMoveThread = "moveThread" // move thread and replies to different channel - AuditEventPatchPost = "patchPost" // update post meta properties - AuditEventRestorePostVersion = "restorePostVersion" // restore post to previous version - AuditEventSaveIsPinnedPost = "saveIsPinnedPost" // pin or unpin post - AuditEventSearchPosts = "searchPosts" // search for posts - AuditEventUpdatePost = "updatePost" // update post content - AuditEventRevealPost = "revealPost" // reveal a post that was hidden due to burn on read - AuditEventBurnPost = "burnPost" // burn a post that was hidden due to burn on read + AuditEventCreateEphemeralPost = "createEphemeralPost" // create ephemeral post + AuditEventCreatePost = "createPost" // create post + AuditEventDeletePost = "deletePost" // delete post + AuditEventGetEditHistoryForPost = "getEditHistoryForPost" // get edit history for post + AuditEventGetFlaggedPosts = "getFlaggedPosts" // get flagged posts + AuditEventGetPostsForChannel = "getPostsForChannel" // get posts for channel + AuditEventGetPostsForChannelAroundLastUnread = "getPostsForChannelAroundLastUnread" // get posts for channel around last unread + AuditEventGetPost = "getPost" // get post + AuditEventGetPostThread = "getPostThread" // get post thread + AuditEventGetPostsByIds = "getPostsByIds" // get posts by ids + AuditEventGetThreadForUser = "getThreadForUser" // get thread for user + AuditEventLocalDeletePost = "localDeletePost" // delete post locally + AuditEventMoveThread = "moveThread" // move thread and replies to different channel + AuditEventNotificationAck = "notificationAck" // notification ack + AuditEventPatchPost = "patchPost" // update post meta properties + AuditEventRestorePostVersion = "restorePostVersion" // restore post to previous version + AuditEventSaveIsPinnedPost = "saveIsPinnedPost" // pin or unpin post + AuditEventSearchPosts = "searchPosts" // search for posts + AuditEventUpdatePost = "updatePost" // update post content + AuditEventRevealPost = "revealPost" // reveal a post that was hidden due to burn on read + AuditEventBurnPost = "burnPost" // burn a post that was hidden due to burn on read + AuditEventWebsocketPost = "websocketPost" // post received via websocket ) // Recaps