diff --git a/base/audit_events.go b/base/audit_events.go index f16bd8f7ff..4011683411 100644 --- a/base/audit_events.go +++ b/base/audit_events.go @@ -115,14 +115,16 @@ const ( AuditIDISGRAllRead AuditID = 54421 // Documents events - AuditIDDocumentCreate AuditID = 55000 - AuditIDDocumentRead AuditID = 55001 - AuditIDDocumentUpdate AuditID = 55002 - AuditIDDocumentDelete AuditID = 55003 - AuditIDDocumentMetadataRead AuditID = 55004 - AuditIDDocumentImport AuditID = 55005 - AuditIDDocumentResync AuditID = 55006 - AuditIDDocumentRevoke AuditID = 55007 + AuditIDDocumentCreate AuditID = 55000 + AuditIDDocumentRead AuditID = 55001 + AuditIDDocumentUpdate AuditID = 55002 + AuditIDDocumentDelete AuditID = 55003 + AuditIDDocumentMetadataRead AuditID = 55004 + AuditIDDocumentImport AuditID = 55005 + AuditIDDocumentResync AuditID = 55006 + AuditIDDocumentRevoke AuditID = 55007 + AuditIDDocumentChannelHistoryCompact AuditID = 55008 + // Document attachments events AuditIDAttachmentCreate AuditID = 55010 AuditIDAttachmentRead AuditID = 55011 @@ -1193,6 +1195,25 @@ var AuditEvents = events{ FilteringPermitted: true, EventType: eventTypeData, }, + AuditIDDocumentChannelHistoryCompact: { + Name: "Document Channel history compact", + Description: "A document channel history was compacted by the Administrator", + MandatoryFields: AuditFields{ + AuditFieldDocID: "document id", + AuditFieldChannels: []string{"list", "of", "channels"}, + AuditFieldSequence: "sequence", + }, + mandatoryFieldGroups: []fieldGroup{ + fieldGroupAuthenticated, + fieldGroupKeyspace, + }, + optionalFieldGroups: []fieldGroup{ + fieldGroupRequest, + }, + EnabledByDefault: false, + FilteringPermitted: true, + EventType: eventTypeData, + }, AuditIDAttachmentCreate: { Name: "Create attachment", Description: "A new attachment was created", diff --git a/base/audit_events_fields.go b/base/audit_events_fields.go index efea3cad48..ba1d6eea7c 100644 --- a/base/audit_events_fields.go +++ b/base/audit_events_fields.go @@ -86,6 +86,7 @@ const ( AuditFieldDocIDs = "doc_ids" AuditFieldFeedType = "feed_type" AuditFieldIncludeDocs = "include_docs" + AuditFieldSequence = "seq" // Cluster compat version freeze events 53352, 53353 AuditFieldClusterCompatVersion = "cluster_compat_version" diff --git a/rest/audit_test.go b/rest/audit_test.go index d4ddd7356b..f6b4c78f68 100644 --- a/rest/audit_test.go +++ b/rest/audit_test.go @@ -1967,6 +1967,73 @@ func TestDatabaseAuditChanges(t *testing.T) { } } +func TestDocumentChannelHistoryCompactionAudit(t *testing.T) { + rt := createAuditLoggingRestTester(t) + defer rt.Close() + + dbConfig := rt.NewDbConfig() + dbConfig.Logging = &DbLoggingConfig{ + Audit: &DbAuditLoggingConfig{ + Enabled: base.Ptr(true), + EnabledEvents: base.Ptr([]uint{ + uint(base.AuditIDDocumentChannelHistoryCompact), + }), + }, + } + RequireStatus(t, rt.CreateDatabase("db", dbConfig), http.StatusCreated) + + testCases := []struct { + name string + docIDs []string + }{ + { + name: "single doc", + docIDs: []string{"single_doc"}, + }, + { + name: "multiple docs", + docIDs: []string{"multi_doc_1", "multi_doc_2", "multi_doc_3"}, + }, + } + + for _, tc := range testCases { + rt.Run(tc.name, func(t *testing.T) { + for _, docID := range tc.docIDs { + rt.CreateTestDoc(docID) + } + + body := string(base.MustJSONMarshal(t, CompactDocChannelHistoryRequest{ + Seq: 1, + })) + output := base.AuditLogContents(t, func(t testing.TB) { + for _, docID := range tc.docIDs { + RequireStatus(t, rt.SendAdminRequest(http.MethodPost, "/{{.keyspace}}/_channel_history/"+docID+"/compact", body), http.StatusOK) + } + }) + + for _, docID := range tc.docIDs { + requireDocChannelAuditEvent(t, output, base.AuditIDDocumentChannelHistoryCompact, docID) + } + }) + } +} + +func requireDocChannelAuditEvent(t testing.TB, output []byte, eventID base.AuditID, docID string) { + t.Helper() + events := jsonLines(t, output) + countFound := 0 + for _, event := range events { + if base.AuditID(event[base.AuditFieldID].(float64)) != eventID { + continue + } + if event[base.AuditFieldDocID] == docID { + countFound++ + } + } + require.Equal(t, 1, countFound, "expected exactly 1 %s event for doc %q, got %d", + base.AuditEvents[eventID].Name, docID, countFound) +} + // getAuditLoggingTestConfig returns a logging config with audit enabled and other loggers configured without collation to avoid CBG-4129 func getAuditLoggingTestConfig(tempdir string) base.LoggingConfig { return base.LoggingConfig{ diff --git a/rest/doc_api.go b/rest/doc_api.go index 67cb2f9fd8..3c7118dcf4 100644 --- a/rest/doc_api.go +++ b/rest/doc_api.go @@ -941,6 +941,10 @@ func (h *handler) handleGetDocChannelHistory() error { return err } + base.Audit(h.ctx(), base.AuditIDDocumentMetadataRead, base.AuditFields{ + base.AuditFieldDocID: docid, + }) + h.writeJSON(chanHistory) return nil } @@ -977,6 +981,12 @@ func (h *handler) handleCompactDocChannelHistory() error { "compacted_channels": channels, } + base.Audit(h.ctx(), base.AuditIDDocumentChannelHistoryCompact, base.AuditFields{ + base.AuditFieldDocID: docid, + base.AuditFieldChannels: channels, + base.AuditFieldSequence: strconv.FormatUint(req.Seq, 10), + }) + h.writeJSON(res) return nil }