Manual Cherrypick: Add audits for accessing posts without membership (#31266) (#35022)

Automatic Merge
Этот коммит содержится в:
Daniel Espino García
2026-01-26 11:23:28 +01:00
коммит произвёл GitHub
родитель 12dce033d6
Коммит 21a86506f9
79 изменённых файлов: 1707 добавлений и 1001 удалений

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

@@ -37,32 +37,32 @@ const (
var atMentionPattern = regexp.MustCompile(`\B@`)
func (a *App) CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) {
func (a *App) CreatePostAsUser(c 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
}
rp, err := a.CreatePost(c, post, channel, model.CreatePostFlags{TriggerWebhooks: true, SetOnline: setOnline})
rp, isMemberForPreviews, err := a.CreatePost(c, 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:
@@ -115,19 +115,19 @@ func (a *App) CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId
}
}
return rp, nil
return rp, isMemberForPreviews, nil
}
func (a *App) CreatePostMissingChannel(c request.CTX, post *model.Post, triggerWebhooks bool, setOnline bool) (*model.Post, *model.AppError) {
func (a *App) CreatePostMissingChannel(c 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)
}
}
@@ -168,7 +168,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
@@ -181,17 +181,25 @@ func (a *App) deduplicateCreatePost(rctx request.CTX, post *model.Post) (foundPo
return actualPost, nil
}
func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (savedPost *model.Post, err *model.AppError) {
func (a *App) CreatePost(c 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(c, 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(c, previewPost.Post.ChannelId, c.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.
@@ -224,7 +232,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
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)
}
}
@@ -245,9 +253,9 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
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)
}
}
@@ -264,16 +272,18 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
}
var ephemeralPost *model.Post
if post.Type == "" && !a.HasPermissionToChannel(c, 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(c, 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},
}
}
}
}
@@ -283,23 +293,23 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
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)
}
}
post.Hashtags, _ = model.ParseHashtags(post.Message)
if err = a.FillInPostProps(c, 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
@@ -344,7 +354,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
}, plugin.MessageWillBePostedID)
if rejectionError != nil {
return nil, rejectionError
return nil, false, rejectionError
}
// Pre-fill the CreateAt field for link previews to get the correct timestamp.
@@ -364,18 +374,18 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
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 {
@@ -420,7 +430,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
mlog.String("reason", model.NotificationReasonResolvePersistentNotificationError),
mlog.Err(appErr),
)
return nil, appErr
return nil, false, appErr
}
}
@@ -444,12 +454,12 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
a.SendEphemeralPost(c, post.UserId, ephemeralPost)
}
rpost, err = a.SanitizePostMetadataForUser(c, rpost, c.Session().UserId)
rpost, isMemberForPreviews, err = a.SanitizePostMetadataForUser(c, rpost, c.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) {
@@ -515,15 +525,17 @@ func (a *App) FillInPostProps(c request.CTX, post *model.Post, channel *model.Ch
}
for _, mentioned := range mentionedChannels {
if mentioned.Type == model.ChannelTypeOpen && a.HasPermissionToReadChannel(c, post.UserId, mentioned) {
team, err := a.Srv().Store().Team().Get(mentioned.TeamId)
if err != nil {
c.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(c, post.UserId, mentioned); ok {
team, err := a.Srv().Store().Team().Get(mentioned.TeamId)
if err != nil {
c.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,
}
}
}
}
@@ -536,7 +548,12 @@ func (a *App) FillInPostProps(c request.CTX, post *model.Post, channel *model.Ch
}
matched := atMentionPattern.MatchString(post.Message)
if a.Srv().License() != nil && *a.Srv().License().Features.LDAPGroups && matched && !a.HasPermissionToChannel(c, post.UserId, post.ChannelId, model.PermissionUseGroupMentions) {
shouldAddProp := false
if a.Srv().License() != nil && *a.Srv().License().Features.LDAPGroups && matched {
hasPermission, _ := a.HasPermissionToChannel(c, post.UserId, post.ChannelId, model.PermissionUseGroupMentions)
shouldAddProp = !hasPermission
}
if shouldAddProp {
post.AddProp(model.PostPropsGroupHighlightDisabled, true)
}
@@ -593,7 +610,7 @@ func (a *App) handlePostEvents(c request.CTX, post *model.Post, user *model.User
return nil
}
func (a *App) SendEphemeralPost(c request.CTX, userID string, post *model.Post) *model.Post {
func (a *App) SendEphemeralPost(c 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
@@ -612,7 +629,7 @@ func (a *App) SendEphemeralPost(c request.CTX, userID string, post *model.Post)
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false, true)
post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
sanitizedPost, appErr := a.SanitizePostMetadataForUser(c, post, userID)
sanitizedPost, isMemberForPreviews, appErr := a.SanitizePostMetadataForUser(c, post, userID)
if appErr != nil {
c.Logger().Error("Failed to sanitize post metadata for user", mlog.String("user_id", userID), mlog.Err(appErr))
@@ -630,10 +647,10 @@ func (a *App) SendEphemeralPost(c request.CTX, userID string, post *model.Post)
message.Add("post", postJSON)
a.Publish(message)
return post
return post, isMemberForPreviews
}
func (a *App) UpdateEphemeralPost(c request.CTX, userID string, post *model.Post) *model.Post {
func (a *App) UpdateEphemeralPost(c request.CTX, userID string, post *model.Post) (*model.Post, bool) {
post.Type = model.PostTypeEphemeral
post.UpdateAt = model.GetMillis()
@@ -646,7 +663,7 @@ func (a *App) UpdateEphemeralPost(c request.CTX, userID string, post *model.Post
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false, true)
post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
sanitizedPost, appErr := a.SanitizePostMetadataForUser(c, post, userID)
sanitizedPost, isMemberForPreviews, appErr := a.SanitizePostMetadataForUser(c, post, userID)
if appErr != nil {
c.Logger().Error("Failed to sanitize post metadata for user", mlog.String("user_id", userID), mlog.Err(appErr))
@@ -664,7 +681,7 @@ func (a *App) UpdateEphemeralPost(c request.CTX, userID string, post *model.Post
message.Add("post", postJSON)
a.Publish(message)
return post
return post, isMemberForPreviews
}
func (a *App) DeleteEphemeralPost(rctx request.CTX, userID, postID string) {
@@ -685,7 +702,7 @@ func (a *App) DeleteEphemeralPost(rctx request.CTX, userID, postID string) {
a.Publish(message)
}
func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, *model.AppError) {
func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updatePostOptions *model.UpdatePostOptions) (*model.Post, bool, *model.AppError) {
if updatePostOptions == nil {
updatePostOptions = model.DefaultUpdatePostOptions()
}
@@ -698,11 +715,11 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
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]
@@ -710,26 +727,26 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
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.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(c, 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)
}
newPost := oldPost.Clone()
@@ -748,7 +765,7 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
var fileIds []string
fileIds, appErr = a.processPostFileChanges(c, receivedUpdatedPost, oldPost, updatePostOptions)
if appErr != nil {
return nil, appErr
return nil, false, appErr
}
newPost.FileIds = fileIds
}
@@ -759,7 +776,7 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
}
if appErr = a.FillInPostProps(c, newPost, nil); appErr != nil {
return nil, appErr
return nil, false, appErr
}
if receivedUpdatedPost.IsRemote() {
@@ -773,7 +790,7 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
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)
}
// Always use incoming metadata when provided, otherwise retain existing
if receivedUpdatedPost.Metadata != nil {
@@ -788,9 +805,9 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
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)
}
}
@@ -812,20 +829,20 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
rpost, nErr = a.addPostPreviewProp(c, 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(c, rpost, message)
if appErr != nil {
return nil, appErr
return nil, false, appErr
}
a.invalidateCacheForChannelPosts(rpost.ChannelId)
userID := c.Session().UserId
sanitizedPost, appErr := a.SanitizePostMetadataForUser(c, rpost, userID)
sanitizedPost, isMemberForPreviews, appErr := a.SanitizePostMetadataForUser(c, rpost, userID)
if appErr != nil {
mlog.Error("Failed to sanitize post metadata for user", mlog.String("user_id", userID), mlog.Err(appErr))
@@ -836,7 +853,7 @@ func (a *App) UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, updateP
}
rpost = sanitizedPost
return rpost, nil
return rpost, isMemberForPreviews, nil
}
func (a *App) publishWebsocketEventForPost(rctx request.CTX, post *model.Post, message *model.WebSocketEvent) *model.AppError {
@@ -945,7 +962,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})
}
@@ -954,39 +973,39 @@ func (a *App) setupBroadcastHookForPermalink(rctx request.CTX, post *model.Post,
return nil
}
func (a *App) PatchPost(c request.CTX, postID string, patch *model.PostPatch, patchPostOptions *model.UpdatePostOptions) (*model.Post, *model.AppError) {
func (a *App) PatchPost(c 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(c, postID, false)
if err != nil {
return nil, err
return nil, false, err
}
channel, err := a.GetChannel(c, 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
}
if !a.HasPermissionToChannel(c, post.UserId, post.ChannelId, model.PermissionUseChannelMentions) {
if ok, _ := a.HasPermissionToChannel(c, post.UserId, post.ChannelId, model.PermissionUseChannelMentions); !ok {
patch.DisableMentionHighlights()
}
post.Patch(patch)
patchPostOptions.SafeUpdate = false
updatedPost, err := a.UpdatePost(c, post, patchPostOptions)
updatedPost, isMemberForPreviews, err := a.UpdatePost(c, post, patchPostOptions)
if err != nil {
return nil, err
return nil, false, err
}
return updatedPost, nil
return updatedPost, isMemberForPreviews, nil
}
func (a *App) GetPostsPage(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
@@ -1715,13 +1734,13 @@ func (a *App) SearchPostsInTeam(teamID string, paramsList []*model.SearchParams)
})
}
func (a *App) SearchPostsForUser(c 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(c 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)
includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels
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{}
@@ -1748,7 +1767,7 @@ func (a *App) SearchPostsForUser(c request.CTX, terms string, userID string, tea
// 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(c, finalParamsList, userID, teamID, page, perPage)
@@ -1756,26 +1775,27 @@ func (a *App) SearchPostsForUser(c request.CTX, terms string, userID string, tea
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(c, postSearchResults.PostList, userID); appErr != nil {
return nil, appErr
allPostHaveMembership, appErr := a.FilterPostsByChannelPermissions(c, postSearchResults.PostList, userID)
if appErr != nil {
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)
@@ -1789,7 +1809,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
@@ -1799,6 +1819,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]
@@ -1809,10 +1830,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] {
@@ -1824,7 +1849,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) {
@@ -2176,28 +2201,29 @@ func (a *App) GetThreadMembershipsForUser(userID, teamID string) ([]*model.Threa
return a.Srv().Store().Thread().GetMembershipsForUser(userID, teamID)
}
func (a *App) GetPostIfAuthorized(c request.CTX, postID string, session *model.Session, includeDeleted bool) (*model.Post, *model.AppError) {
func (a *App) GetPostIfAuthorized(c request.CTX, postID string, session *model.Session, includeDeleted bool) (*model.Post, *model.AppError, bool) {
post, err := a.GetSinglePost(c, postID, includeDeleted)
if err != nil {
return nil, err
return nil, err, false
}
channel, err := a.GetChannel(c, post.ChannelId)
if err != nil {
return nil, err
return nil, err, false
}
if !a.SessionHasPermissionToReadChannel(c, *session, channel) {
ok, isMember := a.SessionHasPermissionToReadChannel(c, *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.
@@ -2380,7 +2406,7 @@ 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))
}
}
@@ -2448,9 +2474,9 @@ func (a *App) GetPostInfo(c request.CTX, postID string) (*model.PostInfo, *model
if channel.Type == model.ChannelTypeOpen {
hasPermissionToAccessChannel = true
} else if channel.Type == model.ChannelTypePrivate {
hasPermissionToAccessChannel = a.HasPermissionToChannel(c, userID, channel.Id, model.PermissionManagePrivateChannelMembers)
hasPermissionToAccessChannel, _ = a.HasPermissionToChannel(c, userID, channel.Id, model.PermissionManagePrivateChannelMembers)
} else if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup {
hasPermissionToAccessChannel = a.HasPermissionToChannel(c, userID, channel.Id, model.PermissionReadChannelContent)
hasPermissionToAccessChannel, _ = a.HasPermissionToChannel(c, userID, channel.Id, model.PermissionReadChannelContent)
}
}
@@ -2567,7 +2593,7 @@ func (a *App) ValidateMoveOrCopy(c request.CTX, wpl *model.WranglerPostList, ori
return nil
}
func (a *App) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, *model.AppError) {
func (a *App) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, bool, *model.AppError) {
var appErr *model.AppError
var newRootPost *model.Post
@@ -2587,15 +2613,15 @@ func (a *App) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, t
for _, fileID := range post.FileIds {
oldFileInfo, appErr = a.GetFileInfo(c, fileID)
if appErr != nil {
return nil, appErr
return nil, false, appErr
}
fileBytes, appErr = a.GetFile(c, fileID)
if appErr != nil {
return nil, appErr
return nil, false, appErr
}
newFileInfo, appErr = a.UploadFile(c, fileBytes, targetChannel.Id, oldFileInfo.Name)
if appErr != nil {
return nil, appErr
return nil, false, appErr
}
newFileIDs = append(newFileIDs, newFileInfo.Id)
@@ -2605,6 +2631,8 @@ func (a *App) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, t
}
}
var isMemberForPreviews bool
for i, post := range wpl.Posts {
var reactions []*model.Reaction
@@ -2620,16 +2648,16 @@ func (a *App) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, t
newPost.ChannelId = targetChannel.Id
if i == 0 {
newPost, appErr = a.CreatePost(c, newPost, targetChannel, model.CreatePostFlags{})
newPost, isMemberForPreviews, appErr = a.CreatePost(c, 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(c, newPost, targetChannel, model.CreatePostFlags{})
newPost, _, appErr = a.CreatePost(c, newPost, targetChannel, model.CreatePostFlags{})
if appErr != nil {
return nil, appErr
return nil, false, appErr
}
}
@@ -2643,7 +2671,7 @@ func (a *App) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, t
}
}
return newRootPost, nil
return newRootPost, isMemberForPreviews, nil
}
func (a *App) MoveThread(c request.CTX, postID string, sourceChannelID, channelID string, user *model.User) *model.AppError {
@@ -2689,7 +2717,7 @@ func (a *App) MoveThread(c request.CTX, postID string, sourceChannelID, channelI
// 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(c, wpl, targetChannel)
newRootPost, _, appErr := a.CopyWranglerPostlist(c, wpl, targetChannel)
if appErr != nil {
return appErr
}
@@ -2702,7 +2730,7 @@ func (a *App) MoveThread(c request.CTX, postID string, sourceChannelID, channelI
ephemeralPostProps := model.StringInterface{
"TranslationID": "app.post.move_thread.from_another_channel",
}
_, appErr = a.CreatePost(c, &model.Post{
_, _, appErr = a.CreatePost(c, &model.Post{
UserId: user.Id,
Type: model.PostTypeWrangler,
RootId: newRootPost.Id,
@@ -2750,7 +2778,7 @@ func (a *App) MoveThread(c request.CTX, postID string, sourceChannelID, channelI
ephemeralPostProps["NumMessages"] = wpl.NumPosts()
_, appErr = a.CreatePost(c, &model.Post{
_, _, appErr = a.CreatePost(c, &model.Post{
UserId: user.Id,
Type: model.PostTypeWrangler,
ChannelId: originalChannel.Id,
@@ -2871,7 +2899,8 @@ func (a *App) SendTestMessage(c request.CTX, userID string) (*model.Post, *model
UserId: bot.UserId,
}
post, err = a.CreatePost(c, 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(c, 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)
}