diff --git a/base/audit_events.go b/base/audit_events.go index bd600b6527..7e099c4e6e 100644 --- a/base/audit_events.go +++ b/base/audit_events.go @@ -949,7 +949,7 @@ var AuditEvents = events{ AuditFieldPurged: true, }, mandatoryFieldGroups: []fieldGroup{ - fieldGroupAuthenticated, + // fieldGroupAuthenticated, // FIXME: CBG-3973 fieldGroupDatabase, fieldGroupKeyspace, }, @@ -966,7 +966,7 @@ var AuditEvents = events{ AuditFieldAttachmentID: "attachment name", }, mandatoryFieldGroups: []fieldGroup{ - fieldGroupAuthenticated, + // fieldGroupAuthenticated, FIXME: CBG-3973 fieldGroupDatabase, fieldGroupKeyspace, fieldGroupRequest, @@ -986,7 +986,7 @@ var AuditEvents = events{ AuditFieldAttachmentID: "attachment name", }, mandatoryFieldGroups: []fieldGroup{ - fieldGroupAuthenticated, + // fieldGroupAuthenticated, FIXME: CBG-3973 fieldGroupDatabase, fieldGroupKeyspace, fieldGroupRequest, @@ -1004,7 +1004,7 @@ var AuditEvents = events{ AuditFieldAttachmentID: "attachment name", }, mandatoryFieldGroups: []fieldGroup{ - fieldGroupAuthenticated, + // fieldGroupAuthenticated, // FIXME: CBG-3973 fieldGroupDatabase, fieldGroupKeyspace, fieldGroupRequest, @@ -1022,7 +1022,7 @@ var AuditEvents = events{ AuditFieldAttachmentID: "attachment name", }, mandatoryFieldGroups: []fieldGroup{ - fieldGroupAuthenticated, + // fieldGroupAuthenticated, // FIXME: CBG-3973 fieldGroupDatabase, fieldGroupKeyspace, fieldGroupRequest, diff --git a/rest/audit_test.go b/rest/audit_test.go index 9211ca90dd..4502fbc028 100644 --- a/rest/audit_test.go +++ b/rest/audit_test.go @@ -581,13 +581,128 @@ func TestAuditDocumentRead(t *testing.T) { RequireStatus(t, resp, http.StatusOK) }) requireDocumentReadEvents(rt, output, testCase.docID, testCase.docReadVersions) - requireDocumentMetadataReadEvents(rt, output, testCase.docID, testCase.docMetadataReadCount) + requireDocumentMetadataReadEvents(rt, output, testCase.docID, docVersion.RevID, testCase.docMetadataReadCount) + }) + } +} + +func TestAuditAttachmentEvents(t *testing.T) { + rt := createAuditLoggingRestTester(t) + defer rt.Close() + + RequireStatus(t, rt.CreateDatabase("db", rt.NewDbConfig()), http.StatusCreated) + const noAttachmentDoc = "doc1" + noAttachmentDocVersion := rt.CreateTestDoc(noAttachmentDoc) + + const hasAttachmentDoc = "doc2" + hasAttachmentDocVersion := rt.CreateTestDoc(hasAttachmentDoc) + rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/doc2/attachment1?rev="+hasAttachmentDocVersion.RevID, "contentdoc2") + hasAttachmentDocVersion, _ = rt.GetDoc(hasAttachmentDoc) + + const willUpdateAttachmentDoc = "doc3" + willUpdateAttachmentDocVersion := rt.CreateTestDoc(willUpdateAttachmentDoc) + rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/doc3/attachment1?rev="+willUpdateAttachmentDocVersion.RevID, "contentdoc3") + willUpdateAttachmentDocVersion, _ = rt.GetDoc(willUpdateAttachmentDoc) + + const bulkWillUpdateAttachmentDoc = "doc4" + bulkWillUpdateAttachmentDocVersion := rt.CreateTestDoc(bulkWillUpdateAttachmentDoc) + rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/doc4/attachment1?rev="+bulkWillUpdateAttachmentDocVersion.RevID, "contentdoc4") + bulkWillUpdateAttachmentDocVersion, _ = rt.GetDoc(bulkWillUpdateAttachmentDoc) + + const willDeleteAttachmentDoc = "doc5" + willDeleteAttachmentDocVersion := rt.CreateTestDoc(willDeleteAttachmentDoc) + rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/doc5/attachment1?rev="+willDeleteAttachmentDocVersion.RevID, "contentdoc5") + willDeleteAttachmentDocVersion, _ = rt.GetDoc(willDeleteAttachmentDoc) + + const bulkWillDeleteAttachmentDoc = "doc6" + bulkWillDeleteAttachmentDocVersion := rt.CreateTestDoc(willDeleteAttachmentDoc) + rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/doc6/attachment1?rev="+bulkWillDeleteAttachmentDocVersion.RevID, "contentdoc5") + bulkWillDeleteAttachmentDocVersion, _ = rt.GetDoc(bulkWillDeleteAttachmentDoc) + + testCases := []struct { + name string + method string + path string + docID string + requestBody string + status int + attachmentCreateCount int + attachmentReadCount int + attachmentUpdateCount int + attachmentDeleteCount int + }{ + { + name: "add attachment", + method: http.MethodPut, + path: "/{{.keyspace}}/doc1/attachment1?rev=" + noAttachmentDocVersion.RevID, + docID: noAttachmentDoc, + status: http.StatusCreated, + requestBody: "content", + attachmentCreateCount: 1, + }, + { + name: "get attachment with rev", + method: http.MethodGet, + path: "/{{.keyspace}}/doc2/attachment1?rev=" + hasAttachmentDocVersion.RevID, + docID: hasAttachmentDoc, + status: http.StatusOK, + attachmentReadCount: 1, + }, + { + name: "_bulk_get attachment with rev", + method: http.MethodPost, + path: "/{{.keyspace}}/_bulk_get?attachments=true", + requestBody: string(base.MustJSONMarshal(t, db.Body{ + "docs": []db.Body{ + {"id": hasAttachmentDoc, "rev": hasAttachmentDocVersion.RevID}, + }, + })), + docID: hasAttachmentDoc, + status: http.StatusOK, + attachmentReadCount: 1, + }, + { + name: "_all_docs attachment with rev", + method: http.MethodGet, + path: "/{{.keyspace}}/_all_docs?include_docs=true", + docID: hasAttachmentDoc, + status: http.StatusOK, + }, + { + name: "update attachment", + method: http.MethodPut, + path: "/{{.keyspace}}/doc3/attachment1?rev=" + hasAttachmentDocVersion.RevID, + docID: willUpdateAttachmentDoc, + status: http.StatusCreated, + requestBody: "content-update", + attachmentUpdateCount: 1, + }, + { + name: "delete attachment", + method: http.MethodDelete, + path: "/{{.keyspace}}/doc4/attachment1?rev=" + willDeleteAttachmentDocVersion.RevID, + docID: willDeleteAttachmentDoc, + status: http.StatusOK, + attachmentDeleteCount: 1, + }, + } + for _, testCase := range testCases { + rt.Run(testCase.name, func(t *testing.T) { + output := base.AuditLogContents(t, func(t testing.TB) { + resp := rt.SendAdminRequest(testCase.method, testCase.path, testCase.requestBody) + RequireStatus(t, resp, testCase.status) + }) + postAttachmentVersion, _ := rt.GetDoc(testCase.docID) + requireAttachmentEvents(rt, base.AuditIDAttachmentCreate, output, testCase.docID, postAttachmentVersion.RevID, testCase.attachmentCreateCount) + requireAttachmentEvents(rt, base.AuditIDAttachmentRead, output, testCase.docID, postAttachmentVersion.RevID, testCase.attachmentReadCount) + requireAttachmentEvents(rt, base.AuditIDAttachmentUpdate, output, testCase.docID, postAttachmentVersion.RevID, testCase.attachmentUpdateCount) + requireAttachmentEvents(rt, base.AuditIDAttachmentDelete, output, testCase.docID, postAttachmentVersion.RevID, testCase.attachmentDeleteCount) }) } } // requireDocumentMetadataReadEvents validates that there read events for each doc version specified. There should be only audit events for a given docid. -func requireDocumentMetadataReadEvents(rt *RestTester, output []byte, docID string, count int) { +func requireDocumentMetadataReadEvents(rt *RestTester, output []byte, docID string, revid string, count int) { events := jsonLines(rt.TB(), output) countFound := 0 for _, event := range events { @@ -617,6 +732,22 @@ func requireDocumentReadEvents(rt *RestTester, output []byte, docID string, docV require.Equal(rt.TB(), docVersions, docVersionsFound) } +// requireAttachmentEvents validates that create attachment events +func requireAttachmentEvents(rt *RestTester, eventID base.AuditID, output []byte, docID, docVersion string, count int) { + events := jsonLines(rt.TB(), output) + countFound := 0 + for _, event := range events { + // skip events that are not document read events + if base.AuditID(event[base.AuditFieldID].(float64)) != eventID { + continue + } + require.Equal(rt.TB(), event[base.AuditFieldDocID], docID) + require.Equal(rt.TB(), docVersion, event[base.AuditFieldDocVersion].(string)) + countFound++ + } + require.Equal(rt.TB(), count, countFound) +} + func createAuditLoggingRestTester(t *testing.T) *RestTester { // get tempdir before resetting global loggers, since the logger cleanup needs to happen before deletion tempdir := t.TempDir() diff --git a/rest/bulk_api.go b/rest/bulk_api.go index 17d388d81e..a23686b1ed 100644 --- a/rest/bulk_api.go +++ b/rest/bulk_api.go @@ -184,7 +184,6 @@ func (h *handler) handleAllDocs() error { } totalRows++ var err error - fmt.Printf("row: %+v value=%+v\n", row, row.Value) err = h.addJSON(row) if err != nil { return false, err @@ -458,6 +457,19 @@ func (h *handler) handleBulkGet() error { base.AuditFieldDocID: docid, base.AuditFieldDocVersion: revid, }) + if atts, ok := body[db.BodyAttachments]; ok && atts != nil { + attsMap, ok := atts.(db.AttachmentsMeta) + if !ok { + base.WarnfCtx(h.ctx(), "Unexpected format of attachments in the body %+v", atts) + } + for attachment := range attsMap { + base.Audit(h.ctx(), base.AuditIDAttachmentRead, base.AuditFields{ + base.AuditFieldDocID: docid, + base.AuditFieldDocVersion: revid, + base.AuditFieldAttachmentID: attachment, + }) + } + } h.db.DbStats.Database().NumDocReadsRest.Add(1) } return nil diff --git a/rest/doc_api.go b/rest/doc_api.go index cafe7a7910..1b0f82c1a6 100644 --- a/rest/doc_api.go +++ b/rest/doc_api.go @@ -302,6 +302,11 @@ func (h *handler) handleGetAttachment() error { h.db.DbStats.CBLReplicationPull().AttachmentPullBytes.Add(int64(len(data))) h.response.WriteHeader(status) _, _ = h.response.Write(data) + base.Audit(h.ctx(), base.AuditIDAttachmentRead, base.AuditFields{ + base.AuditFieldDocID: docid, + base.AuditFieldDocVersion: revid, + base.AuditFieldAttachmentID: attachmentName, + }) return nil } @@ -349,10 +354,13 @@ func (h *handler) handlePutAttachment() error { } } + attachmentUpdated := false // find attachment (if it existed) attachments := db.GetBodyAttachments(body) if attachments == nil { attachments = make(map[string]interface{}) + } else { + _, attachmentUpdated = attachments[attachmentName] } // create new attachment @@ -371,6 +379,16 @@ func (h *handler) handlePutAttachment() error { h.setEtag(newRev) h.writeRawJSONStatus(http.StatusCreated, []byte(`{"id":`+base.ConvertToJSONString(docid)+`,"ok":true,"rev":"`+newRev+`"}`)) + auditFields := base.AuditFields{ + base.AuditFieldDocID: docid, + base.AuditFieldDocVersion: newRev, + base.AuditFieldAttachmentID: attachmentName, + } + if attachmentUpdated { + base.Audit(h.ctx(), base.AuditIDAttachmentUpdate, auditFields) + } else { + base.Audit(h.ctx(), base.AuditIDAttachmentCreate, auditFields) + } return nil } @@ -426,6 +444,11 @@ func (h *handler) handleDeleteAttachment() error { h.setEtag(newRev) h.writeRawJSONStatus(http.StatusOK, []byte(`{"id":`+base.ConvertToJSONString(docid)+`,"ok":true,"rev":"`+newRev+`"}`)) + base.Audit(h.ctx(), base.AuditIDAttachmentDelete, base.AuditFields{ + base.AuditFieldDocID: docid, + base.AuditFieldDocVersion: newRev, + base.AuditFieldAttachmentID: attachmentName, + }) return nil }