snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
657
server/channels/api4/content_flagging.go
Normal file
657
server/channels/api4/content_flagging.go
Normal file
@@ -0,0 +1,657 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"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) InitContentFlagging() {
|
||||
if !api.srv.Config().FeatureFlags.ContentFlagging {
|
||||
return
|
||||
}
|
||||
|
||||
api.BaseRoutes.ContentFlagging.Handle("/flag/config", api.APISessionRequired(contentFlaggingRequired(getFlaggingConfiguration))).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/team/{team_id:[A-Za-z0-9]+}/status", api.APISessionRequired(contentFlaggingRequired(getTeamPostFlaggingFeatureStatus))).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}/flag", api.APISessionRequired(contentFlaggingRequired(flagPost))).Methods(http.MethodPost)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/fields", api.APISessionRequired(contentFlaggingRequired(getContentFlaggingFields))).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}/field_values", api.APISessionRequired(contentFlaggingRequired(getPostPropertyValues))).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}", api.APISessionRequired(contentFlaggingRequired(getFlaggedPost))).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}/remove", api.APISessionRequired(contentFlaggingRequired(removeFlaggedPost))).Methods(http.MethodPut)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}/keep", api.APISessionRequired(contentFlaggingRequired(keepFlaggedPost))).Methods(http.MethodPut)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}/report", api.APISessionRequired(contentFlaggingRequired(generateFlaggedPostReport))).Methods(http.MethodPost)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}/exposure_report", api.APISessionRequired(contentFlaggingRequired(generatePostExposureReport))).Methods(http.MethodPost)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/team/{team_id:[A-Za-z0-9]+}/reviewers/search", api.APISessionRequired(contentFlaggingRequired(searchReviewers))).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/post/{post_id:[A-Za-z0-9]+}/assign/{content_reviewer_id:[A-Za-z0-9]+}", api.APISessionRequired(contentFlaggingRequired(assignFlaggedPostReviewer))).Methods(http.MethodPost)
|
||||
|
||||
api.BaseRoutes.ContentFlagging.Handle("/config", api.APISessionRequired(saveContentFlaggingSettings)).Methods(http.MethodPut)
|
||||
api.BaseRoutes.ContentFlagging.Handle("/config", api.APISessionRequired(getContentFlaggingSettings)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func requireContentFlaggingAvailable(c *Context) {
|
||||
if !model.MinimumEnterpriseAdvancedLicense(c.App.License()) {
|
||||
c.Err = model.NewAppError("requireContentFlaggingEnabled", "api.data_spillage.error.license", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func requireContentFlaggingEnabled(c *Context) {
|
||||
requireContentFlaggingAvailable(c)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
contentFlaggingEnabled := c.App.Config().ContentFlaggingSettings.EnableContentFlagging
|
||||
if contentFlaggingEnabled == nil || !*contentFlaggingEnabled {
|
||||
c.Err = model.NewAppError("requireContentFlaggingEnabled", "api.data_spillage.error.disabled", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func contentFlaggingRequired(h handlerFunc) handlerFunc {
|
||||
return func(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
requireContentFlaggingEnabled(c)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h(c, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func requireTeamContentReviewer(c *Context, userId, teamId string) {
|
||||
isReviewer, appErr := c.App.IsUserTeamContentReviewer(userId, teamId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if !isReviewer {
|
||||
c.Err = model.NewAppError("requireTeamContentReviewer", "api.data_spillage.error.user_not_reviewer", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func requireFlaggedPost(c *Context, postId string) {
|
||||
if postId == "" {
|
||||
c.SetInvalidParam("flagged_post_id")
|
||||
return
|
||||
}
|
||||
|
||||
_, appErr := c.App.GetPostContentFlaggingPropertyValue(c.AppContext, postId, app.ContentFlaggingPropertyNameStatus)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getFlaggingConfiguration(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// A team ID is expected to be specified by a content reviewer.
|
||||
// When specified, we verify that the user is a content reviewer of the team.
|
||||
// If the user is indeed a content reviewer, we return the configuration along with some extra fields
|
||||
// that only a reviewer should be aware of.
|
||||
// If no team ID is specified, we return the configuration as is, without the extra fields.
|
||||
// This is the expected usage for non-reviewers.
|
||||
teamId := r.URL.Query().Get("team_id")
|
||||
asReviewer := false
|
||||
if teamId != "" {
|
||||
requireTeamContentReviewer(c, c.AppContext.Session().UserId, teamId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
asReviewer = true
|
||||
}
|
||||
|
||||
config := getFlaggingConfig(c.App.Config().ContentFlaggingSettings, asReviewer)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(config); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
c.Err = model.NewAppError("getFlaggingConfiguration", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getTeamPostFlaggingFeatureStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireTeamId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
teamID := c.Params.TeamId
|
||||
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamID, model.PermissionViewTeam) {
|
||||
c.SetPermissionError(model.PermissionViewTeam)
|
||||
return
|
||||
}
|
||||
|
||||
enabled, appErr := c.App.ContentFlaggingEnabledForTeam(teamID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
payload := map[string]bool{
|
||||
"enabled": enabled,
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
c.Err = model.NewAppError("getTeamPostFlaggingFeatureStatus", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
func flagPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePostId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var flagRequest model.FlagContentRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&flagRequest); err != nil {
|
||||
c.SetInvalidParamWithErr("flagPost", err)
|
||||
return
|
||||
}
|
||||
|
||||
postId := c.Params.PostId
|
||||
userId := c.AppContext.Session().UserId
|
||||
|
||||
auditRec := c.MakeAuditRecord(model.AuditEventFlagPost, model.AuditStatusFail)
|
||||
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
|
||||
model.AddEventParameterToAuditRec(auditRec, "postId", postId)
|
||||
model.AddEventParameterToAuditRec(auditRec, "userId", userId)
|
||||
|
||||
post, appErr, _ := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
checkPostTypeFlaggable(c, post)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, post.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
checkChannelFlaggable(c, channel)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
enabled, appErr := c.App.ContentFlaggingEnabledForTeam(channel.TeamId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if !enabled {
|
||||
c.Err = model.NewAppError("flagPost", "api.data_spillage.error.not_available_on_team", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
appErr = c.App.FlagPost(c.AppContext, post, channel.TeamId, userId, flagRequest)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventObjectType("post")
|
||||
|
||||
writeOKResponse(w)
|
||||
}
|
||||
|
||||
func getFlaggingConfig(contentFlaggingSettings model.ContentFlaggingSettings, asReviewer bool) *model.ContentFlaggingReportingConfig {
|
||||
config := &model.ContentFlaggingReportingConfig{
|
||||
Reasons: contentFlaggingSettings.AdditionalSettings.Reasons,
|
||||
ReporterCommentRequired: contentFlaggingSettings.AdditionalSettings.ReporterCommentRequired,
|
||||
ReviewerCommentRequired: contentFlaggingSettings.AdditionalSettings.ReviewerCommentRequired,
|
||||
}
|
||||
|
||||
if asReviewer {
|
||||
config.NotifyReporterOnRemoval = new(slices.Contains(contentFlaggingSettings.NotificationSettings.EventTargetMapping[model.EventContentRemoved], model.TargetReporter))
|
||||
|
||||
config.NotifyReporterOnDismissal = new(slices.Contains(contentFlaggingSettings.NotificationSettings.EventTargetMapping[model.EventContentDismissed], model.TargetReporter))
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getContentFlaggingFields(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
groupId, err := c.App.ContentFlaggingGroupId()
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getContentFlaggingGroupId", "app.data_spillage.get_group.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
mappedFields, appErr := c.App.GetContentFlaggingMappedFields(c.AppContext, groupId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(mappedFields); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
c.Err = model.NewAppError("getContentFlaggingFields", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getPostPropertyValues(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePostId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// The requesting user must be a reviewer of the post's team
|
||||
// to be able to fetch the post's Content Flagging property values
|
||||
postId := c.Params.PostId
|
||||
post, appErr := c.App.GetSinglePost(c.AppContext, postId, true)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, post.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
checkChannelFlaggable(c, channel)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.AppContext.Session().UserId
|
||||
requireTeamContentReviewer(c, userId, channel.TeamId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
propertyValues, appErr := c.App.GetPostContentFlaggingPropertyValues(c.AppContext, postId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(propertyValues); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
c.Err = model.NewAppError("getPostPropertyValues", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getFlaggedPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePostId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// A user can obtain a flagged post if-
|
||||
// 1. The post is currently flagged and in any status
|
||||
// 2. The user is a reviewer of the post's team
|
||||
|
||||
// check if user is a reviewer of the post's team
|
||||
postId := c.Params.PostId
|
||||
userId := c.AppContext.Session().UserId
|
||||
|
||||
auditRec := c.MakeAuditRecord(model.AuditEventGetFlaggedPost, model.AuditStatusFail)
|
||||
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
|
||||
model.AddEventParameterToAuditRec(auditRec, "postId", postId)
|
||||
model.AddEventParameterToAuditRec(auditRec, "userId", userId)
|
||||
|
||||
post, appErr := c.App.GetSinglePost(c.AppContext, postId, true)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, post.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
checkChannelFlaggable(c, channel)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
requireTeamContentReviewer(c, userId, channel.TeamId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// This validates that the post is flagged
|
||||
requireFlaggedPost(c, postId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, &model.PreparePostForClientOpts{IncludePriority: true, RetainContent: true, IncludeDeleted: true})
|
||||
post, isMemberForPreviews, err := c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if err := post.EncodeJSON(w); err != nil {
|
||||
c.Err = model.NewAppError("getFlaggedPost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
c.App.RecordPostDelivery(c.AppContext, c.AppContext.Session().UserId, post, model.DeliveryMechanismProduct)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func removeFlaggedPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
actionRequest, userId, post := keepRemoveFlaggedPostChecks(c, r)
|
||||
if c.Err != nil {
|
||||
c.Err.Where = "removeFlaggedPost"
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord(model.AuditEventPermanentlyRemoveFlaggedPost, model.AuditStatusFail)
|
||||
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
|
||||
model.AddEventParameterToAuditRec(auditRec, "postId", post.Id)
|
||||
model.AddEventParameterToAuditRec(auditRec, "userId", userId)
|
||||
|
||||
if appErr := c.App.PermanentDeleteFlaggedPost(c.AppContext, actionRequest, userId, post); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
writeOKResponse(w)
|
||||
}
|
||||
|
||||
func keepFlaggedPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
actionRequest, userId, post := keepRemoveFlaggedPostChecks(c, r)
|
||||
if c.Err != nil {
|
||||
c.Err.Where = "keepFlaggedPost"
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord(model.AuditEventKeepFlaggedPost, model.AuditStatusFail)
|
||||
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
|
||||
model.AddEventParameterToAuditRec(auditRec, "postId", post.Id)
|
||||
model.AddEventParameterToAuditRec(auditRec, "userId", userId)
|
||||
|
||||
if appErr := c.App.KeepFlaggedPost(c.AppContext, actionRequest, userId, post); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
writeOKResponse(w)
|
||||
}
|
||||
|
||||
func keepRemoveFlaggedPostChecks(c *Context, r *http.Request) (*model.FlagContentActionRequest, string, *model.Post) {
|
||||
if c.Err != nil {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
c.RequirePostId()
|
||||
if c.Err != nil {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
var actionRequest model.FlagContentActionRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&actionRequest); err != nil {
|
||||
c.SetInvalidParamWithErr("flagContentActionRequestBody", err)
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
postId := c.Params.PostId
|
||||
userId := c.AppContext.Session().UserId
|
||||
|
||||
post, appErr := c.App.GetSinglePost(c.AppContext, postId, true)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, post.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
checkChannelFlaggable(c, channel)
|
||||
if c.Err != nil {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
requireTeamContentReviewer(c, userId, channel.TeamId)
|
||||
if c.Err != nil {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
commentRequired := c.App.Config().ContentFlaggingSettings.AdditionalSettings.ReviewerCommentRequired
|
||||
if err := actionRequest.IsValid(*commentRequired); err != nil {
|
||||
c.Err = err
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
return &actionRequest, userId, post
|
||||
}
|
||||
|
||||
func saveContentFlaggingSettings(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
requireContentFlaggingAvailable(c)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var config model.ContentFlaggingSettingsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
||||
c.SetInvalidParamWithErr("config", err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord(model.AuditEventUpdateContentFlaggingConfig, model.AuditStatusFail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
config.SetDefaults()
|
||||
if appErr := config.IsValid(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
appErr := c.App.SaveContentFlaggingConfig(config)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
writeOKResponse(w)
|
||||
}
|
||||
|
||||
func getContentFlaggingSettings(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
requireContentFlaggingAvailable(c)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
reviewerIDs, appErr := c.App.GetContentFlaggingConfigReviewerIDs()
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
config := c.App.Config().ContentFlaggingSettings
|
||||
|
||||
fullConfig := model.ContentFlaggingSettingsRequest{
|
||||
ReviewerSettings: &model.ReviewSettingsRequest{
|
||||
ReviewerSettings: *config.ReviewerSettings,
|
||||
ReviewerIDsSettings: *reviewerIDs,
|
||||
},
|
||||
ContentFlaggingSettingsBase: model.ContentFlaggingSettingsBase{
|
||||
EnableContentFlagging: config.EnableContentFlagging,
|
||||
NotificationSettings: config.NotificationSettings,
|
||||
AdditionalSettings: config.AdditionalSettings,
|
||||
},
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(fullConfig); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
c.Err = model.NewAppError("getContentFlaggingSettings", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
func searchReviewers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireTeamId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
teamId := c.Params.TeamId
|
||||
userId := c.AppContext.Session().UserId
|
||||
searchTerm := strings.TrimSpace(r.URL.Query().Get("term"))
|
||||
|
||||
requireTeamContentReviewer(c, userId, teamId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reviewers, appErr := c.App.SearchReviewers(c.AppContext, searchTerm, teamId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(reviewers); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
c.Err = model.NewAppError("searchReviewers", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
func assignFlaggedPostReviewer(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePostId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireContentReviewerId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
postId := c.Params.PostId
|
||||
post, appErr := c.App.GetSinglePost(c.AppContext, postId, true)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
channel, appErr := c.App.GetChannel(c.AppContext, post.ChannelId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
checkChannelFlaggable(c, channel)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
assignedBy := c.AppContext.Session().UserId
|
||||
requireTeamContentReviewer(c, assignedBy, channel.TeamId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reviewerId := c.Params.ContentReviewerId
|
||||
requireTeamContentReviewer(c, reviewerId, channel.TeamId)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord(model.AuditEventSetReviewer, model.AuditStatusFail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
model.AddEventParameterToAuditRec(auditRec, "assigningUserId", assignedBy)
|
||||
model.AddEventParameterToAuditRec(auditRec, "reviewerUserId", reviewerId)
|
||||
|
||||
appErr = c.App.AssignFlaggedPostReviewer(c.AppContext, postId, channel.TeamId, reviewerId, assignedBy)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
writeOKResponse(w)
|
||||
}
|
||||
|
||||
func checkPostTypeFlaggable(c *Context, post *model.Post) {
|
||||
if post.Type == model.PostTypeBurnOnRead || strings.HasPrefix(post.Type, model.PostSystemMessagePrefix) {
|
||||
c.Err = model.NewAppError("checkPostTypeFlaggable", "api.data_spillage.error.invalid_post_type", map[string]any{"PostType": post.Type}, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func checkChannelFlaggable(c *Context, channel *model.Channel) {
|
||||
if channel.IsGroupOrDirect() {
|
||||
c.Err = model.NewAppError("checkChannelFlaggable", "api.data_spillage.error.invalid_channel_type", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user