Skip to content

Commit

Permalink
[Security Solution][Notes] - fix incorrect get_notes api for document…
Browse files Browse the repository at this point in the history
…Ids and savedObjectIds query parameters and adding api integration tests
  • Loading branch information
PhilippeOberti committed Oct 15, 2024
1 parent 8abe259 commit 0ff0bc1
Show file tree
Hide file tree
Showing 3 changed files with 587 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type { IKibanaResponse } from '@kbn/core-http-server';
import { transformError } from '@kbn/securitysolution-es-utils';
import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
import type { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server';
import { nodeBuilder } from '@kbn/es-query';
import { timelineSavedObjectType } from '../../saved_object_mappings';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import { MAX_UNASSOCIATED_NOTES, NOTE_URL } from '../../../../../common/constants';
Expand Down Expand Up @@ -43,78 +45,90 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter) => {
uiSettings: { client: uiSettingsClient },
} = await frameworkRequest.context.core;
const maxUnassociatedNotes = await uiSettingsClient.get<number>(MAX_UNASSOCIATED_NOTES);

// if documentIds is provided, we will search for all the notes associated with the documentIds
const documentIds = queryParams.documentIds ?? null;
const savedObjectIds = queryParams.savedObjectIds ?? null;
if (documentIds != null) {
// search for multiple document ids (like retrieving all the notes for all the alerts within a table)
if (Array.isArray(documentIds)) {
const docIdSearchString = documentIds?.join(' | ');
const options = {
const options: SavedObjectsFindOptions = {
type: noteSavedObjectType,
search: docIdSearchString,
filter: nodeBuilder.or(
documentIds.map((documentId: string) =>
nodeBuilder.is(`${noteSavedObjectType}.attributes.eventId`, documentId)
)
),
page: 1,
perPage: maxUnassociatedNotes,
};
const res = await getAllSavedNote(frameworkRequest, options);
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
} else {
const options = {
type: noteSavedObjectType,
search: documentIds,
page: 1,
perPage: maxUnassociatedNotes,
};
const res = await getAllSavedNote(frameworkRequest, options);
return response.ok({ body: res ?? {} });
}
} else if (savedObjectIds != null) {

// searching for all the notes associated with a specific document id
const options: SavedObjectsFindOptions = {
type: noteSavedObjectType,
filter: nodeBuilder.is(`${noteSavedObjectType}.attributes.eventId`, documentIds),
page: 1,
perPage: maxUnassociatedNotes,
};
const res = await getAllSavedNote(frameworkRequest, options);
return response.ok({ body: res ?? {} });
}

// if savedObjectIds is provided, we will search for all the notes associated with the savedObjectIds
const savedObjectIds = queryParams.savedObjectIds ?? null;
if (savedObjectIds != null) {
// search for multiple saved object ids
if (Array.isArray(savedObjectIds)) {
const soIdSearchString = savedObjectIds?.join(' | ');
const options = {
const options: SavedObjectsFindOptions = {
type: noteSavedObjectType,
hasReference: {
hasReference: savedObjectIds.map((savedObjectId: string) => ({
type: timelineSavedObjectType,
id: soIdSearchString,
},
id: savedObjectId,
})),
page: 1,
perPage: maxUnassociatedNotes,
};
const res = await getAllSavedNote(frameworkRequest, options);
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
} else {
const options = {
type: noteSavedObjectType,
hasReference: {
type: timelineSavedObjectType,
id: savedObjectIds,
},
perPage: maxUnassociatedNotes,
};
const res = await getAllSavedNote(frameworkRequest, options);
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
}
} else {
const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10;
const page = queryParams?.page ? parseInt(queryParams.page, 10) : 1;
const search = queryParams?.search ?? undefined;
const sortField = queryParams?.sortField ?? undefined;
const sortOrder = (queryParams?.sortOrder as SortOrder) ?? undefined;
const filter = queryParams?.filter;
const options = {

// searching for all the notes associated with a specific for saved object id
const options: SavedObjectsFindOptions = {
type: noteSavedObjectType,
perPage,
page,
search,
sortField,
sortOrder,
filter,
hasReference: {
type: timelineSavedObjectType,
id: savedObjectIds,
},
perPage: maxUnassociatedNotes,
};
const res = await getAllSavedNote(frameworkRequest, options);
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
}

// retrieving all the notes following the query parameters
const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10;
const page = queryParams?.page ? parseInt(queryParams.page, 10) : 1;
const search = queryParams?.search ?? undefined;
const sortField = queryParams?.sortField ?? undefined;
const sortOrder = (queryParams?.sortOrder as SortOrder) ?? undefined;
const filter = queryParams?.filter;
const options: SavedObjectsFindOptions = {
type: noteSavedObjectType,
perPage,
page,
search,
sortField,
sortOrder,
filter,
};
const res = await getAllSavedNote(frameworkRequest, options);
const body: GetNotesResponse = res ?? {};
return response.ok({ body });
} catch (err) {
const error = transformError(err);
const siemResponse = buildSiemResponse(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import type SuperTest from 'supertest';
import { v4 as uuidv4 } from 'uuid';
import { TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline';
import { NOTE_URL } from '@kbn/security-solution-plugin/common/constants';
import type { Client } from '@elastic/elasticsearch';
import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
import { noteSavedObjectType } from '@kbn/security-solution-plugin/server/lib/timeline/saved_object_mappings';

export const createBasicTimeline = async (supertest: SuperTest.Agent, titleToSaved: string) =>
await supertest
Expand Down Expand Up @@ -38,3 +42,93 @@ export const createBasicTimelineTemplate = async (
timelineType: TimelineTypeEnum.template,
},
});

export const deleteAllNotes = async (es: Client): Promise<void> => {
await es.deleteByQuery({
index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX,
q: `type:${noteSavedObjectType}`,
wait_for_completion: true,
refresh: true,
body: {},
});
};

export const createNoteAssociatedWithDocumentOnly = async (
supertest: SuperTest.Agent,
documentId: string,
noteText: string
) =>
await supertest
.patch(NOTE_URL)
.set('kbn-xsrf', 'true')
.send({
note: {
eventId: documentId,
timelineId: '',
created: Date.now(),
createdBy: 'elastic',
updated: Date.now(),
updatedBy: 'elastic',
note: noteText,
},
});

export const createNoteAssociatedWithSavedObjectOnly = async (
supertest: SuperTest.Agent,
savedObjectId: string,
noteText: string
) =>
await supertest
.patch(NOTE_URL)
.set('kbn-xsrf', 'true')
.send({
note: {
eventId: '',
timelineId: savedObjectId,
created: Date.now(),
createdBy: 'elastic',
updated: Date.now(),
updatedBy: 'elastic',
note: noteText,
},
});

export const createNoteAssociatedWithDocumentAndSavedObject = async (
supertest: SuperTest.Agent,
documentId: string,
savedObjectId: string,
noteText: string
) =>
await supertest
.patch(NOTE_URL)
.set('kbn-xsrf', 'true')
.send({
note: {
eventId: documentId,
timelineId: savedObjectId,
created: Date.now(),
createdBy: 'elastic',
updated: Date.now(),
updatedBy: 'elastic',
note: noteText,
},
});

export const createNoteAssociatedWithNothing = async (
supertest: SuperTest.Agent,
noteText: string
) =>
await supertest
.patch(NOTE_URL)
.set('kbn-xsrf', 'true')
.send({
note: {
eventId: '',
timelineId: '',
created: Date.now(),
createdBy: 'elastic',
updated: Date.now(),
updatedBy: 'elastic',
note: noteText,
},
});
Loading

0 comments on commit 0ff0bc1

Please sign in to comment.