2025-07-22 05:27:37 -04:00
|
|
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
|
|
|
// See LICENSE.txt for license information.
|
|
|
|
|
|
|
|
|
|
package api4
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
2025-10-02 10:54:29 -04:00
|
|
|
"slices"
|
2025-10-13 23:36:23 -04:00
|
|
|
"strings"
|
2025-07-22 05:27:37 -04:00
|
|
|
|
|
|
|
|
"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
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 19:45:40 -05:00
|
|
|
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("/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)
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
api.BaseRoutes.ContentFlagging.Handle("/config", api.APISessionRequired(saveContentFlaggingSettings)).Methods(http.MethodPut)
|
|
|
|
|
api.BaseRoutes.ContentFlagging.Handle("/config", api.APISessionRequired(getContentFlaggingSettings)).Methods(http.MethodGet)
|
2025-07-22 05:27:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
func requireContentFlaggingAvailable(c *Context) {
|
2025-07-22 05:27:37 -04:00
|
|
|
if !model.MinimumEnterpriseAdvancedLicense(c.App.License()) {
|
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
|
|
|
c.Err = model.NewAppError("requireContentFlaggingEnabled", "api.data_spillage.error.license", nil, "", http.StatusNotImplemented)
|
2025-07-22 05:27:37 -04:00
|
|
|
return
|
|
|
|
|
}
|
2025-10-13 02:54:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func requireContentFlaggingEnabled(c *Context) {
|
|
|
|
|
requireContentFlaggingAvailable(c)
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-22 05:27:37 -04:00
|
|
|
|
|
|
|
|
contentFlaggingEnabled := c.App.Config().ContentFlaggingSettings.EnableContentFlagging
|
|
|
|
|
if contentFlaggingEnabled == nil || !*contentFlaggingEnabled {
|
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
|
|
|
c.Err = model.NewAppError("requireContentFlaggingEnabled", "api.data_spillage.error.disabled", nil, "", http.StatusNotImplemented)
|
2025-07-22 05:27:37 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 19:45:40 -05:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 02:37:30 -04:00
|
|
|
func requireTeamContentReviewer(c *Context, userId, teamId string) {
|
|
|
|
|
isReviewer, appErr := c.App.IsUserTeamContentReviewer(userId, teamId)
|
|
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !isReviewer {
|
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
|
|
|
c.Err = model.NewAppError("requireTeamContentReviewer", "api.data_spillage.error.user_not_reviewer", nil, "", http.StatusForbidden)
|
2025-10-15 02:37:30 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func requireFlaggedPost(c *Context, postId string) {
|
|
|
|
|
if postId == "" {
|
|
|
|
|
c.SetInvalidParam("flagged_post_id")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 23:39:36 -05:00
|
|
|
_, appErr := c.App.GetPostContentFlaggingPropertyValue(postId, app.ContentFlaggingPropertyNameStatus)
|
2025-10-15 02:37:30 -04:00
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-22 05:27:37 -04:00
|
|
|
func getFlaggingConfiguration(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
// A team ID is expected to be specified by a content reviewer.
|
2025-10-02 10:54:29 -04:00
|
|
|
// 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 != "" {
|
2025-10-15 02:37:30 -04:00
|
|
|
requireTeamContentReviewer(c, c.AppContext.Session().UserId, teamId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
asReviewer = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config := getFlaggingConfig(c.App.Config().ContentFlaggingSettings, asReviewer)
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
if err := json.NewEncoder(w).Encode(config); err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
2025-10-13 02:54:01 -04:00
|
|
|
c.Err = model.NewAppError("getFlaggingConfiguration", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
2025-10-02 10:54:29 -04:00
|
|
|
}
|
2025-07-22 05:27:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
enabled, appErr := c.App.ContentFlaggingEnabledForTeam(teamID)
|
|
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-22 05:27:37 -04:00
|
|
|
|
|
|
|
|
payload := map[string]bool{
|
|
|
|
|
"enabled": enabled,
|
|
|
|
|
}
|
2025-10-02 10:54:29 -04:00
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
2025-10-13 02:54:01 -04:00
|
|
|
c.Err = model.NewAppError("getTeamPostFlaggingFeatureStatus", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
2025-10-02 10:54:29 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2026-01-20 04:38:27 -05:00
|
|
|
post, appErr, _ := c.App.GetPostIfAuthorized(c.AppContext, postId, c.AppContext.Session(), false)
|
2025-10-02 10:54:29 -04:00
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
2025-07-22 05:27:37 -04:00
|
|
|
return
|
|
|
|
|
}
|
2025-10-02 10:54:29 -04:00
|
|
|
|
2025-12-22 01:55:54 -05:00
|
|
|
checkPostTypeFlaggable(c, post)
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 10:54:29 -04:00
|
|
|
channel, appErr := c.App.GetChannel(c.AppContext, post.ChannelId)
|
|
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
enabled, appErr := c.App.ContentFlaggingEnabledForTeam(channel.TeamId)
|
|
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 10:54:29 -04:00
|
|
|
if !enabled {
|
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
|
|
|
c.Err = model.NewAppError("flagPost", "api.data_spillage.error.not_available_on_team", nil, "", http.StatusBadRequest)
|
2025-10-02 10:54:29 -04:00
|
|
|
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)
|
2025-07-22 05:27:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-02 10:54:29 -04:00
|
|
|
func getFlaggingConfig(contentFlaggingSettings model.ContentFlaggingSettings, asReviewer bool) *model.ContentFlaggingReportingConfig {
|
|
|
|
|
config := &model.ContentFlaggingReportingConfig{
|
2025-07-22 05:27:37 -04:00
|
|
|
Reasons: contentFlaggingSettings.AdditionalSettings.Reasons,
|
|
|
|
|
ReporterCommentRequired: contentFlaggingSettings.AdditionalSettings.ReporterCommentRequired,
|
2025-10-02 10:54:29 -04:00
|
|
|
ReviewerCommentRequired: contentFlaggingSettings.AdditionalSettings.ReviewerCommentRequired,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if asReviewer {
|
|
|
|
|
config.NotifyReporterOnRemoval = model.NewPointer(slices.Contains(contentFlaggingSettings.NotificationSettings.EventTargetMapping[model.EventContentRemoved], model.TargetReporter))
|
|
|
|
|
|
|
|
|
|
config.NotifyReporterOnDismissal = model.NewPointer(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
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 17:03:43 -04:00
|
|
|
groupId, err := c.App.ContentFlaggingGroupId()
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.Err = model.NewAppError("getContentFlaggingGroupId", "app.data_spillage.get_group.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
2025-10-02 10:54:29 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mappedFields, appErr := c.App.GetContentFlaggingMappedFields(groupId)
|
|
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
if err := json.NewEncoder(w).Encode(mappedFields); err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
2025-10-13 02:54:01 -04:00
|
|
|
c.Err = model.NewAppError("getContentFlaggingFields", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
2025-10-02 10:54:29 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userId := c.AppContext.Session().UserId
|
2025-10-15 02:37:30 -04:00
|
|
|
requireTeamContentReviewer(c, userId, channel.TeamId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
propertyValues, appErr := c.App.GetPostContentFlaggingPropertyValues(postId)
|
|
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 02:54:01 -04:00
|
|
|
if err := json.NewEncoder(w).Encode(propertyValues); err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
2025-10-13 02:54:01 -04:00
|
|
|
c.Err = model.NewAppError("getPostPropertyValues", "api.encoding_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
2025-10-02 10:54:29 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2025-07-22 05:27:37 -04:00
|
|
|
}
|
2025-10-02 10:54:29 -04:00
|
|
|
|
2025-10-15 02:37:30 -04:00
|
|
|
requireTeamContentReviewer(c, userId, channel.TeamId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This validates that the post is flagged
|
2025-10-15 02:37:30 -04:00
|
|
|
requireFlaggedPost(c, postId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-21 05:08:34 -04:00
|
|
|
post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, &model.PreparePostForClientOpts{IncludePriority: true, RetainContent: true, IncludeDeleted: true})
|
2026-01-20 04:38:27 -05:00
|
|
|
post, isMemberForPreviews, err := c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId)
|
2025-10-02 10:54:29 -04:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 04:38:27 -05:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 10:54:29 -04:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 02:37:30 -04:00
|
|
|
requireTeamContentReviewer(c, userId, channel.TeamId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-02 10:54:29 -04:00
|
|
|
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
|
2025-07-22 05:27:37 -04:00
|
|
|
}
|
2025-10-13 02:54:01 -04:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-13 23:36:23 -04:00
|
|
|
|
|
|
|
|
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"))
|
|
|
|
|
|
2025-10-15 02:37:30 -04:00
|
|
|
requireTeamContentReviewer(c, userId, teamId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-13 23:36:23 -04:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assignedBy := c.AppContext.Session().UserId
|
2025-10-15 02:37:30 -04:00
|
|
|
requireTeamContentReviewer(c, assignedBy, channel.TeamId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-13 23:36:23 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reviewerId := c.Params.ContentReviewerId
|
2025-10-15 02:37:30 -04:00
|
|
|
requireTeamContentReviewer(c, reviewerId, channel.TeamId)
|
|
|
|
|
if c.Err != nil {
|
2025-10-13 23:36:23 -04:00
|
|
|
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)
|
|
|
|
|
}
|
2025-12-22 01:55:54 -05:00
|
|
|
|
|
|
|
|
func checkPostTypeFlaggable(c *Context, post *model.Post) {
|
|
|
|
|
if post.Type == model.PostTypeBurnOnRead || strings.HasPrefix(post.Type, model.PostSystemMessagePrefix) {
|
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
|
|
|
c.Err = model.NewAppError("checkPostTypeFlaggable", "api.data_spillage.error.invalid_post_type", map[string]any{"PostType": post.Type}, "", http.StatusBadRequest)
|
2025-12-22 01:55:54 -05:00
|
|
|
}
|
|
|
|
|
}
|