Files
team/server/channels/app/scheduled_post.go
b2493928b7 Guest cannot add file to post without upload_file permission (#34538)
* Guest cannot add file to post without upload_file permission

* Move checks to api layer, addd checks in update patch post scheduled post

* Minor

* Linter fixes

* i18n translations

* removed the duplicated check from scheduled_post app layer

* Move scheduled post permission test from app layer to API layer

The permission check for updating scheduled posts belonging to other
users was moved from the app layer to the API layer in the PR. This
commit moves the corresponding test to the API layer to match.

* Move scheduled post delete permission check to API layer

Move the permission check for deleting scheduled posts from the app
layer to the API layer, consistent with update permission check.
Also enhance API tests to verify posts aren't modified after forbidden
operations.

* Fix inconsistent status code for non-existent scheduled post

Return StatusNotFound instead of StatusInternalServerError when a
scheduled post doesn't exist in UpdateScheduledPost, matching the
API layer behavior.

* Fix flaky TestAddUserToChannelCreatesChannelMemberHistoryRecord test

Use ElementsMatch instead of Equal to compare user ID slices since the
order returned from GetUsersInChannelDuring is not guaranteed.

---------

Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Jesse Hallam <[email protected]>
2026-01-07 10:40:05 -04:00

131 lines
5.9 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"encoding/json"
"net/http"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
)
func (a *App) SaveScheduledPost(rctx request.CTX, scheduledPost *model.ScheduledPost, connectionId string) (*model.ScheduledPost, *model.AppError) {
maxMessageLength := a.Srv().Store().ScheduledPost().GetMaxMessageSize()
scheduledPost.PreSave()
if validationErr := scheduledPost.IsValid(maxMessageLength); validationErr != nil {
return nil, validationErr
}
// validate the channel is not archived
channel, appErr := a.GetChannel(rctx, scheduledPost.ChannelId)
if appErr != nil {
return nil, appErr
}
restrictDM, appErr := a.CheckIfChannelIsRestrictedDM(rctx, channel)
if appErr != nil {
return nil, appErr
}
if restrictDM {
err := model.NewAppError("App.scheduledPostPreSaveChecks", "app.save_scheduled_post.restricted_dm.error", nil, "", http.StatusBadRequest)
return nil, err
}
if channel.DeleteAt > 0 {
return nil, model.NewAppError("App.scheduledPostPreSaveChecks", "app.save_scheduled_post.channel_deleted.app_error", map[string]any{"user_id": scheduledPost.UserId, "channel_id": scheduledPost.ChannelId}, "", http.StatusBadRequest)
}
savedScheduledPost, err := a.Srv().Store().ScheduledPost().CreateScheduledPost(scheduledPost)
if err != nil {
return nil, model.NewAppError("App.ScheduledPost", "app.save_scheduled_post.save.app_error", map[string]any{"user_id": scheduledPost.UserId, "channel_id": scheduledPost.ChannelId}, "", http.StatusBadRequest).Wrap(err)
}
a.PublishScheduledPostEvent(rctx, model.WebsocketScheduledPostCreated, savedScheduledPost, connectionId)
return savedScheduledPost, nil
}
func (a *App) GetUserTeamScheduledPosts(rctx request.CTX, userId, teamId string) ([]*model.ScheduledPost, *model.AppError) {
scheduledPosts, err := a.Srv().Store().ScheduledPost().GetScheduledPostsForUser(userId, teamId)
if err != nil {
return nil, model.NewAppError("App.GetUserTeamScheduledPosts", "app.get_user_team_scheduled_posts.error", map[string]any{"user_id": userId, "team_id": teamId}, "", http.StatusInternalServerError).Wrap(err)
}
if scheduledPosts == nil {
scheduledPosts = []*model.ScheduledPost{}
}
for _, scheduledPost := range scheduledPosts {
a.prepareDraftWithFileInfos(rctx, userId, &scheduledPost.Draft)
}
return scheduledPosts, nil
}
func (a *App) UpdateScheduledPost(rctx request.CTX, userId string, scheduledPost *model.ScheduledPost, connectionId string) (*model.ScheduledPost, *model.AppError) {
maxMessageLength := a.Srv().Store().ScheduledPost().GetMaxMessageSize()
scheduledPost.PreUpdate()
if validationErr := scheduledPost.IsValid(maxMessageLength); validationErr != nil {
return nil, validationErr
}
existingScheduledPost, err := a.Srv().Store().ScheduledPost().Get(scheduledPost.Id)
if err != nil {
return nil, model.NewAppError("app.UpdateScheduledPost", "app.update_scheduled_post.get_scheduled_post.error", map[string]any{"user_id": userId, "scheduled_post_id": scheduledPost.Id}, "", http.StatusInternalServerError).Wrap(err)
}
if existingScheduledPost == nil {
return nil, model.NewAppError("app.UpdateScheduledPost", "app.update_scheduled_post.existing_scheduled_post.not_exist", map[string]any{"user_id": userId, "scheduled_post_id": scheduledPost.Id}, "", http.StatusNotFound)
}
// This step is not required for update but is useful as we want to return the
// updated scheduled post. It's better to do this before calling update than after.
scheduledPost.RestoreNonUpdatableFields(existingScheduledPost)
if err := a.Srv().Store().ScheduledPost().UpdatedScheduledPost(scheduledPost); err != nil {
return nil, model.NewAppError("app.UpdateScheduledPost", "app.update_scheduled_post.update.error", map[string]any{"user_id": userId, "scheduled_post_id": scheduledPost.Id}, "", http.StatusInternalServerError).Wrap(err)
}
a.PublishScheduledPostEvent(rctx, model.WebsocketScheduledPostUpdated, scheduledPost, connectionId)
return scheduledPost, nil
}
func (a *App) DeleteScheduledPost(rctx request.CTX, userId, scheduledPostId, connectionId string) (*model.ScheduledPost, *model.AppError) {
scheduledPost, err := a.Srv().Store().ScheduledPost().Get(scheduledPostId)
if err != nil {
return nil, model.NewAppError("app.DeleteScheduledPost", "app.delete_scheduled_post.get_scheduled_post.error", map[string]any{"user_id": userId, "scheduled_post_id": scheduledPostId}, "", http.StatusInternalServerError).Wrap(err)
}
if scheduledPost == nil {
return nil, model.NewAppError("app.DeleteScheduledPost", "app.delete_scheduled_post.existing_scheduled_post.not_exist", map[string]any{"user_id": userId, "scheduled_post_id": scheduledPostId}, "", http.StatusNotFound)
}
if err := a.Srv().Store().ScheduledPost().PermanentlyDeleteScheduledPosts([]string{scheduledPostId}); err != nil {
return nil, model.NewAppError("app.DeleteScheduledPost", "app.delete_scheduled_post.delete_error", map[string]any{"user_id": userId, "scheduled_post_id": scheduledPostId}, "", http.StatusInternalServerError).Wrap(err)
}
a.PublishScheduledPostEvent(rctx, model.WebsocketScheduledPostDeleted, scheduledPost, connectionId)
return scheduledPost, nil
}
func (a *App) PublishScheduledPostEvent(rctx request.CTX, eventType model.WebsocketEventType, scheduledPost *model.ScheduledPost, connectionId string) {
if scheduledPost == nil {
rctx.Logger().Warn("publishScheduledPostEvent called with nil scheduledPost")
return
}
message := model.NewWebSocketEvent(eventType, "", "", scheduledPost.UserId, nil, connectionId)
scheduledPostJSON, jsonErr := json.Marshal(scheduledPost)
if jsonErr != nil {
rctx.Logger().Warn("publishScheduledPostEvent - Failed to Marshal", mlog.Err(jsonErr))
return
}
message.Add("scheduledPost", string(scheduledPostJSON))
a.Publish(message)
}