mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
Cherry-pick MM-66789: Restrict log downloads to a root path for support packets (#35164)
Some checks are pending
ESR Upgrade / Run ESR upgrade script from 5.37 to 7.8 (push) Waiting to run
ESR Upgrade / Run ESR upgrade script from 5.37 to 6.3 (push) Waiting to run
ESR Upgrade / Run ESR upgrade script from 6.3 to 7.8 (push) Waiting to run
Server CI Master / master-ci (push) Waiting to run
Web App CI Master / master-ci (push) Waiting to run
Some checks are pending
ESR Upgrade / Run ESR upgrade script from 5.37 to 7.8 (push) Waiting to run
ESR Upgrade / Run ESR upgrade script from 5.37 to 6.3 (push) Waiting to run
ESR Upgrade / Run ESR upgrade script from 6.3 to 7.8 (push) Waiting to run
Server CI Master / master-ci (push) Waiting to run
Web App CI Master / master-ci (push) Waiting to run
Automatic Merge
This commit is contained in:
parent
d2594e5046
commit
463f7a0511
13 changed files with 1050 additions and 7 deletions
|
|
@ -106,7 +106,14 @@ func setupTestHelper(tb testing.TB, dbStore store.Store, sqlSettings *model.SqlS
|
|||
consoleLevel = mlog.LvlStdLog.Name
|
||||
}
|
||||
*memoryConfig.LogSettings.ConsoleLevel = consoleLevel
|
||||
*memoryConfig.LogSettings.FileLocation = filepath.Join(tempWorkspace, "logs", "mattermost.log")
|
||||
// Use a subdirectory within the logging root (from MM_LOG_PATH or default)
|
||||
// to ensure the path is within the allowed logging root for security validation.
|
||||
// Each test gets its own subdirectory based on the tempWorkspace name for isolation.
|
||||
testLogsDir := filepath.Join(config.GetLogRootPath(), filepath.Base(tempWorkspace))
|
||||
err = os.MkdirAll(testLogsDir, 0700)
|
||||
require.NoError(tb, err, "failed to create test logs directory")
|
||||
*memoryConfig.LogSettings.FileLocation = testLogsDir
|
||||
*memoryConfig.NotificationLogSettings.FileLocation = testLogsDir
|
||||
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
||||
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
||||
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord(model.AuditEventGenerateSupportPacket, model.AuditStatusFail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
// We support the existing API hence the logs are always included
|
||||
// if nothing specified.
|
||||
includeLogs := true
|
||||
|
|
@ -99,6 +102,9 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
PluginPackets: r.Form["plugin_packets"],
|
||||
}
|
||||
|
||||
auditRec.AddMeta("include_logs", supportPacketOptions.IncludeLogs)
|
||||
auditRec.AddMeta("plugin_packets", supportPacketOptions.PluginPackets)
|
||||
|
||||
// Checking to see if the server has a e10 or e20 license (this feature is only permitted for servers with licenses)
|
||||
if c.App.Channels().License() == nil {
|
||||
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.no_license", nil, "", http.StatusForbidden)
|
||||
|
|
@ -132,6 +138,9 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
fileBytesReader := bytes.NewReader(fileBytes)
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("filename", outputZipFilename)
|
||||
|
||||
// Prevent caching so support packets are always fresh
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,13 @@ func setupTestHelper(dbStore store.Store, sqlStore *sqlstore.SqlStore, sqlSettin
|
|||
*memoryConfig.LogSettings.ConsoleLevel = mlog.LvlStdLog.Name
|
||||
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
||||
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
||||
*memoryConfig.LogSettings.FileLocation = filepath.Join(tempWorkspace, "logs", "mattermost.log")
|
||||
// Use a subdirectory within the logging root (from MM_LOG_PATH or default)
|
||||
// to ensure the path is within the allowed logging root for security validation.
|
||||
// Each test gets its own subdirectory based on the tempWorkspace name for isolation.
|
||||
testLogsDir := filepath.Join(config.GetLogRootPath(), filepath.Base(tempWorkspace))
|
||||
err = os.MkdirAll(testLogsDir, 0700)
|
||||
require.NoError(tb, err, "failed to create test logs directory")
|
||||
*memoryConfig.LogSettings.FileLocation = testLogsDir
|
||||
if updateConfig != nil {
|
||||
updateConfig(memoryConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,9 @@ func (ps *PlatformService) SaveConfig(newCfg *model.Config, sendConfigChangeClus
|
|||
}
|
||||
}
|
||||
|
||||
// Validate log file paths (logs errors for now, will block server startup in future version)
|
||||
config.WarnIfLogPathsOutsideRoot(newCfg)
|
||||
|
||||
oldCfg, newCfg, err := ps.configStore.Set(newCfg)
|
||||
if errors.Is(err, config.ErrReadOnlyConfiguration) {
|
||||
return nil, nil, model.NewAppError("saveConfig", "ent.cluster.save_config.error", nil, "", http.StatusForbidden).Wrap(err)
|
||||
|
|
|
|||
|
|
@ -215,12 +215,22 @@ func (ps *PlatformService) GetLogsSkipSend(rctx request.CTX, page, perPage int,
|
|||
return lines, nil
|
||||
}
|
||||
|
||||
func (ps *PlatformService) GetLogFile(_ request.CTX) (*model.FileData, error) {
|
||||
func (ps *PlatformService) GetLogFile(rctx request.CTX) (*model.FileData, error) {
|
||||
if !*ps.Config().LogSettings.EnableFile {
|
||||
return nil, errors.New("Unable to retrieve mattermost logs because LogSettings.EnableFile is set to false")
|
||||
}
|
||||
|
||||
mattermostLog := config.GetLogFileLocation(*ps.Config().LogSettings.FileLocation)
|
||||
|
||||
// Validate the file path to prevent arbitrary file reads
|
||||
if err := ps.validateLogFilePath(mattermostLog); err != nil {
|
||||
rctx.Logger().Error("Blocked attempt to read log file outside allowed root",
|
||||
mlog.String("path", mattermostLog),
|
||||
mlog.String("config_section", "LogSettings.FileLocation"),
|
||||
mlog.Err(err))
|
||||
return nil, errors.Wrapf(err, "log file path %s is outside allowed logging directory", mattermostLog)
|
||||
}
|
||||
|
||||
mattermostLogFileData, err := os.ReadFile(mattermostLog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed read mattermost log file at path %s", mattermostLog)
|
||||
|
|
@ -232,12 +242,22 @@ func (ps *PlatformService) GetLogFile(_ request.CTX) (*model.FileData, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (ps *PlatformService) GetNotificationLogFile(_ request.CTX) (*model.FileData, error) {
|
||||
func (ps *PlatformService) GetNotificationLogFile(rctx request.CTX) (*model.FileData, error) {
|
||||
if !*ps.Config().NotificationLogSettings.EnableFile {
|
||||
return nil, errors.New("Unable to retrieve notifications logs because NotificationLogSettings.EnableFile is set to false")
|
||||
}
|
||||
|
||||
notificationsLog := config.GetNotificationsLogFileLocation(*ps.Config().NotificationLogSettings.FileLocation)
|
||||
|
||||
// Validate the file path to prevent arbitrary file reads
|
||||
if err := ps.validateLogFilePath(notificationsLog); err != nil {
|
||||
rctx.Logger().Error("Blocked attempt to read log file outside allowed root",
|
||||
mlog.String("path", notificationsLog),
|
||||
mlog.String("config_section", "NotificationLogSettings.FileLocation"),
|
||||
mlog.Err(err))
|
||||
return nil, errors.Wrapf(err, "log file path %s is outside allowed logging directory", notificationsLog)
|
||||
}
|
||||
|
||||
notificationsLogFileData, err := os.ReadFile(notificationsLog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed read notifcation log file at path %s", notificationsLog)
|
||||
|
|
@ -249,12 +269,26 @@ func (ps *PlatformService) GetNotificationLogFile(_ request.CTX) (*model.FileDat
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (ps *PlatformService) GetAdvancedLogs(_ request.CTX) ([]*model.FileData, error) {
|
||||
// validateLogFilePath validates that a log file path is within the logging root directory.
|
||||
// This prevents arbitrary file read/write vulnerabilities in logging configuration.
|
||||
// The logging root is determined by MM_LOG_PATH environment variable or the default logs directory.
|
||||
// Currently used to validate paths when reading logs via GetAdvancedLogs.
|
||||
// In future versions, this will also be used to validate paths when saving logging config.
|
||||
func (ps *PlatformService) validateLogFilePath(filePath string) error {
|
||||
// Get the logging root path (from env var or default logs directory)
|
||||
loggingRoot := config.GetLogRootPath()
|
||||
|
||||
return config.ValidateLogFilePath(filePath, loggingRoot)
|
||||
}
|
||||
|
||||
func (ps *PlatformService) GetAdvancedLogs(rctx request.CTX) ([]*model.FileData, error) {
|
||||
var (
|
||||
rErr *multierror.Error
|
||||
ret []*model.FileData
|
||||
)
|
||||
|
||||
rctx.Logger().Debug("Advanced logs access requested")
|
||||
|
||||
for name, loggingJSON := range map[string]json.RawMessage{
|
||||
"LogSettings.AdvancedLoggingJSON": ps.Config().LogSettings.AdvancedLoggingJSON,
|
||||
"NotificationLogSettings.AdvancedLoggingJSON": ps.Config().NotificationLogSettings.AdvancedLoggingJSON,
|
||||
|
|
@ -282,6 +316,18 @@ func (ps *PlatformService) GetAdvancedLogs(_ request.CTX) ([]*model.FileData, er
|
|||
rErr = multierror.Append(rErr, errors.Wrapf(err, "error decoding file target options in %s", name))
|
||||
continue
|
||||
}
|
||||
|
||||
// Validate the file path to prevent arbitrary file reads
|
||||
if err := ps.validateLogFilePath(fileOption.Filename); err != nil {
|
||||
rctx.Logger().Error("Blocked attempt to read log file outside allowed root",
|
||||
mlog.String("path", fileOption.Filename),
|
||||
mlog.String("config_section", name),
|
||||
mlog.String("user_id", rctx.Session().UserId),
|
||||
mlog.Err(err))
|
||||
rErr = multierror.Append(rErr, errors.Wrapf(err, "log file path %s in %s is outside allowed logging directory", fileOption.Filename, name))
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(fileOption.Filename)
|
||||
if err != nil {
|
||||
rErr = multierror.Append(rErr, errors.Wrapf(err, "failed to read advanced log file at path %s in %s", fileOption.Filename, name))
|
||||
|
|
@ -296,7 +342,7 @@ func (ps *PlatformService) GetAdvancedLogs(_ request.CTX) ([]*model.FileData, er
|
|||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return ret, rErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func isLogFilteredByLevel(logFilter *model.LogFilter, entry *model.LogEntry) bool {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ func TestGetMattermostLog(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow log file reads from our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
// Enable log file but point to an empty directory to get an error trying to read the file
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.EnableFile = true
|
||||
|
|
@ -71,6 +74,33 @@ func TestGetMattermostLog(t *testing.T) {
|
|||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "mattermost.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
|
||||
// Test path validation: FileLocation outside MM_LOG_PATH should be blocked
|
||||
t.Run("path validation prevents reading files outside log directory", func(t *testing.T) {
|
||||
// Create a directory outside the allowed log root
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(outsideDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Create a file that would be read if validation fails
|
||||
outsideLogLocation := config.GetLogFileLocation(outsideDir)
|
||||
err = os.WriteFile(outsideLogLocation, []byte("secret data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set FileLocation to the outside directory (MM_LOG_PATH is still set to 'dir')
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.FileLocation = outsideDir
|
||||
})
|
||||
|
||||
// Should be blocked by path validation
|
||||
fileData, err = th.Service.GetLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "outside allowed logging directory")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetNotificationLogFile(t *testing.T) {
|
||||
|
|
@ -91,10 +121,20 @@ func TestGetNotificationLogFile(t *testing.T) {
|
|||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
// Disable file target before cleaning up to avoid a race between
|
||||
// removing the directory and the file getting written again.
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = false
|
||||
})
|
||||
th.Service.NotificationsLogger().Flush()
|
||||
|
||||
err = os.RemoveAll(dir)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow log file reads from our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
// Enable notifications file but point to an empty directory to get an error trying to read the file
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = true
|
||||
|
|
@ -118,6 +158,33 @@ func TestGetNotificationLogFile(t *testing.T) {
|
|||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "notifications.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
|
||||
// Test path validation: FileLocation outside MM_LOG_PATH should be blocked
|
||||
t.Run("path validation prevents reading files outside log directory", func(t *testing.T) {
|
||||
// Create a directory outside the allowed log root
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(outsideDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Create a file that would be read if validation fails
|
||||
outsideLogLocation := config.GetNotificationsLogFileLocation(outsideDir)
|
||||
err = os.WriteFile(outsideLogLocation, []byte("secret data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set FileLocation to the outside directory (MM_LOG_PATH is still set to 'dir')
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.FileLocation = outsideDir
|
||||
})
|
||||
|
||||
// Should be blocked by path validation
|
||||
fileData, err = th.Service.GetNotificationLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "outside allowed logging directory")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAdvancedLogs(t *testing.T) {
|
||||
|
|
@ -134,6 +201,9 @@ func TestGetAdvancedLogs(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow advanced logging to write to our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
// Setup log files for each setting
|
||||
optLDAP := map[string]string{
|
||||
"filename": path.Join(dir, "ldap.log"),
|
||||
|
|
@ -243,9 +313,10 @@ func TestGetAdvancedLogs(t *testing.T) {
|
|||
require.NotNil(t, notifFile)
|
||||
testlib.AssertLog(t, bytes.NewBuffer(notifFile.Body), mlog.LvlInfo.Name, "Some Notification")
|
||||
})
|
||||
// Disable AdvancedLoggingJSON
|
||||
// Disable AdvancedLoggingJSON for all log settings
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = nil
|
||||
c.NotificationLogSettings.AdvancedLoggingJSON = nil
|
||||
})
|
||||
t.Run("No logs returned when AdvancedLoggingJSON is empty", func(t *testing.T) {
|
||||
// Confirm no logs get returned
|
||||
|
|
@ -253,4 +324,119 @@ func TestGetAdvancedLogs(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, fileDatas, 0)
|
||||
})
|
||||
|
||||
t.Run("path validation prevents reading files outside log directory", func(t *testing.T) {
|
||||
// Create a temporary directory to use as the log root
|
||||
logDir, err := os.MkdirTemp("", "logs")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(logDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to restrict log file access to logDir
|
||||
t.Setenv("MM_LOG_PATH", logDir)
|
||||
|
||||
// Create a file outside the log directory that should not be accessible
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(outsideDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
secretFile := path.Join(outsideDir, "secret.txt")
|
||||
err = os.WriteFile(secretFile, []byte("secret data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a valid log file inside the log directory
|
||||
validLog := path.Join(logDir, "valid.log")
|
||||
err = os.WriteFile(validLog, []byte("valid log data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test 1: Attempt to read file outside log directory using absolute path
|
||||
optOutside := map[string]string{
|
||||
"filename": secretFile,
|
||||
}
|
||||
dataOutside, err := json.Marshal(optOutside)
|
||||
require.NoError(t, err)
|
||||
|
||||
logCfgOutside := mlog.LoggerConfiguration{
|
||||
"malicious": mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
Levels: []mlog.Level{mlog.LvlError},
|
||||
Options: dataOutside,
|
||||
},
|
||||
}
|
||||
logCfgDataOutside, err := json.Marshal(logCfgOutside)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = logCfgDataOutside
|
||||
})
|
||||
|
||||
fileDatas, err := th.Service.GetAdvancedLogs(th.Context)
|
||||
// Should return error indicating path is outside allowed directory
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "outside allowed logging directory")
|
||||
require.Len(t, fileDatas, 0)
|
||||
|
||||
// Test 2: Attempt path traversal attack
|
||||
traversalPath := path.Join(logDir, "..", "..", "etc", "passwd")
|
||||
optTraversal := map[string]string{
|
||||
"filename": traversalPath,
|
||||
}
|
||||
dataTraversal, err := json.Marshal(optTraversal)
|
||||
require.NoError(t, err)
|
||||
|
||||
logCfgTraversal := mlog.LoggerConfiguration{
|
||||
"traversal": mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
Levels: []mlog.Level{mlog.LvlError},
|
||||
Options: dataTraversal,
|
||||
},
|
||||
}
|
||||
logCfgDataTraversal, err := json.Marshal(logCfgTraversal)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = logCfgDataTraversal
|
||||
})
|
||||
|
||||
fileDatas, err = th.Service.GetAdvancedLogs(th.Context)
|
||||
// Should return error for path traversal attempt
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "outside")
|
||||
require.Len(t, fileDatas, 0)
|
||||
|
||||
// Test 3: Valid path within log directory should work
|
||||
optValid := map[string]string{
|
||||
"filename": validLog,
|
||||
}
|
||||
dataValid, err := json.Marshal(optValid)
|
||||
require.NoError(t, err)
|
||||
|
||||
logCfgValid := mlog.LoggerConfiguration{
|
||||
"valid": mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
Levels: []mlog.Level{mlog.LvlError},
|
||||
Options: dataValid,
|
||||
},
|
||||
}
|
||||
logCfgDataValid, err := json.Marshal(logCfgValid)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = logCfgDataValid
|
||||
})
|
||||
|
||||
fileDatas, err = th.Service.GetAdvancedLogs(th.Context)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fileDatas, 1)
|
||||
require.Equal(t, "valid.log", fileDatas[0].Filename)
|
||||
require.Equal(t, []byte("valid log data"), fileDatas[0].Body)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ func TestGenerateSupportPacket(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow log file reads from our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.FileLocation = dir
|
||||
*cfg.NotificationLogSettings.FileLocation = dir
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ func TestGenerateSupportPacket(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow log file reads from our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.FileLocation = dir
|
||||
*cfg.NotificationLogSettings.FileLocation = dir
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ type MainHelper struct {
|
|||
|
||||
status int
|
||||
testResourcePath string
|
||||
testLogsPath string
|
||||
replicas []string
|
||||
storePool *sqlstore.TestPool
|
||||
}
|
||||
|
|
@ -88,6 +89,15 @@ func NewMainHelperWithOptions(options *HelperOptions) *MainHelper {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a logs directory and set MM_LOG_PATH for tests that validate log file paths.
|
||||
// This is done unconditionally so tests don't need to enable full resources just for logging.
|
||||
logsDir, err := os.MkdirTemp("", "testlogs")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create test logs directory: " + err.Error())
|
||||
}
|
||||
os.Setenv("MM_LOG_PATH", logsDir)
|
||||
mainHelper.testLogsPath = logsDir
|
||||
|
||||
if options != nil {
|
||||
mainHelper.Options = *options
|
||||
|
||||
|
|
@ -293,6 +303,10 @@ func (h *MainHelper) Close() error {
|
|||
if h.testResourcePath != "" {
|
||||
os.RemoveAll(h.testResourcePath)
|
||||
}
|
||||
if h.testLogsPath != "" {
|
||||
os.RemoveAll(h.testLogsPath)
|
||||
os.Unsetenv("MM_LOG_PATH")
|
||||
}
|
||||
|
||||
if h.storePool != nil {
|
||||
h.storePool.Close()
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ func SetupTestResources() (string, error) {
|
|||
return "", errors.Wrapf(err, "failed to create client directory %s", clientDir)
|
||||
}
|
||||
|
||||
logsDir := path.Join(tempDir, "logs")
|
||||
err = os.Mkdir(logsDir, 0700)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to create logs directory %s", logsDir)
|
||||
}
|
||||
|
||||
err = setupConfig(path.Join(tempDir, "config"))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to setup config")
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ package config
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/utils"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
||||
)
|
||||
|
||||
|
|
@ -127,6 +129,123 @@ func GetLogSettingsFromNotificationsLogSettings(notificationLogSettings *model.N
|
|||
return settings
|
||||
}
|
||||
|
||||
// GetLogRootPath returns the root directory for all log files.
|
||||
// This is used for security validation to prevent arbitrary file reads via advanced logging.
|
||||
// The logging root is determined by:
|
||||
// 1. MM_LOG_PATH environment variable (if set and non-empty)
|
||||
// 2. The default "logs" directory (found relative to the binary)
|
||||
func GetLogRootPath() string {
|
||||
// Check environment variable first
|
||||
if envPath := os.Getenv("MM_LOG_PATH"); envPath != "" {
|
||||
absPath, err := filepath.Abs(envPath)
|
||||
if err == nil {
|
||||
return absPath
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default logs directory
|
||||
logsDir, _ := fileutils.FindDir("logs")
|
||||
absPath, err := filepath.Abs(logsDir)
|
||||
if err != nil {
|
||||
return logsDir
|
||||
}
|
||||
return absPath
|
||||
}
|
||||
|
||||
// ValidateLogFilePath validates that a log file path is within the logging root directory.
|
||||
// This prevents arbitrary file read/write vulnerabilities in logging configuration.
|
||||
// The logging root is determined by MM_LOG_PATH environment variable or the configured log directory.
|
||||
func ValidateLogFilePath(filePath string, loggingRoot string) error {
|
||||
// Resolve file path to absolute
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot resolve path %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
// Resolve symlinks to prevent bypass via symlink attacks
|
||||
realPath, err := filepath.EvalSymlinks(absPath)
|
||||
if err != nil {
|
||||
// If file doesn't exist, still validate the intended path
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("cannot resolve symlinks for %s: %w", absPath, err)
|
||||
}
|
||||
} else {
|
||||
absPath = realPath
|
||||
}
|
||||
|
||||
// Resolve logging root to absolute
|
||||
absRoot, err := filepath.Abs(loggingRoot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot resolve logging root %s: %w", loggingRoot, err)
|
||||
}
|
||||
|
||||
// Ensure root has trailing separator for proper prefix matching
|
||||
// This prevents /tmp/log matching /tmp/logger
|
||||
rootWithSep := absRoot
|
||||
if !strings.HasSuffix(rootWithSep, string(filepath.Separator)) {
|
||||
rootWithSep += string(filepath.Separator)
|
||||
}
|
||||
|
||||
// Check if file is within the logging root
|
||||
// Allow exact match (absPath == absRoot) or proper prefix match
|
||||
if absPath != absRoot && !strings.HasPrefix(absPath, rootWithSep) {
|
||||
return fmt.Errorf("path %s is outside logging root %s", filePath, absRoot)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WarnIfLogPathsOutsideRoot validates log file paths in the config and logs errors for paths outside the logging root.
|
||||
// This is called during config save to identify configurations that will cause server startup to fail in a future version.
|
||||
// Currently only logs errors; in a future version this will block server startup.
|
||||
func WarnIfLogPathsOutsideRoot(cfg *model.Config) {
|
||||
loggingRoot := GetLogRootPath()
|
||||
|
||||
// Check LogSettings.AdvancedLoggingJSON
|
||||
if !utils.IsEmptyJSON(cfg.LogSettings.AdvancedLoggingJSON) {
|
||||
validateAdvancedLoggingConfig(cfg.LogSettings.AdvancedLoggingJSON, "LogSettings.AdvancedLoggingJSON", loggingRoot)
|
||||
}
|
||||
|
||||
// Check NotificationLogSettings.AdvancedLoggingJSON
|
||||
if !utils.IsEmptyJSON(cfg.NotificationLogSettings.AdvancedLoggingJSON) {
|
||||
validateAdvancedLoggingConfig(cfg.NotificationLogSettings.AdvancedLoggingJSON, "NotificationLogSettings.AdvancedLoggingJSON", loggingRoot)
|
||||
}
|
||||
|
||||
// Check ExperimentalAuditSettings.AdvancedLoggingJSON
|
||||
if !utils.IsEmptyJSON(cfg.ExperimentalAuditSettings.AdvancedLoggingJSON) {
|
||||
validateAdvancedLoggingConfig(cfg.ExperimentalAuditSettings.AdvancedLoggingJSON, "ExperimentalAuditSettings.AdvancedLoggingJSON", loggingRoot)
|
||||
}
|
||||
}
|
||||
|
||||
func validateAdvancedLoggingConfig(loggingJSON json.RawMessage, configName string, loggingRoot string) {
|
||||
logCfg := make(mlog.LoggerConfiguration)
|
||||
if err := json.Unmarshal(loggingJSON, &logCfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for targetName, target := range logCfg {
|
||||
if target.Type != "file" {
|
||||
continue
|
||||
}
|
||||
|
||||
var fileOption struct {
|
||||
Filename string `json:"filename"`
|
||||
}
|
||||
if err := json.Unmarshal(target.Options, &fileOption); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ValidateLogFilePath(fileOption.Filename, loggingRoot); err != nil {
|
||||
mlog.Error("Log file path in logging config is outside logging root directory. This configuration will cause server startup to fail in a future version. To fix, set MM_LOG_PATH environment variable to a parent directory containing all log paths, or move log files to the configured logging root.",
|
||||
mlog.String("config_section", configName),
|
||||
mlog.String("target", targetName),
|
||||
mlog.String("path", fileOption.Filename),
|
||||
mlog.String("logging_root", loggingRoot),
|
||||
mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeSimpleConsoleTarget(level string, outputJSON bool, color bool) (mlog.TargetCfg, error) {
|
||||
levels, err := stdLevels(level)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ package config
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -50,3 +52,169 @@ func TestMloggerConfigFromAuditConfig(t *testing.T) {
|
|||
assert.Equal(t, optionsExpected, optionsReceived)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetLogRootPath(t *testing.T) {
|
||||
t.Run("returns MM_LOG_PATH when set", func(t *testing.T) {
|
||||
// Create a temp directory to use as MM_LOG_PATH
|
||||
dir, err := os.MkdirTemp("", "logroot")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(dir)
|
||||
})
|
||||
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
result := GetLogRootPath()
|
||||
absDir, _ := filepath.Abs(dir)
|
||||
assert.Equal(t, absDir, result)
|
||||
})
|
||||
|
||||
t.Run("finds logs directory relative to binary when MM_LOG_PATH not set", func(t *testing.T) {
|
||||
// When MM_LOG_PATH is not set, GetLogRootPath falls back to FindDir("logs"),
|
||||
// which searches for a "logs" directory relative to the working directory
|
||||
// and the binary location. Create a logs directory relative to the test
|
||||
// binary to verify this behavior.
|
||||
t.Setenv("MM_LOG_PATH", "")
|
||||
|
||||
// Get the test binary location
|
||||
exe, err := os.Executable()
|
||||
require.NoError(t, err)
|
||||
exe, err = filepath.EvalSymlinks(exe)
|
||||
require.NoError(t, err)
|
||||
binaryDir := filepath.Dir(exe)
|
||||
|
||||
// Create a "logs" directory next to the binary
|
||||
logsDir := filepath.Join(binaryDir, "logs")
|
||||
err = os.MkdirAll(logsDir, 0755)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(logsDir)
|
||||
})
|
||||
|
||||
result := GetLogRootPath()
|
||||
|
||||
// Result should be an absolute path
|
||||
assert.True(t, filepath.IsAbs(result), "GetLogRootPath should return an absolute path, got: %s", result)
|
||||
|
||||
// FindDir searches working directory first, then binary directory.
|
||||
// The result should be either the logs directory we created or another
|
||||
// logs directory found earlier in the search path. Either way, it should
|
||||
// be a valid directory path ending in "logs".
|
||||
assert.True(t, filepath.Base(result) == "logs" || result == "./",
|
||||
"GetLogRootPath should return a logs directory path, got: %s", result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateLogFilePath(t *testing.T) {
|
||||
t.Run("valid path within root", func(t *testing.T) {
|
||||
root, err := os.MkdirTemp("", "logroot")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(root)
|
||||
})
|
||||
|
||||
validFile := filepath.Join(root, "app.log")
|
||||
err = os.WriteFile(validFile, []byte("test"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ValidateLogFilePath(validFile, root)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("valid path in subdirectory", func(t *testing.T) {
|
||||
root, err := os.MkdirTemp("", "logroot")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(root)
|
||||
})
|
||||
|
||||
subdir := filepath.Join(root, "subdir")
|
||||
err = os.MkdirAll(subdir, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
validFile := filepath.Join(subdir, "app.log")
|
||||
err = os.WriteFile(validFile, []byte("test"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ValidateLogFilePath(validFile, root)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("rejects absolute path outside root", func(t *testing.T) {
|
||||
root, err := os.MkdirTemp("", "logroot")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(root)
|
||||
})
|
||||
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(outsideDir)
|
||||
})
|
||||
|
||||
outsideFile := filepath.Join(outsideDir, "secret.txt")
|
||||
err = os.WriteFile(outsideFile, []byte("secret"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ValidateLogFilePath(outsideFile, root)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "outside logging root")
|
||||
})
|
||||
|
||||
t.Run("rejects path traversal attack", func(t *testing.T) {
|
||||
root, err := os.MkdirTemp("", "logroot")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(root)
|
||||
})
|
||||
|
||||
traversalPath := filepath.Join(root, "..", "..", "etc", "passwd")
|
||||
|
||||
err = ValidateLogFilePath(traversalPath, root)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "outside logging root")
|
||||
})
|
||||
|
||||
t.Run("rejects symlink pointing outside root", func(t *testing.T) {
|
||||
root, err := os.MkdirTemp("", "logroot")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(root)
|
||||
})
|
||||
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(outsideDir)
|
||||
})
|
||||
|
||||
// Create a file outside the root
|
||||
outsideFile := filepath.Join(outsideDir, "secret.txt")
|
||||
err = os.WriteFile(outsideFile, []byte("secret"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a symlink inside root pointing to the outside file
|
||||
symlinkPath := filepath.Join(root, "sneaky.log")
|
||||
err = os.Symlink(outsideFile, symlinkPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ValidateLogFilePath(symlinkPath, root)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "outside logging root")
|
||||
})
|
||||
|
||||
t.Run("allows non-existent file path within root", func(t *testing.T) {
|
||||
root, err := os.MkdirTemp("", "logroot")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(root)
|
||||
})
|
||||
|
||||
// File doesn't exist but path is within root
|
||||
nonExistentFile := filepath.Join(root, "future.log")
|
||||
|
||||
err = ValidateLogFilePath(nonExistentFile, root)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
473
server/public/model/audit_events.go
Normal file
473
server/public/model/audit_events.go
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
// Access Control & Security
|
||||
const (
|
||||
AuditEventApplyIPFilters = "applyIPFilters" // apply IP address filtering
|
||||
AuditEventAssignAccessPolicy = "assignAccessPolicy" // assign access control policy to channels
|
||||
AuditEventCreateAccessControlPolicy = "createAccessControlPolicy" // create access control policy
|
||||
AuditEventDeleteAccessControlPolicy = "deleteAccessControlPolicy" // delete access control policy
|
||||
AuditEventUnassignAccessPolicy = "unassignAccessPolicy" // remove access control policy from channels
|
||||
AuditEventUpdateActiveStatus = "updateActiveStatus" // update active/inactive status of access control policy
|
||||
AuditEventSetActiveStatus = "setActiveStatus" // set active/inactive status of multiple access control policies
|
||||
)
|
||||
|
||||
// Audit & Certificates
|
||||
const (
|
||||
AuditEventAddAuditLogCertificate = "addAuditLogCertificate" // add certificate for secure audit log transmission
|
||||
AuditEventGetAudits = "getAudits" // get audit log entries
|
||||
AuditEventGetUserAudits = "getUserAudits" // get audit log entries for specific user
|
||||
AuditEventRemoveAuditLogCertificate = "removeAuditLogCertificate" // remove certificate used for audit log transmission
|
||||
)
|
||||
|
||||
// Bots
|
||||
const (
|
||||
AuditEventAssignBot = "assignBot" // assign bot to user
|
||||
AuditEventConvertBotToUser = "convertBotToUser" // convert bot account to regular user account
|
||||
AuditEventConvertUserToBot = "convertUserToBot" // convert regular user account to bot account
|
||||
AuditEventCreateBot = "createBot" // create bot account
|
||||
AuditEventPatchBot = "patchBot" // update bot properties
|
||||
AuditEventUpdateBotActive = "updateBotActive" // enable or disable bot account
|
||||
)
|
||||
|
||||
// Branding
|
||||
const (
|
||||
AuditEventDeleteBrandImage = "deleteBrandImage" // delete brand image
|
||||
AuditEventUploadBrandImage = "uploadBrandImage" // upload brand image
|
||||
)
|
||||
|
||||
// Channel Bookmarks
|
||||
const (
|
||||
AuditEventCreateChannelBookmark = "createChannelBookmark" // create bookmark in channels
|
||||
AuditEventDeleteChannelBookmark = "deleteChannelBookmark" // delete bookmark
|
||||
AuditEventUpdateChannelBookmark = "updateChannelBookmark" // update bookmark
|
||||
AuditEventUpdateChannelBookmarkSortOrder = "updateChannelBookmarkSortOrder" // update display order of bookmarks
|
||||
AuditEventListChannelBookmarksForChannel = "listChannelBookmarksForChannel" // list bookmarks for channel
|
||||
)
|
||||
|
||||
// Channel Categories
|
||||
const (
|
||||
AuditEventCreateCategoryForTeamForUser = "createCategoryForTeamForUser" // create channel category for user
|
||||
AuditEventDeleteCategoryForTeamForUser = "deleteCategoryForTeamForUser" // delete channel category
|
||||
AuditEventUpdateCategoriesForTeamForUser = "updateCategoriesForTeamForUser" // update multiple channel categories
|
||||
AuditEventUpdateCategoryForTeamForUser = "updateCategoryForTeamForUser" // update single channel category
|
||||
AuditEventUpdateCategoryOrderForTeamForUser = "updateCategoryOrderForTeamForUser" // update display order of the categories
|
||||
)
|
||||
|
||||
// Channels
|
||||
const (
|
||||
AuditEventAddChannelMember = "addChannelMember" // add member to channel
|
||||
AuditEventConvertGroupMessageToChannel = "convertGroupMessageToChannel" // convert group message to private channel
|
||||
AuditEventCreateChannel = "createChannel" // create public or private channel
|
||||
AuditEventCreateDirectChannel = "createDirectChannel" // create direct message channel between two users
|
||||
AuditEventCreateGroupChannel = "createGroupChannel" // create group message channel with multiple users
|
||||
AuditEventDeleteChannel = "deleteChannel" // delete channel
|
||||
AuditEventGetPinnedPosts = "getPinnedPosts" // get pinned posts
|
||||
AuditEventLocalAddChannelMember = "localAddChannelMember" // add channel member locally
|
||||
AuditEventLocalCreateChannel = "localCreateChannel" // create channel locally
|
||||
AuditEventLocalDeleteChannel = "localDeleteChannel" // delete channel locally
|
||||
AuditEventLocalMoveChannel = "localMoveChannel" // move channel locally
|
||||
AuditEventLocalPatchChannel = "localPatchChannel" // patch channel locally
|
||||
AuditEventLocalRemoveChannelMember = "localRemoveChannelMember" // remove channel member locally
|
||||
AuditEventLocalRestoreChannel = "localRestoreChannel" // restore channel locally
|
||||
AuditEventLocalUpdateChannelPrivacy = "localUpdateChannelPrivacy" // update channel privacy locally
|
||||
AuditEventMoveChannel = "moveChannel" // move channel to different team
|
||||
AuditEventPatchChannel = "patchChannel" // update channel properties
|
||||
AuditEventPatchChannelModerations = "patchChannelModerations" // update channel moderation settings
|
||||
AuditEventRemoveChannelMember = "removeChannelMember" // remove member from channel
|
||||
AuditEventRestoreChannel = "restoreChannel" // restore previously deleted channel
|
||||
AuditEventUpdateChannel = "updateChannel" // update channel properties
|
||||
AuditEventUpdateChannelMemberNotifyProps = "updateChannelMemberNotifyProps" // update notification preferences
|
||||
AuditEventUpdateChannelMemberRoles = "updateChannelMemberRoles" // update roles and permissions
|
||||
AuditEventUpdateChannelMemberSchemeRoles = "updateChannelMemberSchemeRoles" // update scheme-based roles
|
||||
AuditEventUpdateChannelPrivacy = "updateChannelPrivacy" // change channel privacy settings
|
||||
AuditEventUpdateChannelScheme = "updateChannelScheme" // update permission scheme applied to channel
|
||||
)
|
||||
|
||||
// Commands
|
||||
const (
|
||||
AuditEventCreateCommand = "createCommand" // create slash command
|
||||
AuditEventDeleteCommand = "deleteCommand" // delete command
|
||||
AuditEventExecuteCommand = "executeCommand" // execute command
|
||||
AuditEventLocalCreateCommand = "localCreateCommand" // create command locally
|
||||
AuditEventMoveCommand = "moveCommand" // move command to another team
|
||||
AuditEventRegenCommandToken = "regenCommandToken" // regenerate authentication token for command
|
||||
AuditEventUpdateCommand = "updateCommand" // update command
|
||||
)
|
||||
|
||||
// Compliance
|
||||
const (
|
||||
AuditEventCreateComplianceReport = "createComplianceReport" // create compliance report
|
||||
AuditEventDownloadComplianceReport = "downloadComplianceReport" // download compliance report
|
||||
AuditEventGetComplianceReport = "getComplianceReport" // get specific compliance report
|
||||
AuditEventGetComplianceReports = "getComplianceReports" // get all compliance reports
|
||||
)
|
||||
|
||||
// Configuration
|
||||
const (
|
||||
AuditEventConfigReload = "configReload" // reload server configuration
|
||||
AuditEventGetConfig = "getConfig" // get current server configuration
|
||||
AuditEventLocalGetClientConfig = "localGetClientConfig" // get client configuration locally
|
||||
AuditEventLocalGetConfig = "localGetConfig" // get server configuration locally
|
||||
AuditEventLocalPatchConfig = "localPatchConfig" // update server configuration locally
|
||||
AuditEventLocalUpdateConfig = "localUpdateConfig" // update server configuration locally
|
||||
AuditEventMigrateConfig = "migrateConfig" // migrate configs with file values from one store to another
|
||||
AuditEventPatchConfig = "patchConfig" // update server configuration
|
||||
AuditEventUpdateConfig = "updateConfig" // update server configuration
|
||||
)
|
||||
|
||||
// Custom Profile Attributes
|
||||
const (
|
||||
AuditEventCreateCPAField = "createCPAField" // create custom profile attribute
|
||||
AuditEventDeleteCPAField = "deleteCPAField" // delete custom profile attribute
|
||||
AuditEventPatchCPAField = "patchCPAField" // update custom profile attribute field
|
||||
AuditEventPatchCPAValues = "patchCPAValues" // update custom profile attribute values
|
||||
)
|
||||
|
||||
// Data Retention Policies
|
||||
const (
|
||||
AuditEventAddChannelsToPolicy = "addChannelsToPolicy" // add channels to data retention policy
|
||||
AuditEventAddTeamsToPolicy = "addTeamsToPolicy" // add teams to data retention policy
|
||||
AuditEventCreatePolicy = "createPolicy" // create data retention policy
|
||||
AuditEventDeletePolicy = "deletePolicy" // delete data retention policy
|
||||
AuditEventPatchPolicy = "patchPolicy" // update data retention policy
|
||||
AuditEventRemoveChannelsFromPolicy = "removeChannelsFromPolicy" // remove channels from data retention policy
|
||||
AuditEventRemoveTeamsFromPolicy = "removeTeamsFromPolicy" // remove teams from data retention policy
|
||||
)
|
||||
|
||||
// Emojis
|
||||
const (
|
||||
AuditEventCreateEmoji = "createEmoji" // create emoji
|
||||
AuditEventDeleteEmoji = "deleteEmoji" // delete emoji
|
||||
)
|
||||
|
||||
// Exports
|
||||
const (
|
||||
AuditEventBulkExport = "bulkExport" // bulk export data to a file
|
||||
AuditEventDeleteExport = "deleteExport" // delete exported file
|
||||
AuditEventGeneratePresignURLExport = "generatePresignURLExport" // generate presigned URL to download the exported file
|
||||
AuditEventScheduleExport = "scheduleExport" // schedule export job
|
||||
)
|
||||
|
||||
// Files
|
||||
const (
|
||||
AuditEventGetFile = "getFile" // get or download file
|
||||
AuditEventGetFileLink = "getFileLink" // generate link for file sharing
|
||||
AuditEventUploadFileMultipart = "uploadFileMultipart" // upload file using multipart form data
|
||||
AuditEventUploadFileMultipartLegacy = "uploadFileMultipartLegacy" // upload file using legacy multipart method
|
||||
AuditEventUploadFileSimple = "uploadFileSimple" // upload file using simple direct upload method
|
||||
AuditEventGetFileThumbnail = "getFileThumbnail" // get file thumbnail
|
||||
AuditEventGetFileInfosForPost = "getFileInfosForPost" // get file infos for post
|
||||
AuditEventGetFileInfo = "getFileInfo" // get file info
|
||||
AuditEventGetFilePreview = "getFilePreview" // get file preview
|
||||
AuditEventSearchFiles = "searchFiles" // search for files
|
||||
)
|
||||
|
||||
// Groups
|
||||
const (
|
||||
AuditEventAddGroupMembers = "addGroupMembers" // add members to group
|
||||
AuditEventAddUserToGroupSyncables = "addUserToGroupSyncables" // add user to group-synchronized teams and channels
|
||||
AuditEventCreateGroup = "createGroup" // create group
|
||||
AuditEventDeleteGroup = "deleteGroup" // delete group
|
||||
AuditEventDeleteGroupMembers = "deleteGroupMembers" // remove members from group
|
||||
AuditEventLinkGroupSyncable = "linkGroupSyncable" // link group to team or channel for synchronization
|
||||
AuditEventPatchGroup = "patchGroup" // update group
|
||||
AuditEventPatchGroupSyncable = "patchGroupSyncable" // update group synchronization settings
|
||||
AuditEventRestoreGroup = "restoreGroup" // restore previously deleted group
|
||||
AuditEventUnlinkGroupSyncable = "unlinkGroupSyncable" // unlink group from team or channel synchronization
|
||||
)
|
||||
|
||||
// Imports
|
||||
const (
|
||||
AuditEventBulkImport = "bulkImport" // bulk import data from a file
|
||||
AuditEventDeleteImport = "deleteImport" // delete import file
|
||||
AuditEventSlackImport = "slackImport" // import data from Slack
|
||||
)
|
||||
|
||||
// Jobs
|
||||
const (
|
||||
AuditEventCancelJob = "cancelJob" // cancel a job
|
||||
AuditEventCreateJob = "createJob" // create a job
|
||||
AuditEventJobServer = "jobServer" // start job server
|
||||
AuditEventUpdateJobStatus = "updateJobStatus" // update status of a job
|
||||
)
|
||||
|
||||
// LDAP
|
||||
const (
|
||||
AuditEventAddLdapPrivateCertificate = "addLdapPrivateCertificate" // add private certificate for LDAP
|
||||
AuditEventAddLdapPublicCertificate = "addLdapPublicCertificate" // add public certificate for LDAP
|
||||
AuditEventIdMigrateLdap = "idMigrateLdap" // migrate user ID mapping to another attribute
|
||||
AuditEventLinkLdapGroup = "linkLdapGroup" // link LDAP group to Mattermost team or channel
|
||||
AuditEventRemoveLdapPrivateCertificate = "removeLdapPrivateCertificate" // remove private certificate for LDAP
|
||||
AuditEventRemoveLdapPublicCertificate = "removeLdapPublicCertificate" // remove public certificate for LDAP
|
||||
AuditEventSyncLdap = "syncLdap" // synchronize users and groups from LDAP
|
||||
AuditEventUnlinkLdapGroup = "unlinkLdapGroup" // unlink LDAP group from Mattermost team or channel
|
||||
)
|
||||
|
||||
// Licensing
|
||||
const (
|
||||
AuditEventAddLicense = "addLicense" // add license
|
||||
AuditEventLocalAddLicense = "localAddLicense" // add license locally
|
||||
AuditEventLocalRemoveLicense = "localRemoveLicense" // remove license locally
|
||||
AuditEventRemoveLicense = "removeLicense" // remove license
|
||||
AuditEventRequestTrialLicense = "requestTrialLicense" // request trial license
|
||||
)
|
||||
|
||||
// OAuth
|
||||
const (
|
||||
AuditEventAuthorizeOAuthApp = "authorizeOAuthApp" // authorize OAuth app
|
||||
AuditEventAuthorizeOAuthPage = "authorizeOAuthPage" // authorize OAuth page
|
||||
AuditEventCompleteOAuth = "completeOAuth" // complete OAuth authorization flow
|
||||
AuditEventCreateOAuthApp = "createOAuthApp" // create OAuth app
|
||||
AuditEventCreateOutgoingOauthConnection = "createOutgoingOauthConnection" // create outgoing OAuth connection
|
||||
AuditEventDeauthorizeOAuthApp = "deauthorizeOAuthApp" // revoke OAuth app authorization
|
||||
AuditEventDeleteOAuthApp = "deleteOAuthApp" // delete OAuth app
|
||||
AuditEventDeleteOutgoingOAuthConnection = "deleteOutgoingOAuthConnection" // delete outgoing OAuth connection
|
||||
AuditEventGetAccessToken = "getAccessToken" // get OAuth access token
|
||||
AuditEventLoginWithOAuth = "loginWithOAuth" // login using OAuth authentication provider
|
||||
AuditEventMobileLoginWithOAuth = "mobileLoginWithOAuth" // mobile application login using OAuth authentication provider
|
||||
AuditEventRegenerateOAuthAppSecret = "regenerateOAuthAppSecret" // regenerate secret key for OAuth app
|
||||
AuditEventRegisterOAuthClient = "registerOAuthClient" // register OAuth client via dynamic client registration (RFC 7591)
|
||||
AuditEventSignupWithOAuth = "signupWithOAuth" // create account using OAuth authentication provider
|
||||
AuditEventUpdateOAuthApp = "updateOAuthApp" // update OAuth app
|
||||
AuditEventUpdateOutgoingOAuthConnection = "updateOutgoingOAuthConnection" // update outgoing OAuth connection
|
||||
AuditEventValidateOutgoingOAuthConnectionCredentials = "validateOutgoingOAuthConnectionCredentials" // validate credentials for outgoing OAuth connection
|
||||
|
||||
)
|
||||
|
||||
// Plugins
|
||||
const (
|
||||
AuditEventDisablePlugin = "disablePlugin" // disable installed plugin
|
||||
AuditEventEnablePlugin = "enablePlugin" // enable installed plugin
|
||||
AuditEventGetFirstAdminVisitMarketplaceStatus = "getFirstAdminVisitMarketplaceStatus" // get first admin visit status
|
||||
AuditEventInstallMarketplacePlugin = "installMarketplacePlugin" // install plugin from official marketplace
|
||||
AuditEventInstallPluginFromURL = "installPluginFromURL" // install plugin from external URL
|
||||
AuditEventRemovePlugin = "removePlugin" // delete plugin
|
||||
AuditEventSetFirstAdminVisitMarketplaceStatus = "setFirstAdminVisitMarketplaceStatus" // set first admin visit status
|
||||
AuditEventUploadPlugin = "uploadPlugin" // upload plugin file to server for installation
|
||||
)
|
||||
|
||||
// Posts
|
||||
const (
|
||||
AuditEventCreateEphemeralPost = "createEphemeralPost" // create ephemeral post
|
||||
AuditEventCreatePost = "createPost" // create post
|
||||
AuditEventDeletePost = "deletePost" // delete post
|
||||
AuditEventGetEditHistoryForPost = "getEditHistoryForPost" // get edit history for post
|
||||
AuditEventGetFlaggedPosts = "getFlaggedPosts" // get flagged posts
|
||||
AuditEventGetPostsForChannel = "getPostsForChannel" // get posts for channel
|
||||
AuditEventGetPostsForChannelAroundLastUnread = "getPostsForChannelAroundLastUnread" // get posts for channel around last unread
|
||||
AuditEventGetPost = "getPost" // get post
|
||||
AuditEventGetPostThread = "getPostThread" // get post thread
|
||||
AuditEventGetPostsByIds = "getPostsByIds" // get posts by ids
|
||||
AuditEventGetThreadForUser = "getThreadForUser" // get thread for user
|
||||
AuditEventLocalDeletePost = "localDeletePost" // delete post locally
|
||||
AuditEventMoveThread = "moveThread" // move thread and replies to different channel
|
||||
AuditEventNotificationAck = "notificationAck" // notification ack
|
||||
AuditEventPatchPost = "patchPost" // update post meta properties
|
||||
AuditEventRestorePostVersion = "restorePostVersion" // restore post to previous version
|
||||
AuditEventSaveIsPinnedPost = "saveIsPinnedPost" // pin or unpin post
|
||||
AuditEventSearchPosts = "searchPosts" // search for posts
|
||||
AuditEventUpdatePost = "updatePost" // update post content
|
||||
AuditEventRevealPost = "revealPost" // reveal a post that was hidden due to burn on read
|
||||
AuditEventBurnPost = "burnPost" // burn a post that was hidden due to burn on read
|
||||
AuditEventWebsocketPost = "websocketPost" // post received via websocket
|
||||
)
|
||||
|
||||
// Recaps
|
||||
const (
|
||||
AuditEventCreateRecap = "createRecap" // create recap summarizing channel content
|
||||
AuditEventGetRecap = "getRecap" // view a single recap
|
||||
AuditEventGetRecaps = "getRecaps" // list user's recaps
|
||||
AuditEventMarkRecapAsRead = "markRecapAsRead" // mark recap as read
|
||||
AuditEventRegenerateRecap = "regenerateRecap" // regenerate recap with updated channel content
|
||||
AuditEventDeleteRecap = "deleteRecap" // delete recap
|
||||
)
|
||||
|
||||
// Preferences
|
||||
const (
|
||||
AuditEventDeletePreferences = "deletePreferences" // delete user preferences
|
||||
AuditEventUpdatePreferences = "updatePreferences" // update user preferences
|
||||
)
|
||||
|
||||
// Remote Clusters
|
||||
const (
|
||||
AuditEventCreateRemoteCluster = "createRemoteCluster" // create connection to remote Mattermost cluster
|
||||
AuditEventDeleteRemoteCluster = "deleteRemoteCluster" // delete connection to remote Mattermost cluster
|
||||
AuditEventGenerateRemoteClusterInvite = "generateRemoteClusterInvite" // generate invitation token for remote cluster connection
|
||||
AuditEventInviteRemoteClusterToChannel = "inviteRemoteClusterToChannel" // invite remote cluster users to shared channel
|
||||
AuditEventPatchRemoteCluster = "patchRemoteCluster" // update remote cluster connection settings
|
||||
AuditEventRemoteClusterAcceptInvite = "remoteClusterAcceptInvite" // accept invitation from remote cluster
|
||||
AuditEventRemoteClusterAcceptMessage = "remoteClusterAcceptMessage" // accept message from remote cluster
|
||||
AuditEventRemoteUploadProfileImage = "remoteUploadProfileImage" // upload profile image from remote cluster
|
||||
AuditEventUninviteRemoteClusterToChannel = "uninviteRemoteClusterToChannel" // remove remote cluster access from shared channel
|
||||
AuditEventUploadRemoteData = "uploadRemoteData" // upload data to remote cluster
|
||||
)
|
||||
|
||||
// Roles
|
||||
const (
|
||||
AuditEventPatchRole = "patchRole" // update role permissions
|
||||
)
|
||||
|
||||
// SAML
|
||||
const (
|
||||
AuditEventAddSamlIdpCertificate = "addSamlIdpCertificate" // add SAML identity provider certificate
|
||||
AuditEventAddSamlPrivateCertificate = "addSamlPrivateCertificate" // add SAML private certificate
|
||||
AuditEventAddSamlPublicCertificate = "addSamlPublicCertificate" // add SAML public certificate
|
||||
AuditEventCompleteSaml = "completeSaml" // complete SAML authentication flow
|
||||
AuditEventRemoveSamlIdpCertificate = "removeSamlIdpCertificate" // remove SAML identity provider certificate
|
||||
AuditEventRemoveSamlPrivateCertificate = "removeSamlPrivateCertificate" // remove SAML private certificate
|
||||
AuditEventRemoveSamlPublicCertificate = "removeSamlPublicCertificate" // remove SAML public certificate
|
||||
)
|
||||
|
||||
// Scheduled Posts
|
||||
const (
|
||||
AuditEventCreateSchedulePost = "createSchedulePost" // create post scheduled for future delivery
|
||||
AuditEventDeleteScheduledPost = "deleteScheduledPost" // delete scheduled post before delivery
|
||||
AuditEventUpdateScheduledPost = "updateScheduledPost" // update scheduled post
|
||||
)
|
||||
|
||||
// Schemes
|
||||
const (
|
||||
AuditEventCreateScheme = "createScheme" // create permission scheme with role definitions
|
||||
AuditEventDeleteScheme = "deleteScheme" // delete scheme
|
||||
AuditEventPatchScheme = "patchScheme" // update scheme
|
||||
)
|
||||
|
||||
// Search Indexes
|
||||
const (
|
||||
AuditEventPurgeBleveIndexes = "purgeBleveIndexes" // purge Bleve search indexes
|
||||
AuditEventPurgeElasticsearchIndexes = "purgeElasticsearchIndexes" // purge Elasticsearch search indexes
|
||||
)
|
||||
|
||||
// Server Administration
|
||||
const (
|
||||
AuditEventClearServerBusy = "clearServerBusy" // clear server busy status to allow normal operations
|
||||
AuditEventCompleteOnboarding = "completeOnboarding" // complete system onboarding process
|
||||
AuditEventDatabaseRecycle = "databaseRecycle" // closes active connections
|
||||
AuditEventDownloadLogs = "downloadLogs" // download server log files
|
||||
AuditEventGenerateSupportPacket = "generateSupportPacket" // generate support packet with server diagnostics and logs
|
||||
AuditEventGetAppliedSchemaMigrations = "getAppliedSchemaMigrations" // get list of applied database schema migrations
|
||||
AuditEventGetLogs = "getLogs" // get server log entries
|
||||
AuditEventGetOnboarding = "getOnboarding" // get system onboarding status
|
||||
AuditEventInvalidateCaches = "invalidateCaches" // clear server caches
|
||||
AuditEventLocalCheckIntegrity = "localCheckIntegrity" // check database integrity locally
|
||||
AuditEventQueryLogs = "queryLogs" // search server log entries
|
||||
AuditEventRestartServer = "restartServer" // restart Mattermost server process
|
||||
AuditEventSetServerBusy = "setServerBusy" // set server busy status to disallow any operations
|
||||
AuditEventUpdateViewedProductNotices = "updateViewedProductNotices" // update viewed status of product notices
|
||||
AuditEventUpgradeToEnterprise = "upgradeToEnterprise" // upgrade server to Enterprise edition
|
||||
)
|
||||
|
||||
// Teams
|
||||
const (
|
||||
AuditEventAddTeamMember = "addTeamMember" // add member to team
|
||||
AuditEventAddTeamMembers = "addTeamMembers" // add multiple members to team
|
||||
AuditEventAddUserToTeamFromInvite = "addUserToTeamFromInvite" // add user to team using invitation link
|
||||
AuditEventCreateTeam = "createTeam" // create team
|
||||
AuditEventDeleteTeam = "deleteTeam" // delete team
|
||||
AuditEventImportTeam = "importTeam" // import team data from external source
|
||||
AuditEventInvalidateAllEmailInvites = "invalidateAllEmailInvites" // invalidate all pending email invitations
|
||||
AuditEventInviteGuestsToChannels = "inviteGuestsToChannels" // invite guest users to specific channels
|
||||
AuditEventInviteUsersToTeam = "inviteUsersToTeam" // invite users to team
|
||||
AuditEventLocalCreateTeam = "localCreateTeam" // create team locally
|
||||
AuditEventLocalDeleteTeam = "localDeleteTeam" // delete team locally
|
||||
AuditEventLocalInviteUsersToTeam = "localInviteUsersToTeam" // invite users to team locally
|
||||
AuditEventPatchTeam = "patchTeam" // update team properties
|
||||
AuditEventRegenerateTeamInviteId = "regenerateTeamInviteId" // regenerate team invitation ID
|
||||
AuditEventRemoveTeamIcon = "removeTeamIcon" // remove custom icon from team
|
||||
AuditEventRemoveTeamMember = "removeTeamMember" // remove member from team
|
||||
AuditEventRestoreTeam = "restoreTeam" // restore previously deleted team
|
||||
AuditEventSetTeamIcon = "setTeamIcon" // set custom icon for team
|
||||
AuditEventUpdateTeam = "updateTeam" // update team properties
|
||||
AuditEventUpdateTeamMemberRoles = "updateTeamMemberRoles" // update roles of team members
|
||||
AuditEventUpdateTeamMemberSchemeRoles = "updateTeamMemberSchemeRoles" // update scheme-based roles of team members
|
||||
AuditEventUpdateTeamPrivacy = "updateTeamPrivacy" // change team privacy settings
|
||||
AuditEventUpdateTeamScheme = "updateTeamScheme" // update scheme applied to team
|
||||
)
|
||||
|
||||
// Terms of Service
|
||||
const (
|
||||
AuditEventCreateTermsOfService = "createTermsOfService" // create terms of service
|
||||
AuditEventSaveUserTermsOfService = "saveUserTermsOfService" // save user acceptance of terms of service
|
||||
)
|
||||
|
||||
// Threads
|
||||
const (
|
||||
AuditEventFollowThreadByUser = "followThreadByUser" // follow thread to receive notifications about replies
|
||||
AuditEventSetUnreadThreadByPostId = "setUnreadThreadByPostId" // mark thread as unread for user by post ID
|
||||
AuditEventUnfollowThreadByUser = "unfollowThreadByUser" // unfollow thread to stop receiving notifications about replies
|
||||
AuditEventUpdateReadStateAllThreadsByUser = "updateReadStateAllThreadsByUser" // update read status for all threads for user
|
||||
AuditEventUpdateReadStateThreadByUser = "updateReadStateThreadByUser" // update read status for specific thread for user
|
||||
)
|
||||
|
||||
// Uploads
|
||||
const (
|
||||
AuditEventCreateUpload = "createUpload" // create file upload session
|
||||
AuditEventUploadData = "uploadData" // upload file data to server storage
|
||||
)
|
||||
|
||||
// Users
|
||||
const (
|
||||
AuditEventAttachDeviceId = "attachDeviceId" // attach device ID to user session for mobile app
|
||||
AuditEventCreateUser = "createUser" // create user account
|
||||
AuditEventCreateUserAccessToken = "createUserAccessToken" // create personal access token for user API access
|
||||
AuditEventDeleteUser = "deleteUser" // delete user account
|
||||
AuditEventDemoteUserToGuest = "demoteUserToGuest" // demote regular user to guest account with limited permissions
|
||||
AuditEventDisableUserAccessToken = "disableUserAccessToken" // disable user personal access token
|
||||
AuditEventEnableUserAccessToken = "enableUserAccessToken" // enable user personal access token
|
||||
AuditEventExtendSessionExpiry = "extendSessionExpiry" // extend user session expiration time
|
||||
AuditEventLocalDeleteUser = "localDeleteUser" // delete user locally
|
||||
AuditEventLocalPermanentDeleteAllUsers = "localPermanentDeleteAllUsers" // permanently delete all users locally
|
||||
AuditEventLogin = "login" // user login to system
|
||||
AuditEventLoginWithDesktopToken = "loginWithDesktopToken" // user login to system with desktop token
|
||||
AuditEventLogout = "logout" // user logout from system
|
||||
AuditEventMigrateAuthToLdap = "migrateAuthToLdap" // migrate user authentication method to LDAP
|
||||
AuditEventMigrateAuthToSaml = "migrateAuthToSaml" // migrate user authentication method to SAML
|
||||
AuditEventPatchUser = "patchUser" // update user properties
|
||||
AuditEventPromoteGuestToUser = "promoteGuestToUser" // promote guest account to regular user
|
||||
AuditEventResetPassword = "resetPassword" // reset user password
|
||||
AuditEventResetPasswordFailedAttempts = "resetPasswordFailedAttempts" // reset failed password attempt counter
|
||||
AuditEventRevokeAllSessionsAllUsers = "revokeAllSessionsAllUsers" // revoke all active sessions for all users
|
||||
AuditEventRevokeAllSessionsForUser = "revokeAllSessionsForUser" // revoke all active sessions for specific user
|
||||
AuditEventRevokeSession = "revokeSession" // revoke specific user session
|
||||
AuditEventRevokeUserAccessToken = "revokeUserAccessToken" // revoke user personal access token
|
||||
AuditEventSendPasswordReset = "sendPasswordReset" // send password reset email to user
|
||||
AuditEventSendVerificationEmail = "sendVerificationEmail" // send email verification link to user
|
||||
AuditEventSetDefaultProfileImage = "setDefaultProfileImage" // set user profile image to default avatar
|
||||
AuditEventSetProfileImage = "setProfileImage" // set custom profile image for user
|
||||
AuditEventSwitchAccountType = "switchAccountType" // switch user authentication method from one to another
|
||||
AuditEventUpdatePassword = "updatePassword" // update user password
|
||||
AuditEventUpdateUser = "updateUser" // update user account properties
|
||||
AuditEventUpdateUserActive = "updateUserActive" // update user active status
|
||||
AuditEventUpdateUserAuth = "updateUserAuth" // update user authentication method
|
||||
AuditEventUpdateUserMfa = "updateUserMfa" // update user multi-factor authentication settings
|
||||
AuditEventUpdateUserRoles = "updateUserRoles" // update user roles
|
||||
AuditEventVerifyUserEmail = "verifyUserEmail" // verify user email address using verification token
|
||||
AuditEventVerifyUserEmailWithoutToken = "verifyUserEmailWithoutToken" // verify user email address without verification token
|
||||
)
|
||||
|
||||
// Webhooks
|
||||
const (
|
||||
AuditEventCreateIncomingHook = "createIncomingHook" // create incoming webhook
|
||||
AuditEventCreateOutgoingHook = "createOutgoingHook" // create outgoing webhook
|
||||
AuditEventDeleteIncomingHook = "deleteIncomingHook" // delete incoming webhook
|
||||
AuditEventDeleteOutgoingHook = "deleteOutgoingHook" // delete outgoing webhook
|
||||
AuditEventGetIncomingHook = "getIncomingHook" // get incoming webhook details
|
||||
AuditEventGetOutgoingHook = "getOutgoingHook" // get outgoing webhook details
|
||||
AuditEventLocalCreateIncomingHook = "localCreateIncomingHook" // create incoming webhook locally
|
||||
AuditEventRegenOutgoingHookToken = "regenOutgoingHookToken" // regenerate authentication token
|
||||
AuditEventUpdateIncomingHook = "updateIncomingHook" // update incoming webhook
|
||||
AuditEventUpdateOutgoingHook = "updateOutgoingHook" // update outgoing webhook
|
||||
)
|
||||
|
||||
// Content Flagging
|
||||
const (
|
||||
AuditEventFlagPost = "flagPost" // flag post for review
|
||||
AuditEventGetFlaggedPost = "getFlaggedPost" // get flagged post details
|
||||
AuditEventPermanentlyRemoveFlaggedPost = "permanentlyRemoveFlaggedPost" // permanently remove flagged post
|
||||
AuditEventKeepFlaggedPost = "keepFlaggedPost" // keep flagged post
|
||||
AuditEventUpdateContentFlaggingConfig = "updateContentFlaggingConfig" // update content flagging configuration
|
||||
AuditEventSetReviewer = "setFlaggedPostReviewer" // assign reviewer for flagged post
|
||||
)
|
||||
Loading…
Reference in a new issue