// 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) if c.Err != nil { return } postCardTypeCheckWithContext(where, c, scheduledPost.Type) if c.Err != nil { return } // Validate burn-on-read restrictions for scheduled post post := &model.Post{ ChannelId: scheduledPost.ChannelId, UserId: scheduledPost.UserId, Type: scheduledPost.Type, } postBurnOnReadCheckWithContext(where, c, post, nil) } 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 ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), scheduledPost.ChannelId, model.PermissionUploadFile); !ok { 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 } }