mirror of
https://github.com/mattermost/mattermost.git
synced 2026-03-01 12:51:18 -05:00
* 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 <build@mattermost.com> Co-authored-by: Jesse Hallam <jesse@mattermost.com>
276 lines
8.7 KiB
Go
276 lines
8.7 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
)
|
|
|
|
func (api *API) InitScheduledPost() {
|
|
api.BaseRoutes.Posts.Handle("/schedule", api.APISessionRequired(createSchedulePost)).Methods(http.MethodPost)
|
|
api.BaseRoutes.Posts.Handle("/schedule/{scheduled_post_id:[A-Za-z0-9]+}", api.APISessionRequired(updateScheduledPost)).Methods(http.MethodPut)
|
|
api.BaseRoutes.Posts.Handle("/schedule/{scheduled_post_id:[A-Za-z0-9]+}", api.APISessionRequired(deleteScheduledPost)).Methods(http.MethodDelete)
|
|
api.BaseRoutes.Posts.Handle("/scheduled/team/{team_id:[A-Za-z0-9]+}", api.APISessionRequired(getTeamScheduledPosts)).Methods(http.MethodGet)
|
|
}
|
|
|
|
func scheduledPostChecks(where string, c *Context, scheduledPost *model.ScheduledPost) {
|
|
// ***************************************************************
|
|
// NOTE - if you make any change here, please make sure to apply the
|
|
// same change for scheduled posts job as well in the `canPostScheduledPost()` function
|
|
// in app layer.
|
|
// ***************************************************************
|
|
|
|
userCreatePostPermissionCheckWithContext(c, scheduledPost.ChannelId)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
postHardenedModeCheckWithContext(where, c, scheduledPost.GetProps())
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
postPriorityCheckWithContext(where, c, scheduledPost.GetPriority(), scheduledPost.RootId)
|
|
}
|
|
|
|
func requireScheduledPostsEnabled(c *Context) {
|
|
if !*c.App.Srv().Config().ServiceSettings.ScheduledPosts {
|
|
c.Err = model.NewAppError("", "api.scheduled_posts.feature_disabled", nil, "", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if c.App.Channels().License() == nil {
|
|
c.Err = model.NewAppError("", "api.scheduled_posts.license_error", nil, "", http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
func createSchedulePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
requireScheduledPostsEnabled(c)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
connectionID := r.Header.Get(model.ConnectionId)
|
|
|
|
var scheduledPost model.ScheduledPost
|
|
if err := json.NewDecoder(r.Body).Decode(&scheduledPost); err != nil {
|
|
c.SetInvalidParamWithErr("schedule_post", err)
|
|
return
|
|
}
|
|
scheduledPost.UserId = c.AppContext.Session().UserId
|
|
scheduledPost.SanitizeInput()
|
|
|
|
auditRec := c.MakeAuditRecord(model.AuditEventCreateSchedulePost, model.AuditStatusFail)
|
|
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
|
|
model.AddEventParameterAuditableToAuditRec(auditRec, "scheduledPost", &scheduledPost)
|
|
|
|
if len(scheduledPost.FileIds) > 0 {
|
|
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), scheduledPost.ChannelId, model.PermissionUploadFile) {
|
|
c.SetPermissionError(model.PermissionUploadFile)
|
|
return
|
|
}
|
|
}
|
|
|
|
scheduledPostChecks("Api4.createSchedulePost", c, &scheduledPost)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
createdScheduledPost, appErr := c.App.SaveScheduledPost(c.AppContext, &scheduledPost, connectionID)
|
|
if appErr != nil {
|
|
c.Err = appErr
|
|
return
|
|
}
|
|
|
|
auditRec.Success()
|
|
auditRec.AddEventResultState(createdScheduledPost)
|
|
auditRec.AddEventObjectType("scheduledPost")
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
if err := json.NewEncoder(w).Encode(createdScheduledPost); err != nil {
|
|
mlog.Error("failed to encode scheduled post to return API response", mlog.Err(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
func getTeamScheduledPosts(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
requireScheduledPostsEnabled(c)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
c.RequireTeamId()
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionViewTeam) {
|
|
c.SetPermissionError(model.PermissionViewTeam)
|
|
return
|
|
}
|
|
|
|
teamId := c.Params.TeamId
|
|
userId := c.AppContext.Session().UserId
|
|
|
|
scheduledPosts, appErr := c.App.GetUserTeamScheduledPosts(c.AppContext, userId, teamId)
|
|
if appErr != nil {
|
|
c.Err = appErr
|
|
return
|
|
}
|
|
|
|
response := map[string][]*model.ScheduledPost{}
|
|
response[teamId] = scheduledPosts
|
|
|
|
if r.URL.Query().Get("includeDirectChannels") == "true" {
|
|
directChannelScheduledPosts, appErr := c.App.GetUserTeamScheduledPosts(c.AppContext, userId, "")
|
|
if appErr != nil {
|
|
c.Err = appErr
|
|
return
|
|
}
|
|
|
|
response["directChannels"] = directChannelScheduledPosts
|
|
}
|
|
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
mlog.Error("failed to encode scheduled posts to return API response", mlog.Err(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
func updateScheduledPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
requireScheduledPostsEnabled(c)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
connectionID := r.Header.Get(model.ConnectionId)
|
|
|
|
scheduledPostId := mux.Vars(r)["scheduled_post_id"]
|
|
if scheduledPostId == "" {
|
|
c.SetInvalidURLParam("scheduled_post_id")
|
|
return
|
|
}
|
|
|
|
var scheduledPost model.ScheduledPost
|
|
if err := json.NewDecoder(r.Body).Decode(&scheduledPost); err != nil {
|
|
c.SetInvalidParamWithErr("schedule_post", err)
|
|
return
|
|
}
|
|
|
|
if scheduledPost.Id != scheduledPostId {
|
|
c.SetInvalidURLParam("scheduled_post_id")
|
|
return
|
|
}
|
|
|
|
auditRec := c.MakeAuditRecord(model.AuditEventUpdateScheduledPost, model.AuditStatusFail)
|
|
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
|
|
model.AddEventParameterAuditableToAuditRec(auditRec, "scheduledPost", &scheduledPost)
|
|
|
|
userId := c.AppContext.Session().UserId
|
|
existingScheduledPost, err := c.App.Srv().Store().ScheduledPost().Get(scheduledPost.Id)
|
|
if err != nil {
|
|
c.Err = model.NewAppError("updateScheduledPost", "app.update_scheduled_post.get_scheduled_post.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
return
|
|
}
|
|
if existingScheduledPost == nil {
|
|
c.Err = model.NewAppError("updateScheduledPost", "app.update_scheduled_post.existing_scheduled_post.not_exist", nil, "", http.StatusNotFound)
|
|
return
|
|
}
|
|
if existingScheduledPost.UserId != userId {
|
|
c.Err = model.NewAppError("updateScheduledPost", "app.update_scheduled_post.update_permission.error", nil, "", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
if len(scheduledPost.FileIds) > 0 {
|
|
originalPost, err := existingScheduledPost.ToPost()
|
|
if err != nil {
|
|
c.Err = model.NewAppError("updateScheduledPost", "app.update_scheduled_post.convert_to_post.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
return
|
|
}
|
|
checkUploadFilePermissionForNewFiles(c, scheduledPost.FileIds, originalPost)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
scheduledPostChecks("Api4.updateScheduledPost", c, &scheduledPost)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
updatedScheduledPost, appErr := c.App.UpdateScheduledPost(c.AppContext, userId, &scheduledPost, connectionID)
|
|
if appErr != nil {
|
|
c.Err = appErr
|
|
return
|
|
}
|
|
|
|
auditRec.Success()
|
|
auditRec.AddEventResultState(updatedScheduledPost)
|
|
auditRec.AddEventObjectType("scheduledPost")
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
if err := json.NewEncoder(w).Encode(updatedScheduledPost); err != nil {
|
|
mlog.Error("failed to encode scheduled post to return API response", mlog.Err(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
func deleteScheduledPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
requireScheduledPostsEnabled(c)
|
|
if c.Err != nil {
|
|
return
|
|
}
|
|
|
|
scheduledPostId := mux.Vars(r)["scheduled_post_id"]
|
|
if scheduledPostId == "" {
|
|
c.SetInvalidURLParam("scheduled_post_id")
|
|
return
|
|
}
|
|
|
|
auditRec := c.MakeAuditRecord(model.AuditEventDeleteScheduledPost, model.AuditStatusFail)
|
|
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
|
|
model.AddEventParameterToAuditRec(auditRec, "scheduledPostId", scheduledPostId)
|
|
|
|
userId := c.AppContext.Session().UserId
|
|
|
|
existingScheduledPost, err := c.App.Srv().Store().ScheduledPost().Get(scheduledPostId)
|
|
if err != nil {
|
|
c.Err = model.NewAppError("deleteScheduledPost", "app.delete_scheduled_post.get_scheduled_post.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
return
|
|
}
|
|
if existingScheduledPost == nil {
|
|
c.Err = model.NewAppError("deleteScheduledPost", "app.delete_scheduled_post.existing_scheduled_post.not_exist", nil, "", http.StatusNotFound)
|
|
return
|
|
}
|
|
if existingScheduledPost.UserId != userId {
|
|
c.Err = model.NewAppError("deleteScheduledPost", "app.delete_scheduled_post.delete_permission.error", nil, "", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
connectionID := r.Header.Get(model.ConnectionId)
|
|
deletedScheduledPost, appErr := c.App.DeleteScheduledPost(c.AppContext, userId, scheduledPostId, connectionID)
|
|
if appErr != nil {
|
|
c.Err = appErr
|
|
return
|
|
}
|
|
|
|
auditRec.Success()
|
|
auditRec.AddEventResultState(deletedScheduledPost)
|
|
auditRec.AddEventObjectType("scheduledPost")
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
if err := json.NewEncoder(w).Encode(deletedScheduledPost); err != nil {
|
|
mlog.Error("failed to encode scheduled post to return API response", mlog.Err(err))
|
|
return
|
|
}
|
|
}
|