From 73728333d8be09c2fafc84128e8d81174010f04c Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Mon, 30 Mar 2020 17:42:07 +0100 Subject: [PATCH] [SIEM] Import timeline schema update (#61622) (#61826) * allow users importing data if they are authorized * rename props * rename types * hide import timeline btn if unauthorized * unit test for TimelinesPageComponent * update schemas * update schema Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../pages/timelines/timelines_page.test.tsx | 89 +++++++++++++++++++ .../public/pages/timelines/timelines_page.tsx | 9 +- .../lib/timeline/routes/schemas/schemas.ts | 50 +++++------ 3 files changed, 117 insertions(+), 31 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx new file mode 100644 index 0000000000000..62399891c9606 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TimelinesPageComponent } from './timelines_page'; +import { useKibana } from '../../lib/kibana'; +import { shallow, ShallowWrapper } from 'enzyme'; +import React from 'react'; +import ApolloClient from 'apollo-client'; + +jest.mock('../../lib/kibana', () => { + return { + useKibana: jest.fn(), + }; +}); +describe('TimelinesPageComponent', () => { + const mockAppollloClient = {} as ApolloClient; + let wrapper: ShallowWrapper; + + describe('If the user is authorised', () => { + beforeAll(() => { + ((useKibana as unknown) as jest.Mock).mockReturnValue({ + services: { + application: { + capabilities: { + siem: { + crud: true, + }, + }, + }, + }, + }); + wrapper = shallow(); + }); + + afterAll(() => { + ((useKibana as unknown) as jest.Mock).mockReset(); + }); + + test('should not show the import timeline modal by default', () => { + expect( + wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle') + ).toEqual(false); + }); + + test('should show the import timeline button', () => { + expect(wrapper.find('[data-test-subj="open-import-data-modal-btn"]').exists()).toEqual(true); + }); + + test('should show the import timeline modal after user clicking on the button', () => { + wrapper.find('[data-test-subj="open-import-data-modal-btn"]').simulate('click'); + expect( + wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle') + ).toEqual(true); + }); + }); + + describe('If the user is not authorised', () => { + beforeAll(() => { + ((useKibana as unknown) as jest.Mock).mockReturnValue({ + services: { + application: { + capabilities: { + siem: { + crud: false, + }, + }, + }, + }, + }); + wrapper = shallow(); + }); + + afterAll(() => { + ((useKibana as unknown) as jest.Mock).mockReset(); + }); + test('should not show the import timeline modal by default', () => { + expect( + wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle') + ).toEqual(false); + }); + + test('should not show the import timeline button', () => { + expect(wrapper.find('[data-test-subj="open-import-data-modal-btn"]').exists()).toEqual(false); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index 75bef7a04a4c9..73070d2b94aac 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -28,7 +28,7 @@ type OwnProps = TimelinesProps; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; -const TimelinesPageComponent: React.FC = ({ apolloClient }) => { +export const TimelinesPageComponent: React.FC = ({ apolloClient }) => { const [importDataModalToggle, setImportDataModalToggle] = useState(false); const onImportTimelineBtnClick = useCallback(() => { setImportDataModalToggle(true); @@ -43,7 +43,11 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => { {capabilitiesCanUserCRUD && ( - + {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} )} @@ -57,6 +61,7 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => { importDataModalToggle={importDataModalToggle && capabilitiesCanUserCRUD} setImportDataModalToggle={setImportDataModalToggle} title={i18n.ALL_TIMELINES_PANEL_TITLE} + data-test-subj="stateful-open-timeline" /> diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts index 63aee97729141..6552f973a66fa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts @@ -6,14 +6,14 @@ import Joi from 'joi'; const allowEmptyString = Joi.string().allow([null, '']); -const columnHeaderType = Joi.string(); +const columnHeaderType = allowEmptyString; export const created = Joi.number().allow(null); -export const createdBy = Joi.string(); +export const createdBy = allowEmptyString; export const description = allowEmptyString; export const end = Joi.number(); export const eventId = allowEmptyString; -export const eventType = Joi.string(); +export const eventType = allowEmptyString; export const filters = Joi.array() .items( @@ -24,19 +24,11 @@ export const filters = Joi.array() disabled: Joi.boolean().allow(null), field: allowEmptyString, formattedValue: allowEmptyString, - index: { - type: 'keyword', - }, - key: { - type: 'keyword', - }, - negate: { - type: 'boolean', - }, + index: allowEmptyString, + key: allowEmptyString, + negate: Joi.boolean().allow(null), params: allowEmptyString, - type: { - type: 'keyword', - }, + type: allowEmptyString, value: allowEmptyString, }), exists: allowEmptyString, @@ -68,22 +60,22 @@ export const version = allowEmptyString; export const columns = Joi.array().items( Joi.object({ aggregatable: Joi.boolean().allow(null), - category: Joi.string(), + category: allowEmptyString, columnHeaderType, description, example: allowEmptyString, indexes: allowEmptyString, - id: Joi.string(), + id: allowEmptyString, name, placeholder: allowEmptyString, searchable: Joi.boolean().allow(null), - type: Joi.string(), + type: allowEmptyString, }).required() ); export const dataProviders = Joi.array() .items( Joi.object({ - id: Joi.string(), + id: allowEmptyString, name: allowEmptyString, enabled: Joi.boolean().allow(null), excluded: Joi.boolean().allow(null), @@ -98,7 +90,7 @@ export const dataProviders = Joi.array() and: Joi.array() .items( Joi.object({ - id: Joi.string(), + id: allowEmptyString, name, enabled: Joi.boolean().allow(null), excluded: Joi.boolean().allow(null), @@ -122,9 +114,9 @@ export const dateRange = Joi.object({ }); export const favorite = Joi.array().items( Joi.object({ - keySearch: Joi.string(), - fullName: Joi.string(), - userName: Joi.string(), + keySearch: allowEmptyString, + fullName: allowEmptyString, + userName: allowEmptyString, favoriteDate: Joi.number(), }).allow(null) ); @@ -141,26 +133,26 @@ const noteItem = Joi.object({ }); export const eventNotes = Joi.array().items(noteItem); export const globalNotes = Joi.array().items(noteItem); -export const kqlMode = Joi.string(); +export const kqlMode = allowEmptyString; export const kqlQuery = Joi.object({ filterQuery: Joi.object({ kuery: Joi.object({ - kind: Joi.string(), + kind: allowEmptyString, expression: allowEmptyString, }), serializedQuery: allowEmptyString, }), }); export const pinnedEventIds = Joi.array() - .items(Joi.string()) + .items(allowEmptyString) .allow(null); export const sort = Joi.object({ - columnId: Joi.string(), - sortDirection: Joi.string(), + columnId: allowEmptyString, + sortDirection: allowEmptyString, }); /* eslint-disable @typescript-eslint/camelcase */ -export const ids = Joi.array().items(Joi.string()); +export const ids = Joi.array().items(allowEmptyString); export const exclude_export_details = Joi.boolean(); export const file_name = allowEmptyString;