Skip to content

Commit

Permalink
fix import timeline and clean up
Browse files Browse the repository at this point in the history
fix unit tests

apply failure checker

clean up error message

fix update template
  • Loading branch information
angorayc committed May 7, 2020
1 parent 7d0ac59 commit 67e7406
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export const mockGetTimelineValue = {
kqlMode: 'filter',
kqlQuery: { filterQuery: [] },
title: 'My duplicate timeline',
timelineType: TimelineType.default,
dateRange: { start: 1584523907294, end: 1584610307294 },
savedQueryId: null,
sort: { columnId: '@timestamp', sortDirection: 'desc' },
Expand All @@ -152,7 +153,7 @@ export const mockGetTimelineValue = {
export const mockGetTemplateTimelineValue = {
...mockGetTimelineValue,
timelineType: TimelineType.template,
templateTimelineId: '79deb4c0-6bc1-11ea-a90b-f5341fb7a189',
templateTimelineId: '79deb4c0-6bc1-0000-0000-f5341fb7a189',
templateTimelineVersion: 1,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ export const createTimelineWithTimelineId = {
export const createTemplateTimelineWithTimelineId = {
...createTemplateTimelineWithoutTimelineId,
timelineId: '79deb4c0-6bc1-11ea-a90b-f5341fb7a189',
templateTimelineId: 'existing template timeline id',
};

export const updateTimelineWithTimelineId = {
Expand All @@ -108,7 +107,7 @@ export const updateTimelineWithTimelineId = {
export const updateTemplateTimelineWithTimelineId = {
timeline: {
...inputTemplateTimeline,
templateTimelineId: '79deb4c0-6bc1-11ea-a90b-f5341fb7a189',
templateTimelineId: '79deb4c0-6bc1-0000-0000-f5341fb7a189',
templateTimelineVersion: 1,
},
timelineId: '79deb4c0-6bc1-11ea-a90b-f5341fb7a189',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
createTimelineWithTimelineId,
createTemplateTimelineWithoutTimelineId,
createTemplateTimelineWithTimelineId,
updateTemplateTimelineWithTimelineId,
} from './__mocks__/request_responses';
import {
CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE,
Expand All @@ -34,6 +35,7 @@ describe('create timelines', () => {
let securitySetup: SecurityPluginSetup;
let { context } = requestContextMock.createTools();
let mockGetTimeline: jest.Mock;
let mockGetTemplateTimeline: jest.Mock;
let mockPersistTimeline: jest.Mock;
let mockPersistPinnedEventOnTimeline: jest.Mock;
let mockPersistNote: jest.Mock;
Expand All @@ -55,6 +57,7 @@ describe('create timelines', () => {
} as unknown) as SecurityPluginSetup;

mockGetTimeline = jest.fn();
mockGetTemplateTimeline = jest.fn();
mockPersistTimeline = jest.fn();
mockPersistPinnedEventOnTimeline = jest.fn();
mockPersistNote = jest.fn();
Expand Down Expand Up @@ -231,11 +234,14 @@ describe('create timelines', () => {
});
});

describe('Import a template timeline already exist', () => {
describe('Create a template timeline already exist', () => {
beforeEach(() => {
jest.doMock('../saved_object', () => {
return {
getTimeline: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue),
getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({
timeline: [mockGetTemplateTimelineValue],
}),
persistTimeline: mockPersistTimeline,
};
});
Expand All @@ -259,7 +265,7 @@ describe('create timelines', () => {

test('returns error message', async () => {
const response = await server.inject(
getCreateTimelinesRequest(createTemplateTimelineWithTimelineId),
getCreateTimelinesRequest(updateTemplateTimelineWithTimelineId),
context
);
expect(response.body).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,8 @@ import { buildRouteValidation } from '../../../utils/build_validation/route_vali
import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils';

import { createTimelineSchema } from './schemas/create_timelines_schema';
import { buildFrameworkRequest } from './utils/common';
import {
createTimelines,
getTimeline,
getTemplateTimeline,
CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE,
CREATE_TIMELINE_ERROR_MESSAGE,
} from './utils/create_timelines';
import { buildFrameworkRequest, TimelinesStatus, TimelineStatusActions } from './utils/common';
import { createTimelines } from './utils/create_timelines';

export const createTimelinesRoute = (
router: IRouter,
Expand All @@ -46,37 +40,43 @@ export const createTimelinesRoute = (
const frameworkRequest = await buildFrameworkRequest(context, security, request);

const { timelineId, timeline, version } = request.body;
const { templateTimelineId, timelineType } = timeline;
const isHandlingTemplateTimeline = timelineType === TimelineType.template;
const { templateTimelineId, templateTimelineVersion, timelineType } = timeline;

const existTimeline =
timelineId != null ? await getTimeline(frameworkRequest, timelineId) : null;
const existTemplateTimeline =
templateTimelineId != null
? await getTemplateTimeline(frameworkRequest, templateTimelineId)
: null;
const timelineStatus = new TimelinesStatus({
timelineType: timelineType ?? TimelineType.default,
timelineInput: {
id: timelineId ?? null,
type: TimelineType.default,
version: version ?? null,
},
templateTimelineInput: {
id: templateTimelineId ?? null,
type: TimelineType.template,
version: templateTimelineVersion ?? null,
},
frameworkRequest,
});

if (
(!isHandlingTemplateTimeline && existTimeline != null) ||
(isHandlingTemplateTimeline && (existTemplateTimeline != null || existTimeline != null))
) {
return siemResponse.error({
body: isHandlingTemplateTimeline
? CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE
: CREATE_TIMELINE_ERROR_MESSAGE,
statusCode: 405,
});
}
await timelineStatus.setAvailableActions();

// Create timeline
const newTimeline = await createTimelines(frameworkRequest, timeline, null, version);
return response.ok({
body: {
data: {
persistTimeline: newTimeline,
if (timelineStatus.isCreatable) {
const newTimeline = await createTimelines(frameworkRequest, timeline, null, version);
return response.ok({
body: {
data: {
persistTimeline: newTimeline,
},
},
},
});
});
} else {
return siemResponse.error(
timelineStatus.checkIsFailureCases(TimelineStatusActions.create) || {
statusCode: 405,
body: 'update timeline error',
}
);
}
} catch (err) {
const error = transformError(err);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
import { createTimelinesStreamFromNdJson } from '../create_timelines_stream_from_ndjson';

import { ImportTimelinesPayloadSchemaRt } from './schemas/import_timelines_schema';
import { buildFrameworkRequest } from './utils/common';
import { buildFrameworkRequest, TimelinesStatus, TimelineStatusActions } from './utils/common';
import {
getTupleDuplicateErrorsAndUniqueTimeline,
isBulkError,
Expand All @@ -38,9 +38,8 @@ import {
PromiseFromStreams,
timelineSavedObjectOmittedFields,
} from './utils/import_timelines';
import { createTimelines, getTimeline, getTemplateTimeline } from './utils/create_timelines';
import { createTimelines } from './utils/create_timelines';
import { TimelineType } from '../../../../common/types/timeline';
import { checkIsFailureCases } from './utils/update_timelines';

const CHUNK_PARSED_OBJECT_SIZE = 10;

Expand Down Expand Up @@ -123,9 +122,9 @@ export const importTimelinesRoute = (
pinnedEventIds,
globalNotes,
eventNotes,
templateTimelineId,
templateTimelineVersion,
timelineType,
templateTimelineId = null,
templateTimelineVersion = null,
timelineType = TimelineType.default,
version = null,
} = parsedTimeline;
const parsedTimelineObject = omit(
Expand All @@ -135,28 +134,32 @@ export const importTimelinesRoute = (

let newTimeline = null;
try {
const templateTimeline =
templateTimelineId != null
? await getTemplateTimeline(frameworkRequest, templateTimelineId)
: null;
const timelineStatus = new TimelinesStatus({
timelineType,
timelineInput: {
id: savedObjectId,
type: TimelineType.default,
version,
},
templateTimelineInput: {
id: templateTimelineId,
type: TimelineType.template,
version: templateTimelineVersion,
},
frameworkRequest,
});

const timeline =
savedObjectId != null &&
(await getTimeline(frameworkRequest, savedObjectId));
const isHandlingTemplateTimeline = timelineType === TimelineType.template;
await timelineStatus.setAvailableActions();

if (
(timeline == null && !isHandlingTemplateTimeline) ||
(timeline == null && templateTimeline == null && isHandlingTemplateTimeline)
) {
if (timelineStatus.isCreatableViaImport) {
// create timeline / template timeline
newTimeline = await createTimelines(
frameworkRequest,
parsedTimelineObject,
null, // timelineSavedObjectId
null, // timelineVersion
pinnedEventIds,
isHandlingTemplateTimeline
timelineStatus.isHandlingTemplateTimeline
? globalNotes
: [...globalNotes, ...eventNotes],
[] // existing note ids
Expand All @@ -166,44 +169,51 @@ export const importTimelinesRoute = (
timeline_id: newTimeline.timeline.savedObjectId,
status_code: 200,
});
} else if (
timeline &&
timeline != null &&
templateTimeline != null &&
isHandlingTemplateTimeline
) {
// update template timeline
const errorObj = checkIsFailureCases(
isHandlingTemplateTimeline,
version,
templateTimelineVersion ?? null,
timeline,
templateTimeline
} else if (!timelineStatus.isHandlingTemplateTimeline) {
timelineStatus.checkIsFailureCases(TimelineStatusActions.createViaImport);
const message =
timelineStatus?.errorMessage?.body != null
? timelineStatus.errorMessage.body
: `${timelineType} timeline ${TimelineStatusActions.createViaImport} error`;

resolve(
createBulkErrorObject({
id: savedObjectId ?? 'unknown',
statusCode: 409,
message,
})
);
if (errorObj != null) {
return siemResponse.error(errorObj);
}
}

if (timelineStatus.isUpdatableViaImport) {
// update template timeline

newTimeline = await createTimelines(
frameworkRequest,
{ ...parsedTimelineObject, templateTimelineId, templateTimelineVersion },
timeline.savedObjectId, // timelineSavedObjectId
timeline.version, // timelineVersion
parsedTimelineObject,
timelineStatus.timelineInput.id, // timelineSavedObjectId
timelineStatus.timelineInput.version?.toString() ?? null, // timelineVersion
pinnedEventIds,
globalNotes,
[] // existing note ids
timelineStatus.timelineInput?.data?.noteIds ?? [] // existing note ids
);

resolve({
timeline_id: newTimeline.timeline.savedObjectId,
status_code: 200,
});
} else {
timelineStatus.checkIsFailureCases(TimelineStatusActions.updateViaImport);
const message =
timelineStatus?.errorMessage?.body != null
? timelineStatus.errorMessage.body
: `${timelineType} timeline ${TimelineStatusActions.updateViaImport} error`;

resolve(
createBulkErrorObject({
id: savedObjectId ?? 'unknown',
statusCode: 409,
message: `timeline_id: "${savedObjectId}" already exists`,
message,
})
);
}
Expand Down Expand Up @@ -253,7 +263,6 @@ export const importTimelinesRoute = (
} catch (err) {
const error = transformError(err);
const siemResponse = buildSiemResponse(response);

return siemResponse.error({
body: error.message,
statusCode: error.statusCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
import {
UPDATE_TIMELINE_ERROR_MESSAGE,
UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE,
} from './utils/update_timelines';
} from './utils/failure_cases';

describe('update timelines', () => {
let server: ReturnType<typeof serverMock.create>;
Expand Down Expand Up @@ -178,7 +178,7 @@ describe('update timelines', () => {
timeline: [mockGetTemplateTimelineValue],
}),
persistTimeline: mockPersistTimeline.mockReturnValue({
timeline: updateTimelineWithTimelineId.timeline,
timeline: updateTemplateTimelineWithTimelineId.timeline,
}),
};
});
Expand Down Expand Up @@ -211,7 +211,7 @@ describe('update timelines', () => {

test('should Update existing template timeline with template timelineId', async () => {
expect(mockGetTemplateTimeline.mock.calls[0][1]).toEqual(
updateTemplateTimelineWithTimelineId.timelineId
updateTemplateTimelineWithTimelineId.timeline.templateTimelineId
);
});

Expand Down
Loading

0 comments on commit 67e7406

Please sign in to comment.