From 6a27ed4a1d254c91709f82791ffba33f0534e5f2 Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Wed, 8 Apr 2020 00:52:30 -0400 Subject: [PATCH] MM-22785 audit server CLI (#14144) Add auditing to server CLI. Also: - simplify auditing in API layer - reduce number of AddMeta calls - have models serialize themselves - more consistent field naming --- api4/bot.go | 8 +- api4/channel.go | 55 +- api4/command.go | 22 +- api4/compliance.go | 15 +- api4/config.go | 5 + api4/emoji.go | 7 +- api4/file.go | 25 +- api4/group.go | 11 +- api4/job.go | 4 +- api4/ldap.go | 10 +- api4/oauth.go | 12 +- api4/post.go | 24 +- api4/role.go | 6 +- api4/scheme.go | 21 +- api4/system.go | 14 +- api4/team.go | 72 ++- api4/user.go | 102 ++-- api4/webhook.go | 7 +- app/audit.go | 46 ++ audit/record.go | 22 + audit/record_test.go | 76 +++ audit/syslogtls.go | 3 +- cmd/mattermost/commands/channel.go | 61 ++- cmd/mattermost/commands/command.go | 35 +- cmd/mattermost/commands/config.go | 32 +- cmd/mattermost/commands/export.go | 17 +- cmd/mattermost/commands/group.go | 17 + cmd/mattermost/commands/import.go | 9 + cmd/mattermost/commands/jobserver.go | 6 + cmd/mattermost/commands/ldap.go | 5 + cmd/mattermost/commands/license.go | 5 + cmd/mattermost/commands/permissions.go | 11 + cmd/mattermost/commands/plugin.go | 25 +- cmd/mattermost/commands/reset.go | 4 + cmd/mattermost/commands/roles.go | 23 +- cmd/mattermost/commands/sampledata.go | 6 + cmd/mattermost/commands/team.go | 50 +- cmd/mattermost/commands/user.go | 68 ++- cmd/mattermost/commands/webhook.go | 56 ++- go.mod | 7 +- go.sum | 1 + model/auditconv.go | 663 +++++++++++++++++++++++++ model/auditconv_test.go | 51 ++ vendor/modules.txt | 1 + web/context.go | 12 +- 45 files changed, 1488 insertions(+), 244 deletions(-) create mode 100644 audit/record_test.go create mode 100644 model/auditconv.go create mode 100644 model/auditconv_test.go diff --git a/api4/bot.go b/api4/bot.go index 9be54a1b4d..95d516bffc 100644 --- a/api4/bot.go +++ b/api4/bot.go @@ -42,6 +42,7 @@ func createBot(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createBot", audit.Fail) defer c.LogAuditRec(auditRec) + auditRec.AddMeta("bot", bot) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_CREATE_BOT) { c.SetPermissionError(model.PERMISSION_CREATE_BOT) @@ -67,9 +68,7 @@ func createBot(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - // Note that the primary key of a bot is the UserId, and matches the primary key of the - // corresponding user. - auditRec.AddMeta("bot_id", createdBot.UserId) + auditRec.AddMeta("bot", createdBot) // overwrite meta w.WriteHeader(http.StatusCreated) w.Write(createdBot.ToJson()) @@ -104,6 +103,7 @@ func patchBot(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() + auditRec.AddMeta("bot", updatedBot) w.Write(updatedBot.ToJson()) } @@ -214,6 +214,7 @@ func updateBotActive(c *Context, w http.ResponseWriter, r *http.Request, active } auditRec.Success() + auditRec.AddMeta("bot", bot) w.Write(bot.ToJson()) } @@ -251,6 +252,7 @@ func assignBot(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() + auditRec.AddMeta("bot", bot) w.Write(bot.ToJson()) } diff --git a/api4/channel.go b/api4/channel.go index e048fbbb30..dd653efb47 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -73,7 +73,7 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createChannel", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_name", channel.Name) + auditRec.AddMeta("channel", channel) if channel.Type == model.CHANNEL_OPEN && !c.App.SessionHasPermissionToTeam(*c.App.Session(), channel.TeamId, model.PERMISSION_CREATE_PUBLIC_CHANNEL) { c.SetPermissionError(model.PERMISSION_CREATE_PUBLIC_CHANNEL) @@ -92,7 +92,7 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("channel_id", sc.Id) + auditRec.AddMeta("channel", sc) // overwrite meta c.LogAudit("name=" + channel.Name) w.WriteHeader(http.StatusCreated) @@ -120,7 +120,6 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateChannel", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", channel.Id) originalOldChannel, err := c.App.GetChannel(channel.Id) if err != nil { @@ -129,7 +128,7 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { } oldChannel := originalOldChannel.DeepCopy() - auditRec.AddMeta("channel_name", oldChannel.Name) + auditRec.AddMeta("channel", oldChannel) switch oldChannel.Type { case model.CHANNEL_OPEN: @@ -146,7 +145,7 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { case model.CHANNEL_GROUP, model.CHANNEL_DIRECT: // Modifying the header is not linked to any specific permission for group/dm channels, so just check for membership. - if _, err := c.App.GetChannelMember(channel.Id, c.App.Session().UserId); err != nil { + if _, errGet := c.App.GetChannelMember(channel.Id, c.App.Session().UserId); errGet != nil { c.Err = model.NewAppError("updateChannel", "api.channel.patch_update_channel.forbidden.app_error", nil, "", http.StatusForbidden) return } @@ -191,10 +190,12 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { oldChannel.GroupConstrained = channel.GroupConstrained } - if _, err := c.App.UpdateChannel(oldChannel); err != nil { + updatedChannel, err := c.App.UpdateChannel(oldChannel) + if err != nil { c.Err = err return } + auditRec.AddMeta("update", updatedChannel) if oldChannelDisplayName != channel.DisplayName { if err := c.App.PostUpdateChannelDisplayNameMessage(c.App.Session().UserId, channel, oldChannelDisplayName, channel.DisplayName); err != nil { @@ -222,8 +223,7 @@ func convertChannelToPrivate(c *Context, w http.ResponseWriter, r *http.Request) auditRec := c.MakeAuditRecord("convertChannelToPrivate", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", oldPublicChannel.Id) - auditRec.AddMeta("channel_name", oldPublicChannel.Name) + auditRec.AddMeta("channel", oldPublicChannel) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), oldPublicChannel.TeamId, model.PERMISSION_MANAGE_TEAM) { c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) @@ -245,6 +245,7 @@ func convertChannelToPrivate(c *Context, w http.ResponseWriter, r *http.Request) c.Err = err return } + auditRec.AddMeta("user", user) oldPublicChannel.Type = model.CHANNEL_PRIVATE @@ -281,10 +282,8 @@ func updateChannelPrivacy(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateChannelPrivacy", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", channel.Id) - auditRec.AddMeta("channel_name", channel.Name) - auditRec.AddMeta("channel_type", channel.Type) - auditRec.AddMeta("new_channel_type", privacy) + auditRec.AddMeta("channel", channel) + auditRec.AddMeta("new_type", privacy) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), channel.TeamId, model.PERMISSION_MANAGE_TEAM) { c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) @@ -301,6 +300,7 @@ func updateChannelPrivacy(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } + auditRec.AddMeta("user", user) channel.Type = privacy @@ -337,8 +337,7 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("patchChannel", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", oldChannel.Id) - auditRec.AddMeta("channel_name", oldChannel.Name) + auditRec.AddMeta("channel", oldChannel) switch oldChannel.Type { case model.CHANNEL_OPEN: @@ -379,6 +378,7 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.Success() c.LogAudit("") + auditRec.AddMeta("patch", rchannel) w.Write([]byte(rchannel.ToJson())) } @@ -398,8 +398,7 @@ func restoreChannel(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("restoreChannel", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", channel.Id) - auditRec.AddMeta("channel_name", channel.Name) + auditRec.AddMeta("channel", channel) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), teamId, model.PERMISSION_MANAGE_TEAM) { c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) @@ -475,8 +474,7 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("channel_id", sc.Id) - auditRec.AddMeta("channel_name", sc.Name) + auditRec.AddMeta("channel", sc) w.WriteHeader(http.StatusCreated) w.Write([]byte(sc.ToJson())) @@ -555,8 +553,7 @@ func createGroupChannel(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("channel_id", groupChannel.Id) - auditRec.AddMeta("channel_name", groupChannel.Name) + auditRec.AddMeta("channel", groupChannel) w.WriteHeader(http.StatusCreated) w.Write([]byte(groupChannel.ToJson())) @@ -1012,8 +1009,7 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("deleteChannel", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", channel.Id) - auditRec.AddMeta("channel_name", channel.Name) + auditRec.AddMeta("channeld", channel) if channel.Type == model.CHANNEL_DIRECT || channel.Type == model.CHANNEL_GROUP { c.Err = model.NewAppError("deleteChannel", "api.channel.delete_channel.type.invalid", nil, "", http.StatusBadRequest) @@ -1404,8 +1400,7 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("addChannelMember", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", channel.Id) - auditRec.AddMeta("channel_name", channel.Name) + auditRec.AddMeta("channel", channel) if channel.Type == model.CHANNEL_DIRECT || channel.Type == model.CHANNEL_GROUP { c.Err = model.NewAppError("addUserToChannel", "api.channel.add_user_to_channel.type.app_error", nil, "", http.StatusBadRequest) @@ -1506,8 +1501,7 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("removeChannelMember", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", channel.Id) - auditRec.AddMeta("channel_name", channel.Name) + auditRec.AddMeta("channel", channel) auditRec.AddMeta("remove_user_id", user.Id) if !(channel.Type == model.CHANNEL_OPEN || channel.Type == model.CHANNEL_PRIVATE) { @@ -1586,8 +1580,7 @@ func updateChannelScheme(c *Context, w http.ResponseWriter, r *http.Request) { return } - auditRec.AddMeta("channel_id", channel.Id) - auditRec.AddMeta("channel_name", channel.Name) + auditRec.AddMeta("channel", channel) auditRec.AddMeta("old_scheme_id", channel.SchemeId) channel.SchemeId = &scheme.Id @@ -1701,6 +1694,9 @@ func patchChannelModerations(c *Context, w http.ResponseWriter, r *http.Request) return } + auditRec := c.MakeAuditRecord("patchChannelModerations", audit.Fail) + defer c.LogAuditRec(auditRec) + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return @@ -1711,6 +1707,7 @@ func patchChannelModerations(c *Context, w http.ResponseWriter, r *http.Request) c.Err = err return } + auditRec.AddMeta("channel", channel) channelModerationsPatch := model.ChannelModerationsPatchFromJson(r.Body) channelModerations, err := c.App.PatchChannelModerationsForChannel(channel, channelModerationsPatch) @@ -1718,6 +1715,7 @@ func patchChannelModerations(c *Context, w http.ResponseWriter, r *http.Request) c.Err = err return } + auditRec.AddMeta("patch", channelModerationsPatch) b, marshalErr := json.Marshal(channelModerations) if marshalErr != nil { @@ -1725,5 +1723,6 @@ func patchChannelModerations(c *Context, w http.ResponseWriter, r *http.Request) return } + auditRec.Success() w.Write(b) } diff --git a/api4/command.go b/api4/command.go index 6e9f5a19ea..49904276e9 100644 --- a/api4/command.go +++ b/api4/command.go @@ -51,8 +51,8 @@ func createCommand(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("command_id", rcmd.Id) c.LogAudit("success") + auditRec.AddMeta("command", rcmd) w.WriteHeader(http.StatusCreated) w.Write([]byte(rcmd.ToJson())) @@ -71,15 +71,16 @@ func updateCommand(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec := c.MakeAuditRecord("updateCommand", audit.Fail) - auditRec.AddMeta("command_id", c.Params.CommandId) defer c.LogAuditRec(auditRec) c.LogAudit("attempt") oldCmd, err := c.App.GetCommand(c.Params.CommandId) if err != nil { + auditRec.AddMeta("command_id", c.Params.CommandId) c.SetCommandNotFoundError() return } + auditRec.AddMeta("command", oldCmd) if cmd.TeamId != oldCmd.TeamId { c.Err = model.NewAppError("updateCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.App.Session().UserId, http.StatusBadRequest) @@ -125,8 +126,6 @@ func moveCommand(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec := c.MakeAuditRecord("moveCommand", audit.Fail) - auditRec.AddMeta("command_id", c.Params.CommandId) - auditRec.AddMeta("to_team_id", cmr.TeamId) defer c.LogAuditRec(auditRec) c.LogAudit("attempt") @@ -135,6 +134,7 @@ func moveCommand(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = appErr return } + auditRec.AddMeta("team", newTeam) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), newTeam.Id, model.PERMISSION_MANAGE_SLASH_COMMANDS) { c.LogAudit("fail - inappropriate permissions") @@ -147,7 +147,7 @@ func moveCommand(c *Context, w http.ResponseWriter, r *http.Request) { c.SetCommandNotFoundError() return } - auditRec.AddMeta("from_team_id", cmd.TeamId) + auditRec.AddMeta("command", cmd) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { c.LogAudit("fail - inappropriate permissions") @@ -175,7 +175,6 @@ func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec := c.MakeAuditRecord("deleteCommand", audit.Fail) - auditRec.AddMeta("command_id", c.Params.CommandId) defer c.LogAuditRec(auditRec) c.LogAudit("attempt") @@ -184,6 +183,7 @@ func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { c.SetCommandNotFoundError() return } + auditRec.AddMeta("command", cmd) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { c.LogAudit("fail - inappropriate permissions") @@ -301,6 +301,10 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord("executeCommand", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("commandargs", commandArgs) + // checks that user is a member of the specified channel, and that they have permission to use slash commands in it if !c.App.SessionHasPermissionToChannel(*c.App.Session(), commandArgs.ChannelId, model.PERMISSION_USE_SLASH_COMMANDS) { c.SetPermissionError(model.PERMISSION_USE_SLASH_COMMANDS) @@ -333,12 +337,15 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { commandArgs.Session = *c.App.Session() commandArgs.SiteURL = c.GetSiteURLHeader() + auditRec.AddMeta("commandargs", commandArgs) // overwrite in case teamid changed + response, err := c.App.ExecuteCommand(commandArgs) if err != nil { c.Err = err return } + auditRec.Success() w.Write([]byte(response.ToJson())) } @@ -369,15 +376,16 @@ func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec := c.MakeAuditRecord("regenCommandToken", audit.Fail) - auditRec.AddMeta("command_id", c.Params.CommandId) defer c.LogAuditRec(auditRec) c.LogAudit("attempt") cmd, err := c.App.GetCommand(c.Params.CommandId) if err != nil { + auditRec.AddMeta("command_id", c.Params.CommandId) c.SetCommandNotFoundError() return } + auditRec.AddMeta("command", cmd) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { c.LogAudit("fail - inappropriate permissions") diff --git a/api4/compliance.go b/api4/compliance.go index 38f2efe7f4..d47e9481bc 100644 --- a/api4/compliance.go +++ b/api4/compliance.go @@ -57,12 +57,16 @@ func getComplianceReports(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord("getComplianceReports", audit.Fail) + defer c.LogAuditRec(auditRec) + crs, err := c.App.GetComplianceReports(c.Params.Page, c.Params.PerPage) if err != nil { c.Err = err return } + auditRec.Success() w.Write([]byte(crs.ToJson())) } @@ -72,6 +76,9 @@ func getComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord("getComplianceReport", audit.Fail) + defer c.LogAuditRec(auditRec) + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return @@ -83,6 +90,10 @@ func getComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec.Success() + auditRec.AddMeta("compliance_id", job.Id) + auditRec.AddMeta("compliance_desc", job.Desc) + w.Write([]byte(job.ToJson())) } @@ -106,14 +117,16 @@ func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request c.Err = err return } + auditRec.AddMeta("compliance_id", job.Id) + auditRec.AddMeta("compliance_desc", job.Desc) reportBytes, err := c.App.GetComplianceFile(job) if err != nil { c.Err = err return } + auditRec.AddMeta("length", len(reportBytes)) - auditRec.AddMeta("compliance_desc", job.Desc) c.LogAudit("downloaded " + job.Desc) w.Header().Set("Cache-Control", "max-age=2592000, public") diff --git a/api4/config.go b/api4/config.go index 95fdfc2b6c..728b6e2640 100644 --- a/api4/config.go +++ b/api4/config.go @@ -28,8 +28,13 @@ func getConfig(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord("getConfig", audit.Fail) + defer c.LogAuditRec(auditRec) + cfg := c.App.GetSanitizedConfig() + auditRec.Success() + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Write([]byte(cfg.ToJson())) } diff --git a/api4/emoji.go b/api4/emoji.go index 294c29e22f..49402cc710 100644 --- a/api4/emoji.go +++ b/api4/emoji.go @@ -87,8 +87,7 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } - auditRec.AddMeta("emoji_id", emoji.Id) - auditRec.AddMeta("emoji_name", emoji.Name) + auditRec.AddMeta("emoji", emoji) newEmoji, err := c.App.CreateEmoji(c.App.Session().UserId, emoji, m) if err != nil { @@ -129,14 +128,14 @@ func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("deleteEmoji", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("emoji_id", c.Params.EmojiId) emoji, err := c.App.GetEmoji(c.Params.EmojiId) if err != nil { + auditRec.AddMeta("emoji_id", c.Params.EmojiId) c.Err = err return } - auditRec.AddMeta("emoji_name", emoji.Name) + auditRec.AddMeta("emoji", emoji) // Allow any user with DELETE_EMOJIS permission at Team level to delete emojis at system level memberships, err := c.App.GetTeamMembersForUser(c.App.Session().UserId) diff --git a/api4/file.go b/api4/file.go index 212ada2f2e..6fc596d949 100644 --- a/api4/file.go +++ b/api4/file.go @@ -157,7 +157,6 @@ func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.F auditRec := c.MakeAuditRecord("uploadFileSimple", audit.Fail) defer c.LogAuditRec(auditRec) auditRec.AddMeta("channel_id", c.Params.ChannelId) - auditRec.AddMeta("filename", c.Params.Filename) if !c.App.SessionHasPermissionToChannel(*c.App.Session(), c.Params.ChannelId, model.PERMISSION_UPLOAD_FILE) { c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) @@ -166,7 +165,6 @@ func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.F clientId := r.Form.Get("client_id") auditRec.AddMeta("client_id", clientId) - auditRec.AddMeta("content_length", r.ContentLength) info, appErr := c.App.UploadFileX(c.Params.ChannelId, c.Params.Filename, r.Body, app.UploadFileSetTeamId(FILE_TEAM_ID), @@ -178,6 +176,7 @@ func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.F c.Err = appErr return nil } + auditRec.AddMeta("file", info) fileUploadResponse := &model.FileUploadResponse{ FileInfos: []*model.FileInfo{info}, @@ -328,7 +327,6 @@ NEXT_PART: auditRec := c.MakeAuditRecord("uploadFileMultipart", audit.Fail) auditRec.AddMeta("channel_id", c.Params.ChannelId) - auditRec.AddMeta("filename", filename) auditRec.AddMeta("client_id", clientId) info, appErr := c.App.UploadFileX(c.Params.ChannelId, filename, part, @@ -342,6 +340,8 @@ NEXT_PART: c.LogAuditRec(auditRec) return nil } + auditRec.AddMeta("file", info) + auditRec.Success() c.LogAuditRec(auditRec) @@ -430,7 +430,6 @@ func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader, auditRec := c.MakeAuditRecord("uploadFileMultipartLegacy", audit.Fail) defer c.LogAuditRec(auditRec) auditRec.AddMeta("channel_id", channelId) - auditRec.AddMeta("filename", fileHeader.Filename) auditRec.AddMeta("client_id", clientId) info, appErr := c.App.UploadFileX(c.Params.ChannelId, fileHeader.Filename, f, @@ -445,6 +444,7 @@ func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader, c.LogAuditRec(auditRec) return nil } + auditRec.AddMeta("file", info) auditRec.Success() c.LogAuditRec(auditRec) @@ -469,11 +469,16 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { forceDownload = false } + auditRec := c.MakeAuditRecord("getFile", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("force_download", forceDownload) + info, err := c.App.GetFileInfo(c.Params.FileId) if err != nil { c.Err = err return } + auditRec.AddMeta("file", info) if info.CreatorId != c.App.Session().UserId && !c.App.SessionHasPermissionToChannelByPost(*c.App.Session(), info.PostId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) @@ -488,6 +493,8 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { } defer fileReader.Close() + auditRec.Success() + err = writeFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) if err != nil { c.Err = err @@ -548,11 +555,15 @@ func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord("getFileLink", audit.Fail) + defer c.LogAuditRec(auditRec) + info, err := c.App.GetFileInfo(c.Params.FileId) if err != nil { c.Err = err return } + auditRec.AddMeta("file", info) if info.CreatorId != c.App.Session().UserId && !c.App.SessionHasPermissionToChannelByPost(*c.App.Session(), info.PostId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) @@ -565,7 +576,11 @@ func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { } resp := make(map[string]string) - resp["link"] = c.App.GeneratePublicLink(c.GetSiteURLHeader(), info) + link := c.App.GeneratePublicLink(c.GetSiteURLHeader(), info) + resp["link"] = link + + auditRec.Success() + auditRec.AddMeta("link", link) w.Write([]byte(model.MapToJson(resp))) } diff --git a/api4/group.go b/api4/group.go index b87b4b4609..5d6fdc7402 100644 --- a/api4/group.go +++ b/api4/group.go @@ -108,7 +108,6 @@ func patchGroup(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("patchGroup", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("group_id", c.Params.GroupId) if c.App.License() == nil || !*c.App.License().Features.LDAPGroups { c.Err = model.NewAppError("Api4.patchGroup", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented) @@ -125,9 +124,7 @@ func patchGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("old_group_name", group.Name) - auditRec.AddMeta("old_group_display", group.DisplayName) - auditRec.AddMeta("old_group_desc", group.Description) + auditRec.AddMeta("group", group) group.Patch(groupPatch) @@ -136,10 +133,7 @@ func patchGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - - auditRec.AddMeta("new_group_name", group.Name) - auditRec.AddMeta("new_group_display", group.DisplayName) - auditRec.AddMeta("new_group_desc", group.Description) + auditRec.AddMeta("patch", group) b, marshalErr := json.Marshal(group) if marshalErr != nil { @@ -148,7 +142,6 @@ func patchGroup(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - w.Write(b) } diff --git a/api4/job.go b/api4/job.go index b82043a89c..8d83d936b6 100644 --- a/api4/job.go +++ b/api4/job.go @@ -47,7 +47,7 @@ func createJob(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createJob", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("job_type", job.Type) + auditRec.AddMeta("job", job) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_JOBS) { c.SetPermissionError(model.PERMISSION_MANAGE_JOBS) @@ -61,7 +61,7 @@ func createJob(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("job_id", job.Id) + auditRec.AddMeta("job", job) // overwrite meta w.WriteHeader(http.StatusCreated) w.Write([]byte(job.ToJson())) diff --git a/api4/ldap.go b/api4/ldap.go index 5ebbf89d18..3e43c4401d 100644 --- a/api4/ldap.go +++ b/api4/ldap.go @@ -149,8 +149,7 @@ func linkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("ldap_group_id", ldapGroup.Id) - auditRec.AddMeta("ldap_group_desc", ldapGroup.Description) + auditRec.AddMeta("ldap_group", ldapGroup) if ldapGroup == nil { c.Err = model.NewAppError("Api4.linkLdapGroup", "api.ldap_group.not_found", nil, "", http.StatusNotFound) @@ -163,8 +162,7 @@ func linkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { return } if group != nil { - auditRec.AddMeta("group_id", group.Id) - auditRec.AddMeta("group_name", group.Name) + auditRec.AddMeta("group", group) } var status int @@ -177,7 +175,6 @@ func linkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { } else { displayName = ldapGroup.DisplayName } - auditRec.AddMeta("ldap_group_display", displayName) // Group has been previously linked if group != nil { @@ -251,8 +248,7 @@ func unlinkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("group_id", group.Id) - auditRec.AddMeta("group_name", group.Name) + auditRec.AddMeta("group", group) if group.DeleteAt == 0 { _, err = c.App.DeleteGroup(group.Id) diff --git a/api4/oauth.go b/api4/oauth.go index 1fea587ce7..ed27bd4eb6 100644 --- a/api4/oauth.go +++ b/api4/oauth.go @@ -32,8 +32,6 @@ func createOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createOAuthApp", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("oauth_app_name", oauthApp.Name) - auditRec.AddMeta("oauth_app_desc", oauthApp.Description) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_OAUTH) { c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH) @@ -53,8 +51,7 @@ func createOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("oauth_app_id", rapp.Id) - auditRec.AddMeta("client_id", rapp.Id) + auditRec.AddMeta("oauth_app", rapp) c.LogAudit("client_id=" + rapp.Id) w.WriteHeader(http.StatusCreated) @@ -82,7 +79,6 @@ func updateOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { c.SetInvalidParam("oauth_app") return } - auditRec.AddMeta("oauth_app_name", oauthApp.Name) // The app being updated in the payload must be the same one as indicated in the URL. if oauthApp.Id != c.Params.AppId { @@ -95,6 +91,7 @@ func updateOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } + auditRec.AddMeta("oauth_app", oldOauthApp) if c.App.Session().UserId != oldOauthApp.CreatorId && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) @@ -112,6 +109,7 @@ func updateOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() + auditRec.AddMeta("update", updatedOauthApp) c.LogAudit("success") w.Write([]byte(updatedOauthApp.ToJson())) @@ -204,7 +202,7 @@ func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("oauth_app_name", oauthApp.Name) + auditRec.AddMeta("oauth_app", oauthApp) if c.App.Session().UserId != oauthApp.CreatorId && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) @@ -243,7 +241,7 @@ func regenerateOAuthAppSecret(c *Context, w http.ResponseWriter, r *http.Request c.Err = err return } - auditRec.AddMeta("oauth_app_name", oauthApp.Name) + auditRec.AddMeta("oauth_app", oauthApp) if oauthApp.CreatorId != c.App.Session().UserId && !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) diff --git a/api4/post.go b/api4/post.go index 6bd465b759..ebb81a2fd5 100644 --- a/api4/post.go +++ b/api4/post.go @@ -46,7 +46,7 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createPost", audit.Fail) defer c.LogAuditRecWithLevel(auditRec, app.RestContentLevel) - auditRec.AddMeta("channel_id", post.ChannelId) + auditRec.AddMeta("post", post) hasPermission := false if c.App.SessionHasPermissionToChannel(*c.App.Session(), post.ChannelId, model.PERMISSION_CREATE_POST) { @@ -73,7 +73,7 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) { return } auditRec.Success() - auditRec.AddMeta("post_id", rp.Id) + auditRec.AddMeta("post", rp) // overwrite meta setOnline := r.URL.Query().Get("set_online") setOnlineBool := true // By default, always set online. @@ -378,8 +378,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(model.PERMISSION_DELETE_POST) return } - auditRec.AddMeta("channel_id", post.ChannelId) - auditRec.AddMeta("creator_user_id", post.UserId) + auditRec.AddMeta("post", post) if c.App.Session().UserId == post.UserId { if !c.App.SessionHasPermissionToChannel(*c.App.Session(), post.ChannelId, model.PERMISSION_DELETE_POST) { @@ -532,9 +531,6 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updatePost", audit.Fail) defer c.LogAuditRecWithLevel(auditRec, app.RestContentLevel) - auditRec.AddMeta("post_id", post.Id) - auditRec.AddMeta("channel_id", post.ChannelId) - auditRec.AddMeta("creator_user_id", post.UserId) // The post being updated in the payload must be the same one as indicated in the URL. if post.Id != c.Params.PostId { @@ -552,6 +548,7 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(model.PERMISSION_EDIT_POST) return } + auditRec.AddMeta("post", originalPost) // Updating the file_ids of a post is not a supported operation and will be ignored post.FileIds = originalPost.FileIds @@ -572,6 +569,7 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() + auditRec.AddMeta("update", rpost) w.Write([]byte(rpost.ToJson())) } @@ -591,7 +589,6 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("patchPost", audit.Fail) defer c.LogAuditRecWithLevel(auditRec, app.RestContentLevel) - auditRec.AddMeta("post_id", c.Params.PostId) // Updating the file_ids of a post is not a supported operation and will be ignored post.FileIds = nil @@ -606,8 +603,7 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { c.SetPermissionError(model.PERMISSION_EDIT_POST) return } - auditRec.AddMeta("channel_id", originalPost.ChannelId) - auditRec.AddMeta("creator_user_id", originalPost.UserId) + auditRec.AddMeta("post", originalPost) if c.App.Session().UserId != originalPost.UserId { if !c.App.SessionHasPermissionToChannelByPost(*c.App.Session(), c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) { @@ -623,6 +619,7 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() + auditRec.AddMeta("patch", patchedPost) w.Write([]byte(patchedPost.ToJson())) } @@ -657,7 +654,6 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, r *http.Request, isPinn auditRec := c.MakeAuditRecord("saveIsPinnedPost", audit.Fail) defer c.LogAuditRecWithLevel(auditRec, app.RestContentLevel) - auditRec.AddMeta("post_id", c.Params.PostId) if !c.App.SessionHasPermissionToChannelByPost(*c.App.Session(), c.Params.PostId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) @@ -676,8 +672,7 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, r *http.Request, isPinn c.Err = err return } - auditRec.AddMeta("channel_id", post.ChannelId) - auditRec.AddMeta("creator_user_id", post.UserId) + auditRec.AddMeta("post", post) channel, err := c.App.GetChannel(post.ChannelId) if err != nil { @@ -696,11 +691,12 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, r *http.Request, isPinn patch := &model.PostPatch{} patch.IsPinned = model.NewBool(isPinned) - _, err = c.App.PatchPost(c.Params.PostId, patch) + patchedPost, err := c.App.PatchPost(c.Params.PostId, patch) if err != nil { c.Err = err return } + auditRec.AddMeta("patch", patchedPost) auditRec.Success() ReturnStatusOK(w) diff --git a/api4/role.go b/api4/role.go index e8d888acfc..f0f52f4e80 100644 --- a/api4/role.go +++ b/api4/role.go @@ -93,16 +93,13 @@ func patchRole(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("patchRole", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("role_id", c.Params.RoleId) oldRole, err := c.App.GetRole(c.Params.RoleId) if err != nil { c.Err = err return } - auditRec.AddMeta("role_id", oldRole.Name) - auditRec.AddMeta("role_desc", oldRole.Description) - auditRec.AddMeta("role_display", oldRole.DisplayName) + auditRec.AddMeta("role", oldRole) if c.App.License() == nil && patch.Permissions != nil { if oldRole.Name == "system_guest" || oldRole.Name == "team_guest" || oldRole.Name == "channel_guest" { @@ -154,6 +151,7 @@ func patchRole(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() + auditRec.AddMeta("patch", role) c.LogAudit("") w.Write([]byte(role.ToJson())) diff --git a/api4/scheme.go b/api4/scheme.go index 4df56a1744..183d22190f 100644 --- a/api4/scheme.go +++ b/api4/scheme.go @@ -29,9 +29,7 @@ func createScheme(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createScheme", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("scheme_name", scheme.Name) - auditRec.AddMeta("scheme_display", scheme.DisplayName) - auditRec.AddMeta("scheme_desc", scheme.Description) + auditRec.AddMeta("scheme", scheme) if c.App.License() == nil || !*c.App.License().Features.CustomPermissionsSchemes { c.Err = model.NewAppError("Api4.CreateScheme", "api.scheme.create_scheme.license.error", nil, "", http.StatusNotImplemented) @@ -50,7 +48,7 @@ func createScheme(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("scheme_id", scheme.Id) + auditRec.AddMeta("scheme", scheme) // overwrite meta w.WriteHeader(http.StatusCreated) w.Write([]byte(scheme.ToJson())) @@ -173,10 +171,6 @@ func patchScheme(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("patchScheme", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("scheme_id", c.Params.SchemeId) - auditRec.AddMeta("new_scheme_name", patch.Name) - auditRec.AddMeta("new_scheme_display", patch.DisplayName) - auditRec.AddMeta("new_scheme_desc", patch.Description) if c.App.License() == nil || !*c.App.License().Features.CustomPermissionsSchemes { c.Err = model.NewAppError("Api4.PatchScheme", "api.scheme.patch_scheme.license.error", nil, "", http.StatusNotImplemented) @@ -188,9 +182,7 @@ func patchScheme(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("old_scheme_name", scheme.Name) - auditRec.AddMeta("old_scheme_display", scheme.DisplayName) - auditRec.AddMeta("old_scheme_desc", scheme.Description) + auditRec.AddMeta("scheme", scheme) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) @@ -202,6 +194,7 @@ func patchScheme(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } + auditRec.AddMeta("patch", scheme) auditRec.Success() c.LogAudit("") @@ -217,7 +210,6 @@ func deleteScheme(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("deleteScheme", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("scheme_id", c.Params.SchemeId) if c.App.License() == nil || !*c.App.License().Features.CustomPermissionsSchemes { c.Err = model.NewAppError("Api4.DeleteScheme", "api.scheme.delete_scheme.license.error", nil, "", http.StatusNotImplemented) @@ -229,11 +221,14 @@ func deleteScheme(c *Context, w http.ResponseWriter, r *http.Request) { return } - if _, err := c.App.DeleteScheme(c.Params.SchemeId); err != nil { + scheme, err := c.App.DeleteScheme(c.Params.SchemeId) + if err != nil { c.Err = err return } auditRec.Success() + auditRec.AddMeta("scheme", scheme) + ReturnStatusOK(w) } diff --git a/api4/system.go b/api4/system.go index 883e3ef7ae..f407461800 100644 --- a/api4/system.go +++ b/api4/system.go @@ -183,18 +183,24 @@ func testSiteURL(c *Context, w http.ResponseWriter, r *http.Request) { } func getAudits(c *Context, w http.ResponseWriter, r *http.Request) { + auditRec := c.MakeAuditRecord("getAudits", audit.Fail) + defer c.LogAuditRec(auditRec) + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return } audits, err := c.App.GetAuditsPage("", c.Params.Page, c.Params.PerPage) - if err != nil { c.Err = err return } + auditRec.Success() + auditRec.AddMeta("page", c.Params.Page) + auditRec.AddMeta("audits_per_page", c.Params.LogsPerPage) + w.Write([]byte(audits.ToJson())) } @@ -245,6 +251,9 @@ func invalidateCaches(c *Context, w http.ResponseWriter, r *http.Request) { } func getLogs(c *Context, w http.ResponseWriter, r *http.Request) { + auditRec := c.MakeAuditRecord("getLogs", audit.Fail) + defer c.LogAuditRec(auditRec) + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return @@ -256,6 +265,9 @@ func getLogs(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec.AddMeta("page", c.Params.Page) + auditRec.AddMeta("logs_per_page", c.Params.LogsPerPage) + w.Write([]byte(model.ArrayToJson(lines))) } diff --git a/api4/team.go b/api4/team.go index 4625197a7d..251421bdcd 100644 --- a/api4/team.go +++ b/api4/team.go @@ -84,8 +84,7 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createTeam", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_name", team.Name) - auditRec.AddMeta("team_display", team.DisplayName) + auditRec.AddMeta("team", team) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_CREATE_TEAM) { c.Err = model.NewAppError("createTeam", "api.team.is_team_creation_allowed.disabled.app_error", nil, "", http.StatusForbidden) @@ -101,7 +100,7 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { // Don't sanitize the team here since the user will be a team admin and their session won't reflect that yet auditRec.Success() - auditRec.AddMeta("team_id", rteam.Id) + auditRec.AddMeta("team", team) // overwrite meta w.WriteHeader(http.StatusCreated) w.Write([]byte(rteam.ToJson())) @@ -171,9 +170,7 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateTeam", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) - auditRec.AddMeta("team_name", team.Name) - auditRec.AddMeta("team_display", team.DisplayName) + auditRec.AddMeta("team", team) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), c.Params.TeamId, model.PERMISSION_MANAGE_TEAM) { c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) @@ -187,6 +184,7 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() + auditRec.AddMeta("update", updatedTeam) c.App.SanitizeTeam(*c.App.Session(), updatedTeam) w.Write([]byte(updatedTeam.ToJson())) @@ -207,13 +205,16 @@ func patchTeam(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("patchTeam", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), c.Params.TeamId, model.PERMISSION_MANAGE_TEAM) { c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) return } + if oldTeam, err := c.App.GetTeam(c.Params.TeamId); err == nil { + auditRec.AddMeta("team", oldTeam) + } + patchedTeam, err := c.App.PatchTeam(c.Params.TeamId, team) if err != nil { @@ -224,8 +225,7 @@ func patchTeam(c *Context, w http.ResponseWriter, r *http.Request) { c.App.SanitizeTeam(*c.App.Session(), patchedTeam) auditRec.Success() - auditRec.AddMeta("team_name", patchedTeam.Name) - auditRec.AddMeta("team_display", patchedTeam.DisplayName) + auditRec.AddMeta("patched", patchedTeam) c.LogAudit("") w.Write([]byte(patchedTeam.ToJson())) @@ -244,7 +244,6 @@ func regenerateTeamInviteId(c *Context, w http.ResponseWriter, r *http.Request) auditRec := c.MakeAuditRecord("regenerateTeamInviteId", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) patchedTeam, err := c.App.RegenerateTeamInviteId(c.Params.TeamId) if err != nil { @@ -255,8 +254,7 @@ func regenerateTeamInviteId(c *Context, w http.ResponseWriter, r *http.Request) c.App.SanitizeTeam(*c.App.Session(), patchedTeam) auditRec.Success() - auditRec.AddMeta("team_name", patchedTeam.Name) - auditRec.AddMeta("team_display", patchedTeam.DisplayName) + auditRec.AddMeta("team", patchedTeam) c.LogAudit("") w.Write([]byte(patchedTeam.ToJson())) @@ -275,7 +273,10 @@ func deleteTeam(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("deleteTeam", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) + + if team, err := c.App.GetTeam(c.Params.TeamId); err == nil { + auditRec.AddMeta("team", team) + } var err *model.AppError if c.Params.Permanent && *c.App.Config().ServiceSettings.EnableAPITeamDeletion { @@ -488,8 +489,7 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("addTeamMember", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) - auditRec.AddMeta("add_user_id", member.UserId) + auditRec.AddMeta("member", member) if member.UserId == c.App.Session().UserId { var team *model.Team @@ -519,8 +519,7 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("team_name", team.Name) - auditRec.AddMeta("team_display", team.DisplayName) + auditRec.AddMeta("team", team) if team.IsGroupConstrained() { nonMembers, err := c.App.FilterNonGroupTeamMembers([]string{member.UserId}, team) @@ -582,7 +581,7 @@ func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) auditRec.Success() if member != nil { - auditRec.AddMeta("add_user_id", member.UserId) + auditRec.AddMeta("member", member) } w.WriteHeader(http.StatusCreated) @@ -612,7 +611,6 @@ func addTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("addTeamMembers", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) auditRec.AddMeta("count", len(members)) var memberIDs []string @@ -626,8 +624,7 @@ func addTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("team_name", team.Name) - auditRec.AddMeta("team_display", team.DisplayName) + auditRec.AddMeta("team", team) if team.IsGroupConstrained() { nonMembers, err := c.App.FilterNonGroupTeamMembers(memberIDs, team) @@ -702,7 +699,6 @@ func removeTeamMember(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("removeTeamMember", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) if c.App.Session().UserId != c.Params.UserId { if !c.App.SessionHasPermissionToTeam(*c.App.Session(), c.Params.TeamId, model.PERMISSION_REMOVE_USER_FROM_TEAM) { @@ -716,15 +712,14 @@ func removeTeamMember(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("team_name", team.Name) - auditRec.AddMeta("team_display", team.DisplayName) + auditRec.AddMeta("team", team) user, err := c.App.GetUser(c.Params.UserId) if err != nil { c.Err = err return } - auditRec.AddMeta("remove_user_id", user.Id) + auditRec.AddMeta("user", user) if team.IsGroupConstrained() && (c.Params.UserId != c.App.Session().UserId) && !user.IsBot { c.Err = model.NewAppError("removeTeamMember", "api.team.remove_member.group_constrained.app_error", nil, "", http.StatusBadRequest) @@ -807,20 +802,22 @@ func updateTeamMemberRoles(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateTeamMemberRoles", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) - auditRec.AddMeta("update_user_id", c.Params.UserId) + auditRec.AddMeta("roles", newRoles) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), c.Params.TeamId, model.PERMISSION_MANAGE_TEAM_ROLES) { c.SetPermissionError(model.PERMISSION_MANAGE_TEAM_ROLES) return } - if _, err := c.App.UpdateTeamMemberRoles(c.Params.TeamId, c.Params.UserId, newRoles); err != nil { + teamMember, err := c.App.UpdateTeamMemberRoles(c.Params.TeamId, c.Params.UserId, newRoles) + if err != nil { c.Err = err return } auditRec.Success() + auditRec.AddMeta("member", teamMember) + ReturnStatusOK(w) } @@ -838,23 +835,22 @@ func updateTeamMemberSchemeRoles(c *Context, w http.ResponseWriter, r *http.Requ auditRec := c.MakeAuditRecord("updateTeamMemberSchemeRoles", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) - auditRec.AddMeta("update_user_id", c.Params.UserId) - auditRec.AddMeta("new_scheme_admin", schemeRoles.SchemeAdmin) - auditRec.AddMeta("new_scheme_user", schemeRoles.SchemeUser) - auditRec.AddMeta("new_scheme_guest", schemeRoles.SchemeGuest) + auditRec.AddMeta("roles", schemeRoles) if !c.App.SessionHasPermissionToTeam(*c.App.Session(), c.Params.TeamId, model.PERMISSION_MANAGE_TEAM_ROLES) { c.SetPermissionError(model.PERMISSION_MANAGE_TEAM_ROLES) return } - if _, err := c.App.UpdateTeamMemberSchemeRoles(c.Params.TeamId, c.Params.UserId, schemeRoles.SchemeGuest, schemeRoles.SchemeUser, schemeRoles.SchemeAdmin); err != nil { + teamMember, err := c.App.UpdateTeamMemberSchemeRoles(c.Params.TeamId, c.Params.UserId, schemeRoles.SchemeGuest, schemeRoles.SchemeUser, schemeRoles.SchemeAdmin) + if err != nil { c.Err = err return } auditRec.Success() + auditRec.AddMeta("member", teamMember) + ReturnStatusOK(w) } @@ -1370,7 +1366,6 @@ func updateTeamScheme(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateTeamScheme", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("team_id", c.Params.TeamId) if c.App.License() == nil { c.Err = model.NewAppError("Api4.UpdateTeamScheme", "api.team.update_team_scheme.license.error", nil, "", http.StatusNotImplemented) @@ -1388,9 +1383,7 @@ func updateTeamScheme(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("scheme_id", scheme.Id) - auditRec.AddMeta("scheme_name", scheme.Name) - auditRec.AddMeta("scheme_display", scheme.DisplayName) + auditRec.AddMeta("scheme", scheme) if scheme.Scope != model.SCHEME_SCOPE_TEAM { c.Err = model.NewAppError("Api4.UpdateTeamScheme", "api.team.update_team_scheme.scheme_scope.error", nil, "", http.StatusBadRequest) @@ -1403,8 +1396,7 @@ func updateTeamScheme(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("team_name", team.Name) - auditRec.AddMeta("team_display", team.DisplayName) + auditRec.AddMeta("team", team) team.SchemeId = schemeID diff --git a/api4/user.go b/api4/user.go index 7af8a82cc0..0cade5761e 100644 --- a/api4/user.go +++ b/api4/user.go @@ -96,7 +96,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createUser", audit.Fail) defer c.LogAuditRec(auditRec) auditRec.AddMeta("invite_id", inviteId) - auditRec.AddMeta("create_username", user.Username) + auditRec.AddMeta("user", user) // No permission check required @@ -137,7 +137,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("create_user_id", ruser.Id) + auditRec.AddMeta("user", ruser) // overwrite meta w.WriteHeader(http.StatusCreated) w.Write([]byte(ruser.ToJson())) @@ -425,11 +425,14 @@ func setProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("setProfileImage", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("set_user_id", c.Params.UserId) if imageArray[0] != nil { auditRec.AddMeta("filename", imageArray[0].Filename) } + if user, err := c.App.GetUser(c.Params.UserId); err == nil { + auditRec.AddMeta("user", user) + } + imageData := imageArray[0] if err := c.App.SetProfileImage(c.Params.UserId, imageData); err != nil { c.Err = err @@ -460,14 +463,13 @@ func setDefaultProfileImage(c *Context, w http.ResponseWriter, r *http.Request) auditRec := c.MakeAuditRecord("setDefaultProfileImage", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("set_user_id", c.Params.UserId) user, err := c.App.GetUser(c.Params.UserId) if err != nil { c.Err = err return } - auditRec.AddMeta("set_username", user.Username) + auditRec.AddMeta("user", user) if err := c.App.SetDefaultProfileImage(user); err != nil { c.Err = err @@ -888,7 +890,6 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateUser", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("update_user_id", user.Id) if !c.App.SessionHasPermissionToUser(*c.App.Session(), user.Id) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -900,6 +901,7 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } + auditRec.AddMeta("user", ouser) if c.App.Session().IsOAuth { if ouser.Email != user.Email { @@ -925,8 +927,7 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("update_username", ruser.Username) - auditRec.AddMeta("update_email", ruser.Email) + auditRec.AddMeta("update", ruser) c.LogAudit("") w.Write([]byte(ruser.ToJson())) @@ -946,7 +947,6 @@ func patchUser(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("patchUser", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("patch_user_id", c.Params.UserId) if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -958,6 +958,7 @@ func patchUser(c *Context, w http.ResponseWriter, r *http.Request) { c.SetInvalidParam("user_id") return } + auditRec.AddMeta("user", ouser) if c.App.Session().IsOAuth && patch.Email != nil { if ouser.Email != *patch.Email { @@ -989,8 +990,7 @@ func patchUser(c *Context, w http.ResponseWriter, r *http.Request) { c.App.SetAutoResponderStatus(ruser, ouser.NotifyProps) auditRec.Success() - auditRec.AddMeta("patch_username", ruser.Username) - auditRec.AddMeta("patch_email", ruser.Email) + auditRec.AddMeta("patch", ruser) c.LogAudit("") w.Write([]byte(ruser.ToJson())) @@ -1006,7 +1006,6 @@ func deleteUser(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("deleteUser", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("delete_user_id", c.Params.UserId) if !c.App.SessionHasPermissionToUser(*c.App.Session(), userId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -1024,7 +1023,7 @@ func deleteUser(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("delete_username", user.Username) + auditRec.AddMeta("user", user) if _, err = c.App.UpdateActive(user, false); err != nil { c.Err = err @@ -1051,20 +1050,21 @@ func updateUserRoles(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateUserRoles", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("update_user_id", c.Params.UserId) - auditRec.AddMeta("new_roles", newRoles) + auditRec.AddMeta("roles", newRoles) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_ROLES) { c.SetPermissionError(model.PERMISSION_MANAGE_ROLES) return } - if _, err := c.App.UpdateUserRoles(c.Params.UserId, newRoles, true); err != nil { + user, err := c.App.UpdateUserRoles(c.Params.UserId, newRoles, true) + if err != nil { c.Err = err return } auditRec.Success() + auditRec.AddMeta("user", user) c.LogAudit(fmt.Sprintf("user=%s roles=%s", c.Params.UserId, newRoles)) ReturnStatusOK(w) @@ -1086,8 +1086,7 @@ func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateUserActive", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("update_user_id", c.Params.UserId) - auditRec.AddMeta("new_active", active) + auditRec.AddMeta("active", active) // true when you're trying to de-activate yourself isSelfDeactive := !active && c.Params.UserId == c.App.Session().UserId @@ -1108,6 +1107,7 @@ func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } + auditRec.AddMeta("user", user) if active && user.IsGuest() && !*c.App.Config().GuestAccountsSettings.Enable { c.Err = model.NewAppError("updateUserActive", "api.user.update_active.cannot_enable_guest_when_guest_feature_is_disabled.app_error", nil, "userId="+c.Params.UserId, http.StatusUnauthorized) @@ -1144,7 +1144,6 @@ func updateUserAuth(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateUserAuth", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("update_user_id", c.Params.UserId) userAuth := model.UserAuthFromJson(r.Body) if userAuth == nil { @@ -1152,6 +1151,10 @@ func updateUserAuth(c *Context, w http.ResponseWriter, r *http.Request) { return } + if user, err := c.App.GetUser(c.Params.UserId); err == nil { + auditRec.AddMeta("user", user) + } + user, err := c.App.UpdateUserAuth(c.Params.UserId, userAuth) if err != nil { c.Err = err @@ -1207,7 +1210,6 @@ func updateUserMfa(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updateUserMfa", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("update_user_id", c.Params.UserId) if c.App.Session().IsOAuth { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -1220,6 +1222,10 @@ func updateUserMfa(c *Context, w http.ResponseWriter, r *http.Request) { return } + if user, err := c.App.GetUser(c.Params.UserId); err == nil { + auditRec.AddMeta("user", user) + } + props := model.StringInterfaceFromJson(r.Body) activate, ok := props["activate"].(bool) if !ok { @@ -1290,9 +1296,12 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("updatePassword", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("update_user_id", c.Params.UserId) c.LogAudit("attempted") + if user, err := c.App.GetUser(c.Params.UserId); err == nil { + auditRec.AddMeta("user", user) + } + var err *model.AppError if c.Params.UserId == c.App.Session().UserId { currentPassword := props["current_password"] @@ -1478,7 +1487,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta(audit.KeyUserID, user.Id) + auditRec.AddMeta("user", user) if user.IsGuest() { if c.App.License() == nil { @@ -1575,7 +1584,6 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("revokeSession", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("revoke_user_id", c.Params.UserId) if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -1594,7 +1602,7 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("device_id", session.DeviceId) + auditRec.AddMeta("session", session) if session.UserId != c.Params.UserId { c.SetInvalidUrlParam("user_id") @@ -1620,7 +1628,7 @@ func revokeAllSessionsForUser(c *Context, w http.ResponseWriter, r *http.Request auditRec := c.MakeAuditRecord("revokeAllSessionsForUser", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("revoke_user_id", c.Params.UserId) + auditRec.AddMeta("user_id", c.Params.UserId) if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -1720,6 +1728,13 @@ func getUserAudits(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord("getUserAudits", audit.Fail) + defer c.LogAuditRec(auditRec) + + if user, err := c.App.GetUser(c.Params.UserId); err == nil { + auditRec.AddMeta("user", user) + } + if !c.App.SessionHasPermissionToUser(*c.App.Session(), c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return @@ -1731,6 +1746,10 @@ func getUserAudits(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec.Success() + auditRec.AddMeta("page", c.Params.Page) + auditRec.AddMeta("audits_per_page", c.Params.LogsPerPage) + w.Write([]byte(audits.ToJson())) } @@ -1777,8 +1796,7 @@ func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) { ReturnStatusOK(w) return } - auditRec.AddMeta("send_user_id", user.Id) - auditRec.AddMeta("send_username", user.Username) + auditRec.AddMeta("user", user) if err = c.App.SendEmailVerification(user, user.Email); err != nil { // Don't want to leak whether the email is valid or not @@ -1844,7 +1862,10 @@ func createUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createUserAccessToken", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("create_user_id", c.Params.UserId) + + if user, err := c.App.GetUser(c.Params.UserId); err == nil { + auditRec.AddMeta("user", user) + } if c.App.Session().IsOAuth { c.SetPermissionError(model.PERMISSION_CREATE_USER_ACCESS_TOKEN) @@ -2004,7 +2025,10 @@ func revokeUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("revoke_user_id", accessToken.UserId) + + if user, errGet := c.App.GetUser(accessToken.UserId); errGet == nil { + auditRec.AddMeta("user", user) + } if !c.App.SessionHasPermissionToUserOrBot(*c.App.Session(), accessToken.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -2046,7 +2070,10 @@ func disableUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) c.Err = err return } - auditRec.AddMeta("disable_user_id", accessToken.UserId) + + if user, errGet := c.App.GetUser(accessToken.UserId); errGet == nil { + auditRec.AddMeta("user", user) + } if !c.App.SessionHasPermissionToUserOrBot(*c.App.Session(), accessToken.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -2088,7 +2115,10 @@ func enableUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("enabled_user_id", accessToken.UserId) + + if user, errGet := c.App.GetUser(accessToken.UserId); errGet == nil { + auditRec.AddMeta("user", user) + } if !c.App.SessionHasPermissionToUserOrBot(*c.App.Session(), accessToken.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) @@ -2118,6 +2148,10 @@ func saveUserTermsOfService(c *Context, w http.ResponseWriter, r *http.Request) auditRec.AddMeta("terms_id", termsOfServiceId) auditRec.AddMeta("accepted", accepted) + if user, err := c.App.GetUser(userId); err == nil { + auditRec.AddMeta("user", user) + } + if _, err := c.App.GetTermsOfService(termsOfServiceId); err != nil { c.Err = err return @@ -2152,7 +2186,6 @@ func promoteGuestToUser(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("promoteGuestToUser", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("promote_user_id", c.Params.UserId) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_PROMOTE_GUEST) { c.SetPermissionError(model.PERMISSION_PROMOTE_GUEST) @@ -2164,7 +2197,7 @@ func promoteGuestToUser(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("promote_username", user.Username) + auditRec.AddMeta("user", user) if !user.IsGuest() { c.Err = model.NewAppError("Api4.promoteGuestToUser", "api.user.promote_guest_to_user.no_guest.app_error", nil, "", http.StatusNotImplemented) @@ -2198,7 +2231,6 @@ func demoteUserToGuest(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("demoteUserToGuest", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("demote_user_id", c.Params.UserId) if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_DEMOTE_TO_GUEST) { c.SetPermissionError(model.PERMISSION_DEMOTE_TO_GUEST) @@ -2210,7 +2242,7 @@ func demoteUserToGuest(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } - auditRec.AddMeta("demote_username", user.Username) + auditRec.AddMeta("user", user) if user.IsGuest() { c.Err = model.NewAppError("Api4.demoteUserToGuest", "api.user.demote_user_to_guest.already_guest.app_error", nil, "", http.StatusNotImplemented) diff --git a/api4/webhook.go b/api4/webhook.go index c2697f9b01..58595bfd29 100644 --- a/api4/webhook.go +++ b/api4/webhook.go @@ -40,9 +40,7 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { auditRec := c.MakeAuditRecord("createIncomingHook", audit.Fail) defer c.LogAuditRec(auditRec) - auditRec.AddMeta("channel_id", channel.Id) - auditRec.AddMeta("channel_name", channel.Name) - auditRec.AddMeta("team_id", channel.TeamId) + auditRec.AddMeta("channel", channel) c.LogAudit("attempt") if !c.App.SessionHasPermissionToTeam(*c.App.Session(), channel.TeamId, model.PERMISSION_MANAGE_INCOMING_WEBHOOKS) { @@ -63,8 +61,7 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec.Success() - auditRec.AddMeta("hook_id", incomingHook.Id) - auditRec.AddMeta("hook_display", incomingHook.DisplayName) + auditRec.AddMeta("hook", incomingHook) c.LogAudit("success") w.WriteHeader(http.StatusCreated) diff --git a/app/audit.go b/app/audit.go index 4c3e0624ef..9d00eaeebe 100644 --- a/app/audit.go +++ b/app/audit.go @@ -5,6 +5,7 @@ package app import ( "fmt" + "os/user" "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/mlog" @@ -33,6 +34,51 @@ func (a *App) GetAuditsPage(userId string, page int, perPage int) (model.Audits, return a.Srv().Store.Audit().Get(userId, page*perPage, perPage) } +// LogAuditRec logs an audit record using default CLILevel. +func (a *App) LogAuditRec(rec *audit.Record, err error) { + a.LogAuditRecWithLevel(rec, CLILevel, err) +} + +// LogAuditRecWithLevel logs an audit record using specified Level. +func (a *App) LogAuditRecWithLevel(rec *audit.Record, level audit.Level, err error) { + if rec == nil { + return + } + if err != nil { + if appErr, ok := err.(*model.AppError); ok { + rec.AddMeta("err", appErr.Error()) + rec.AddMeta("code", appErr.StatusCode) + } else { + rec.AddMeta("err", err) + } + rec.Fail() + } + a.Srv().Audit.LogRecord(level, *rec) +} + +// MakeAuditRecord creates a audit record pre-populated with defaults. +func (a *App) MakeAuditRecord(event string, initialStatus string) *audit.Record { + var userID string + user, err := user.Current() + if err == nil { + userID = fmt.Sprintf("%s:%s", user.Uid, user.Username) + } + + rec := &audit.Record{ + APIPath: "", + Event: event, + Status: initialStatus, + UserID: userID, + SessionID: "", + Client: fmt.Sprintf("server %s-%s", model.BuildNumber, model.BuildHash), + IPAddress: "", + Meta: audit.Meta{audit.KeyClusterID: a.GetClusterId()}, + } + rec.AddMetaTypeConverter(model.AuditModelTypeConv) + + return rec +} + func (s *Server) configureAudit(adt *audit.Audit) { adt.OnQueueFull = s.onAuditTargetQueueFull adt.OnError = s.onAuditError diff --git a/audit/record.go b/audit/record.go index 887ec7516c..08aebcb59c 100644 --- a/audit/record.go +++ b/audit/record.go @@ -6,6 +6,10 @@ package audit // Meta represents metadata that can be added to a audit record as name/value pairs. type Meta map[string]interface{} +// FuncMetaTypeConv defines a function that can convert meta data types into something +// that serializes well for audit records. +type FuncMetaTypeConv func(val interface{}) (newVal interface{}, converted bool) + // Record provides a consistent set of fields used for all audit logging. type Record struct { APIPath string @@ -16,6 +20,7 @@ type Record struct { Client string IPAddress string Meta Meta + metaConv []FuncMetaTypeConv } // Success marks the audit record status as successful. @@ -33,5 +38,22 @@ func (rec *Record) AddMeta(name string, val interface{}) { if rec.Meta == nil { rec.Meta = Meta{} } + + // possibly convert val to something better suited for serializing + // via zero or more conversion functions. + var converted bool + for _, conv := range rec.metaConv { + val, converted = conv(val) + if converted { + break + } + } + rec.Meta[name] = val } + +// AddMetaTypeConverter adds a function capable of converting meta field types +// into something more suitable for serialization. +func (rec *Record) AddMetaTypeConverter(f FuncMetaTypeConv) { + rec.metaConv = append(rec.metaConv, f) +} diff --git a/audit/record_test.go b/audit/record_test.go new file mode 100644 index 0000000000..4c809d4f42 --- /dev/null +++ b/audit/record_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package audit + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type bloated struct { + fld1 string + fld2 string + fld3 string + fld4 string +} + +type wilted struct { + wilt1 string +} + +func conv(val interface{}) (interface{}, bool) { + switch v := val.(type) { + case *bloated: + return &wilted{wilt1: v.fld1}, true + } + return val, false +} + +func TestRecord_AddMeta(t *testing.T) { + type fields struct { + metaConv []FuncMetaTypeConv + } + type args struct { + name string + val interface{} + } + tests := []struct { + name string + fields fields + args args + wantWilt bool + wantVal string + }{ + {name: "no converter", wantWilt: false, wantVal: "ok", fields: fields{}, args: args{name: "prop", val: "ok"}}, + {name: "don't convert", wantWilt: false, wantVal: "ok", fields: fields{metaConv: []FuncMetaTypeConv{conv}}, args: args{name: "prop", val: "ok"}}, + {name: "convert", wantWilt: true, wantVal: "1", fields: fields{metaConv: []FuncMetaTypeConv{conv}}, args: args{name: "prop", val: &bloated{ + fld1: "1", fld2: "2", fld3: "3", fld4: "4"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rec := &Record{ + metaConv: tt.fields.metaConv, + } + rec.AddMeta(tt.args.name, tt.args.val) + + // fetch the prop store in auditRecord meta data + got, ok := rec.Meta["prop"] + require.True(t, ok) + + // check if conversion was expected + val, ok := got.(*wilted) + require.Equal(t, tt.wantWilt, ok) + + if ok { + // if converted to wilt then make sure field was copied + require.Equal(t, tt.wantVal, val.wilt1) + } else { + // if not converted, make sure val is unchanged + require.Equal(t, tt.wantVal, got) + } + }) + } +} diff --git a/audit/syslogtls.go b/audit/syslogtls.go index cc6f65d139..f9b92f8816 100644 --- a/audit/syslogtls.go +++ b/audit/syslogtls.go @@ -54,8 +54,7 @@ func NewSyslogTLSTarget(filter logr.Filter, formatter logr.Formatter, params *Sy return s, nil } -// Shutdown stops processing log records after making best -// effort to flush queue. +// Shutdown stops processing log records after making best effort to flush queue. func (s *SyslogTLS) Shutdown(ctx context.Context) error { errs := merror.New() diff --git a/cmd/mattermost/commands/channel.go b/cmd/mattermost/commands/channel.go index f7eef14bf5..b36dcde73d 100644 --- a/cmd/mattermost/commands/channel.go +++ b/cmd/mattermost/commands/channel.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/mattermost/mattermost-server/v5/app" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -215,6 +216,11 @@ func createChannelCmdF(command *cobra.Command, args []string) error { return errCreatedChannel } + auditRec := a.MakeAuditRecord("createChannel", audit.Success) + auditRec.AddMeta("channel", createdChannel) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) + CommandPrettyPrintln("Id: " + createdChannel.Id) CommandPrettyPrintln("Name: " + createdChannel.Name) CommandPrettyPrintln("Display Name: " + createdChannel.DisplayName) @@ -251,7 +257,6 @@ func removeChannelUsersCmdF(command *cobra.Command, args []string) error { removeUserFromChannel(a, channel, user, args[i+1]) } } - return nil } @@ -262,13 +267,24 @@ func removeUserFromChannel(a *app.App, channel *model.Channel, user *model.User, } if err := a.RemoveUserFromChannel(user.Id, "", channel); err != nil { CommandPrintErrorln("Unable to remove '" + userArg + "' from " + channel.Name + ". Error: " + err.Error()) + return } + + auditRec := a.MakeAuditRecord("removeUserFromChannel", audit.Success) + auditRec.AddMeta("channel", channel) + auditRec.AddMeta("user", user) + a.LogAuditRec(auditRec, nil) } func removeAllUsersFromChannel(a *app.App, channel *model.Channel) { if err := a.Srv().Store.Channel().PermanentDeleteMembersByChannel(channel.Id); err != nil { CommandPrintErrorln("Unable to remove all users from " + channel.Name + ". Error: " + err.Error()) + return } + + auditRec := a.MakeAuditRecord("removeAllUsersFromChannel", audit.Success) + auditRec.AddMeta("channel", channel) + a.LogAuditRec(auditRec, nil) } func addChannelUsersCmdF(command *cobra.Command, args []string) error { @@ -287,7 +303,6 @@ func addChannelUsersCmdF(command *cobra.Command, args []string) error { for i, user := range users { addUserToChannel(a, channel, user, args[i+1]) } - return nil } @@ -298,7 +313,13 @@ func addUserToChannel(a *app.App, channel *model.Channel, user *model.User, user } if _, err := a.AddUserToChannel(user, channel); err != nil { CommandPrintErrorln("Unable to add '" + userArg + "' from " + channel.Name + ". Error: " + err.Error()) + return } + + auditRec := a.MakeAuditRecord("addUserToChannel", audit.Success) + auditRec.AddMeta("channel", channel) + auditRec.AddMeta("user", user) + a.LogAuditRec(auditRec, nil) } func archiveChannelsCmdF(command *cobra.Command, args []string) error { @@ -316,9 +337,12 @@ func archiveChannelsCmdF(command *cobra.Command, args []string) error { } if err := a.Srv().Store.Channel().Delete(channel.Id, model.GetMillis()); err != nil { CommandPrintErrorln("Unable to archive channel '" + channel.Name + "' error: " + err.Error()) + continue } + auditRec := a.MakeAuditRecord("archiveChannel", audit.Success) + auditRec.AddMeta("channel", channel) + a.LogAuditRec(auditRec, nil) } - return nil } @@ -349,9 +373,12 @@ func deleteChannelsCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to delete channel '" + channel.Name + "' error: " + err.Error()) } else { CommandPrettyPrintln("Deleted channel '" + channel.Name + "'") + + auditRec := a.MakeAuditRecord("deleteChannel", audit.Success) + auditRec.AddMeta("channel", channel) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -392,7 +419,6 @@ func moveChannelsCmdF(command *cobra.Command, args []string) error { CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".") } } - return nil } @@ -403,6 +429,11 @@ func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *mod return err } + auditRec := a.MakeAuditRecord("moveChannel", audit.Success) + auditRec.AddMeta("channel", channel) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) + if incomingWebhooks, err := a.GetIncomingWebhooksForTeamPage(oldTeamId, 0, 10000000); err != nil { return err } else { @@ -479,7 +510,11 @@ func restoreChannelsCmdF(command *cobra.Command, args []string) error { } if err := a.Srv().Store.Channel().SetDeleteAt(channel.Id, 0, model.GetMillis()); err != nil { CommandPrintErrorln("Unable to restore channel '" + args[i] + "'") + continue } + auditRec := a.MakeAuditRecord("restoreChannel", audit.Success) + auditRec.AddMeta("channel", channel) + a.LogAuditRec(auditRec, nil) } suffix := "" @@ -528,10 +563,17 @@ func modifyChannelCmdF(command *cobra.Command, args []string) error { return fmt.Errorf("Unable to find user: '%v'", username) } - if _, err := a.UpdateChannelPrivacy(channel, user); err != nil { + updatedChannel, errUpdate := a.UpdateChannelPrivacy(channel, user) + if errUpdate != nil { return errors.Wrapf(err, "Failed to update channel ('%s') privacy", args[0]) } + auditRec := a.MakeAuditRecord("modifyChannel", audit.Success) + auditRec.AddMeta("channel", channel) + auditRec.AddMeta("user", user) + auditRec.AddMeta("update", updatedChannel) + a.LogAuditRec(auditRec, nil) + return nil } @@ -554,11 +596,16 @@ func renameChannelCmdF(command *cobra.Command, args []string) error { return errdn } - _, errch := a.RenameChannel(channel, newChannelName, newDisplayName) + updatedChannel, errch := a.RenameChannel(channel, newChannelName, newDisplayName) if errch != nil { return errors.Wrapf(errch, "Error in updating channel from %s to %s", channel.Name, newChannelName) } + auditRec := a.MakeAuditRecord("renameChannel", audit.Success) + auditRec.AddMeta("channel", channel) + auditRec.AddMeta("update", updatedChannel) + a.LogAuditRec(auditRec, nil) + return nil } diff --git a/cmd/mattermost/commands/command.go b/cmd/mattermost/commands/command.go index 87ad6347d9..2f0b4206bc 100644 --- a/cmd/mattermost/commands/command.go +++ b/cmd/mattermost/commands/command.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/mattermost/mattermost-server/v5/app" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/spf13/cobra" ) @@ -172,11 +173,16 @@ func createCommandCmdF(command *cobra.Command, args []string) error { URL: url, } - if _, err := a.CreateCommand(newCommand); err != nil { - return errors.New("unable to create command '" + newCommand.DisplayName + "'. " + err.Error()) + createdCommand, errCreate := a.CreateCommand(newCommand) + if errCreate != nil { + return errors.New("unable to create command '" + newCommand.DisplayName + "'. " + errCreate.Error()) } CommandPrettyPrintln("created command '" + newCommand.DisplayName + "'") + auditRec := a.MakeAuditRecord("createCommand", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("command", createdCommand) + a.LogAuditRec(auditRec, nil) return nil } @@ -224,9 +230,13 @@ func moveCommandCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to move command '" + command.DisplayName + "' error: " + err.Error()) } else { CommandPrettyPrintln("Moved command '" + command.DisplayName + "'") + + auditRec := a.MakeAuditRecord("moveCommand", audit.Success) + auditRec.AddMeta("team", team) + auditRec.AddMeta("command", command) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -282,15 +292,20 @@ func deleteCommandCmdF(command *cobra.Command, args []string) error { command.SilenceUsage = true return errors.New("Unable to find command '" + args[0] + "'") } + if err := a.DeleteCommand(slashCommand.Id); err != nil { command.SilenceUsage = true return errors.New("Unable to delete command '" + slashCommand.Id + "' error: " + err.Error()) } CommandPrettyPrintln("Deleted command '" + slashCommand.Id + "' (" + slashCommand.DisplayName + ")") + + auditRec := a.MakeAuditRecord("deleteCommand", audit.Success) + auditRec.AddMeta("command", slashCommand) + a.LogAuditRec(auditRec, nil) return nil } -func modifyCommandCmdF(command *cobra.Command, args []string) error { +func modifyCommandCmdF(command *cobra.Command, args []string) (cmdError error) { a, err := InitDBCommandContextCobra(command) if err != nil { return err @@ -304,6 +319,10 @@ func modifyCommandCmdF(command *cobra.Command, args []string) error { } modifiedCommand := oldCommand + auditRec := a.MakeAuditRecord("modifyCommand", audit.Fail) + defer func() { a.LogAuditRec(auditRec, cmdError) }() + auditRec.AddMeta("command", oldCommand) + // get creator user creator, _ := command.Flags().GetString("creator") if creator != "" { @@ -376,10 +395,14 @@ func modifyCommandCmdF(command *cobra.Command, args []string) error { } modifiedCommand.Method = method - if _, err := a.UpdateCommand(oldCommand, modifiedCommand); err != nil { - return errors.New("unable to modify command '" + modifiedCommand.DisplayName + "'. " + err.Error()) + updatedCommand, errUpdated := a.UpdateCommand(oldCommand, modifiedCommand) + if errUpdated != nil { + return errors.New("unable to modify command '" + modifiedCommand.DisplayName + "'. " + errUpdated.Error()) } CommandPrettyPrintln("modified command '" + modifiedCommand.DisplayName + "'") + auditRec.Success() + auditRec.AddMeta("update", updatedCommand) + return nil } diff --git a/cmd/mattermost/commands/config.go b/cmd/mattermost/commands/config.go index 1616df9f9f..b288c04e77 100644 --- a/cmd/mattermost/commands/config.go +++ b/cmd/mattermost/commands/config.go @@ -251,10 +251,23 @@ func configSetCmdF(command *cobra.Command, args []string) error { return errors.New("Invalid locale configuration") } - if _, err := configStore.Set(newConfig); err != nil { - return errors.Wrap(err, "failed to set config") + if _, errSet := configStore.Set(newConfig); errSet != nil { + return errors.Wrap(errSet, "failed to set config") } + /* + Uncomment when CI unit test fail resolved. + + a, errInit := InitDBCommandContextCobra(command) + if errInit == nil { + auditRec := a.MakeAuditRecord("configSet", audit.Success) + auditRec.AddMeta("setting", configSetting) + auditRec.AddMeta("new_value", newVal) + a.LogAuditRec(auditRec, nil) + a.Shutdown() + } + */ + return nil } @@ -456,10 +469,21 @@ func configResetCmdF(command *cobra.Command, args []string) error { return errors.New("Invalid locale configuration") } - if _, err := configStore.Set(tempConfig); err != nil { - return errors.Wrap(err, "failed to set config") + if _, errSet := configStore.Set(tempConfig); errSet != nil { + return errors.Wrap(errSet, "failed to set config") } + /* + Uncomment when CI unit test fail resolved. + + a, errInit := InitDBCommandContextCobra(command) + if errInit == nil { + auditRec := a.MakeAuditRecord("configReset", audit.Success) + a.LogAuditRec(auditRec, nil) + a.Shutdown() + } + */ + return nil } diff --git a/cmd/mattermost/commands/export.go b/cmd/mattermost/commands/export.go index 4e7ce4cba6..06ebfbdc1a 100644 --- a/cmd/mattermost/commands/export.go +++ b/cmd/mattermost/commands/export.go @@ -8,6 +8,7 @@ import ( "os" "time" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -130,9 +131,13 @@ func scheduleExportCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("ERROR: Message export job failed. Please check the server logs") } else { CommandPrettyPrintln("SUCCESS: Message export job complete") + + auditRec := a.MakeAuditRecord("scheduleExport", audit.Success) + auditRec.AddMeta("format", format) + auditRec.AddMeta("start", startTime) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -162,6 +167,11 @@ func buildExportCmdF(format string) func(command *cobra.Command, args []string) } CommandPrettyPrintln("SUCCESS: Your data was exported.") + auditRec := a.MakeAuditRecord("buildExport", audit.Success) + auditRec.AddMeta("format", format) + auditRec.AddMeta("start", startTime) + a.LogAuditRec(auditRec, nil) + return nil } } @@ -199,5 +209,10 @@ func bulkExportCmdF(command *cobra.Command, args []string) error { return err } + auditRec := a.MakeAuditRecord("bulkExport", audit.Success) + auditRec.AddMeta("all_teams", allTeams) + auditRec.AddMeta("file", args[0]) + a.LogAuditRec(auditRec, nil) + return nil } diff --git a/cmd/mattermost/commands/group.go b/cmd/mattermost/commands/group.go index 9a3dccca8c..767e58e821 100644 --- a/cmd/mattermost/commands/group.go +++ b/cmd/mattermost/commands/group.go @@ -6,6 +6,7 @@ package commands import ( "fmt" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/pkg/errors" @@ -146,6 +147,10 @@ func channelGroupEnableCmdF(command *cobra.Command, args []string) error { return appErr } + auditRec := a.MakeAuditRecord("channelGroupEnable", audit.Success) + auditRec.AddMeta("channel", channel) + a.LogAuditRec(auditRec, nil) + return nil } @@ -166,6 +171,10 @@ func channelGroupDisableCmdF(command *cobra.Command, args []string) error { return appErr } + auditRec := a.MakeAuditRecord("channelGroupDisable", audit.Success) + auditRec.AddMeta("channel", channel) + a.LogAuditRec(auditRec, nil) + return nil } @@ -240,6 +249,10 @@ func teamGroupEnableCmdF(command *cobra.Command, args []string) error { return appErr } + auditRec := a.MakeAuditRecord("teamGroupEnable", audit.Success) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) + return nil } @@ -260,6 +273,10 @@ func teamGroupDisableCmdF(command *cobra.Command, args []string) error { return appErr } + auditRec := a.MakeAuditRecord("teamGroupDisable", audit.Success) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) + return nil } diff --git a/cmd/mattermost/commands/import.go b/cmd/mattermost/commands/import.go index 271714a9e3..29e6954c15 100644 --- a/cmd/mattermost/commands/import.go +++ b/cmd/mattermost/commands/import.go @@ -9,6 +9,7 @@ import ( "fmt" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/spf13/cobra" ) @@ -87,6 +88,11 @@ func slackImportCmdF(command *cobra.Command, args []string) error { CommandPrettyPrintln("Finished Slack Import.") CommandPrettyPrintln("") + auditRec := a.MakeAuditRecord("slackImport", audit.Success) + auditRec.AddMeta("team", team) + auditRec.AddMeta("file", args[1]) + a.LogAuditRec(auditRec, nil) + return nil } @@ -147,6 +153,9 @@ func bulkImportCmdF(command *cobra.Command, args []string) error { if apply { CommandPrettyPrintln("Finished Bulk Import.") + auditRec := a.MakeAuditRecord("bulkImport", audit.Success) + auditRec.AddMeta("file", args[0]) + a.LogAuditRec(auditRec, nil) } else { CommandPrettyPrintln("Validation complete. You can now perform the import by rerunning this command with the --apply flag.") } diff --git a/cmd/mattermost/commands/jobserver.go b/cmd/mattermost/commands/jobserver.go index bf548b2369..8ff3282e21 100644 --- a/cmd/mattermost/commands/jobserver.go +++ b/cmd/mattermost/commands/jobserver.go @@ -8,6 +8,7 @@ import ( "os/signal" "syscall" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/viper" "github.com/spf13/cobra" @@ -55,6 +56,11 @@ func jobserverCmdF(command *cobra.Command, args []string) error { defer a.Srv().Jobs.StopSchedulers() } + if !noJobs || !noSchedule { + auditRec := a.MakeAuditRecord("jobServer", audit.Success) + a.LogAuditRec(auditRec, nil) + } + signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) <-signalChan diff --git a/cmd/mattermost/commands/ldap.go b/cmd/mattermost/commands/ldap.go index 305db32c16..6265529f8b 100644 --- a/cmd/mattermost/commands/ldap.go +++ b/cmd/mattermost/commands/ldap.go @@ -4,6 +4,7 @@ package commands import ( + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/spf13/cobra" ) @@ -51,6 +52,8 @@ func ldapSyncCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("ERROR: AD/LDAP Synchronization please check the server logs") } else { CommandPrettyPrintln("SUCCESS: AD/LDAP Synchronization Complete") + auditRec := a.MakeAuditRecord("ldapSync", audit.Success) + a.LogAuditRec(auditRec, nil) } } @@ -70,6 +73,8 @@ func ldapIdMigrateCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("ERROR: AD/LDAP IdAttribute migration failed! Error: " + err.Error()) } else { CommandPrettyPrintln("SUCCESS: AD/LDAP IdAttribute migration complete. You can now change your IdAttribute to: " + toAttribute) + auditRec := a.MakeAuditRecord("ldapMigrate", audit.Success) + a.LogAuditRec(auditRec, nil) } } diff --git a/cmd/mattermost/commands/license.go b/cmd/mattermost/commands/license.go index 77946c4073..c6661e7bc0 100644 --- a/cmd/mattermost/commands/license.go +++ b/cmd/mattermost/commands/license.go @@ -7,6 +7,7 @@ import ( "errors" "io/ioutil" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/spf13/cobra" ) @@ -50,5 +51,9 @@ func uploadLicenseCmdF(command *cobra.Command, args []string) error { CommandPrettyPrintln("Uploaded license file") + auditRec := a.MakeAuditRecord("uploadLicense", audit.Success) + auditRec.AddMeta("file", args[0]) + a.LogAuditRec(auditRec, nil) + return nil } diff --git a/cmd/mattermost/commands/permissions.go b/cmd/mattermost/commands/permissions.go index 4e992df789..502aa7a2b8 100644 --- a/cmd/mattermost/commands/permissions.go +++ b/cmd/mattermost/commands/permissions.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/cobra" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/utils" ) @@ -86,6 +87,9 @@ func resetPermissionsCmdF(command *cobra.Command, args []string) error { CommandPrettyPrintln("Changes will take effect gradually as the server caches expire.") CommandPrettyPrintln("For the changes to take effect immediately, go to the Mattermost System Console > General > Configuration and click \"Purge All Caches\".") + auditRec := a.MakeAuditRecord("resetPermissions", audit.Success) + a.LogAuditRec(auditRec, nil) + return nil } @@ -104,6 +108,9 @@ func exportPermissionsCmdF(command *cobra.Command, args []string) error { return errors.New(err.Error()) } + auditRec := a.MakeAuditRecord("exportPermissions", audit.Success) + a.LogAuditRec(auditRec, nil) + return nil } @@ -124,5 +131,9 @@ func importPermissionsCmdF(command *cobra.Command, args []string) error { } defer file.Close() + auditRec := a.MakeAuditRecord("importPermissions", audit.Success) + auditRec.AddMeta("file", args[0]) + a.LogAuditRec(auditRec, nil) + return a.ImportPermissions(file) } diff --git a/cmd/mattermost/commands/plugin.go b/cmd/mattermost/commands/plugin.go index dd3e241615..b8a46f62c9 100644 --- a/cmd/mattermost/commands/plugin.go +++ b/cmd/mattermost/commands/plugin.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -122,10 +123,12 @@ func pluginAddCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to add plugin: " + args[i] + ". Error: " + err.Error()) } else { CommandPrettyPrintln("Added plugin: " + plugin) + auditRec := a.MakeAuditRecord("pluginAdd", audit.Success) + auditRec.AddMeta("plugin", plugin) + a.LogAuditRec(auditRec, nil) } fileReader.Close() } - return nil } @@ -145,9 +148,11 @@ func pluginDeleteCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to delete plugin: " + plugin + ". Error: " + err.Error()) } else { CommandPrettyPrintln("Deleted plugin: " + plugin) + auditRec := a.MakeAuditRecord("pluginDelete", audit.Success) + auditRec.AddMeta("plugin", plugin) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -167,9 +172,11 @@ func pluginEnableCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to enable plugin: " + plugin + ". Error: " + err.Error()) } else { CommandPrettyPrintln("Enabled plugin: " + plugin) + auditRec := a.MakeAuditRecord("pluginEnable", audit.Success) + auditRec.AddMeta("plugin", plugin) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -189,9 +196,11 @@ func pluginDisableCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to disable plugin: " + plugin + ". Error: " + err.Error()) } else { CommandPrettyPrintln("Disabled plugin: " + plugin) + auditRec := a.MakeAuditRecord("pluginDisable", audit.Success) + auditRec.AddMeta("plugin", plugin) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -277,9 +286,11 @@ func pluginAddPublicKeyCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to add public key: " + pkFile + ". Error: " + err.Error()) } else { CommandPrettyPrintln("Added public key: " + pkFile) + auditRec := a.MakeAuditRecord("pluginAddPublicKey", audit.Success) + auditRec.AddMeta("file", pkFile) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -299,8 +310,10 @@ func pluginDeletePublicKeyCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to delete public key: " + pkFile + ". Error: " + err.Error()) } else { CommandPrettyPrintln("Deleted public key: " + pkFile) + auditRec := a.MakeAuditRecord("pluginDeletePublicKey", audit.Success) + auditRec.AddMeta("file", pkFile) + a.LogAuditRec(auditRec, nil) } } - return nil } diff --git a/cmd/mattermost/commands/reset.go b/cmd/mattermost/commands/reset.go index a9494e24ac..569a81f395 100644 --- a/cmd/mattermost/commands/reset.go +++ b/cmd/mattermost/commands/reset.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/spf13/cobra" ) @@ -49,5 +50,8 @@ func resetCmdF(command *cobra.Command, args []string) error { a.Srv().Store.DropAllTables() CommandPrettyPrintln("Database successfully reset") + auditRec := a.MakeAuditRecord("reset", audit.Success) + a.LogAuditRec(auditRec, nil) + return nil } diff --git a/cmd/mattermost/commands/roles.go b/cmd/mattermost/commands/roles.go index ff9a61f652..cab364be83 100644 --- a/cmd/mattermost/commands/roles.go +++ b/cmd/mattermost/commands/roles.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" ) @@ -78,11 +79,16 @@ func makeSystemAdminCmdF(command *cobra.Command, args []string) error { roles = append(roles, model.SYSTEM_ADMIN_ROLE_ID) } - if _, err := a.UpdateUserRoles(user.Id, strings.Join(roles, " "), true); err != nil { - return err + updatedUser, errUpdate := a.UpdateUserRoles(user.Id, strings.Join(roles, " "), true) + if errUpdate != nil { + return errUpdate } - } + auditRec := a.MakeAuditRecord("makeSystemAdmin", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("update", updatedUser) + a.LogAuditRec(auditRec, nil) + } return nil } @@ -122,10 +128,15 @@ func makeMemberCmdF(command *cobra.Command, args []string) error { newRoles = append(roles, model.SYSTEM_USER_ROLE_ID) } - if _, err := a.UpdateUserRoles(user.Id, strings.Join(newRoles, " "), true); err != nil { - return err + updatedUser, errUpdate := a.UpdateUserRoles(user.Id, strings.Join(newRoles, " "), true) + if errUpdate != nil { + return errUpdate } - } + auditRec := a.MakeAuditRecord("makeMember", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("update", updatedUser) + a.LogAuditRec(auditRec, nil) + } return nil } diff --git a/cmd/mattermost/commands/sampledata.go b/cmd/mattermost/commands/sampledata.go index d11bca2dc4..8fb3efd229 100644 --- a/cmd/mattermost/commands/sampledata.go +++ b/cmd/mattermost/commands/sampledata.go @@ -17,6 +17,7 @@ import ( "github.com/icrowley/fake" "github.com/mattermost/mattermost-server/v5/app" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/utils" "github.com/spf13/cobra" @@ -365,10 +366,15 @@ func sampleDataCmdF(command *cobra.Command, args []string) error { if err != nil { return errors.New("Unable to read correctly the temporary file.") } + + var importErr *model.AppError importErr, lineNumber := a.BulkImport(bulkFile, false, workers) if importErr != nil { return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber) } + auditRec := a.MakeAuditRecord("sampleData", audit.Success) + auditRec.AddMeta("file", bulkFile.Name()) + a.LogAuditRec(auditRec, nil) } else if bulk != "-" { err := bulkFile.Close() if err != nil { diff --git a/cmd/mattermost/commands/team.go b/cmd/mattermost/commands/team.go index d954713d08..0962f3d852 100644 --- a/cmd/mattermost/commands/team.go +++ b/cmd/mattermost/commands/team.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/mattermost/mattermost-server/v5/app" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/spf13/cobra" ) @@ -169,10 +170,15 @@ func createTeamCmdF(command *cobra.Command, args []string) error { Type: teamType, } - if _, err := a.CreateTeam(team); err != nil { - return errors.New("Team creation failed: " + err.Error()) + createdTeam, errCreate := a.CreateTeam(team) + if errCreate != nil { + return errors.New("Team creation failed: " + errCreate.Error()) } + auditRec := a.MakeAuditRecord("createTeam", audit.Success) + auditRec.AddMeta("team", createdTeam) + a.LogAuditRec(auditRec, nil) + return nil } @@ -203,7 +209,13 @@ func removeUserFromTeam(a *app.App, team *model.Team, user *model.User, userArg } if err := a.LeaveTeam(team, user, ""); err != nil { CommandPrintErrorln("Unable to remove '" + userArg + "' from " + team.Name + ". Error: " + err.Error()) + return } + + auditRec := a.MakeAuditRecord("removeUserFromTeam", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) } func addUsersCmdF(command *cobra.Command, args []string) error { @@ -222,7 +234,6 @@ func addUsersCmdF(command *cobra.Command, args []string) error { for i, user := range users { addUserToTeam(a, team, user, args[i+1]) } - return nil } @@ -233,7 +244,13 @@ func addUserToTeam(a *app.App, team *model.Team, user *model.User, userArg strin } if err := a.JoinUserToTeam(team, user, ""); err != nil { CommandPrintErrorln("Unable to add '" + userArg + "' to " + team.Name) + return } + + auditRec := a.MakeAuditRecord("addUserToTeam", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) } func deleteTeamsCmdF(command *cobra.Command, args []string) error { @@ -269,9 +286,12 @@ func deleteTeamsCmdF(command *cobra.Command, args []string) error { CommandPrintErrorln("Unable to delete team '" + team.Name + "' error: " + err.Error()) } else { CommandPrettyPrintln("Deleted team '" + team.Name + "'") + + auditRec := a.MakeAuditRecord("deleteTeams", audit.Success) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -349,6 +369,10 @@ func restoreTeamsCmdF(command *cobra.Command, args []string) error { err := a.RestoreTeam(team.Id) if err != nil { CommandPrintErrorln("Unable to restore team '" + team.Name + "' error: " + err.Error()) + } else { + auditRec := a.MakeAuditRecord("restoreTeams", audit.Success) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) } } return nil @@ -385,9 +409,12 @@ func archiveTeamCmdF(command *cobra.Command, args []string) error { } if err := a.SoftDeleteTeam(team.Id); err != nil { CommandPrintErrorln("Unable to archive team '"+team.Name+"' error: ", err) + } else { + auditRec := a.MakeAuditRecord("archiveTeam", audit.Success) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) } } - return nil } @@ -418,11 +445,16 @@ func renameTeamCmdF(command *cobra.Command, args []string) error { return errdn } - _, errrt := a.RenameTeam(team, newTeamName, newDisplayName) + updatedTeam, errrt := a.RenameTeam(team, newTeamName, newDisplayName) if errrt != nil { CommandPrintErrorln("Unable to rename team to '"+newTeamName+"' error: ", errrt) } + auditRec := a.MakeAuditRecord("renameTeam", audit.Success) + auditRec.AddMeta("team", team) + auditRec.AddMeta("update", updatedTeam) + a.LogAuditRec(auditRec, nil) + return nil } @@ -457,5 +489,11 @@ func modifyTeamCmdF(command *cobra.Command, args []string) error { return errors.New("Failed to update privacy for team" + args[0]) } + auditRec := a.MakeAuditRecord("modifyTeam", audit.Success) + auditRec.AddMeta("team", team) + auditRec.AddMeta("type", team.Type) + auditRec.AddMeta("allow_open_invite", team.AllowOpenInvite) + a.LogAuditRec(auditRec, nil) + return nil } diff --git a/cmd/mattermost/commands/user.go b/cmd/mattermost/commands/user.go index fe19d51377..08ed43941d 100644 --- a/cmd/mattermost/commands/user.go +++ b/cmd/mattermost/commands/user.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/mattermost/mattermost-server/v5/app" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/spf13/cobra" ) @@ -303,10 +304,16 @@ func changeUserActiveStatus(a *app.App, user *model.User, userArg string, activa if user.IsSSOUser() { fmt.Println("You must also deactivate this user in the SSO provider or they will be reactivated on next login or sync.") } - if _, err := a.UpdateActive(user, activate); err != nil { + updatedUser, err := a.UpdateActive(user, activate) + if err != nil { return fmt.Errorf("Unable to change activation status of user: %v", userArg) } + auditRec := a.MakeAuditRecord("changeActiveUserStatus", audit.Success) + auditRec.AddMeta("user", updatedUser) + auditRec.AddMeta("activate", activate) + a.LogAuditRec(auditRec, nil) + return nil } @@ -388,6 +395,11 @@ func userCreateCmdF(command *cobra.Command, args []string) error { CommandPrettyPrintln("email: " + ruser.Email) CommandPrettyPrintln("auth_service: " + ruser.AuthService) + auditRec := a.MakeAuditRecord("userCreate", audit.Success) + auditRec.AddMeta("user", ruser) + auditRec.AddMeta("system_admin", systemAdmin) + a.LogAuditRec(auditRec, nil) + return nil } @@ -406,6 +418,10 @@ func usersToBots(args []string, a *app.App) { } CommandPrettyPrintln(fmt.Sprintf("User %s is converted to bot successfully", bot.UserId)) + + auditRec := a.MakeAuditRecord("userToBot", audit.Success) + auditRec.AddMeta("user", user) + a.LogAuditRec(auditRec, nil) } } @@ -526,6 +542,12 @@ func botToUser(command *cobra.Command, args []string, a *app.App) error { CommandPrettyPrintln("last_name: " + user.LastName) CommandPrettyPrintln("roles: " + user.Roles) CommandPrettyPrintln("locale: " + user.Locale) + + auditRec := a.MakeAuditRecord("botToUser", audit.Success) + auditRec.AddMeta("bot", user) + auditRec.AddMeta("user", user) + a.LogAuditRec(auditRec, nil) + return nil } @@ -604,6 +626,11 @@ func inviteUser(a *app.App, email string, team *model.Team, teamArg string) erro a.SendInviteEmails(team, "Administrator", "Mattermost CLI "+model.NewId(), invites, *a.Config().ServiceSettings.SiteURL) CommandPrettyPrintln("Invites may or may not have been sent.") + auditRec := a.MakeAuditRecord("inviteUser", audit.Success) + auditRec.AddMeta("email", email) + auditRec.AddMeta("team", team) + a.LogAuditRec(auditRec, nil) + return nil } @@ -628,6 +655,10 @@ func resetUserPasswordCmdF(command *cobra.Command, args []string) error { return err } + auditRec := a.MakeAuditRecord("resetUserPassword", audit.Success) + auditRec.AddMeta("user", user) + a.LogAuditRec(auditRec, nil) + return nil } @@ -663,6 +694,11 @@ func updateUserEmailCmdF(command *cobra.Command, args []string) error { return errors.New(errUpdate.Message) } + auditRec := a.MakeAuditRecord("updateUserEmail", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("email", newEmail) + a.LogAuditRec(auditRec, nil) + return nil } @@ -678,7 +714,6 @@ func resetUserMfaCmdF(command *cobra.Command, args []string) error { } users := getUsersFromUserArgs(a, args) - for i, user := range users { if user == nil { return errors.New("Unable to find user '" + args[i] + "'") @@ -687,6 +722,10 @@ func resetUserMfaCmdF(command *cobra.Command, args []string) error { if err := a.DeactivateMfa(user.Id); err != nil { return err } + + auditRec := a.MakeAuditRecord("resetUserMfa", audit.Success) + auditRec.AddMeta("user", user) + a.LogAuditRec(auditRec, nil) } return nil @@ -735,6 +774,11 @@ func deleteUserCmdF(command *cobra.Command, args []string) error { return err } } + + auditRec := a.MakeAuditRecord("deleteUser", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("isBot", user.IsBot) + a.LogAuditRec(auditRec, nil) } return nil @@ -770,9 +814,11 @@ func deleteAllUsersCommandF(command *cobra.Command, args []string) error { if err := a.PermanentDeleteAllUsers(); err != nil { return err } - CommandPrettyPrintln("All user accounts successfully deleted.") + auditRec := a.MakeAuditRecord("deleteAllUsers", audit.Success) + a.LogAuditRec(auditRec, nil) + return nil } @@ -815,8 +861,15 @@ func migrateAuthToLdapCmdF(command *cobra.Command, args []string) error { } CommandPrettyPrintln("Successfully migrated accounts.") - } + if !dryRunFlag { + auditRec := a.MakeAuditRecord("migrateAuthToLdap", audit.Success) + auditRec.AddMeta("fromAuth", fromAuth) + auditRec.AddMeta("matchField", matchField) + auditRec.AddMeta("force", forceFlag) + a.LogAuditRec(auditRec, nil) + } + } return nil } @@ -871,8 +924,13 @@ func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error { } CommandPrettyPrintln("Successfully migrated accounts.") - } + if !dryRunFlag { + auditRec := a.MakeAuditRecord("migrateAuthToSaml", audit.Success) + auditRec.AddMeta("auto", autoFlag) + a.LogAuditRec(auditRec, nil) + } + } return nil } diff --git a/cmd/mattermost/commands/webhook.go b/cmd/mattermost/commands/webhook.go index 549654940a..031cf8b13e 100644 --- a/cmd/mattermost/commands/webhook.go +++ b/cmd/mattermost/commands/webhook.go @@ -8,6 +8,7 @@ import ( "net/http" "strings" + "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/store" "github.com/pkg/errors" @@ -194,10 +195,16 @@ func createIncomingWebhookCmdF(command *cobra.Command, args []string) error { CommandPrettyPrintln("Id: " + createdIncoming.Id) CommandPrettyPrintln("Display Name: " + createdIncoming.DisplayName) + auditRec := app.MakeAuditRecord("createIncomingWebhook", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("channel", channel) + auditRec.AddMeta("hook", createdIncoming) + app.LogAuditRec(auditRec, nil) + return nil } -func modifyIncomingWebhookCmdF(command *cobra.Command, args []string) error { +func modifyIncomingWebhookCmdF(command *cobra.Command, args []string) (cmdError error) { app, err := InitDBCommandContextCobra(command) if err != nil { return err @@ -216,6 +223,10 @@ func modifyIncomingWebhookCmdF(command *cobra.Command, args []string) error { updatedHook := oldHook + auditRec := app.MakeAuditRecord("createIncomingWebhook", audit.Fail) + defer func() { app.LogAuditRec(auditRec, cmdError) }() + auditRec.AddMeta("hook", oldHook) + channelArg, _ := command.Flags().GetString("channel") if channelArg != "" { channel := getChannelFromChannelArg(app, channelArg) @@ -240,10 +251,14 @@ func modifyIncomingWebhookCmdF(command *cobra.Command, args []string) error { channelLocked, _ := command.Flags().GetBool("lock-to-channel") updatedHook.ChannelLocked = channelLocked - if _, err := app.UpdateIncomingWebhook(oldHook, updatedHook); err != nil { - return err + updatedIncomingHook, errUpdated := app.UpdateIncomingWebhook(oldHook, updatedHook) + if errUpdated != nil { + return errUpdated } + auditRec.Success() + auditRec.AddMeta("update", updatedIncomingHook) + return nil } @@ -313,9 +328,10 @@ func createOutgoingWebhookCmdF(command *cobra.Command, args []string) error { IconURL: iconURL, } + var channel *model.Channel channelArg, _ := command.Flags().GetString("channel") if channelArg != "" { - channel := getChannelFromChannelArg(app, channelArg) + channel = getChannelFromChannelArg(app, channelArg) if channel != nil { outgoingWebhook.ChannelId = channel.Id } @@ -329,6 +345,14 @@ func createOutgoingWebhookCmdF(command *cobra.Command, args []string) error { CommandPrettyPrintln("Id: " + createdOutgoing.Id) CommandPrettyPrintln("Display Name: " + createdOutgoing.DisplayName) + auditRec := app.MakeAuditRecord("createOutgoingWebhook", audit.Success) + auditRec.AddMeta("user", user) + auditRec.AddMeta("hook", createdOutgoing) + if channel != nil { + auditRec.AddMeta("channel", channel) + } + app.LogAuditRec(auditRec, nil) + return nil } @@ -409,10 +433,16 @@ func modifyOutgoingWebhookCmdF(command *cobra.Command, args []string) error { updatedHook.CallbackURLs = callbackURLs } - if _, appErr := app.UpdateOutgoingWebhook(oldHook, updatedHook); appErr != nil { + updatedWebhook, appErr := app.UpdateOutgoingWebhook(oldHook, updatedHook) + if appErr != nil { return appErr } + auditRec := app.MakeAuditRecord("modifyOutgoingWebhook", audit.Success) + auditRec.AddMeta("hook", oldHook) + auditRec.AddMeta("update", updatedWebhook) + app.LogAuditRec(auditRec, nil) + return nil } @@ -435,6 +465,10 @@ func deleteWebhookCmdF(command *cobra.Command, args []string) error { return errors.New("Unable to delete webhook '" + webhookId + "'") } + auditRec := app.MakeAuditRecord("deleteWebhook", audit.Success) + auditRec.AddMeta("hook_id", webhookId) + app.LogAuditRec(auditRec, nil) + return nil } @@ -458,7 +492,7 @@ func showWebhookCmdF(command *cobra.Command, args []string) error { return errors.New("Webhook with id " + webhookId + " not found") } -func moveOutgoingWebhookCmd(command *cobra.Command, args []string) error { +func moveOutgoingWebhookCmd(command *cobra.Command, args []string) (cmdError error) { app, err := InitDBCommandContextCobra(command) if err != nil { return err @@ -484,6 +518,10 @@ func moveOutgoingWebhookCmd(command *cobra.Command, args []string) error { return appError } + auditRec := app.MakeAuditRecord("moveOutgoingWebhook", audit.Fail) + defer func() { app.LogAuditRec(auditRec, cmdError) }() + auditRec.AddMeta("hook", webhook) + channelName, channelErr := command.Flags().GetString("channel") if channelErr != nil { return channelErr @@ -507,10 +545,14 @@ func moveOutgoingWebhookCmd(command *cobra.Command, args []string) error { webhook.Id = "" webhook.TeamId = newTeamId - _, createErr := app.CreateOutgoingWebhook(webhook) + updatedWebHook, createErr := app.CreateOutgoingWebhook(webhook) if createErr != nil { return model.NewAppError("moveOutgoingWebhookCmd", "cli.outgoing_webhook.inconsistent_state.app_error", nil, "", http.StatusInternalServerError) } + + auditRec.Success() + auditRec.AddMeta("update", updatedWebHook) + return nil } diff --git a/go.mod b/go.mod index a9dd7c62b4..fb6b8ba48c 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 github.com/fatih/color v1.9.0 // indirect github.com/fortytw2/leaktest v1.3.0 // indirect + github.com/francoispqt/gojay v1.2.13 github.com/fsnotify/fsnotify v1.4.7 github.com/go-gorp/gorp v2.0.0+incompatible // indirect github.com/go-sql-driver/mysql v1.5.0 @@ -43,7 +44,7 @@ require ( github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 github.com/jmoiron/sqlx v1.2.0 - github.com/jonboulle/clockwork v0.1.0 + github.com/jonboulle/clockwork v0.1.0 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/lib/pq v1.3.0 github.com/magiconair/properties v1.8.1 // indirect @@ -69,9 +70,9 @@ require ( github.com/pelletier/go-toml v1.6.0 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.4.0 - github.com/prometheus/client_model v0.2.0 + github.com/prometheus/client_model v0.2.0 // indirect github.com/rs/cors v1.7.0 - github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 + github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd github.com/segmentio/analytics-go v3.1.0+incompatible github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect diff --git a/go.sum b/go.sum index 3e0bfc796b..c682d5ec91 100644 --- a/go.sum +++ b/go.sum @@ -282,6 +282,7 @@ github.com/mattermost/gosaml2 v0.3.2 h1:kq2dY5qUe6fPPHra171GVlgo+ycBsEog0gZMetxL github.com/mattermost/gosaml2 v0.3.2/go.mod h1:Z429EIOiEi9kbq6yHoApfzlcXpa6dzRDc6pO+Vy2Ksk= github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d h1:2DV7VIlEv6J5R5o6tUcb3ZMKJYeeZuWZL7Rv1m23TgQ= github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= +github.com/mattermost/mattermost-server v5.11.1+incompatible h1:LPzKY0+2Tic/ik67qIg6VrydRCgxNXZQXOeaiJ2rMBY= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs= github.com/mattermost/viper v1.0.4 h1:cMYOz4PhguscGSPxrSokUtib5HrG4gCpiUh27wyA3d0= diff --git a/model/auditconv.go b/model/auditconv.go new file mode 100644 index 0000000000..2b40200e6e --- /dev/null +++ b/model/auditconv.go @@ -0,0 +1,663 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package model + +import "github.com/francoispqt/gojay" + +// AuditModelTypeConv converts key model types to something better suited for audit output. +func AuditModelTypeConv(val interface{}) (newVal interface{}, converted bool) { + if val == nil { + return nil, false + } + switch v := val.(type) { + case *Channel: + return newAuditChannel(v), true + case *Team: + return newAuditTeam(v), true + case *User: + return newAuditUser(v), true + case *Command: + return newAuditCommand(v), true + case *CommandArgs: + return newAuditCommandArgs(v), true + case *Bot: + return newAuditBot(v), true + case *ChannelModerationPatch: + return newAuditChannelModerationPatch(v), true + case *Emoji: + return newAuditEmoji(v), true + case *FileInfo: + return newAuditFileInfo(v), true + case *Group: + return newAuditGroup(v), true + case *Job: + return newAuditJob(v), true + case *OAuthApp: + return newAuditOAuthApp(v), true + case *Post: + return newAuditPost(v), true + case *Role: + return newAuditRole(v), true + case *Scheme: + return newAuditScheme(v), true + case *SchemeRoles: + return newAuditSchemeRoles(v), true + case *Session: + return newAuditSession(v), true + case *IncomingWebhook: + return newAuditIncomingWebhook(v), true + case *OutgoingWebhook: + return newAuditOutgoingWebhook(v), true + } + return val, false +} + +type auditChannel struct { + ID string + Name string + Type string +} + +// newAuditChannel creates a simplified representation of Channel for output to audit log. +func newAuditChannel(c *Channel) auditChannel { + var channel auditChannel + if c != nil { + channel.ID = c.Id + channel.Name = c.Name + channel.Type = c.Type + } + return channel +} + +func (c auditChannel) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", c.ID) + enc.StringKey("name", c.Name) + enc.StringKey("type", c.Type) +} + +func (c auditChannel) IsNil() bool { + return false +} + +type auditTeam struct { + ID string + Name string + Type string +} + +// newAuditTeam creates a simplified representation of Team for output to audit log. +func newAuditTeam(t *Team) auditTeam { + var team auditTeam + if t != nil { + team.ID = t.Id + team.Name = t.Name + team.Type = t.Type + } + return team +} + +func (t auditTeam) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", t.ID) + enc.StringKey("name", t.Name) + enc.StringKey("type", t.Type) +} + +func (t auditTeam) IsNil() bool { + return false +} + +type auditUser struct { + ID string + Name string + Roles string +} + +// newAuditUser creates a simplified representation of User for output to audit log. +func newAuditUser(u *User) auditUser { + var user auditUser + if u != nil { + user.ID = u.Id + user.Name = u.Username + user.Roles = u.Roles + } + return user +} + +func (u auditUser) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", u.ID) + enc.StringKey("name", u.Name) + enc.StringKey("roles", u.Roles) +} + +func (u auditUser) IsNil() bool { + return false +} + +type auditCommand struct { + ID string + CreatorID string + TeamID string + Trigger string + Method string + Username string + IconURL string + AutoComplete bool + AutoCompleteDesc string + AutoCompleteHint string + DisplayName string + Description string + URL string +} + +// newAuditCommand creates a simplified representation of Command for output to audit log. +func newAuditCommand(c *Command) auditCommand { + var cmd auditCommand + if c != nil { + cmd.ID = c.Id + cmd.CreatorID = c.CreatorId + cmd.TeamID = c.TeamId + cmd.Trigger = c.Trigger + cmd.Method = c.Method + cmd.Username = c.Username + cmd.IconURL = c.IconURL + cmd.AutoComplete = c.AutoComplete + cmd.AutoCompleteDesc = c.AutoCompleteDesc + cmd.AutoCompleteHint = c.AutoCompleteHint + cmd.DisplayName = c.DisplayName + cmd.Description = c.Description + cmd.URL = c.URL + } + return cmd +} + +func (cmd auditCommand) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", cmd.ID) + enc.StringKey("creator_id", cmd.CreatorID) + enc.StringKey("team_id", cmd.TeamID) + enc.StringKey("trigger", cmd.Trigger) + enc.StringKey("method", cmd.Method) + enc.StringKey("username", cmd.Username) + enc.StringKey("icon_url", cmd.IconURL) + enc.BoolKey("auto_complete", cmd.AutoComplete) + enc.StringKey("auto_complete_desc", cmd.AutoCompleteDesc) + enc.StringKey("auto_complete_hint", cmd.AutoCompleteHint) + enc.StringKey("display", cmd.DisplayName) + enc.StringKey("desc", cmd.Description) + enc.StringKey("url", cmd.URL) +} + +func (cmd auditCommand) IsNil() bool { + return false +} + +type auditCommandArgs struct { + ChannelID string + TeamID string + TriggerID string + Command string +} + +// newAuditCommandArgs creates a simplified representation of CommandArgs for output to audit log. +func newAuditCommandArgs(ca *CommandArgs) auditCommandArgs { + var cmdargs auditCommandArgs + if ca != nil { + cmdargs.ChannelID = ca.ChannelId + cmdargs.TeamID = ca.TeamId + cmdargs.TriggerID = ca.TriggerId + cmdargs.Command = ca.Command + } + return cmdargs +} + +func (ca auditCommandArgs) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("channel_id", ca.ChannelID) + enc.StringKey("team_id", ca.TriggerID) + enc.StringKey("trigger_id", ca.TeamID) + enc.StringKey("command", ca.Command) +} + +func (ca auditCommandArgs) IsNil() bool { + return false +} + +type auditBot struct { + UserID string + Username string + Displayname string +} + +// newAuditBot creates a simplified representation of Bot for output to audit log. +func newAuditBot(b *Bot) auditBot { + var bot auditBot + if b != nil { + bot.UserID = b.UserId + bot.Username = b.Username + bot.Displayname = b.DisplayName + } + return bot +} + +func (b auditBot) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("user_id", b.UserID) + enc.StringKey("username", b.Username) + enc.StringKey("display", b.Displayname) +} + +func (b auditBot) IsNil() bool { + return false +} + +type auditChannelModerationPatch struct { + Name string + RoleGuests bool + RoleMembers bool +} + +// newAuditChannelModerationPatch creates a simplified representation of ChannelModerationPatch for output to audit log. +func newAuditChannelModerationPatch(p *ChannelModerationPatch) auditChannelModerationPatch { + var patch auditChannelModerationPatch + if p != nil { + if p.Name != nil { + patch.Name = *p.Name + } + if p.Roles.Guests != nil { + patch.RoleGuests = *p.Roles.Guests + } + if p.Roles.Members != nil { + patch.RoleMembers = *p.Roles.Members + } + } + return patch +} + +func (p auditChannelModerationPatch) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("name", p.Name) + enc.BoolKey("role_guests", p.RoleGuests) + enc.BoolKey("role_members", p.RoleMembers) +} + +func (p auditChannelModerationPatch) IsNil() bool { + return false +} + +type auditEmoji struct { + ID string + Name string +} + +// newAuditEmoji creates a simplified representation of Emoji for output to audit log. +func newAuditEmoji(e *Emoji) auditEmoji { + var emoji auditEmoji + if e != nil { + emoji.ID = e.Id + emoji.Name = e.Name + } + return emoji +} + +func (e auditEmoji) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", e.ID) + enc.StringKey("name", e.Name) +} + +func (e auditEmoji) IsNil() bool { + return false +} + +type auditFileInfo struct { + ID string + PostID string + Path string + Name string + Extension string + Size int64 +} + +// newAuditFileInfo creates a simplified representation of FileInfo for output to audit log. +func newAuditFileInfo(f *FileInfo) auditFileInfo { + var fi auditFileInfo + if f != nil { + fi.ID = f.Id + fi.PostID = f.PostId + fi.Path = f.Path + fi.Name = f.Name + fi.Extension = f.Extension + fi.Size = f.Size + } + return fi +} + +func (fi auditFileInfo) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", fi.ID) + enc.StringKey("post_id", fi.PostID) + enc.StringKey("path", fi.Path) + enc.StringKey("name", fi.Name) + enc.StringKey("ext", fi.Extension) + enc.Int64Key("size", fi.Size) +} + +func (fi auditFileInfo) IsNil() bool { + return false +} + +type auditGroup struct { + ID string + Name string + DisplayName string + Description string +} + +// newAuditGroup creates a simplified representation of Group for output to audit log. +func newAuditGroup(g *Group) auditGroup { + var group auditGroup + if g != nil { + group.ID = g.Id + group.Name = g.Name + group.DisplayName = g.DisplayName + group.Description = g.Description + } + return group +} + +func (g auditGroup) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", g.ID) + enc.StringKey("name", g.Name) + enc.StringKey("display", g.DisplayName) + enc.StringKey("desc", g.Description) +} + +func (g auditGroup) IsNil() bool { + return false +} + +type auditJob struct { + ID string + Type string + Priority int64 + StartAt int64 +} + +// newAuditJob creates a simplified representation of Job for output to audit log. +func newAuditJob(j *Job) auditJob { + var job auditJob + if j != nil { + job.ID = j.Id + job.Type = j.Type + job.Priority = j.Priority + job.StartAt = j.StartAt + } + return job +} + +func (j auditJob) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", j.ID) + enc.StringKey("type", j.Type) + enc.Int64Key("priority", j.Priority) + enc.Int64Key("start_at", j.StartAt) +} + +func (j auditJob) IsNil() bool { + return false +} + +type auditOAuthApp struct { + ID string + CreatorID string + Name string + Description string + IsTrusted bool +} + +// newAuditOAuthApp creates a simplified representation of OAuthApp for output to audit log. +func newAuditOAuthApp(o *OAuthApp) auditOAuthApp { + var oauth auditOAuthApp + if o != nil { + oauth.ID = o.Id + oauth.CreatorID = o.CreatorId + oauth.Name = o.Name + oauth.Description = o.Description + oauth.IsTrusted = o.IsTrusted + } + return oauth +} + +func (o auditOAuthApp) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", o.ID) + enc.StringKey("creator_id", o.CreatorID) + enc.StringKey("name", o.Name) + enc.StringKey("desc", o.Description) + enc.BoolKey("trusted", o.IsTrusted) +} + +func (o auditOAuthApp) IsNil() bool { + return false +} + +type auditPost struct { + ID string + ChannelID string + Type string + IsPinned bool +} + +// newAuditPost creates a simplified representation of Post for output to audit log. +func newAuditPost(p *Post) auditPost { + var post auditPost + if p != nil { + post.ID = p.Id + post.ChannelID = p.ChannelId + post.Type = p.Type + post.IsPinned = p.IsPinned + } + return post +} + +func (p auditPost) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", p.ID) + enc.StringKey("channel_id", p.ChannelID) + enc.StringKey("type", p.Type) + enc.BoolKey("pinned", p.IsPinned) +} + +func (p auditPost) IsNil() bool { + return false +} + +type auditRole struct { + ID string + Name string + DisplayName string + Permissions []string + SchemeManaged bool + BuiltIn bool +} + +// newAuditRole creates a simplified representation of Role for output to audit log. +func newAuditRole(r *Role) auditRole { + var role auditRole + if r != nil { + role.ID = r.Id + role.Name = r.Name + role.DisplayName = r.DisplayName + role.Permissions = append(role.Permissions, r.Permissions...) + role.SchemeManaged = r.SchemeManaged + role.BuiltIn = r.BuiltIn + } + return role +} + +func (r auditRole) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", r.ID) + enc.StringKey("name", r.Name) + enc.StringKey("display", r.DisplayName) + enc.SliceStringKey("perms", r.Permissions) + enc.BoolKey("schemeManaged", r.SchemeManaged) + enc.BoolKey("builtin", r.BuiltIn) +} + +func (r auditRole) IsNil() bool { + return false +} + +type auditScheme struct { + ID string + Name string + DisplayName string + Scope string +} + +// newAuditScheme creates a simplified representation of Scheme for output to audit log. +func newAuditScheme(s *Scheme) auditScheme { + var scheme auditScheme + if s != nil { + scheme.ID = s.Id + scheme.Name = s.Name + scheme.DisplayName = s.DisplayName + scheme.Scope = s.Scope + } + return scheme +} + +func (s auditScheme) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", s.ID) + enc.StringKey("name", s.Name) + enc.StringKey("display", s.DisplayName) + enc.StringKey("scope", s.Scope) +} + +func (s auditScheme) IsNil() bool { + return false +} + +type auditSchemeRoles struct { + SchemeAdmin bool + SchemeUser bool + SchemeGuest bool +} + +// newAuditSchemeRoles creates a simplified representation of SchemeRoles for output to audit log. +func newAuditSchemeRoles(s *SchemeRoles) auditSchemeRoles { + var roles auditSchemeRoles + if s != nil { + roles.SchemeAdmin = s.SchemeAdmin + roles.SchemeUser = s.SchemeUser + roles.SchemeGuest = s.SchemeGuest + } + return roles +} + +func (s auditSchemeRoles) MarshalJSONObject(enc *gojay.Encoder) { + enc.BoolKey("admin", s.SchemeAdmin) + enc.BoolKey("user", s.SchemeUser) + enc.BoolKey("guest", s.SchemeGuest) +} + +func (s auditSchemeRoles) IsNil() bool { + return false +} + +type auditSession struct { + ID string + UserId string + DeviceId string +} + +// newAuditSession creates a simplified representation of Session for output to audit log. +func newAuditSession(s *Session) auditSession { + var session auditSession + if s != nil { + session.ID = s.Id + session.UserId = s.UserId + session.DeviceId = s.DeviceId + } + return session +} + +func (s auditSession) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", s.ID) + enc.StringKey("user_id", s.UserId) + enc.StringKey("device_id", s.DeviceId) +} + +func (s auditSession) IsNil() bool { + return false +} + +type auditIncomingWebhook struct { + ID string + ChannelID string + TeamId string + DisplayName string + Description string +} + +// newAuditIncomingWebhook creates a simplified representation of IncomingWebhook for output to audit log. +func newAuditIncomingWebhook(h *IncomingWebhook) auditIncomingWebhook { + var hook auditIncomingWebhook + if h != nil { + hook.ID = h.Id + hook.ChannelID = h.ChannelId + hook.TeamId = h.TeamId + hook.DisplayName = h.DisplayName + hook.Description = h.Description + } + return hook +} + +func (h auditIncomingWebhook) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", h.ID) + enc.StringKey("channel_id", h.ChannelID) + enc.StringKey("team_id", h.TeamId) + enc.StringKey("display", h.DisplayName) + enc.StringKey("desc", h.Description) +} + +func (h auditIncomingWebhook) IsNil() bool { + return false +} + +type auditOutgoingWebhook struct { + ID string + ChannelID string + TeamID string + TriggerWords StringArray + TriggerWhen int + DisplayName string + Description string + ContentType string + Username string +} + +// newAuditOutgoingWebhook creates a simplified representation of OutgoingWebhook for output to audit log. +func newAuditOutgoingWebhook(h *OutgoingWebhook) auditOutgoingWebhook { + var hook auditOutgoingWebhook + if h != nil { + hook.ID = h.Id + hook.ChannelID = h.ChannelId + hook.TeamID = h.TeamId + hook.TriggerWords = h.TriggerWords + hook.TriggerWhen = h.TriggerWhen + hook.DisplayName = h.DisplayName + hook.Description = h.Description + hook.ContentType = h.ContentType + hook.Username = h.Username + } + return hook +} + +func (h auditOutgoingWebhook) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("id", h.ID) + enc.StringKey("channel_id", h.ChannelID) + enc.StringKey("team_id", h.TeamID) + enc.SliceStringKey("trigger_words", h.TriggerWords) + enc.IntKey("trigger_when", h.TriggerWhen) + enc.StringKey("display", h.DisplayName) + enc.StringKey("desc", h.Description) + enc.StringKey("content_type", h.ContentType) + enc.StringKey("username", h.Username) +} + +func (h auditOutgoingWebhook) IsNil() bool { + return false +} diff --git a/model/auditconv_test.go b/model/auditconv_test.go new file mode 100644 index 0000000000..5d2128c018 --- /dev/null +++ b/model/auditconv_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type Sample struct { + flag bool + name string +} + +func TestAuditModelTypeConv(t *testing.T) { + sample := &Sample{flag: true, name: "sample"} + sample2 := &Sample{name: "sample2"} + sampleArr := []*Sample{sample, sample2} + + user := &User{} + + type args struct { + val interface{} + } + tests := []struct { + name string + args args + wantConverted bool + wantNewVal interface{} + }{ + {name: "nil value", args: args{val: nil}, wantConverted: false, wantNewVal: nil}, + {name: "string value", args: args{val: "hello"}, wantConverted: false, wantNewVal: "hello"}, + {name: "string array", args: args{val: []string{"hello", "there"}}, wantConverted: false, wantNewVal: []string{"hello", "there"}}, + {name: "int value", args: args{val: 77}, wantConverted: false, wantNewVal: 77}, + {name: "int array", args: args{val: []int{77, 68}}, wantConverted: false, wantNewVal: []int{77, 68}}, + {name: "struct pointer value", args: args{val: sample}, wantConverted: false, wantNewVal: sample}, + {name: "struct pointer array", args: args{val: sampleArr}, wantConverted: false, wantNewVal: sampleArr}, + {name: "model user", args: args{val: user}, wantConverted: true, wantNewVal: "XXX"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotNewVal, gotConverted := AuditModelTypeConv(tt.args.val) + assert.Equal(t, tt.wantConverted, gotConverted) + if !tt.wantConverted { + assert.Equal(t, tt.wantNewVal, gotNewVal) + } + }) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 64fe062b3f..37a705c089 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -49,6 +49,7 @@ github.com/fatih/color # github.com/fortytw2/leaktest v1.3.0 ## explicit # github.com/francoispqt/gojay v1.2.13 +## explicit github.com/francoispqt/gojay # github.com/fsnotify/fsnotify v1.4.7 ## explicit diff --git a/web/context.go b/web/context.go index d3859f0612..c12e71defe 100644 --- a/web/context.go +++ b/web/context.go @@ -29,7 +29,7 @@ func (c *Context) LogAuditRec(rec *audit.Record) { c.LogAuditRecWithLevel(rec, app.RestLevel) } -// LogAuditRec logs an audit record using specificed Level. +// LogAuditRec logs an audit record using specified Level. func (c *Context) LogAuditRecWithLevel(rec *audit.Record, level audit.Level) { if rec == nil { return @@ -42,15 +42,12 @@ func (c *Context) LogAuditRecWithLevel(rec *audit.Record, level audit.Level) { } rec.Fail() } - if cid := c.App.GetClusterId(); cid != "" { - rec.AddMeta(audit.KeyClusterID, cid) - } c.App.Srv().Audit.LogRecord(level, *rec) } // MakeAuditRecord creates a audit record pre-populated with data from this context. func (c *Context) MakeAuditRecord(event string, initialStatus string) *audit.Record { - return &audit.Record{ + rec := &audit.Record{ APIPath: c.App.Path(), Event: event, Status: initialStatus, @@ -58,8 +55,11 @@ func (c *Context) MakeAuditRecord(event string, initialStatus string) *audit.Rec SessionID: c.App.Session().Id, Client: c.App.UserAgent(), IPAddress: c.App.IpAddress(), - Meta: audit.Meta{}, + Meta: audit.Meta{audit.KeyClusterID: c.App.GetClusterId()}, } + rec.AddMetaTypeConverter(model.AuditModelTypeConv) + + return rec } func (c *Context) LogAudit(extraInfo string) {