From d744d18b1921655c12b82bb6760f9bc8348e2a3c Mon Sep 17 00:00:00 2001 From: Bohdan Tsymbala Date: Mon, 20 Jul 2020 17:44:17 +0200 Subject: [PATCH 01/15] [ENDPOINT] Added unerolling status for host. (#72303) * Added unerolling status for host. * Added unenrolling status to frontend tests. --- .../common/endpoint/types.ts | 5 ++++ .../endpoint_hosts/view/host_constants.ts | 1 + .../pages/endpoint_hosts/view/index.test.tsx | 25 ++++++++++++------- .../pages/endpoint_hosts/view/index.tsx | 2 +- .../server/endpoint/routes/metadata/index.ts | 1 + .../endpoint/routes/metadata/metadata.test.ts | 4 +-- 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts index 5e7e4d22f8c3c..a982f9ffe8f21 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types.ts @@ -419,6 +419,11 @@ export enum HostStatus { * Host is offline as indicated by its checkin status during the last checkin window */ OFFLINE = 'offline', + + /** + * Host is unenrolling as indicated by its checkin status during the last checkin window + */ + UNENROLLING = 'unenrolling', } export type HostInfo = Immutable<{ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts index 790bbd3cb98da..4204d4f79f19c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts @@ -15,6 +15,7 @@ export const HOST_STATUS_TO_HEALTH_COLOR = Object.freeze< [HostStatus.ERROR]: 'danger', [HostStatus.ONLINE]: 'success', [HostStatus.OFFLINE]: 'subdued', + [HostStatus.UNENROLLING]: 'warning', }); export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index a61088e2edd29..47227244b7066 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -112,14 +112,16 @@ describe('when on the hosts page', () => { let firstPolicyID: string; beforeEach(() => { reactTestingLibrary.act(() => { - const hostListData = mockHostResultList({ total: 3 }); + const hostListData = mockHostResultList({ total: 4 }); firstPolicyID = hostListData.hosts[0].metadata.Endpoint.policy.applied.id; - [HostStatus.ERROR, HostStatus.ONLINE, HostStatus.OFFLINE].forEach((status, index) => { - hostListData.hosts[index] = { - metadata: hostListData.hosts[index].metadata, - host_status: status, - }; - }); + [HostStatus.ERROR, HostStatus.ONLINE, HostStatus.OFFLINE, HostStatus.UNENROLLING].forEach( + (status, index) => { + hostListData.hosts[index] = { + metadata: hostListData.hosts[index].metadata, + host_status: status, + }; + } + ); hostListData.hosts.forEach((item, index) => { generatedPolicyStatuses[index] = item.metadata.Endpoint.policy.applied.status; }); @@ -134,12 +136,12 @@ describe('when on the hosts page', () => { it('should display rows in the table', async () => { const renderResult = render(); const rows = await renderResult.findAllByRole('row'); - expect(rows).toHaveLength(4); + expect(rows).toHaveLength(5); }); it('should show total', async () => { const renderResult = render(); const total = await renderResult.findByTestId('hostListTableTotal'); - expect(total.textContent).toEqual('3 Hosts'); + expect(total.textContent).toEqual('4 Hosts'); }); it('should display correct status', async () => { const renderResult = render(); @@ -157,6 +159,11 @@ describe('when on the hosts page', () => { expect( hostStatuses[2].querySelector('[data-euiicon-type][color="subdued"]') ).not.toBeNull(); + + expect(hostStatuses[3].textContent).toEqual('Unenrolling'); + expect( + hostStatuses[3].querySelector('[data-euiicon-type][color="warning"]') + ).not.toBeNull(); }); it('should display correct policy status', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 4c8d2c5a6df4e..c5ed71cba46d9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -226,7 +226,7 @@ export const HostList = () => { > diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts index cb9889ca0cb76..fe7a8296608d2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts @@ -35,6 +35,7 @@ interface MetadataRequestContext { const HOST_STATUS_MAPPING = new Map([ ['online', HostStatus.ONLINE], ['offline', HostStatus.OFFLINE], + ['unenrolling', HostStatus.UNENROLLING], ]); /** diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 321eb0195aac3..8d967656065d1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -335,7 +335,7 @@ describe('test endpoint route', () => { expect(result.host_status).toEqual(HostStatus.ERROR); }); - it('should return a single endpoint with status error when status is not offline or online', async () => { + it('should return a single endpoint with status error when status is not offline, online or enrolling', async () => { const response = createSearchResponse(new EndpointDocGenerator().generateHostMetadata()); const mockRequest = httpServerMock.createKibanaRequest({ @@ -368,7 +368,7 @@ describe('test endpoint route', () => { expect(result.host_status).toEqual(HostStatus.ERROR); }); - it('should throw error when endpoint egent is not active', async () => { + it('should throw error when endpoint agent is not active', async () => { const response = createSearchResponse(new EndpointDocGenerator().generateHostMetadata()); const mockRequest = httpServerMock.createKibanaRequest({ From 96d965d4e305e7891500b7ad6cb83f4356d6117c Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 20 Jul 2020 17:55:27 +0200 Subject: [PATCH 02/15] Unskip dashboard embeddable rendering tests (#71824) --- test/functional/apps/dashboard/embeddable_rendering.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js index 9ba0c07c744fc..c00f01d060f4a 100644 --- a/test/functional/apps/dashboard/embeddable_rendering.js +++ b/test/functional/apps/dashboard/embeddable_rendering.js @@ -98,8 +98,7 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.vegaTextsDoNotExist(['5,000']); }; - // FLAKY: https://github.com/elastic/kibana/issues/46305 - describe.skip('dashboard embeddable rendering', function describeIndexTests() { + describe('dashboard embeddable rendering', function describeIndexTests() { before(async () => { await esArchiver.load('dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ From 2094f33537df6185ec0894316f8af107110bd071 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Mon, 20 Jul 2020 12:28:46 -0400 Subject: [PATCH 03/15] [Security Solution] Cleanup endpoint telemetry (#71950) Co-authored-by: Elastic Machine --- .../server/usage/collector.ts | 2 +- .../server/usage/endpoints/endpoint.mocks.ts | 60 +++- .../server/usage/endpoints/endpoint.test.ts | 332 +++++++++++++++--- .../usage/endpoints/fleet_saved_objects.ts | 16 +- .../server/usage/endpoints/index.ts | 239 +++++++------ 5 files changed, 480 insertions(+), 169 deletions(-) diff --git a/x-pack/plugins/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index 9740f57450e80..9a7ad6fc2db74 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -12,7 +12,7 @@ import { EndpointUsage, getEndpointTelemetryFromFleet } from './endpoints'; export type RegisterCollector = (deps: CollectorDependencies) => void; export interface UsageData { detections: DetectionsUsage; - endpoints: EndpointUsage; + endpoints: EndpointUsage | {}; } export async function getInternalSavedObjectsClient(core: CoreSetup) { diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts index 1369a3d398265..e3f0f7bde2fed 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts @@ -14,6 +14,7 @@ import { FLEET_ENDPOINT_PACKAGE_CONSTANT } from './fleet_saved_objects'; const testAgentId = 'testAgentId'; const testConfigId = 'testConfigId'; +const testHostId = 'randoHostId'; /** Mock OS Platform for endpoint telemetry */ export const MockOSPlatform = 'somePlatform'; @@ -30,6 +31,7 @@ export const MockOSFullName = 'somePlatformFullName'; * @description We request the install and OS related telemetry information from the 'fleet-agents' saved objects in ingest_manager. This mocks that response */ export const mockFleetObjectsResponse = ( + hasDuplicates = true, lastCheckIn = new Date().toISOString() ): SavedObjectsFindResponse => ({ page: 1, @@ -56,7 +58,44 @@ export const mockFleetObjectsResponse = ( host: { hostname: 'testDesktop', name: 'testDesktop', - id: 'randoHostId', + id: testHostId, + }, + os: { + platform: MockOSPlatform, + version: MockOSVersion, + name: MockOSName, + full: MockOSFullName, + }, + }, + packages: [FLEET_ENDPOINT_PACKAGE_CONSTANT, 'system'], + last_checkin: lastCheckIn, + }, + references: [], + updated_at: lastCheckIn, + version: 'WzI4MSwxXQ==', + score: 0, + }, + { + type: AGENT_SAVED_OBJECT_TYPE, + id: testAgentId, + attributes: { + active: true, + id: 'oldTestAgentId', + config_id: 'randoConfigId', + type: 'PERMANENT', + user_provided_metadata: {}, + enrolled_at: lastCheckIn, + current_error_events: [], + local_metadata: { + elastic: { + agent: { + id: 'oldTestAgentId', + }, + }, + host: { + hostname: 'testDesktop', + name: 'testDesktop', + id: hasDuplicates ? testHostId : 'oldRandoHostId', }, os: { platform: MockOSPlatform, @@ -76,7 +115,10 @@ export const mockFleetObjectsResponse = ( ], }); -const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') => +const mockPolicyPayload = ( + policyStatus: 'success' | 'warning' | 'failure', + policyMode: 'prevent' | 'detect' | 'off' = 'prevent' +) => JSON.stringify({ 'endpoint-security': { Endpoint: { @@ -105,7 +147,7 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') => file: 'info', }, malware: { - mode: 'prevent', + mode: policyMode, }, }, windows: { @@ -122,7 +164,7 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') => file: 'info', }, malware: { - mode: 'prevent', + mode: policyMode, }, }, }, @@ -151,11 +193,11 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') => 'detect_file_open_events', 'detect_sync_image_load_events', ], - status: `${malwareStatus}`, + status: `${policyStatus}`, }, }, }, - status: `${malwareStatus}`, + status: `${policyStatus}`, }, }, }, @@ -186,7 +228,9 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') => */ export const mockFleetEventsObjectsResponse = ( running?: boolean, - updatedDate = new Date().toISOString() + updatedDate = new Date().toISOString(), + policyStatus: 'success' | 'failure' = running ? 'success' : 'failure', + policyMode: 'prevent' | 'detect' | 'off' = 'prevent' ): SavedObjectsFindResponse => { return { page: 1, @@ -204,7 +248,7 @@ export const mockFleetEventsObjectsResponse = ( message: `Application: endpoint-security--8.0.0[d8f7f6e8-9375-483c-b456-b479f1d7a4f2]: State changed to ${ running ? 'RUNNING' : 'FAILED' }: `, - payload: mockPolicyPayload(running ? 'success' : 'failure'), + payload: running ? mockPolicyPayload(policyStatus, policyMode) : undefined, config_id: testConfigId, }, references: [], diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts index 06755192bd818..e2f7a3be6d80a 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts @@ -51,17 +51,31 @@ describe('test security solution endpoint telemetry', () => { `); }); + describe('when a request for endpoint agents fails', () => { + it('should return an empty object', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.reject(Error('No agents for you')) + ); + + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(getFleetSavedObjectsMetadataSpy).toHaveBeenCalled(); + expect(endpointUsage).toEqual({}); + }); + }); + describe('when an agent has not been installed', () => { it('should return the default shape if no agents are found', async () => { getFleetSavedObjectsMetadataSpy.mockImplementation(() => Promise.resolve({ saved_objects: [], total: 0, per_page: 0, page: 0 }) ); - const emptyEndpointTelemetryData = await endpointTelemetry.getEndpointTelemetryFromFleet( + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( mockSavedObjectsRepository ); expect(getFleetSavedObjectsMetadataSpy).toHaveBeenCalled(); - expect(emptyEndpointTelemetryData).toEqual({ + expect(endpointUsage).toEqual({ total_installed: 0, active_within_last_24_hours: 0, os: [], @@ -76,68 +90,274 @@ describe('test security solution endpoint telemetry', () => { }); }); - describe('when an agent has been installed', () => { - it('should show one enpoint installed but it is inactive', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => - Promise.resolve(mockFleetObjectsResponse()) - ); - getLatestFleetEndpointEventSpy.mockImplementation(() => - Promise.resolve(mockFleetEventsObjectsResponse()) - ); + describe('when agent(s) have been installed', () => { + describe('when a request for events has failed', () => { + it('should show only one endpoint installed but it is inactive', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse()) + ); + getLatestFleetEndpointEventSpy.mockImplementation(() => + Promise.reject(Error('No events for you')) + ); - const emptyEndpointTelemetryData = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository - ); - expect(emptyEndpointTelemetryData).toEqual({ - total_installed: 1, - active_within_last_24_hours: 0, - os: [ - { - full_name: MockOSFullName, - platform: MockOSPlatform, - version: MockOSVersion, - count: 1, - }, - ], - policies: { - malware: { - failure: 1, - active: 0, - inactive: 0, + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 1, + active_within_last_24_hours: 0, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 1, + }, + ], + policies: { + malware: { + failure: 0, + active: 0, + inactive: 0, + }, }, - }, + }); }); }); - it('should show one endpoint installed and it is active', async () => { - getFleetSavedObjectsMetadataSpy.mockImplementation(() => - Promise.resolve(mockFleetObjectsResponse()) - ); - getLatestFleetEndpointEventSpy.mockImplementation(() => - Promise.resolve(mockFleetEventsObjectsResponse(true)) - ); + describe('when a request for events is successful', () => { + it('should show one endpoint installed but endpoint has failed to run', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse()) + ); + getLatestFleetEndpointEventSpy.mockImplementation(() => + Promise.resolve(mockFleetEventsObjectsResponse()) + ); - const emptyEndpointTelemetryData = await endpointTelemetry.getEndpointTelemetryFromFleet( - mockSavedObjectsRepository - ); - expect(emptyEndpointTelemetryData).toEqual({ - total_installed: 1, - active_within_last_24_hours: 1, - os: [ - { - full_name: MockOSFullName, - platform: MockOSPlatform, - version: MockOSVersion, - count: 1, + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 1, + active_within_last_24_hours: 0, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 1, + }, + ], + policies: { + malware: { + failure: 0, + active: 0, + inactive: 0, + }, }, - ], - policies: { - malware: { - failure: 0, - active: 1, - inactive: 0, + }); + }); + + it('should show two endpoints installed but both endpoints have failed to run', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse(false)) + ); + getLatestFleetEndpointEventSpy.mockImplementation(() => + Promise.resolve(mockFleetEventsObjectsResponse()) + ); + + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 2, + active_within_last_24_hours: 0, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 2, + }, + ], + policies: { + malware: { + failure: 0, + active: 0, + inactive: 0, + }, }, - }, + }); + }); + + it('should show two endpoints installed but agents have not checked in within past day', async () => { + const twoDaysAgo = new Date(); + twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); + const twoDaysAgoISOString = twoDaysAgo.toISOString(); + + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse(false, twoDaysAgoISOString)) + ); + getLatestFleetEndpointEventSpy.mockImplementation( + () => Promise.resolve(mockFleetEventsObjectsResponse(true, twoDaysAgoISOString)) // agent_id doesn't matter for mock here + ); + + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 2, + active_within_last_24_hours: 0, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 2, + }, + ], + policies: { + malware: { + failure: 0, + active: 2, + inactive: 0, + }, + }, + }); + }); + + it('should show one endpoint installed and endpoint is running', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse()) + ); + getLatestFleetEndpointEventSpy.mockImplementation(() => + Promise.resolve(mockFleetEventsObjectsResponse(true)) + ); + + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 1, + active_within_last_24_hours: 1, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 1, + }, + ], + policies: { + malware: { + failure: 0, + active: 1, + inactive: 0, + }, + }, + }); + }); + + describe('malware policy', () => { + it('should have failed to enable', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse()) + ); + getLatestFleetEndpointEventSpy.mockImplementation(() => + Promise.resolve( + mockFleetEventsObjectsResponse(true, new Date().toISOString(), 'failure') + ) + ); + + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 1, + active_within_last_24_hours: 1, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 1, + }, + ], + policies: { + malware: { + failure: 1, + active: 0, + inactive: 0, + }, + }, + }); + }); + + it('should be enabled successfully', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse()) + ); + getLatestFleetEndpointEventSpy.mockImplementation(() => + Promise.resolve(mockFleetEventsObjectsResponse(true)) + ); + + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 1, + active_within_last_24_hours: 1, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 1, + }, + ], + policies: { + malware: { + failure: 0, + active: 1, + inactive: 0, + }, + }, + }); + }); + + it('should be disabled successfully', async () => { + getFleetSavedObjectsMetadataSpy.mockImplementation(() => + Promise.resolve(mockFleetObjectsResponse()) + ); + getLatestFleetEndpointEventSpy.mockImplementation(() => + Promise.resolve( + mockFleetEventsObjectsResponse(true, new Date().toISOString(), 'success', 'off') + ) + ); + + const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet( + mockSavedObjectsRepository + ); + expect(endpointUsage).toEqual({ + total_installed: 1, + active_within_last_24_hours: 1, + os: [ + { + full_name: MockOSFullName, + platform: MockOSPlatform, + version: MockOSVersion, + count: 1, + }, + ], + policies: { + malware: { + failure: 0, + active: 0, + inactive: 1, + }, + }, + }); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts index 7e05fdec36169..42c1ec0e2eed2 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts @@ -16,8 +16,16 @@ export const FLEET_ENDPOINT_PACKAGE_CONSTANT = FleetDefaultPackages.endpoint; export const getFleetSavedObjectsMetadata = async (savedObjectsClient: ISavedObjectsRepository) => savedObjectsClient.find({ + // Get up to 10000 agents with endpoint installed type: AGENT_SAVED_OBJECT_TYPE, - fields: ['packages', 'last_checkin', 'local_metadata'], + fields: [ + 'packages', + 'last_checkin', + 'local_metadata.agent.id', + 'local_metadata.host.id', + 'local_metadata.elastic.agent.id', + 'local_metadata.os', + ], filter: `${AGENT_SAVED_OBJECT_TYPE}.attributes.packages: ${FLEET_ENDPOINT_PACKAGE_CONSTANT}`, perPage: 10000, sortField: 'enrolled_at', @@ -29,9 +37,11 @@ export const getLatestFleetEndpointEvent = async ( agentId: string ) => savedObjectsClient.find({ + // Get the most recent endpoint event. type: AGENT_EVENT_SAVED_OBJECT_TYPE, - filter: `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.agent_id: ${agentId} and ${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.message: "${FLEET_ENDPOINT_PACKAGE_CONSTANT}"`, - perPage: 1, // Get the most recent endpoint event. + fields: ['agent_id', 'subtype', 'payload'], + filter: `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.message: "${FLEET_ENDPOINT_PACKAGE_CONSTANT}"`, + perPage: 1, sortField: 'timestamp', sortOrder: 'desc', search: agentId, diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts index ab5669d503275..9e071f4adff25 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts @@ -3,8 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { cloneDeep } from 'lodash'; import { ISavedObjectsRepository } from 'src/core/server'; +import { SavedObject } from './../../../../../../src/core/types/saved_objects'; +import { Agent, NewAgentEvent } from './../../../../ingest_manager/common/types/models/agent'; import { AgentMetadata } from '../../../../ingest_manager/common/types/models/agent'; import { getFleetSavedObjectsMetadata, getLatestFleetEndpointEvent } from './fleet_saved_objects'; @@ -51,7 +53,7 @@ export interface AgentLocalMetadata extends AgentMetadata { } type OSTracker = Record; -type AgentDailyActiveTracker = Map; + /** * @description returns an empty telemetry object to be incrmented and updated within the `getEndpointTelemetryFromFleet` fn */ @@ -69,13 +71,14 @@ export const getDefaultEndpointTelemetry = (): EndpointUsage => ({ }); /** - * @description this fun + * @description this function updates the os telemetry. We use the fullName field as the key as it contains the name and version details. + * If it has already been tracked, the count will be updated, otherwise a tracker will be initialized for that fullName. */ -export const trackEndpointOSTelemetry = ( +export const updateEndpointOSTelemetry = ( os: AgentLocalMetadata['os'], osTracker: OSTracker ): OSTracker => { - const updatedOSTracker = { ...osTracker }; + const updatedOSTracker = cloneDeep(osTracker); const { version: osVersion, platform: osPlatform, full: osFullName } = os; if (osFullName && osVersion) { if (updatedOSTracker[osFullName]) updatedOSTracker[osFullName].count += 1; @@ -93,18 +96,32 @@ export const trackEndpointOSTelemetry = ( }; /** - * @description This iterates over all unique agents that currently track an endpoint package. It takes a list of agents who have checked in in the last 24 hours - * and then checks whether those agents have endpoints whose latest status is 'RUNNING' to determine an active_within_last_24_hours. Since the policy information is also tracked in these events - * we pull out the status of the current protection (malware) type. This must be done in a compound manner as the desired status is reflected in the config, and the successful application of that policy - * is tracked in the policy.applied.response.configurations[protectionsType].status. Using these two we can determine whether the policy is toggled on, off, or failed to turn on. + * @description we take the latest endpoint specific agent event, get the status of the endpoint, and if it is running + * and the agent itself has been active within the last 24 hours, we can safely assume the endpoint has been active within + * the same time span. */ -export const addEndpointDailyActivityAndPolicyDetailsToTelemetry = async ( - agentDailyActiveTracker: AgentDailyActiveTracker, - savedObjectsClient: ISavedObjectsRepository, - endpointTelemetry: EndpointUsage -): Promise => { - const updatedEndpointTelemetry = { ...endpointTelemetry }; +export const updateEndpointDailyActiveCount = ( + latestEndpointEvent: SavedObject, + lastAgentCheckin: Agent['last_checkin'], + currentCount: number +) => { + const aDayAgo = new Date(); + aDayAgo.setDate(aDayAgo.getDate() - 1); + + const agentWasActiveOverLastDay = !!lastAgentCheckin && new Date(lastAgentCheckin) > aDayAgo; + return agentWasActiveOverLastDay && latestEndpointEvent.attributes.subtype === 'RUNNING' + ? currentCount + 1 + : currentCount; +}; +/** + * @description We take the latest endpoint specific agent event, and as long as it provides the payload with policy details, we will parse that policy + * to populate the success of it's application. The policy is provided in the agent health checks. + */ +export const updateEndpointPolicyTelemetry = ( + latestEndpointEvent: SavedObject, + policiesTracker: PoliciesTelemetry +): PoliciesTelemetry => { const policyHostTypeToPolicyType = { Linux: 'linux', macOs: 'mac', @@ -112,58 +129,60 @@ export const addEndpointDailyActivityAndPolicyDetailsToTelemetry = async ( }; const enabledMalwarePolicyTypes = ['prevent', 'detect']; - for (const agentId of agentDailyActiveTracker.keys()) { - const { saved_objects: agentEvents } = await getLatestFleetEndpointEvent( - savedObjectsClient, - agentId - ); - - const latestEndpointEvent = agentEvents[0]; - if (latestEndpointEvent) { - /* - We can assume that if the last status of the endpoint is RUNNING and the agent has checked in within the last 24 hours - then the endpoint has still been running within the last 24 hours. - */ - const { subtype, payload } = latestEndpointEvent.attributes; - const endpointIsActive = - subtype === 'RUNNING' && agentDailyActiveTracker.get(agentId) === true; - - if (endpointIsActive) { - updatedEndpointTelemetry.active_within_last_24_hours += 1; - } + // The policy details are sent as a string on the 'payload' attribute of the agent event + const { payload } = latestEndpointEvent.attributes; - // The policy details are sent as a string on the 'payload' attribute of the agent event - const endpointPolicyDetails = payload ? JSON.parse(payload) : null; - if (endpointPolicyDetails) { - // We get the setting the user desired to enable (treating prevent and detect as 'active' states) and then see if it succeded or failed. - const hostType = - policyHostTypeToPolicyType[ - endpointPolicyDetails['endpoint-security']?.host?.os?.name as EndpointOSNames - ]; - const userDesiredMalwareState = - endpointPolicyDetails['endpoint-security'].Endpoint?.configuration?.inputs[0]?.policy[ - hostType - ]?.malware?.mode; - - const isAnActiveMalwareState = enabledMalwarePolicyTypes.includes(userDesiredMalwareState); - const malwareStatus = - endpointPolicyDetails['endpoint-security'].Endpoint?.policy?.applied?.response - ?.configurations?.malware?.status; - - if (isAnActiveMalwareState && malwareStatus !== 'failure') { - updatedEndpointTelemetry.policies.malware.active += 1; - } - if (!isAnActiveMalwareState) { - updatedEndpointTelemetry.policies.malware.inactive += 1; - } - if (isAnActiveMalwareState && malwareStatus === 'failure') { - updatedEndpointTelemetry.policies.malware.failure += 1; - } - } - } + if (!payload) { + // This payload may not always be provided depending on the state of the endpoint. Guard again situations where it is not sent + return policiesTracker; + } + + let endpointPolicyPayload; + try { + endpointPolicyPayload = JSON.parse(latestEndpointEvent.attributes.payload); + } catch (error) { + return policiesTracker; + } + + // Get the platform: windows, mac, or linux + const hostType = + policyHostTypeToPolicyType[ + endpointPolicyPayload['endpoint-security']?.host?.os?.name as EndpointOSNames + ]; + // Get whether the malware setting for the platform on the most recently provided config is active (prevent or detect is on) or off + const userDesiredMalwareState = + endpointPolicyPayload['endpoint-security'].Endpoint?.configuration?.inputs[0]?.policy[hostType] + ?.malware?.mode; + + // Get the status of the application of the malware protection + const malwareStatus = + endpointPolicyPayload['endpoint-security'].Endpoint?.policy?.applied?.response?.configurations + ?.malware?.status; + + if (!userDesiredMalwareState || !malwareStatus) { + // If we get policy information without the mode or status, then nothing to track or update + return policiesTracker; } - return updatedEndpointTelemetry; + const updatedPoliciesTracker = { + malware: { ...policiesTracker.malware }, + }; + + const isAnActiveMalwareState = enabledMalwarePolicyTypes.includes(userDesiredMalwareState); + + // we only check for 'not failure' as the 'warning' state for malware is still technically actively enabled (with warnings) + const successfullyEnabled = !!malwareStatus && malwareStatus !== 'failure'; + const failedToEnable = !!malwareStatus && malwareStatus === 'failure'; + + if (isAnActiveMalwareState && successfullyEnabled) { + updatedPoliciesTracker.malware.active += 1; + } else if (!isAnActiveMalwareState && successfullyEnabled) { + updatedPoliciesTracker.malware.inactive += 1; + } else if (isAnActiveMalwareState && failedToEnable) { + updatedPoliciesTracker.malware.failure += 1; + } + + return updatedPoliciesTracker; }; /** @@ -173,53 +192,71 @@ export const addEndpointDailyActivityAndPolicyDetailsToTelemetry = async ( * to confirm whether or not the endpoint is still active */ export const getEndpointTelemetryFromFleet = async ( - savedObjectsClient: ISavedObjectsRepository -): Promise => { - // Retrieve every agent that references the endpoint as an installed package. It will not be listed if it was never installed - const { saved_objects: endpointAgents } = await getFleetSavedObjectsMetadata(savedObjectsClient); + soClient: ISavedObjectsRepository +): Promise => { + // Retrieve every agent (max 10000) that references the endpoint as an installed package. It will not be listed if it was never installed + let endpointAgents; + try { + const response = await getFleetSavedObjectsMetadata(soClient); + endpointAgents = response.saved_objects; + } catch (error) { + // Better to provide an empty object rather than default telemetry as this better informs us of an error + return {}; + } + + const endpointAgentsCount = endpointAgents.length; const endpointTelemetry = getDefaultEndpointTelemetry(); // If there are no installed endpoints return the default telemetry object - if (!endpointAgents || endpointAgents.length < 1) return endpointTelemetry; + if (!endpointAgents || endpointAgentsCount < 1) return endpointTelemetry; // Use unique hosts to prevent any potential duplicates const uniqueHostIds: Set = new Set(); - // Need agents to get events data for those that have run in last 24 hours as well as policy details - const agentDailyActiveTracker: AgentDailyActiveTracker = new Map(); - - const aDayAgo = new Date(); - aDayAgo.setDate(aDayAgo.getDate() - 1); let osTracker: OSTracker = {}; + let dailyActiveCount = 0; + let policyTracker: PoliciesTelemetry = { malware: { active: 0, inactive: 0, failure: 0 } }; + + for (let i = 0; i < endpointAgentsCount; i += 1) { + const { attributes: metadataAttributes } = endpointAgents[i]; + const { last_checkin: lastCheckin, local_metadata: localMetadata } = metadataAttributes; + const { host, os, elastic } = localMetadata as AgentLocalMetadata; // AgentMetadata is just an empty blob, casting for our use case + + if (!uniqueHostIds.has(host.id)) { + uniqueHostIds.add(host.id); + const agentId = elastic?.agent?.id; + osTracker = updateEndpointOSTelemetry(os, osTracker); + + if (agentId) { + let agentEvents; + try { + const response = await getLatestFleetEndpointEvent(soClient, agentId); + agentEvents = response.saved_objects; + } catch (error) { + // If the request fails we do not obtain `active within last 24 hours for this agent` or policy specifics + } - const endpointMetadataTelemetry = endpointAgents.reduce( - (metadataTelemetry, { attributes: metadataAttributes }) => { - const { last_checkin: lastCheckin, local_metadata: localMetadata } = metadataAttributes; - const { host, os, elastic } = localMetadata as AgentLocalMetadata; // AgentMetadata is just an empty blob, casting for our use case - - if (host && uniqueHostIds.has(host.id)) { - // use hosts since new agents could potentially be re-installed on existing hosts - return metadataTelemetry; - } else { - uniqueHostIds.add(host.id); - const isActiveWithinLastDay = !!lastCheckin && new Date(lastCheckin) > aDayAgo; - agentDailyActiveTracker.set(elastic.agent.id, isActiveWithinLastDay); - osTracker = trackEndpointOSTelemetry(os, osTracker); - return metadataTelemetry; + // AgentEvents will have a max length of 1 + if (agentEvents && agentEvents.length > 0) { + const latestEndpointEvent = agentEvents[0]; + dailyActiveCount = updateEndpointDailyActiveCount( + latestEndpointEvent, + lastCheckin, + dailyActiveCount + ); + policyTracker = updateEndpointPolicyTelemetry(latestEndpointEvent, policyTracker); + } } - }, - endpointTelemetry - ); + } + } - // All unique hosts with an endpoint installed. + // All unique hosts with an endpoint installed, thus all unique endpoint installs endpointTelemetry.total_installed = uniqueHostIds.size; + // Set the daily active count for the endpoints + endpointTelemetry.active_within_last_24_hours = dailyActiveCount; // Get the objects to populate our OS Telemetry - endpointMetadataTelemetry.os = Object.values(osTracker); - // Populate endpoint telemetry with the finalized 24 hour count and policy details - const finalizedEndpointTelemetryData = await addEndpointDailyActivityAndPolicyDetailsToTelemetry( - agentDailyActiveTracker, - savedObjectsClient, - endpointMetadataTelemetry - ); - - return finalizedEndpointTelemetryData; + endpointTelemetry.os = Object.values(osTracker); + // Provide the updated policy information + endpointTelemetry.policies = policyTracker; + + return endpointTelemetry; }; From 5741a868bc524dbb8363b85f4a0f37fd8ab321f8 Mon Sep 17 00:00:00 2001 From: spalger Date: Mon, 20 Jul 2020 09:32:41 -0700 Subject: [PATCH 04/15] Revert "skip flaky suite (#72146)" This reverts commit 45a4393459e0400171564f1d096784ebc97cc8ed. --- test/functional/apps/dashboard/dashboard_error_handling.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/functional/apps/dashboard/dashboard_error_handling.ts b/test/functional/apps/dashboard/dashboard_error_handling.ts index 38803739ff129..6bd8327a110b9 100644 --- a/test/functional/apps/dashboard/dashboard_error_handling.ts +++ b/test/functional/apps/dashboard/dashboard_error_handling.ts @@ -28,8 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { /** * Common test suite for testing exception scenarious within dashboard */ - // Flaky: https://github.com/elastic/kibana/issues/72146 - describe.skip('dashboard error handling', () => { + describe('dashboard error handling', () => { before(async () => { await esArchiver.loadIfNeeded('dashboard/current/kibana'); await PageObjects.common.navigateToApp('dashboard'); From 75e4c7a2b73c5e0606e11cb3fecf08337a0ea81e Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Mon, 20 Jul 2020 12:40:59 -0400 Subject: [PATCH 05/15] [Resolver] no longer pass related event stats to process node component (#72435) --- .../public/resolver/store/data/selectors.ts | 38 ++++--------------- .../public/resolver/store/selectors.ts | 6 ++- .../public/resolver/view/map.tsx | 4 -- .../public/resolver/view/panel.tsx | 5 ++- .../resolver/view/process_event_dot.tsx | 20 +++------- 5 files changed, 20 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 109b3abddcc77..4098c6fc6c5dd 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -116,13 +116,14 @@ export const tree = createSelector(graphableProcesses, function indexedTree( */ export const relatedEventsStats: ( state: DataState -) => Map | null = createSelector( +) => (nodeID: string) => ResolverNodeStats | undefined = createSelector( resolverTreeResponse, (resolverTree?: ResolverTree) => { if (resolverTree) { - return resolverTreeModel.relatedEventsStats(resolverTree); + const map = resolverTreeModel.relatedEventsStats(resolverTree); + return (nodeID: string) => map.get(nodeID); } else { - return null; + return () => undefined; } } ); @@ -213,12 +214,8 @@ export const relatedEventInfoByEntityId: ( relatedEventsStats /* eslint-enable no-shadow */ ) { - if (!relatedEventsStats) { - // If there are no related event stats, there are no related event info objects - return () => null; - } return (entityId) => { - const stats = relatedEventsStats.get(entityId); + const stats = relatedEventsStats(entityId); if (!stats) { return null; } @@ -524,37 +521,16 @@ export function databaseDocumentIDToAbort(state: DataState): string | null { } } -/** - * `ResolverNodeStats` for a process (`ResolverEvent`) - */ -const relatedEventStatsForProcess: ( - state: DataState -) => (event: ResolverEvent) => ResolverNodeStats | null = createSelector( - relatedEventsStats, - (statsMap) => { - if (!statsMap) { - return () => null; - } - return (event: ResolverEvent) => { - const nodeStats = statsMap.get(uniquePidForProcess(event)); - if (!nodeStats) { - return null; - } - return nodeStats; - }; - } -); - /** * The sum of all related event categories for a process. */ export const relatedEventTotalForProcess: ( state: DataState ) => (event: ResolverEvent) => number | null = createSelector( - relatedEventStatsForProcess, + relatedEventsStats, (statsForProcess) => { return (event: ResolverEvent) => { - const stats = statsForProcess(event); + const stats = statsForProcess(uniquePidForProcess(event)); if (!stats) { return null; } diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 040e2920ce554..09293d0b3b683 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -10,7 +10,7 @@ import * as dataSelectors from './data/selectors'; import * as uiSelectors from './ui/selectors'; import { ResolverState, IsometricTaxiLayout } from '../types'; import { uniquePidForProcess } from '../models/process_event'; -import { ResolverEvent } from '../../../common/endpoint/types'; +import { ResolverEvent, ResolverNodeStats } from '../../../common/endpoint/types'; /** * A matrix that when applied to a Vector2 will convert it from world coordinates to screen coordinates. @@ -99,7 +99,9 @@ export const terminatedProcesses = composeSelectors( /** * Returns a map of `ResolverEvent` entity_id to their related event and alert statistics */ -export const relatedEventsStats = composeSelectors( +export const relatedEventsStats: ( + state: ResolverState +) => (nodeID: string) => ResolverNodeStats | undefined = composeSelectors( dataStateSelector, dataSelectors.relatedEventsStats ); diff --git a/x-pack/plugins/security_solution/public/resolver/view/map.tsx b/x-pack/plugins/security_solution/public/resolver/view/map.tsx index b366e2f220652..930e96c3f3e40 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/map.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/map.tsx @@ -60,7 +60,6 @@ export const ResolverMap = React.memo(function ({ const { processNodePositions, connectingEdgeLineSegments } = useSelector( selectors.visibleNodesAndEdgeLines )(timeAtRender); - const relatedEventsStats = useSelector(selectors.relatedEventsStats); const terminatedProcesses = useSelector(selectors.terminatedProcesses); const { projectionMatrix, ref, onMouseDown } = useCamera(); const isLoading = useSelector(selectors.isLoading); @@ -110,9 +109,6 @@ export const ResolverMap = React.memo(function ({ position={position} projectionMatrix={projectionMatrix} event={processEvent} - relatedEventsStatsForProcess={ - relatedEventsStats ? relatedEventsStats.get(entityId(processEvent)) : undefined - } isProcessTerminated={terminatedProcesses.has(processEntityId)} isProcessOrigin={false} timeAtRender={timeAtRender} diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx index 47ce9b949fa59..efb2d95396ef5 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx @@ -99,8 +99,9 @@ const PanelContent = memo(function PanelContent() { const relatedEventStats = useSelector(selectors.relatedEventsStats); const { crumbId, crumbEvent } = queryParams; - const relatedStatsForIdFromParams: ResolverNodeStats | undefined = - idFromParams && relatedEventStats ? relatedEventStats.get(idFromParams) : undefined; + const relatedStatsForIdFromParams: ResolverNodeStats | undefined = idFromParams + ? relatedEventStats(idFromParams) + : undefined; /** * Determine which set of breadcrumbs to display based on the query parameters diff --git a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx index 7666d1ac7c88a..aab4193bf031d 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx @@ -14,7 +14,7 @@ import { NodeSubMenu, subMenuAssets } from './submenu'; import { applyMatrix3 } from '../models/vector2'; import { Vector2, Matrix3 } from '../types'; import { SymbolIds, useResolverTheme, calculateResolverFontSize } from './assets'; -import { ResolverEvent, ResolverNodeStats } from '../../../common/endpoint/types'; +import { ResolverEvent } from '../../../common/endpoint/types'; import { useResolverDispatch } from './use_resolver_dispatch'; import * as eventModel from '../../../common/endpoint/models/event'; import * as processEventModel from '../models/process_event'; @@ -73,7 +73,6 @@ const UnstyledProcessEventDot = React.memo( projectionMatrix, isProcessTerminated, isProcessOrigin, - relatedEventsStatsForProcess, timeAtRender, }: { /** @@ -100,12 +99,6 @@ const UnstyledProcessEventDot = React.memo( * Whether or not to show the process as the originating event. */ isProcessOrigin: boolean; - /** - * A collection of events related to the current node and statistics (e.g. counts indexed by event type) - * to provide the user some visibility regarding the contents thereof. - * Statistics for the number of related events and alerts for this process node - */ - relatedEventsStatsForProcess?: ResolverNodeStats; /** * The time (unix epoch) at render. @@ -127,6 +120,7 @@ const UnstyledProcessEventDot = React.memo( const activeDescendantId = useSelector(selectors.uiActiveDescendantId); const selectedDescendantId = useSelector(selectors.uiSelectedDescendantId); const nodeID = processEventModel.uniquePidForProcess(event); + const relatedEventStats = useSelector(selectors.relatedEventsStats)(nodeID); // define a standard way of giving HTML IDs to nodes based on their entity_id/nodeID. // this is used to link nodes via aria attributes @@ -270,15 +264,13 @@ const UnstyledProcessEventDot = React.memo( const relatedEventOptions = useMemo(() => { const relatedStatsList = []; - if (!relatedEventsStatsForProcess) { + if (!relatedEventStats) { // Return an empty set of options if there are no stats to report return []; } // If we have entries to show, map them into options to display in the selectable list - for (const [category, total] of Object.entries( - relatedEventsStatsForProcess.events.byCategory - )) { + for (const [category, total] of Object.entries(relatedEventStats.events.byCategory)) { relatedStatsList.push({ prefix: , optionTitle: category, @@ -296,9 +288,9 @@ const UnstyledProcessEventDot = React.memo( }); } return relatedStatsList; - }, [relatedEventsStatsForProcess, dispatch, event, pushToQueryParams, nodeID]); + }, [relatedEventStats, dispatch, event, pushToQueryParams, nodeID]); - const relatedEventStatusOrOptions = !relatedEventsStatsForProcess + const relatedEventStatusOrOptions = !relatedEventStats ? subMenuAssets.initialMenuStatus : relatedEventOptions; From afae94a85ec8d58221f673c23f76aa664fe0cea0 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 20 Jul 2020 11:00:06 -0600 Subject: [PATCH 06/15] [SIEM][Detection Engine][Lists] Adds conflict versioning and io-ts improvements to lists (#72337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary * Adds conflict versioning by exposing the "_version" from the saved object system. It renames "version" to "_version" so that we can use regular "version" later for versioning things for pre-packaged lists abilities. * Utilizes `t.OutputOf` in the requests and the data types to give us more correctly types * Removes the `Identity` utility as that is adding confusion and can confuse vs code rather than improves things * Removes extra types that were causing confusion which was an idiom from io-ts * Changes the wording of `Partial` by removing that and instead focuses the request types on either client side or server side at this point. NOTE: The UI can migrate to holding onto the `_version` and then push it back down when it wants to migrate to using the conflict resolution. If the UI does not push it down, then a value of undefined will be used which is indicating that no conflict errors are wanted. Output example of posting an exception list: ❯ ./post_exception_list.sh ```ts { "_tags": [ "endpoint", "process", "malware", "os:linux" ], "_version": "Wzk4NiwxXQ==", "created_at": "2020-07-17T18:59:22.872Z", "created_by": "yo", "description": "This is a sample endpoint type exception", "id": "a08795b0-c85f-11ea-b1a6-c155df988a92", "list_id": "simple_list", "name": "Sample Endpoint Exception List", "namespace_type": "single", "tags": [ "user added string for a tag", "malware" ], "tie_breaker_id": "b789ec05-3e0f-4344-a156-0c0f5b6e2f9c", "type": "detection", "updated_at": "2020-07-17T18:59:22.891Z", "updated_by": "yo" } ``` Output example of posting an exception list item ❯ ./post_exception_list_item.sh ```ts { "_tags": [ "endpoint", "process", "malware", "os:linux" ], "_version": "Wzk4NywxXQ==", "comments": [], "created_at": "2020-07-17T18:59:30.286Z", "created_by": "yo", "description": "This is a sample endpoint type exception", "entries": [ { "field": "actingProcess.file.signer", "operator": "excluded", "type": "exists" }, { "field": "host.name", "operator": "included", "type": "match_any", "value": [ "some host", "another host" ] } ], "id": "a4f2b800-c85f-11ea-b1a6-c155df988a92", "item_id": "simple_list_item", "list_id": "simple_list", "name": "Sample Endpoint Exception List", "namespace_type": "single", "tags": [ "user added string for a tag", "malware" ], "tie_breaker_id": "1dc456bc-7aa9-44b4-bca3-131689cf729f", "type": "simple", "updated_at": "2020-07-17T18:59:30.304Z", "updated_by": "yo" } ``` Output example of when you get an exception list: ❯ ./get_exception_list.sh simple_list ```ts { "_tags": [ "endpoint", "process", "malware", "os:linux" ], "_version": "WzEwNzcsMV0=", "created_at": "2020-07-17T18:59:22.872Z", "created_by": "yo", "description": "Different description", "id": "a08795b0-c85f-11ea-b1a6-c155df988a92", "list_id": "simple_list", "name": "Sample Endpoint Exception List", "namespace_type": "single", "tags": [ "user added string for a tag", "malware" ], "tie_breaker_id": "b789ec05-3e0f-4344-a156-0c0f5b6e2f9c", "type": "endpoint", "updated_at": "2020-07-17T20:01:24.958Z", "updated_by": "yo" } ``` Example of the error you get if you do an update of an exception list and someone else has changed it: ```ts { "message": "[exception-list:a08795b0-c85f-11ea-b1a6-c155df988a92]: version conflict, required seqNo [1074], primary term [1]. current document has seqNo [1077] and primary term [1]: [version_conflict_engine_exception] [exception-list:a08795b0-c85f-11ea-b1a6-c155df988a92]: version conflict, required seqNo [1074], primary term [1]. current document has seqNo [1077] and primary term [1], with { index_uuid=\"a2mgXBO6Tl2ULDq-MTs1Tw\" & shard=\"0\" & index=\".kibana-hassanabad_1\" }", "status_code": 409 } ``` Lists are the same way and flavor, they encode the _version the same way that saved objects do. To see those work you run these scripts: ```ts ./post_list.sh ./post_list_item.sh ./find_list.sh ./find_list_item.sh ``` ### Checklist - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- x-pack/plugins/lists/common/constants.mock.ts | 1 + .../lists/common/schemas/common/schemas.ts | 4 ++ .../elastic_query/create_es_bulk_type.ts | 2 +- .../index_es_list_item_schema.ts | 2 +- .../elastic_query/index_es_list_schema.ts | 2 +- .../update_es_list_item_schema.ts | 2 +- .../elastic_query/update_es_list_schema.ts | 2 +- .../create_endpoint_list_item_schema.ts | 28 ++++++-------- .../create_exception_list_item_schema.ts | 33 +++++++---------- .../request/create_exception_list_schema.ts | 24 ++++++------ .../request/create_list_item_schema.ts | 8 ++-- .../schemas/request/create_list_schema.ts | 6 +-- .../delete_endpoint_list_item_schema.ts | 7 +++- .../delete_exception_list_item_schema.ts | 5 ++- .../request/delete_exception_list_schema.ts | 8 +++- .../request/delete_list_item_schema.ts | 8 ++-- .../schemas/request/delete_list_schema.ts | 3 +- .../request/export_list_item_query_schema.ts | 5 ++- .../find_endpoint_list_item_schema.mock.ts | 8 ++-- .../find_endpoint_list_item_schema.test.ts | 6 +-- .../request/find_endpoint_list_item_schema.ts | 9 +---- .../find_exception_list_item_schema.mock.ts | 12 +++--- .../find_exception_list_item_schema.test.ts | 16 +++++--- .../find_exception_list_item_schema.ts | 15 ++------ .../find_exception_list_schema.mock.ts | 8 ++-- .../find_exception_list_schema.test.ts | 15 +++++--- .../request/find_exception_list_schema.ts | 15 ++------ .../request/find_list_item_schema.mock.ts | 9 ++--- .../request/find_list_item_schema.test.ts | 10 ++--- .../schemas/request/find_list_item_schema.ts | 8 ++-- .../schemas/request/find_list_schema.mock.ts | 1 + .../schemas/request/find_list_schema.test.ts | 5 +-- .../schemas/request/find_list_schema.ts | 3 +- .../request/import_list_item_query_schema.ts | 8 ++-- .../request/import_list_item_schema.ts | 3 +- .../schemas/request/patch_list_item_schema.ts | 12 +++--- .../schemas/request/patch_list_schema.ts | 10 ++--- .../request/read_endpoint_list_item_schema.ts | 9 +---- .../read_exception_list_item_schema.ts | 13 ++----- .../request/read_exception_list_schema.ts | 13 ++----- .../schemas/request/read_list_item_schema.ts | 6 +-- .../schemas/request/read_list_schema.ts | 2 +- .../update_endpoint_list_item_schema.mock.ts | 1 + .../update_endpoint_list_item_schema.ts | 28 +++++++------- .../update_exception_list_item_schema.mock.ts | 1 + .../update_exception_list_item_schema.ts | 33 +++++++---------- .../update_exception_list_schema.mock.ts | 1 + .../request/update_exception_list_schema.ts | 30 ++++++++------- .../request/update_list_item_schema.ts | 11 ++++-- .../schemas/request/update_list_schema.ts | 9 +++-- .../create_endpoint_list_schema.test.ts | 2 +- .../exception_list_item_schema.mock.ts | 36 ++++++++++++------ .../response/exception_list_item_schema.ts | 2 + .../response/exception_list_schema.mock.ts | 25 +++++++++---- .../schemas/response/exception_list_schema.ts | 2 + .../schemas/response/list_item_schema.mock.ts | 1 + .../schemas/response/list_item_schema.ts | 2 + .../schemas/response/list_schema.mock.ts | 1 + .../common/schemas/response/list_schema.ts | 2 + .../schemas/types/default_comments_array.ts | 8 +--- .../types/default_create_comments_array.ts | 4 +- .../schemas/types/default_entries_array.ts | 8 +--- .../common/schemas/types/default_namespace.ts | 4 +- .../types/default_namespace_array.test.ts | 18 ++++----- .../schemas/types/default_namespace_array.ts | 4 +- .../types/default_update_comments_array.ts | 4 +- .../schemas/types/empty_string_array.ts | 2 - .../types/non_empty_string_array.test.ts | 16 ++++---- .../schemas/types/non_empty_string_array.ts | 4 +- x-pack/plugins/lists/common/types.ts | 8 ---- .../server/routes/find_list_item_route.ts | 4 +- .../server/routes/patch_list_item_route.ts | 3 +- .../lists/server/routes/patch_list_route.ts | 4 +- .../routes/update_endpoint_list_item_route.ts | 2 + .../update_exception_list_item_route.ts | 2 + .../routes/update_exception_list_route.ts | 2 + .../server/routes/update_list_item_route.ts | 3 +- .../lists/server/routes/update_list_route.ts | 4 +- .../updates/simple_update.json | 2 +- .../exception_lists/exception_list_client.ts | 6 +++ .../exception_list_client_types.ts | 4 ++ .../exception_lists/update_exception_list.ts | 6 +++ .../update_exception_list_item.ts | 6 +++ .../server/services/exception_lists/utils.ts | 8 ++++ .../server/services/items/create_list_item.ts | 2 + .../server/services/items/find_list_item.ts | 7 +++- .../server/services/items/get_list_item.ts | 7 +++- .../services/items/update_list_item.mock.ts | 1 + .../server/services/items/update_list_item.ts | 7 ++++ .../server/services/lists/create_list.ts | 2 + .../lists/server/services/lists/find_list.ts | 7 +++- .../lists/server/services/lists/get_list.ts | 7 +++- .../server/services/lists/list_client.ts | 4 ++ .../services/lists/list_client_types.ts | 3 ++ .../server/services/lists/update_list.mock.ts | 1 + .../server/services/lists/update_list.ts | 7 ++++ .../server/services/utils/decode_version.ts | 37 +++++++++++++++++++ .../services/utils/encode_hit_version.ts | 27 ++++++++++++++ .../utils/transform_elastic_to_list.ts | 3 ++ .../utils/transform_elastic_to_list_item.ts | 2 + .../schemas/types/default_actions_array.ts | 6 +-- .../schemas/types/default_boolean_false.ts | 4 +- .../schemas/types/default_boolean_true.ts | 4 +- .../schemas/types/default_empty_string.ts | 4 +- .../schemas/types/default_export_file_name.ts | 4 +- .../schemas/types/default_from_string.ts | 4 +- .../schemas/types/default_interval_string.ts | 4 +- .../schemas/types/default_language_string.ts | 4 +- .../types/default_max_signals_number.ts | 8 +--- .../schemas/types/default_page.ts | 4 +- .../schemas/types/default_per_page.ts | 4 +- .../types/default_risk_score_mapping_array.ts | 8 ++-- .../types/default_severity_mapping_array.ts | 8 ++-- .../schemas/types/default_string_array.ts | 7 ++-- .../types/default_string_boolean_false.ts | 2 +- .../schemas/types/default_threat_array.ts | 4 +- .../schemas/types/default_throttle_null.ts | 4 +- .../schemas/types/default_to_string.ts | 4 +- .../schemas/types/default_uuid.ts | 4 +- .../schemas/types/default_version_number.ts | 4 +- .../schemas/types/lists_default_array.test.ts | 4 +- .../schemas/types/lists_default_array.ts | 4 +- .../schemas/types/only_false_allowed.ts | 4 +- .../schemas/types/positive_integer.ts | 2 - .../positive_integer_greater_than_zero.ts | 2 - .../schemas/types/references_default_array.ts | 4 +- .../components/exceptions/helpers.test.tsx | 16 ++++---- .../exception_item/exception_details.test.tsx | 8 ++-- 128 files changed, 517 insertions(+), 435 deletions(-) create mode 100644 x-pack/plugins/lists/server/services/utils/decode_version.ts create mode 100644 x-pack/plugins/lists/server/services/utils/encode_hit_version.ts diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 4924ba24426af..6ed1d19611c68 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -60,3 +60,4 @@ export const TAGS = []; export const COMMENTS = []; export const FILTER = 'name:Nicolas Bourbaki'; export const CURSOR = 'c29tZXN0cmluZ2ZvcnlvdQ=='; +export const _VERSION = 'WzI5NywxXQ=='; diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.ts b/x-pack/plugins/lists/common/schemas/common/schemas.ts index 6199a5f16f109..8f1666bb542d9 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.ts @@ -307,3 +307,7 @@ export type Deserializer = t.TypeOf; export const deserializerOrUndefined = t.union([deserializer, t.undefined]); export type DeserializerOrUndefined = t.TypeOf; + +export const _version = t.string; +export const _versionOrUndefined = t.union([_version, t.undefined]); +export type _VersionOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/create_es_bulk_type.ts b/x-pack/plugins/lists/common/schemas/elastic_query/create_es_bulk_type.ts index 4a825382c06e4..3104ee27c57de 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/create_es_bulk_type.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/create_es_bulk_type.ts @@ -14,4 +14,4 @@ export const createEsBulkTypeSchema = t.exact( }) ); -export type CreateEsBulkTypeSchema = t.TypeOf; +export type CreateEsBulkTypeSchema = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.ts index 006600ee5b7fd..8dc5a376d1495 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.ts @@ -38,4 +38,4 @@ export const indexEsListItemSchema = t.intersection([ esDataTypeUnion, ]); -export type IndexEsListItemSchema = t.TypeOf; +export type IndexEsListItemSchema = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.ts b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.ts index fd1018bc46a8d..3ee598291149f 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.ts @@ -38,4 +38,4 @@ export const indexEsListSchema = t.exact( }) ); -export type IndexEsListSchema = t.TypeOf; +export type IndexEsListSchema = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_item_schema.ts index e4cf46bc39429..20187de535a8e 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_item_schema.ts @@ -21,4 +21,4 @@ export const updateEsListItemSchema = t.intersection([ esDataTypeUnion, ]); -export type UpdateEsListItemSchema = t.TypeOf; +export type UpdateEsListItemSchema = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts index 8f23f3744e563..80b9733908d39 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts @@ -26,4 +26,4 @@ export const updateEsListSchema = t.exact( }) ); -export type UpdateEsListSchema = t.TypeOf; +export type UpdateEsListSchema = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts index 5311c7a43cdb5..3f0e1a12894d4 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts @@ -19,7 +19,7 @@ import { name, tags, } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; import { CreateCommentsArray, DefaultCreateCommentsArray, DefaultEntryArray } from '../types'; import { EntriesArray } from '../types/entries'; import { DefaultUuid } from '../../siem_common_deps'; @@ -44,20 +44,16 @@ export const createEndpointListItemSchema = t.intersection([ ), ]); -export type CreateEndpointListItemSchemaPartial = Identity< - t.TypeOf ->; -export type CreateEndpointListItemSchema = RequiredKeepUndefined< - t.TypeOf ->; +export type CreateEndpointListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type CreateEndpointListItemSchemaDecoded = Identity< - Omit & { - _tags: _Tags; - comments: CreateCommentsArray; - tags: Tags; - item_id: ItemId; - entries: EntriesArray; - } ->; +export type CreateEndpointListItemSchemaDecoded = Omit< + RequiredKeepUndefined>, + '_tags' | 'tags' | 'item_id' | 'entries' | 'comments' +> & { + _tags: _Tags; + comments: CreateCommentsArray; + tags: Tags; + item_id: ItemId; + entries: EntriesArray; +}; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts index 4b7db3eee35bc..c2ccf18ed8720 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts @@ -21,7 +21,7 @@ import { namespace_type, tags, } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; import { CreateCommentsArray, DefaultCreateCommentsArray, @@ -53,24 +53,17 @@ export const createExceptionListItemSchema = t.intersection([ ), ]); -export type CreateExceptionListItemSchemaPartial = Identity< - t.TypeOf ->; -export type CreateExceptionListItemSchema = RequiredKeepUndefined< - t.TypeOf ->; +export type CreateExceptionListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type CreateExceptionListItemSchemaDecoded = Identity< - Omit< - CreateExceptionListItemSchema, - '_tags' | 'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' - > & { - _tags: _Tags; - comments: CreateCommentsArray; - tags: Tags; - item_id: ItemId; - entries: EntriesArray; - namespace_type: NamespaceType; - } ->; +export type CreateExceptionListItemSchemaDecoded = Omit< + RequiredKeepUndefined>, + '_tags' | 'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' +> & { + _tags: _Tags; + comments: CreateCommentsArray; + tags: Tags; + item_id: ItemId; + entries: EntriesArray; + namespace_type: NamespaceType; +}; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts index 66cca4ab9ca53..8f714760621ff 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts @@ -20,7 +20,7 @@ import { namespace_type, tags, } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; import { DefaultUuid } from '../../siem_common_deps'; import { NamespaceType } from '../types'; @@ -43,17 +43,15 @@ export const createExceptionListSchema = t.intersection([ ), ]); -export type CreateExceptionListSchemaPartial = Identity>; -export type CreateExceptionListSchema = RequiredKeepUndefined< - t.TypeOf ->; +export type CreateExceptionListSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type CreateExceptionListSchemaDecoded = Identity< - Omit & { - _tags: _Tags; - tags: Tags; - list_id: ListId; - namespace_type: NamespaceType; - } ->; +export type CreateExceptionListSchemaDecoded = Omit< + RequiredKeepUndefined>, + '_tags' | 'tags' | 'list_id' | 'namespace_type' +> & { + _tags: _Tags; + tags: Tags; + list_id: ListId; + namespace_type: NamespaceType; +}; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts index 6d16f2074864c..351eae48a638d 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { id, list_id, meta, value } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; export const createListItemSchema = t.intersection([ t.exact( @@ -21,5 +21,7 @@ export const createListItemSchema = t.intersection([ t.exact(t.partial({ id, meta })), ]); -export type CreateListItemSchemaPartial = Identity>; -export type CreateListItemSchema = RequiredKeepUndefined>; +export type CreateListItemSchema = t.OutputOf; +export type CreateListItemSchemaDecoded = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts index fcf4465f88c8d..38d6167ea63f3 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { description, deserializer, id, meta, name, serializer, type } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; export const createListSchema = t.intersection([ t.exact( @@ -20,5 +20,5 @@ export const createListSchema = t.intersection([ t.exact(t.partial({ deserializer, id, meta, serializer })), ]); -export type CreateListSchemaPartial = Identity>; -export type CreateListSchema = RequiredKeepUndefined>; +export type CreateListSchema = t.OutputOf; +export type CreateListSchemaDecoded = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts index 311af3a4c0437..5af5bcd17e744 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { id, item_id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const deleteEndpointListItemSchema = t.exact( t.partial({ @@ -17,7 +18,9 @@ export const deleteEndpointListItemSchema = t.exact( }) ); -export type DeleteEndpointListItemSchema = t.TypeOf; +export type DeleteEndpointListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type DeleteEndpointListItemSchemaDecoded = DeleteEndpointListItemSchema; +export type DeleteEndpointListItemSchemaDecoded = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts index 909960c9fffc0..da6516f4b6fe4 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { id, item_id, namespace_type } from '../common/schemas'; import { NamespaceType } from '../types'; +import { RequiredKeepUndefined } from '../../types'; export const deleteExceptionListItemSchema = t.exact( t.partial({ @@ -19,11 +20,11 @@ export const deleteExceptionListItemSchema = t.exact( }) ); -export type DeleteExceptionListItemSchema = t.TypeOf; +export type DeleteExceptionListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. export type DeleteExceptionListItemSchemaDecoded = Omit< - DeleteExceptionListItemSchema, + RequiredKeepUndefined>, 'namespace_type' > & { namespace_type: NamespaceType; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts index 3bf5e7a4d0782..0911a9342f7a9 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { id, list_id, namespace_type } from '../common/schemas'; import { NamespaceType } from '../types'; +import { RequiredKeepUndefined } from '../../types'; export const deleteExceptionListSchema = t.exact( t.partial({ @@ -19,9 +20,12 @@ export const deleteExceptionListSchema = t.exact( }) ); -export type DeleteExceptionListSchema = t.TypeOf; +export type DeleteExceptionListSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type DeleteExceptionListSchemaDecoded = Omit & { +export type DeleteExceptionListSchemaDecoded = Omit< + RequiredKeepUndefined>, + 'namespace_type' +> & { namespace_type: NamespaceType; }; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts index 91887395e747d..5e2425271c463 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { id, list_id, valueOrUndefined } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; export const deleteListItemSchema = t.intersection([ t.exact( @@ -20,5 +20,7 @@ export const deleteListItemSchema = t.intersection([ t.exact(t.partial({ id, list_id })), ]); -export type DeleteListItemSchemaPartial = Identity>; -export type DeleteListItemSchema = RequiredKeepUndefined>; +export type DeleteListItemSchema = t.OutputOf; +export type DeleteListItemSchemaDecoded = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts index 6f6fc7a9ea33c..830e7fe695d1d 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const deleteListSchema = t.exact( t.type({ @@ -16,5 +17,5 @@ export const deleteListSchema = t.exact( }) ); -export type DeleteListSchema = t.TypeOf; +export type DeleteListSchema = RequiredKeepUndefined>; export type DeleteListSchemaEncoded = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.ts index 58092ffc563b1..8d14f015d3805 100644 --- a/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { list_id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const exportListItemQuerySchema = t.exact( t.type({ @@ -17,5 +18,7 @@ export const exportListItemQuerySchema = t.exact( }) ); -export type ExportListItemQuerySchema = t.TypeOf; +export type ExportListItemQuerySchema = RequiredKeepUndefined< + t.TypeOf +>; export type ExportListItemQuerySchemaEncoded = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.mock.ts index bff55dedf3064..469936eae96c9 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.mock.ts @@ -7,11 +7,11 @@ import { FILTER } from '../../constants.mock'; import { - FindEndpointListItemSchemaPartial, - FindEndpointListItemSchemaPartialDecoded, + FindEndpointListItemSchema, + FindEndpointListItemSchemaDecoded, } from './find_endpoint_list_item_schema'; -export const getFindEndpointListItemSchemaMock = (): FindEndpointListItemSchemaPartial => ({ +export const getFindEndpointListItemSchemaMock = (): FindEndpointListItemSchema => ({ filter: FILTER, page: '1', per_page: '25', @@ -19,7 +19,7 @@ export const getFindEndpointListItemSchemaMock = (): FindEndpointListItemSchemaP sort_order: undefined, }); -export const getFindEndpointListItemSchemaDecodedMock = (): FindEndpointListItemSchemaPartialDecoded => ({ +export const getFindEndpointListItemSchemaDecodedMock = (): FindEndpointListItemSchemaDecoded => ({ filter: FILTER, page: 1, per_page: 25, diff --git a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts index f9eeaa33230f9..8249b1e2d49c2 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.test.ts @@ -14,7 +14,7 @@ import { getFindEndpointListItemSchemaMock, } from './find_endpoint_list_item_schema.mock'; import { - FindEndpointListItemSchemaPartial, + FindEndpointListItemSchema, findEndpointListItemSchema, } from './find_endpoint_list_item_schema'; @@ -29,7 +29,7 @@ describe('find_endpoint_list_item_schema', () => { }); test('it should validate and empty object since everything is optional and has defaults', () => { - const payload: FindEndpointListItemSchemaPartial = {}; + const payload: FindEndpointListItemSchema = {}; const decoded = findEndpointListItemSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -98,7 +98,7 @@ describe('find_endpoint_list_item_schema', () => { }); test('it should not allow an extra key to be sent in', () => { - const payload: FindEndpointListItemSchemaPartial & { + const payload: FindEndpointListItemSchema & { extraKey: string; } = { ...getFindEndpointListItemSchemaMock(), extraKey: 'some new value' }; const decoded = findEndpointListItemSchema.decode(payload); diff --git a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts index c9ee46994d720..bc839ce1346f3 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts @@ -22,16 +22,9 @@ export const findEndpointListItemSchema = t.exact( }) ); -export type FindEndpointListItemSchemaPartial = t.OutputOf; - -// This type is used after a decode since some things are defaults after a decode. -export type FindEndpointListItemSchemaPartialDecoded = t.TypeOf; +export type FindEndpointListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. export type FindEndpointListItemSchemaDecoded = RequiredKeepUndefined< - FindEndpointListItemSchemaPartialDecoded ->; - -export type FindEndpointListItemSchema = RequiredKeepUndefined< t.TypeOf >; diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.mock.ts index f22e6685fe0ac..d2733531eada4 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.mock.ts @@ -7,11 +7,11 @@ import { FILTER, LIST_ID, NAMESPACE_TYPE } from '../../constants.mock'; import { - FindExceptionListItemSchemaPartial, - FindExceptionListItemSchemaPartialDecoded, + FindExceptionListItemSchema, + FindExceptionListItemSchemaDecoded, } from './find_exception_list_item_schema'; -export const getFindExceptionListItemSchemaMock = (): FindExceptionListItemSchemaPartial => ({ +export const getFindExceptionListItemSchemaMock = (): FindExceptionListItemSchema => ({ filter: FILTER, list_id: LIST_ID, namespace_type: NAMESPACE_TYPE, @@ -21,7 +21,7 @@ export const getFindExceptionListItemSchemaMock = (): FindExceptionListItemSchem sort_order: undefined, }); -export const getFindExceptionListItemSchemaMultipleMock = (): FindExceptionListItemSchemaPartial => ({ +export const getFindExceptionListItemSchemaMultipleMock = (): FindExceptionListItemSchema => ({ filter: 'name:Sofia Kovalevskaya,name:Hypatia,name:Sophie Germain', list_id: 'list-1,list-2,list-3', namespace_type: 'single,single,agnostic', @@ -31,7 +31,7 @@ export const getFindExceptionListItemSchemaMultipleMock = (): FindExceptionListI sort_order: undefined, }); -export const getFindExceptionListItemSchemaDecodedMock = (): FindExceptionListItemSchemaPartialDecoded => ({ +export const getFindExceptionListItemSchemaDecodedMock = (): FindExceptionListItemSchemaDecoded => ({ filter: [FILTER], list_id: [LIST_ID], namespace_type: [NAMESPACE_TYPE], @@ -41,7 +41,7 @@ export const getFindExceptionListItemSchemaDecodedMock = (): FindExceptionListIt sort_order: undefined, }); -export const getFindExceptionListItemSchemaDecodedMultipleMock = (): FindExceptionListItemSchemaPartialDecoded => ({ +export const getFindExceptionListItemSchemaDecodedMultipleMock = (): FindExceptionListItemSchemaDecoded => ({ filter: ['name:Sofia Kovalevskaya', 'name:Hypatia', 'name:Sophie Germain'], list_id: ['list-1', 'list-2', 'list-3'], namespace_type: ['single', 'single', 'agnostic'], diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts index ba64bb434d50b..f402f22b093ad 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.test.ts @@ -17,8 +17,8 @@ import { getFindExceptionListItemSchemaMultipleMock, } from './find_exception_list_item_schema.mock'; import { - FindExceptionListItemSchemaPartial, - FindExceptionListItemSchemaPartialDecoded, + FindExceptionListItemSchema, + FindExceptionListItemSchemaDecoded, findExceptionListItemSchema, } from './find_exception_list_item_schema'; @@ -42,15 +42,19 @@ describe('find_list_item_schema', () => { }); test('it should validate just a list_id where it decodes into an array for list_id and adds a namespace_type of "single" as an array', () => { - const payload: FindExceptionListItemSchemaPartial = { list_id: LIST_ID }; + const payload: FindExceptionListItemSchema = { list_id: LIST_ID }; const decoded = findExceptionListItemSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: FindExceptionListItemSchemaPartialDecoded = { + const expected: FindExceptionListItemSchemaDecoded = { filter: [], list_id: [LIST_ID], namespace_type: ['single'], + page: undefined, + per_page: undefined, + sort_field: undefined, + sort_order: undefined, }; expect(message.schema).toEqual(expected); }); @@ -86,7 +90,7 @@ describe('find_list_item_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: FindExceptionListItemSchemaPartialDecoded = { + const expected: FindExceptionListItemSchemaDecoded = { ...getFindExceptionListItemSchemaDecodedMock(), filter: [], }; @@ -118,7 +122,7 @@ describe('find_list_item_schema', () => { }); test('it should not allow an extra key to be sent in', () => { - const payload: FindExceptionListItemSchemaPartial & { + const payload: FindExceptionListItemSchema & { extraKey: string; } = { ...getFindExceptionListItemSchemaMock(), extraKey: 'some new value' }; const decoded = findExceptionListItemSchema.decode(payload); diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts index aa53fa0fd912c..634c080d70b75 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts @@ -36,22 +36,13 @@ export const findExceptionListItemSchema = t.intersection([ ), ]); -export type FindExceptionListItemSchemaPartial = t.OutputOf; +export type FindExceptionListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type FindExceptionListItemSchemaPartialDecoded = Omit< - t.TypeOf, +export type FindExceptionListItemSchemaDecoded = Omit< + RequiredKeepUndefined>, 'namespace_type' | 'filter' > & { filter: EmptyStringArrayDecoded; namespace_type: DefaultNamespaceArrayTypeDecoded; }; - -// This type is used after a decode since some things are defaults after a decode. -export type FindExceptionListItemSchemaDecoded = RequiredKeepUndefined< - FindExceptionListItemSchemaPartialDecoded ->; - -export type FindExceptionListItemSchema = RequiredKeepUndefined< - t.TypeOf ->; diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.mock.ts index 8080d10ca451c..96f4b7e1cbd63 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.mock.ts @@ -7,11 +7,11 @@ import { FILTER, NAMESPACE_TYPE } from '../../constants.mock'; import { - FindExceptionListSchemaPartial, - FindExceptionListSchemaPartialDecoded, + FindExceptionListSchema, + FindExceptionListSchemaDecoded, } from './find_exception_list_schema'; -export const getFindExceptionListSchemaMock = (): FindExceptionListSchemaPartial => ({ +export const getFindExceptionListSchemaMock = (): FindExceptionListSchema => ({ filter: FILTER, namespace_type: NAMESPACE_TYPE, page: '1', @@ -20,7 +20,7 @@ export const getFindExceptionListSchemaMock = (): FindExceptionListSchemaPartial sort_order: undefined, }); -export const getFindExceptionListSchemaDecodedMock = (): FindExceptionListSchemaPartialDecoded => ({ +export const getFindExceptionListSchemaDecodedMock = (): FindExceptionListSchemaDecoded => ({ filter: FILTER, namespace_type: NAMESPACE_TYPE, page: 1, diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts index 42356066176d5..ef96346c732b8 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.test.ts @@ -14,8 +14,8 @@ import { getFindExceptionListSchemaMock, } from './find_exception_list_schema.mock'; import { - FindExceptionListSchemaPartial, - FindExceptionListSchemaPartialDecoded, + FindExceptionListSchema, + FindExceptionListSchemaDecoded, findExceptionListSchema, } from './find_exception_list_schema'; @@ -30,13 +30,18 @@ describe('find_exception_list_schema', () => { }); test('it should validate and empty object since everything is optional and will respond only with namespace_type filled out to be "single"', () => { - const payload: FindExceptionListSchemaPartial = {}; + const payload: FindExceptionListSchema = {}; const decoded = findExceptionListSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: FindExceptionListSchemaPartialDecoded = { + const expected: FindExceptionListSchemaDecoded = { + filter: undefined, namespace_type: 'single', + page: undefined, + per_page: undefined, + sort_field: undefined, + sort_order: undefined, }; expect(message.schema).toEqual(expected); }); @@ -102,7 +107,7 @@ describe('find_exception_list_schema', () => { }); test('it should not allow an extra key to be sent in', () => { - const payload: FindExceptionListSchemaPartial & { + const payload: FindExceptionListSchema & { extraKey: string; } = { ...getFindExceptionListSchemaMock(), extraKey: 'some new value' }; const decoded = findExceptionListSchema.decode(payload); diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts index 4fa9d2e42c5d1..7ce01c79bbe42 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts @@ -24,21 +24,12 @@ export const findExceptionListSchema = t.exact( }) ); -export type FindExceptionListSchemaPartial = t.OutputOf; +export type FindExceptionListSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type FindExceptionListSchemaPartialDecoded = Omit< - t.TypeOf, +export type FindExceptionListSchemaDecoded = Omit< + RequiredKeepUndefined>, 'namespace_type' > & { namespace_type: NamespaceType; }; - -// This type is used after a decode since some things are defaults after a decode. -export type FindExceptionListSchemaDecoded = RequiredKeepUndefined< - FindExceptionListSchemaPartialDecoded ->; - -export type FindExceptionListSchema = RequiredKeepUndefined< - t.TypeOf ->; diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.mock.ts index a1e91f6acd264..ccde93eec4580 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.mock.ts @@ -6,12 +6,9 @@ import { CURSOR, FILTER, LIST_ID } from '../../constants.mock'; -import { - FindListItemSchemaPartial, - FindListItemSchemaPartialDecoded, -} from './find_list_item_schema'; +import { FindListItemSchema, FindListItemSchemaDecoded } from './find_list_item_schema'; -export const getFindListItemSchemaMock = (): FindListItemSchemaPartial => ({ +export const getFindListItemSchemaMock = (): FindListItemSchema => ({ cursor: CURSOR, filter: FILTER, list_id: LIST_ID, @@ -21,7 +18,7 @@ export const getFindListItemSchemaMock = (): FindListItemSchemaPartial => ({ sort_order: undefined, }); -export const getFindListItemSchemaDecodedMock = (): FindListItemSchemaPartialDecoded => ({ +export const getFindListItemSchemaDecodedMock = (): FindListItemSchemaDecoded => ({ cursor: CURSOR, filter: FILTER, list_id: LIST_ID, diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts index 42803fffc53c2..59d4b4485b578 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.test.ts @@ -15,8 +15,8 @@ import { getFindListItemSchemaMock, } from './find_list_item_schema.mock'; import { - FindListItemSchemaPartial, - FindListItemSchemaPartialDecoded, + FindListItemSchema, + FindListItemSchemaDecoded, findListItemSchema, } from './find_list_item_schema'; @@ -31,12 +31,12 @@ describe('find_list_item_schema', () => { }); test('it should validate just a list_id where it decodes into an array for list_id and adds a namespace_type of "single"', () => { - const payload: FindListItemSchemaPartial = { list_id: LIST_ID }; + const payload: FindListItemSchema = { list_id: LIST_ID }; const decoded = findListItemSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: FindListItemSchemaPartialDecoded = { + const expected: FindListItemSchemaDecoded = { cursor: undefined, filter: undefined, list_id: LIST_ID, @@ -97,7 +97,7 @@ describe('find_list_item_schema', () => { }); test('it should not allow an extra key to be sent in', () => { - const payload: FindListItemSchemaPartial & { + const payload: FindListItemSchema & { extraKey: string; } = { ...getFindListItemSchemaMock(), extraKey: 'some new value' }; const decoded = findListItemSchema.decode(payload); diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts index bbd3c7b5ec642..ba3dfc6ee33ec 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_item_schema.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { cursor, filter, list_id, sort_field, sort_order } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; import { StringToPositiveNumber } from '../types/string_to_positive_number'; export const findListItemSchema = t.intersection([ @@ -26,9 +26,7 @@ export const findListItemSchema = t.intersection([ ), ]); -export type FindListItemSchemaPartial = Identity>; +export type FindListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type FindListItemSchemaPartialDecoded = RequiredKeepUndefined< - t.TypeOf ->; +export type FindListItemSchemaDecoded = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/find_list_schema.mock.ts index dcb18dac8cfb6..bb9e15a439b3b 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_schema.mock.ts @@ -17,6 +17,7 @@ export const getFindListSchemaMock = (): FindListSchemaEncoded => ({ }); export const getFindListSchemaDecodedMock = (): FindListSchema => ({ + cursor: undefined, filter: FILTER, page: 1, per_page: 25, diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts index a343fb4b08bfc..63f29a64b4bf9 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_schema.test.ts @@ -10,7 +10,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; import { getFindListSchemaDecodedMock, getFindListSchemaMock } from './find_list_schema.mock'; -import { FindListSchema, FindListSchemaEncoded, findListSchema } from './find_list_schema'; +import { FindListSchemaEncoded, findListSchema } from './find_list_schema'; describe('find_list_schema', () => { test('it should validate a typical find item request', () => { @@ -28,8 +28,7 @@ describe('find_list_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: FindListSchema = {}; - expect(message.schema).toEqual(expected); + expect(message.schema).toEqual(payload); }); test('it should validate with page missing', () => { diff --git a/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts index 212232f6bc9c1..e5020cc8eff84 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_list_schema.ts @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { cursor, filter, sort_field, sort_order } from '../common/schemas'; import { StringToPositiveNumber } from '../types/string_to_positive_number'; +import { RequiredKeepUndefined } from '../../types'; export const findListSchema = t.exact( t.partial({ @@ -22,5 +23,5 @@ export const findListSchema = t.exact( }) ); -export type FindListSchema = t.TypeOf; +export type FindListSchema = RequiredKeepUndefined>; export type FindListSchemaEncoded = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts index 2c671466702e0..e45f77ca18ae1 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts @@ -8,14 +8,14 @@ import * as t from 'io-ts'; +import { RequiredKeepUndefined } from '../../types'; import { deserializer, list_id, serializer, type } from '../common/schemas'; -import { Identity } from '../../types'; export const importListItemQuerySchema = t.exact( t.partial({ deserializer, list_id, serializer, type }) ); -export type ImportListItemQuerySchemaPartial = Identity>; - -export type ImportListItemQuerySchema = t.TypeOf; +export type ImportListItemQuerySchema = RequiredKeepUndefined< + t.TypeOf +>; export type ImportListItemQuerySchemaEncoded = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts index 7370eecf690c7..671aeda757eff 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { file } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const importListItemSchema = t.exact( t.type({ @@ -16,5 +17,5 @@ export const importListItemSchema = t.exact( }) ); -export type ImportListItemSchema = t.TypeOf; +export type ImportListItemSchema = RequiredKeepUndefined>; export type ImportListItemSchemaEncoded = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts index 2016069f32fb3..9c5284c15ca99 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; -import { id, meta, value } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { _version, id, meta, value } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const patchListItemSchema = t.intersection([ t.exact( @@ -17,8 +17,10 @@ export const patchListItemSchema = t.intersection([ id, }) ), - t.exact(t.partial({ meta, value })), + t.exact(t.partial({ _version, meta, value })), ]); -export type PatchListItemSchemaPartial = Identity>; -export type PatchListItemSchema = RequiredKeepUndefined>; +export type PatchListItemSchema = t.OutputOf; +export type PatchListItemSchemaDecoded = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts index 653a42dc91653..e0cd1571afc81 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; -import { description, id, meta, name } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { _version, description, id, meta, name } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const patchListSchema = t.intersection([ t.exact( @@ -17,8 +17,8 @@ export const patchListSchema = t.intersection([ id, }) ), - t.exact(t.partial({ description, meta, name })), + t.exact(t.partial({ _version, description, meta, name })), ]); -export type PatchListSchemaPartial = Identity>; -export type PatchListSchema = RequiredKeepUndefined>>; +export type PatchListSchema = t.OutputOf; +export type PatchListSchemaDecoded = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts index 22750f5db6a1d..d6c54e289effe 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts @@ -18,14 +18,9 @@ export const readEndpointListItemSchema = t.exact( }) ); -export type ReadEndpointListItemSchemaPartial = t.TypeOf; - -// This type is used after a decode since some things are defaults after a decode. -export type ReadEndpointListItemSchemaPartialDecoded = ReadEndpointListItemSchemaPartial; +export type ReadEndpointListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. export type ReadEndpointListItemSchemaDecoded = RequiredKeepUndefined< - ReadEndpointListItemSchemaPartialDecoded + t.TypeOf >; - -export type ReadEndpointListItemSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts index d8864a6fc66e5..a2ba8126c7788 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts @@ -20,19 +20,12 @@ export const readExceptionListItemSchema = t.exact( }) ); -export type ReadExceptionListItemSchemaPartial = t.TypeOf; +export type ReadExceptionListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type ReadExceptionListItemSchemaPartialDecoded = Omit< - ReadExceptionListItemSchemaPartial, +export type ReadExceptionListItemSchemaDecoded = Omit< + RequiredKeepUndefined>, 'namespace_type' > & { namespace_type: NamespaceType; }; - -// This type is used after a decode since some things are defaults after a decode. -export type ReadExceptionListItemSchemaDecoded = RequiredKeepUndefined< - ReadExceptionListItemSchemaPartialDecoded ->; - -export type ReadExceptionListItemSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts index 613fb22a99d61..f22eca6a8ab15 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts @@ -20,19 +20,12 @@ export const readExceptionListSchema = t.exact( }) ); -export type ReadExceptionListSchemaPartial = t.TypeOf; +export type ReadExceptionListSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type ReadExceptionListSchemaPartialDecoded = Omit< - ReadExceptionListSchemaPartial, +export type ReadExceptionListSchemaDecoded = Omit< + RequiredKeepUndefined>, 'namespace_type' > & { namespace_type: NamespaceType; }; - -// This type is used after a decode since some things are defaults after a decode. -export type ReadExceptionListSchemaDecoded = RequiredKeepUndefined< - ReadExceptionListSchemaPartialDecoded ->; - -export type ReadExceptionListSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts index 394c1f1e2289a..063f430aa9cea 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts @@ -9,9 +9,9 @@ import * as t from 'io-ts'; import { id, list_id, value } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; export const readListItemSchema = t.exact(t.partial({ id, list_id, value })); -export type ReadListItemSchemaPartial = Identity>; -export type ReadListItemSchema = RequiredKeepUndefined>; +export type ReadListItemSchema = t.OutputOf; +export type ReadListItemSchemaDecoded = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts index 8803346709c31..e395875462cb4 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_schema.ts @@ -16,4 +16,4 @@ export const readListSchema = t.exact( }) ); -export type ReadListSchema = t.TypeOf; +export type ReadListSchema = t.OutputOf; diff --git a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.mock.ts index 30bbbe2d22ea4..0847389dac922 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.mock.ts @@ -21,6 +21,7 @@ import { UpdateEndpointListItemSchema } from './update_endpoint_list_item_schema export const getUpdateEndpointListItemSchemaMock = (): UpdateEndpointListItemSchema => ({ _tags: _TAGS, + _version: undefined, comments: COMMENTS, description: DESCRIPTION, entries: ENTRIES, diff --git a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts index dbe38f6d468e2..4430aa98b8e3d 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts @@ -12,6 +12,7 @@ import { Tags, _Tags, _tags, + _version, description, exceptionListItemType, id, @@ -19,7 +20,7 @@ import { name, tags, } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; import { DefaultEntryArray, DefaultUpdateCommentsArray, @@ -38,6 +39,7 @@ export const updateEndpointListItemSchema = t.intersection([ t.exact( t.partial({ _tags, // defaults to empty array if not set during decode + _version, // defaults to undefined if not set during decode comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode entries: DefaultEntryArray, // defaults to empty array if not set during decode id, // defaults to undefined if not set during decode @@ -48,19 +50,15 @@ export const updateEndpointListItemSchema = t.intersection([ ), ]); -export type UpdateEndpointListItemSchemaPartial = Identity< - t.TypeOf ->; -export type UpdateEndpointListItemSchema = RequiredKeepUndefined< - t.TypeOf ->; +export type UpdateEndpointListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type UpdateEndpointListItemSchemaDecoded = Identity< - Omit & { - _tags: _Tags; - comments: UpdateCommentsArray; - tags: Tags; - entries: EntriesArray; - } ->; +export type UpdateEndpointListItemSchemaDecoded = Omit< + RequiredKeepUndefined>, + '_tags' | 'tags' | 'entries' | 'comments' +> & { + _tags: _Tags; + comments: UpdateCommentsArray; + tags: Tags; + entries: EntriesArray; +}; diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.mock.ts index e8936f0bdc6d4..90d70c273f490 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.mock.ts @@ -22,6 +22,7 @@ import { UpdateExceptionListItemSchema } from './update_exception_list_item_sche export const getUpdateExceptionListItemSchemaMock = (): UpdateExceptionListItemSchema => ({ _tags: _TAGS, + _version: undefined, comments: COMMENTS, description: DESCRIPTION, entries: ENTRIES, diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts index 20a63e0fc7dac..9e0a1759fc9f4 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts @@ -12,6 +12,7 @@ import { Tags, _Tags, _tags, + _version, description, exceptionListItemType, id, @@ -20,7 +21,7 @@ import { namespace_type, tags, } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; import { DefaultEntryArray, DefaultUpdateCommentsArray, @@ -40,6 +41,7 @@ export const updateExceptionListItemSchema = t.intersection([ t.exact( t.partial({ _tags, // defaults to empty array if not set during decode + _version, // defaults to undefined if not set during decode comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode entries: DefaultEntryArray, // defaults to empty array if not set during decode id, // defaults to undefined if not set during decode @@ -51,23 +53,16 @@ export const updateExceptionListItemSchema = t.intersection([ ), ]); -export type UpdateExceptionListItemSchemaPartial = Identity< - t.TypeOf ->; -export type UpdateExceptionListItemSchema = RequiredKeepUndefined< - t.TypeOf ->; +export type UpdateExceptionListItemSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type UpdateExceptionListItemSchemaDecoded = Identity< - Omit< - UpdateExceptionListItemSchema, - '_tags' | 'tags' | 'entries' | 'namespace_type' | 'comments' - > & { - _tags: _Tags; - comments: UpdateCommentsArray; - tags: Tags; - entries: EntriesArray; - namespace_type: NamespaceType; - } ->; +export type UpdateExceptionListItemSchemaDecoded = Omit< + RequiredKeepUndefined>, + '_tags' | 'tags' | 'entries' | 'namespace_type' | 'comments' +> & { + _tags: _Tags; + comments: UpdateCommentsArray; + tags: Tags; + entries: EntriesArray; + namespace_type: NamespaceType; +}; diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.mock.ts index 48a8baf9aea16..22af29e6af0b7 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.mock.ts @@ -10,6 +10,7 @@ import { UpdateExceptionListSchema } from './update_exception_list_schema'; export const getUpdateExceptionListSchemaMock = (): UpdateExceptionListSchema => ({ _tags: _TAGS, + _version: undefined, description: DESCRIPTION, id: ID, list_id: LIST_ID, diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts index 0b5f3a8a01794..5d7294ae27af2 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts @@ -12,14 +12,17 @@ import { Tags, _Tags, _tags, + _version, description, exceptionListType, + id, + list_id, meta, name, namespace_type, tags, } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { RequiredKeepUndefined } from '../../types'; import { NamespaceType } from '../types'; export const updateExceptionListSchema = t.intersection([ @@ -33,8 +36,9 @@ export const updateExceptionListSchema = t.intersection([ t.exact( t.partial({ _tags, // defaults to empty array if not set during decode - id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode - list_id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode + _version, // defaults to undefined if not set during decode + id, // defaults to undefined if not set during decode + list_id, // defaults to undefined if not set during decode meta, // defaults to undefined if not set during decode namespace_type, // defaults to 'single' if not set during decode tags, // defaults to empty array if not set during decode @@ -42,16 +46,14 @@ export const updateExceptionListSchema = t.intersection([ ), ]); -export type UpdateExceptionListSchemaPartial = Identity>; -export type UpdateExceptionListSchema = RequiredKeepUndefined< - t.TypeOf ->; +export type UpdateExceptionListSchema = t.OutputOf; // This type is used after a decode since the arrays turn into defaults of empty arrays. -export type UpdateExceptionListSchemaDecoded = Identity< - Omit & { - _tags: _Tags; - tags: Tags; - namespace_type: NamespaceType; - } ->; +export type UpdateExceptionListSchemaDecoded = Omit< + RequiredKeepUndefined>, + '_tags | tags | namespace_type' +> & { + _tags: _Tags; + tags: Tags; + namespace_type: NamespaceType; +}; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts index 3a42cf28665f5..c6ed5ef0e9517 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; -import { id, meta, value } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { _version, id, meta, value } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const updateListItemSchema = t.intersection([ t.exact( @@ -20,10 +20,13 @@ export const updateListItemSchema = t.intersection([ ), t.exact( t.partial({ + _version, // defaults to undefined if not set during decode meta, // defaults to undefined if not set during decode }) ), ]); -export type UpdateListItemSchemaPartial = Identity>; -export type UpdateListItemSchema = RequiredKeepUndefined>; +export type UpdateListItemSchema = t.OutputOf; +export type UpdateListItemSchemaDecoded = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts index 4c5c8c429a14c..19a39d362c241 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; -import { description, id, meta, name } from '../common/schemas'; -import { Identity, RequiredKeepUndefined } from '../../types'; +import { _version, description, id, meta, name } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; export const updateListSchema = t.intersection([ t.exact( @@ -21,10 +21,11 @@ export const updateListSchema = t.intersection([ ), t.exact( t.partial({ + _version, // defaults to undefined if not set during decode meta, // defaults to undefined if not set during decode }) ), ]); -export type UpdateListSchemaPartial = Identity>; -export type UpdateListSchema = RequiredKeepUndefined>; +export type UpdateListSchema = t.OutputOf; +export type UpdateListSchemaDecoded = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts index d346ea72ca310..646cc3d97f8ee 100644 --- a/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/create_endpoint_list_schema.test.ts @@ -41,7 +41,7 @@ describe('create_endpoint_list_schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'invalid keys "_tags,["endpoint","process","malware","os:linux"],created_at,created_by,description,id,meta,{},name,namespace_type,tags,["user added string for a tag","malware"],tie_breaker_id,type,updated_at,updated_by"', + 'invalid keys "_tags,["endpoint","process","malware","os:linux"],_version,created_at,created_by,description,id,meta,{},name,namespace_type,tags,["user added string for a tag","malware"],tie_breaker_id,type,updated_at,updated_by"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts index 9e1a88ceb28bd..c0d04c9823ca3 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts @@ -3,26 +3,38 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ENTRIES } from '../../constants.mock'; +import { + COMMENTS, + DATE_NOW, + DESCRIPTION, + ENTRIES, + ITEM_TYPE, + META, + NAME, + NAMESPACE_TYPE, + TIE_BREAKER, + USER, +} from '../../constants.mock'; import { ExceptionListItemSchema } from './exception_list_item_schema'; export const getExceptionListItemSchemaMock = (): ExceptionListItemSchema => ({ _tags: ['endpoint', 'process', 'malware', 'os:linux'], - comments: [], - created_at: '2020-04-23T00:19:13.289Z', - created_by: 'user_name', - description: 'This is a sample endpoint type exception', + _version: undefined, + comments: COMMENTS, + created_at: DATE_NOW, + created_by: USER, + description: DESCRIPTION, entries: ENTRIES, id: '1', item_id: 'endpoint_list_item', list_id: 'endpoint_list_id', - meta: {}, - name: 'Sample Endpoint Exception List', - namespace_type: 'single', + meta: META, + name: NAME, + namespace_type: NAMESPACE_TYPE, tags: ['user added string for a tag', 'malware'], - tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f', - type: 'simple', - updated_at: '2020-04-23T00:19:13.289Z', - updated_by: 'user_name', + tie_breaker_id: TIE_BREAKER, + type: ITEM_TYPE, + updated_at: DATE_NOW, + updated_by: USER, }); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts index c8440e9d3f3d0..54907f3f8a854 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { _tags, + _versionOrUndefined, created_at, created_by, description, @@ -30,6 +31,7 @@ import { commentsArray, entriesArray } from '../types'; export const exceptionListItemSchema = t.exact( t.type({ _tags, + _version: _versionOrUndefined, comments: commentsArray, created_at, created_by, diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts index 906dcf6560ee5..f790ad9544d53 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts @@ -4,23 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + DATE_NOW, + DESCRIPTION, + ENDPOINT_TYPE, + META, + TIE_BREAKER, + USER, + _VERSION, +} from '../../constants.mock'; import { ENDPOINT_LIST_ID } from '../..'; import { ExceptionListSchema } from './exception_list_schema'; - export const getExceptionListSchemaMock = (): ExceptionListSchema => ({ _tags: ['endpoint', 'process', 'malware', 'os:linux'], - created_at: '2020-04-23T00:19:13.289Z', - created_by: 'user_name', - description: 'This is a sample endpoint type exception', + _version: _VERSION, + created_at: DATE_NOW, + created_by: USER, + description: DESCRIPTION, id: '1', list_id: ENDPOINT_LIST_ID, - meta: {}, + meta: META, name: 'Sample Endpoint Exception List', namespace_type: 'agnostic', tags: ['user added string for a tag', 'malware'], - tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f', - type: 'endpoint', - updated_at: '2020-04-23T00:19:13.289Z', + tie_breaker_id: TIE_BREAKER, + type: ENDPOINT_TYPE, + updated_at: DATE_NOW, updated_by: 'user_name', }); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts index 0fb2bfca4a48f..11c23bc2ff354 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { _tags, + _versionOrUndefined, created_at, created_by, description, @@ -28,6 +29,7 @@ import { export const exceptionListSchema = t.exact( t.type({ _tags, + _version: _versionOrUndefined, created_at, created_by, description, diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts index 16e8057974917..e122f6a2bbe3b 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts @@ -17,6 +17,7 @@ import { } from '../../../common/constants.mock'; export const getListItemResponseMock = (): ListItemSchema => ({ + _version: undefined, created_at: DATE_NOW, created_by: USER, deserializer: undefined, diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts index c2104aaf18b53..9ee801298f950 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_schema.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ import { + _versionOrUndefined, created_at, created_by, deserializerOrUndefined, @@ -25,6 +26,7 @@ import { export const listItemSchema = t.exact( t.type({ + _version: _versionOrUndefined, created_at, created_by, deserializer: deserializerOrUndefined, diff --git a/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts index c165c4ed8e745..339beddb00f8e 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts @@ -17,6 +17,7 @@ import { } from '../../../common/constants.mock'; export const getListResponseMock = (): ListSchema => ({ + _version: undefined, created_at: DATE_NOW, created_by: USER, description: DESCRIPTION, diff --git a/x-pack/plugins/lists/common/schemas/response/list_schema.ts b/x-pack/plugins/lists/common/schemas/response/list_schema.ts index 1950831bee694..7e2bc202a6520 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_schema.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { + _versionOrUndefined, created_at, created_by, description, @@ -25,6 +26,7 @@ import { export const listSchema = t.exact( t.type({ + _version: _versionOrUndefined, created_at, created_by, description, diff --git a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts index e8be299246ab8..342cf8b0d7091 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts @@ -9,17 +9,11 @@ import { Either } from 'fp-ts/lib/Either'; import { CommentsArray, comments } from './comments'; -export type DefaultCommentsArrayC = t.Type; - /** * Types the DefaultCommentsArray as: * - If null or undefined, then a default array of type entry will be set */ -export const DefaultCommentsArray: DefaultCommentsArrayC = new t.Type< - CommentsArray, - CommentsArray, - unknown ->( +export const DefaultCommentsArray = new t.Type( 'DefaultCommentsArray', t.array(comments).is, (input): Either => diff --git a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts index 51431b9c39850..7fd79782836e3 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts @@ -9,13 +9,11 @@ import { Either } from 'fp-ts/lib/Either'; import { CreateCommentsArray, createComments } from './create_comments'; -export type DefaultCreateCommentsArrayC = t.Type; - /** * Types the DefaultCreateComments as: * - If null or undefined, then a default array of type entry will be set */ -export const DefaultCreateCommentsArray: DefaultCreateCommentsArrayC = new t.Type< +export const DefaultCreateCommentsArray = new t.Type< CreateCommentsArray, CreateCommentsArray, unknown diff --git a/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts index da67fb286affa..a85fdf8537f39 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts @@ -9,17 +9,11 @@ import { Either } from 'fp-ts/lib/Either'; import { EntriesArray, entriesArray } from './entries'; -export type DefaultEntriesArrayC = t.Type; - /** * Types the DefaultEntriesArray as: * - If null or undefined, then a default array of type entry will be set */ -export const DefaultEntryArray: DefaultEntriesArrayC = new t.Type< - EntriesArray, - EntriesArray, - unknown ->( +export const DefaultEntryArray = new t.Type( 'DefaultEntryArray', entriesArray.is, (input): Either => diff --git a/x-pack/plugins/lists/common/schemas/types/default_namespace.ts b/x-pack/plugins/lists/common/schemas/types/default_namespace.ts index ecc45d3c84313..b61497a78aff0 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_namespace.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_namespace.ts @@ -14,12 +14,10 @@ export type NamespaceType = t.TypeOf; * Types the DefaultNamespace as: * - If null or undefined, then a default string/enumeration of "single" will be used. */ -export const DefaultNamespace = new t.Type( +export const DefaultNamespace = new t.Type( 'DefaultNamespace', namespaceType.is, (input, context): Either => input == null ? t.success('single') : namespaceType.validate(input, context), t.identity ); - -export type DefaultNamespaceC = typeof DefaultNamespace; diff --git a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_namespace_array.test.ts index 055f93069950e..255c89959b610 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_namespace_array.test.ts @@ -9,11 +9,11 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; -import { DefaultNamespaceArray, DefaultNamespaceArrayTypeEncoded } from './default_namespace_array'; +import { DefaultNamespaceArray, DefaultNamespaceArrayType } from './default_namespace_array'; describe('default_namespace_array', () => { test('it should validate "null" single item as an array with a "single" value', () => { - const payload: DefaultNamespaceArrayTypeEncoded = null; + const payload: DefaultNamespaceArrayType = null; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -33,7 +33,7 @@ describe('default_namespace_array', () => { }); test('it should validate "undefined" item as an array with a "single" value', () => { - const payload: DefaultNamespaceArrayTypeEncoded = undefined; + const payload: DefaultNamespaceArrayType = undefined; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -42,7 +42,7 @@ describe('default_namespace_array', () => { }); test('it should validate "single" as an array of a "single" value', () => { - const payload: DefaultNamespaceArrayTypeEncoded = 'single'; + const payload: DefaultNamespaceArrayType = 'single'; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -51,7 +51,7 @@ describe('default_namespace_array', () => { }); test('it should validate "agnostic" as an array of a "agnostic" value', () => { - const payload: DefaultNamespaceArrayTypeEncoded = 'agnostic'; + const payload: DefaultNamespaceArrayType = 'agnostic'; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -60,7 +60,7 @@ describe('default_namespace_array', () => { }); test('it should validate "single,agnostic" as an array of 2 values of ["single", "agnostic"] values', () => { - const payload: DefaultNamespaceArrayTypeEncoded = 'agnostic,single'; + const payload: DefaultNamespaceArrayType = 'agnostic,single'; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -69,7 +69,7 @@ describe('default_namespace_array', () => { }); test('it should validate 3 elements of "single,agnostic,single" as an array of 3 values of ["single", "agnostic", "single"] values', () => { - const payload: DefaultNamespaceArrayTypeEncoded = 'single,agnostic,single'; + const payload: DefaultNamespaceArrayType = 'single,agnostic,single'; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -78,7 +78,7 @@ describe('default_namespace_array', () => { }); test('it should validate 3 elements of "single,agnostic, single" as an array of 3 values of ["single", "agnostic", "single"] values when there are spaces', () => { - const payload: DefaultNamespaceArrayTypeEncoded = ' single, agnostic, single '; + const payload: DefaultNamespaceArrayType = ' single, agnostic, single '; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -87,7 +87,7 @@ describe('default_namespace_array', () => { }); test('it should not validate 3 elements of "single,agnostic,junk" since the 3rd value is junk', () => { - const payload: DefaultNamespaceArrayTypeEncoded = 'single,agnostic,junk'; + const payload: DefaultNamespaceArrayType = 'single,agnostic,junk'; const decoded = DefaultNamespaceArray.decode(payload); const message = pipe(decoded, foldLeftRight); diff --git a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.ts b/x-pack/plugins/lists/common/schemas/types/default_namespace_array.ts index c4099a48ffbcc..b0ae85e65b22a 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_namespace_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_namespace_array.ts @@ -39,7 +39,5 @@ export const DefaultNamespaceArray = new t.Type< String ); -export type DefaultNamespaceC = typeof DefaultNamespaceArray; - -export type DefaultNamespaceArrayTypeEncoded = t.OutputOf; +export type DefaultNamespaceArrayType = t.OutputOf; export type DefaultNamespaceArrayTypeDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts index c2593826a6358..854b7cf7ada7e 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts @@ -9,13 +9,11 @@ import { Either } from 'fp-ts/lib/Either'; import { UpdateCommentsArray, updateCommentsArray } from './update_comments'; -export type DefaultUpdateCommentsArrayC = t.Type; - /** * Types the DefaultCommentsUpdate as: * - If null or undefined, then a default array of type entry will be set */ -export const DefaultUpdateCommentsArray: DefaultUpdateCommentsArrayC = new t.Type< +export const DefaultUpdateCommentsArray = new t.Type< UpdateCommentsArray, UpdateCommentsArray, unknown diff --git a/x-pack/plugins/lists/common/schemas/types/empty_string_array.ts b/x-pack/plugins/lists/common/schemas/types/empty_string_array.ts index 389dc4a410cc9..bbaa66260e76e 100644 --- a/x-pack/plugins/lists/common/schemas/types/empty_string_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/empty_string_array.ts @@ -39,7 +39,5 @@ export const EmptyStringArray = new t.Type; export type EmptyStringArrayDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.test.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.test.ts index 6124487cdd7fb..fac088568f85e 100644 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.test.ts @@ -9,11 +9,11 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; -import { NonEmptyStringArray, NonEmptyStringArrayEncoded } from './non_empty_string_array'; +import { NonEmptyStringArray } from './non_empty_string_array'; describe('non_empty_string_array', () => { test('it should NOT validate "null"', () => { - const payload: NonEmptyStringArrayEncoded | null = null; + const payload: NonEmptyStringArray | null = null; const decoded = NonEmptyStringArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -24,7 +24,7 @@ describe('non_empty_string_array', () => { }); test('it should NOT validate "undefined"', () => { - const payload: NonEmptyStringArrayEncoded | undefined = undefined; + const payload: NonEmptyStringArray | undefined = undefined; const decoded = NonEmptyStringArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -35,7 +35,7 @@ describe('non_empty_string_array', () => { }); test('it should NOT validate a single value of an empty string ""', () => { - const payload: NonEmptyStringArrayEncoded = ''; + const payload: NonEmptyStringArray = ''; const decoded = NonEmptyStringArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -46,7 +46,7 @@ describe('non_empty_string_array', () => { }); test('it should validate a single value of "a" into an array of size 1 of ["a"]', () => { - const payload: NonEmptyStringArrayEncoded = 'a'; + const payload: NonEmptyStringArray = 'a'; const decoded = NonEmptyStringArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -55,7 +55,7 @@ describe('non_empty_string_array', () => { }); test('it should validate 2 values of "a,b" into an array of size 2 of ["a", "b"]', () => { - const payload: NonEmptyStringArrayEncoded = 'a,b'; + const payload: NonEmptyStringArray = 'a,b'; const decoded = NonEmptyStringArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -64,7 +64,7 @@ describe('non_empty_string_array', () => { }); test('it should validate 3 values of "a,b,c" into an array of size 3 of ["a", "b", "c"]', () => { - const payload: NonEmptyStringArrayEncoded = 'a,b,c'; + const payload: NonEmptyStringArray = 'a,b,c'; const decoded = NonEmptyStringArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -84,7 +84,7 @@ describe('non_empty_string_array', () => { }); test('it should validate 3 values of " a, b, c " into an array of size 3 of ["a", "b", "c"] even though they have spaces', () => { - const payload: NonEmptyStringArrayEncoded = ' a, b, c '; + const payload: NonEmptyStringArray = ' a, b, c '; const decoded = NonEmptyStringArray.decode(payload); const message = pipe(decoded, foldLeftRight); diff --git a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.ts b/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.ts index c4a640e7cdbad..90475f7935875 100644 --- a/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/non_empty_string_array.ts @@ -35,7 +35,5 @@ export const NonEmptyStringArray = new t.Type( String ); -export type NonEmptyStringArrayC = typeof NonEmptyStringArray; - -export type NonEmptyStringArrayEncoded = t.OutputOf; +export type NonEmptyStringArray = t.OutputOf; export type NonEmptyStringArrayDecoded = t.TypeOf; diff --git a/x-pack/plugins/lists/common/types.ts b/x-pack/plugins/lists/common/types.ts index 1539c5ae01ff5..cee5567a55a6c 100644 --- a/x-pack/plugins/lists/common/types.ts +++ b/x-pack/plugins/lists/common/types.ts @@ -20,11 +20,3 @@ export type RequiredKeepUndefined = { [K in keyof T]-?: [T[K]] } extends infe ? { [K in keyof U]: U[K][0] } : never : never; - -/** - * This is just a helper to cleanup nasty intersections and unions to make them - * readable from io.ts, it's an identity that strips away the uglyness of them. - */ -export type Identity = { - [P in keyof T]: T[P]; -}; diff --git a/x-pack/plugins/lists/server/routes/find_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_list_item_route.ts index 52d534b08df2b..8a0e06aa0c7d8 100644 --- a/x-pack/plugins/lists/server/routes/find_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_item_route.ts @@ -10,7 +10,7 @@ import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/siem_common_deps'; import { - FindListItemSchemaPartialDecoded, + FindListItemSchemaDecoded, findListItemSchema, foundListItemSchema, } from '../../common/schemas'; @@ -26,7 +26,7 @@ export const findListItemRoute = (router: IRouter): void => { }, path: `${LIST_ITEM_URL}/_find`, validate: { - query: buildRouteValidation( + query: buildRouteValidation( findListItemSchema ), }, diff --git a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts index f706559dffdbd..9a74beb45bafd 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts @@ -27,9 +27,10 @@ export const patchListItemRoute = (router: IRouter): void => { async (context, request, response) => { const siemResponse = buildSiemResponse(response); try { - const { value, id, meta } = request.body; + const { value, id, meta, _version } = request.body; const lists = getListClient(context); const listItem = await lists.updateListItem({ + _version, id, meta, value, diff --git a/x-pack/plugins/lists/server/routes/patch_list_route.ts b/x-pack/plugins/lists/server/routes/patch_list_route.ts index 3a0d8714a14cd..06a76559dee9a 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_route.ts @@ -27,9 +27,9 @@ export const patchListRoute = (router: IRouter): void => { async (context, request, response) => { const siemResponse = buildSiemResponse(response); try { - const { name, description, id, meta } = request.body; + const { name, description, id, meta, _version } = request.body; const lists = getListClient(context); - const list = await lists.updateList({ description, id, meta, name }); + const list = await lists.updateList({ _version, description, id, meta, name }); if (list == null) { return siemResponse.error({ body: `list id: "${id}" found found`, diff --git a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts index 1ecf4e8a9765d..8415c64633a06 100644 --- a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts @@ -41,6 +41,7 @@ export const updateEndpointListItemRoute = (router: IRouter): void => { meta, type, _tags, + _version, comments, entries, item_id: itemId, @@ -49,6 +50,7 @@ export const updateEndpointListItemRoute = (router: IRouter): void => { const exceptionLists = getExceptionListClient(context); const exceptionListItem = await exceptionLists.updateEndpointListItem({ _tags, + _version, comments, description, entries, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index f6c7bcebedc13..2aa1e016d51ed 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -41,6 +41,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => { meta, type, _tags, + _version, comments, entries, item_id: itemId, @@ -50,6 +51,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => { const exceptionLists = getExceptionListClient(context); const exceptionListItem = await exceptionLists.updateExceptionListItem({ _tags, + _version, comments, description, entries, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts index cff78614d05ba..331ec064fa663 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -36,6 +36,7 @@ export const updateExceptionListRoute = (router: IRouter): void => { try { const { _tags, + _version, tags, name, description, @@ -54,6 +55,7 @@ export const updateExceptionListRoute = (router: IRouter): void => { } else { const list = await exceptionLists.updateExceptionList({ _tags, + _version, description, id, listId, diff --git a/x-pack/plugins/lists/server/routes/update_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_list_item_route.ts index 3e231e319104b..0f5d11afcda09 100644 --- a/x-pack/plugins/lists/server/routes/update_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_item_route.ts @@ -27,9 +27,10 @@ export const updateListItemRoute = (router: IRouter): void => { async (context, request, response) => { const siemResponse = buildSiemResponse(response); try { - const { value, id, meta } = request.body; + const { value, id, meta, _version } = request.body; const lists = getListClient(context); const listItem = await lists.updateListItem({ + _version, id, meta, value, diff --git a/x-pack/plugins/lists/server/routes/update_list_route.ts b/x-pack/plugins/lists/server/routes/update_list_route.ts index a6d9f8329c7c8..2fae910c1b398 100644 --- a/x-pack/plugins/lists/server/routes/update_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_route.ts @@ -27,9 +27,9 @@ export const updateListRoute = (router: IRouter): void => { async (context, request, response) => { const siemResponse = buildSiemResponse(response); try { - const { name, description, id, meta } = request.body; + const { name, description, id, meta, _version } = request.body; const lists = getListClient(context); - const list = await lists.updateList({ description, id, meta, name }); + const list = await lists.updateList({ _version, description, id, meta, name }); if (list == null) { return siemResponse.error({ body: `list id: "${id}" found found`, diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json index a7fbe1ea48c02..8d07b29d7b428 100644 --- a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json @@ -1,5 +1,5 @@ { - "list_id": "endpoint_list", + "list_id": "simple_list", "_tags": ["endpoint", "process", "malware", "os:linux"], "tags": ["user added string for a tag", "malware"], "type": "endpoint", diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts index 5c9607e2d956d..08b1f517036a9 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -131,6 +131,7 @@ export class ExceptionListClient { */ public updateEndpointListItem = async ({ _tags, + _version, comments, description, entries, @@ -145,6 +146,7 @@ export class ExceptionListClient { await this.createEndpointList(); return updateExceptionListItem({ _tags, + _version, comments, description, entries, @@ -198,6 +200,7 @@ export class ExceptionListClient { public updateExceptionList = async ({ _tags, + _version, id, description, listId, @@ -210,6 +213,7 @@ export class ExceptionListClient { const { savedObjectsClient, user } = this; return updateExceptionList({ _tags, + _version, description, id, listId, @@ -270,6 +274,7 @@ export class ExceptionListClient { public updateExceptionListItem = async ({ _tags, + _version, comments, description, entries, @@ -284,6 +289,7 @@ export class ExceptionListClient { const { savedObjectsClient, user } = this; return updateExceptionListItem({ _tags, + _version, comments, description, entries, diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index 89f8310281648..b972b6564bb8a 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -38,6 +38,7 @@ import { UpdateCommentsArray, _Tags, _TagsOrUndefined, + _VersionOrUndefined, } from '../../../common/schemas'; export interface ConstructorOptions { @@ -64,6 +65,7 @@ export interface CreateExceptionListOptions { export interface UpdateExceptionListOptions { _tags: _TagsOrUndefined; + _version: _VersionOrUndefined; id: IdOrUndefined; listId: ListIdOrUndefined; namespaceType: NamespaceType; @@ -130,6 +132,7 @@ export interface CreateEndpointListItemOptions { export interface UpdateExceptionListItemOptions { _tags: _TagsOrUndefined; + _version: _VersionOrUndefined; comments: UpdateCommentsArray; entries: EntriesArrayOrUndefined; id: IdOrUndefined; @@ -144,6 +147,7 @@ export interface UpdateExceptionListItemOptions { export interface UpdateEndpointListItemOptions { _tags: _TagsOrUndefined; + _version: _VersionOrUndefined; comments: UpdateCommentsArray; entries: EntriesArrayOrUndefined; id: IdOrUndefined; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts index a739366c67331..99c42e56f4888 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts @@ -18,6 +18,7 @@ import { NamespaceType, TagsOrUndefined, _TagsOrUndefined, + _VersionOrUndefined, } from '../../../common/schemas'; import { getSavedObjectType, transformSavedObjectUpdateToExceptionList } from './utils'; @@ -26,6 +27,7 @@ import { getExceptionList } from './get_exception_list'; interface UpdateExceptionListOptions { id: IdOrUndefined; _tags: _TagsOrUndefined; + _version: _VersionOrUndefined; name: NameOrUndefined; description: DescriptionOrUndefined; savedObjectsClient: SavedObjectsClientContract; @@ -40,6 +42,7 @@ interface UpdateExceptionListOptions { export const updateExceptionList = async ({ _tags, + _version, id, savedObjectsClient, namespaceType, @@ -67,6 +70,9 @@ export const updateExceptionList = async ({ tags, type, updated_by: user, + }, + { + version: _version, } ); return transformSavedObjectUpdateToExceptionList({ exceptionList, savedObject }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts index a5ed1e38df374..f26dd7e18dd5c 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts @@ -20,6 +20,7 @@ import { TagsOrUndefined, UpdateCommentsArrayOrUndefined, _TagsOrUndefined, + _VersionOrUndefined, } from '../../../common/schemas'; import { @@ -33,6 +34,7 @@ interface UpdateExceptionListItemOptions { id: IdOrUndefined; comments: UpdateCommentsArrayOrUndefined; _tags: _TagsOrUndefined; + _version: _VersionOrUndefined; name: NameOrUndefined; description: DescriptionOrUndefined; entries: EntriesArrayOrUndefined; @@ -48,6 +50,7 @@ interface UpdateExceptionListItemOptions { export const updateExceptionListItem = async ({ _tags, + _version, comments, entries, id, @@ -89,6 +92,9 @@ export const updateExceptionListItem = async ({ tags, type, updated_by: user, + }, + { + version: _version, } ); return transformSavedObjectUpdateToExceptionListItem({ diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.ts index ded39933fe9d8..d5e1965efcc89 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.ts @@ -72,6 +72,7 @@ export const transformSavedObjectToExceptionList = ({ }): ExceptionListSchema => { const dateNow = new Date().toISOString(); const { + version: _version, attributes: { _tags, created_at, @@ -93,6 +94,7 @@ export const transformSavedObjectToExceptionList = ({ // TODO: Do a throw if after the decode this is not the correct "list_type: list" return { _tags, + _version, created_at, created_by, description, @@ -118,6 +120,7 @@ export const transformSavedObjectUpdateToExceptionList = ({ }): ExceptionListSchema => { const dateNow = new Date().toISOString(); const { + version: _version, attributes: { _tags, description, meta, name, tags, type, updated_by: updatedBy }, id, updated_at: updatedAt, @@ -127,6 +130,7 @@ export const transformSavedObjectUpdateToExceptionList = ({ // TODO: Do a throw if after the decode this is not the correct "list_type: list" return { _tags: _tags ?? exceptionList._tags, + _version, created_at: exceptionList.created_at, created_by: exceptionList.created_by, description: description ?? exceptionList.description, @@ -150,6 +154,7 @@ export const transformSavedObjectToExceptionListItem = ({ }): ExceptionListItemSchema => { const dateNow = new Date().toISOString(); const { + version: _version, attributes: { _tags, comments, @@ -174,6 +179,7 @@ export const transformSavedObjectToExceptionListItem = ({ // TODO: Do a throw if item_id or entries is not defined. return { _tags, + _version, comments: comments ?? [], created_at, created_by, @@ -202,6 +208,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({ }): ExceptionListItemSchema => { const dateNow = new Date().toISOString(); const { + version: _version, attributes: { _tags, comments, @@ -223,6 +230,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({ // defaulting return { _tags: _tags ?? exceptionListItem._tags, + _version, comments: comments ?? exceptionListItem.comments, created_at: exceptionListItem.created_at, created_by: exceptionListItem.created_by, diff --git a/x-pack/plugins/lists/server/services/items/create_list_item.ts b/x-pack/plugins/lists/server/services/items/create_list_item.ts index aa17fc00b25c6..5332e2f6c7f4e 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.ts @@ -18,6 +18,7 @@ import { Type, } from '../../../common/schemas'; import { transformListItemToElasticQuery } from '../utils'; +import { encodeHitVersion } from '../utils/encode_hit_version'; export interface CreateListItemOptions { deserializer: DeserializerOrUndefined; @@ -75,6 +76,7 @@ export const createListItem = async ({ }); return { + _version: encodeHitVersion(response), id: response._id, type, value, diff --git a/x-pack/plugins/lists/server/services/items/find_list_item.ts b/x-pack/plugins/lists/server/services/items/find_list_item.ts index 93fa682631b06..a997b10004e76 100644 --- a/x-pack/plugins/lists/server/services/items/find_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/find_list_item.ts @@ -5,6 +5,7 @@ */ import { LegacyAPICaller } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; import { Filter, @@ -82,7 +83,10 @@ export const findListItem = async ({ }); if (scroll.validSearchAfterFound) { - const response = await callCluster('search', { + // Note: This typing of response = await callCluster> + // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have + // to explicitly define the type . + const response = await callCluster>('search', { body: { query, search_after: scroll.searchAfter, @@ -90,6 +94,7 @@ export const findListItem = async ({ }, ignoreUnavailable: true, index: listItemIndex, + seq_no_primary_term: true, size: perPage, }); return { diff --git a/x-pack/plugins/lists/server/services/items/get_list_item.ts b/x-pack/plugins/lists/server/services/items/get_list_item.ts index 6f2a7ad63a973..ad9ae8763fe94 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item.ts @@ -5,6 +5,7 @@ */ import { LegacyAPICaller } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; import { Id, ListItemSchema, SearchEsListItemSchema } from '../../../common/schemas'; import { transformElasticToListItem } from '../utils'; @@ -21,7 +22,10 @@ export const getListItem = async ({ callCluster, listItemIndex, }: GetListItemOptions): Promise => { - const listItemES = await callCluster('search', { + // Note: This typing of response = await callCluster> + // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have + // to explicitly define the type . + const listItemES = await callCluster>('search', { body: { query: { term: { @@ -31,6 +35,7 @@ export const getListItem = async ({ }, ignoreUnavailable: true, index: listItemIndex, + seq_no_primary_term: true, }); if (listItemES.hits.hits.length) { diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.mock.ts b/x-pack/plugins/lists/server/services/items/update_list_item.mock.ts index 7ee8664b04d6b..8fc4b55338344 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.mock.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.mock.ts @@ -15,6 +15,7 @@ import { } from '../../../common/constants.mock'; export const getUpdateListItemOptionsMock = (): UpdateListItemOptions => ({ + _version: undefined, callCluster: getCallClusterMock(), dateNow: DATE_NOW, id: LIST_ITEM_ID, diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.ts b/x-pack/plugins/lists/server/services/items/update_list_item.ts index eb20f1cfe3b30..8387d916b12f2 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.ts @@ -12,12 +12,16 @@ import { ListItemSchema, MetaOrUndefined, UpdateEsListItemSchema, + _VersionOrUndefined, } from '../../../common/schemas'; import { transformListItemToElasticQuery } from '../utils'; +import { decodeVersion } from '../utils/decode_version'; +import { encodeHitVersion } from '../utils/encode_hit_version'; import { getListItem } from './get_list_item'; export interface UpdateListItemOptions { + _version: _VersionOrUndefined; id: Id; value: string | null | undefined; callCluster: LegacyAPICaller; @@ -28,6 +32,7 @@ export interface UpdateListItemOptions { } export const updateListItem = async ({ + _version, id, value, callCluster, @@ -57,6 +62,7 @@ export const updateListItem = async ({ }; const response = await callCluster('update', { + ...decodeVersion(_version), body: { doc, }, @@ -65,6 +71,7 @@ export const updateListItem = async ({ refresh: 'wait_for', }); return { + _version: encodeHitVersion(response), created_at: listItem.created_at, created_by: listItem.created_by, deserializer: listItem.deserializer, diff --git a/x-pack/plugins/lists/server/services/lists/create_list.ts b/x-pack/plugins/lists/server/services/lists/create_list.ts index 3d396cf4d5af9..f97399e6dc131 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.ts @@ -8,6 +8,7 @@ import uuid from 'uuid'; import { CreateDocumentResponse } from 'elasticsearch'; import { LegacyAPICaller } from 'kibana/server'; +import { encodeHitVersion } from '../utils/encode_hit_version'; import { Description, DeserializerOrUndefined, @@ -70,6 +71,7 @@ export const createList = async ({ refresh: 'wait_for', }); return { + _version: encodeHitVersion(response), id: response._id, ...body, }; diff --git a/x-pack/plugins/lists/server/services/lists/find_list.ts b/x-pack/plugins/lists/server/services/lists/find_list.ts index 86cead9e868d6..363794b6affe4 100644 --- a/x-pack/plugins/lists/server/services/lists/find_list.ts +++ b/x-pack/plugins/lists/server/services/lists/find_list.ts @@ -5,6 +5,7 @@ */ import { LegacyAPICaller } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; import { Filter, @@ -71,7 +72,10 @@ export const findList = async ({ }); if (scroll.validSearchAfterFound) { - const response = await callCluster('search', { + // Note: This typing of response = await callCluster> + // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have + // to explicitly define the type . + const response = await callCluster>('search', { body: { query, search_after: scroll.searchAfter, @@ -79,6 +83,7 @@ export const findList = async ({ }, ignoreUnavailable: true, index: listIndex, + seq_no_primary_term: true, size: perPage, }); return { diff --git a/x-pack/plugins/lists/server/services/lists/get_list.ts b/x-pack/plugins/lists/server/services/lists/get_list.ts index 13550eb7d30dd..860e8e9f97f87 100644 --- a/x-pack/plugins/lists/server/services/lists/get_list.ts +++ b/x-pack/plugins/lists/server/services/lists/get_list.ts @@ -5,6 +5,7 @@ */ import { LegacyAPICaller } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; import { Id, ListSchema, SearchEsListSchema } from '../../../common/schemas'; import { transformElasticToList } from '../utils/transform_elastic_to_list'; @@ -20,7 +21,10 @@ export const getList = async ({ callCluster, listIndex, }: GetListOptions): Promise => { - const response = await callCluster('search', { + // Note: This typing of response = await callCluster> + // is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have + // to explicitly define the type . + const response = await callCluster>('search', { body: { query: { term: { @@ -30,6 +34,7 @@ export const getList = async ({ }, ignoreUnavailable: true, index: listIndex, + seq_no_primary_term: true, }); const list = transformElasticToList({ response }); return list[0] ?? null; diff --git a/x-pack/plugins/lists/server/services/lists/list_client.ts b/x-pack/plugins/lists/server/services/lists/list_client.ts index 4acc2e7092491..9bece64fa943f 100644 --- a/x-pack/plugins/lists/server/services/lists/list_client.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client.ts @@ -395,6 +395,7 @@ export class ListClient { }; public updateListItem = async ({ + _version, id, value, meta, @@ -402,6 +403,7 @@ export class ListClient { const { callCluster, user } = this; const listItemIndex = this.getListItemIndex(); return updateListItem({ + _version, callCluster, id, listItemIndex, @@ -412,6 +414,7 @@ export class ListClient { }; public updateList = async ({ + _version, id, name, description, @@ -420,6 +423,7 @@ export class ListClient { const { callCluster, user } = this; const listIndex = this.getListIndex(); return updateList({ + _version, callCluster, description, id, diff --git a/x-pack/plugins/lists/server/services/lists/list_client_types.ts b/x-pack/plugins/lists/server/services/lists/list_client_types.ts index 68a018fa2fc16..7fa1727be118b 100644 --- a/x-pack/plugins/lists/server/services/lists/list_client_types.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client_types.ts @@ -26,6 +26,7 @@ import { SortFieldOrUndefined, SortOrderOrUndefined, Type, + _VersionOrUndefined, } from '../../../common/schemas'; import { ConfigType } from '../../config'; @@ -106,12 +107,14 @@ export interface CreateListItemOptions { } export interface UpdateListItemOptions { + _version: _VersionOrUndefined; id: Id; value: string | null | undefined; meta: MetaOrUndefined; } export interface UpdateListOptions { + _version: _VersionOrUndefined; id: Id; name: NameOrUndefined; description: DescriptionOrUndefined; diff --git a/x-pack/plugins/lists/server/services/lists/update_list.mock.ts b/x-pack/plugins/lists/server/services/lists/update_list.mock.ts index ff974b6e7352b..fc3d63277c5b5 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.mock.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.mock.ts @@ -16,6 +16,7 @@ import { } from '../../../common/constants.mock'; export const getUpdateListOptionsMock = (): UpdateListOptions => ({ + _version: undefined, callCluster: getCallClusterMock(), dateNow: DATE_NOW, description: DESCRIPTION, diff --git a/x-pack/plugins/lists/server/services/lists/update_list.ts b/x-pack/plugins/lists/server/services/lists/update_list.ts index f84ca787eaa7c..fba57ca744f9d 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.ts @@ -7,6 +7,8 @@ import { CreateDocumentResponse } from 'elasticsearch'; import { LegacyAPICaller } from 'kibana/server'; +import { decodeVersion } from '../utils/decode_version'; +import { encodeHitVersion } from '../utils/encode_hit_version'; import { DescriptionOrUndefined, Id, @@ -14,11 +16,13 @@ import { MetaOrUndefined, NameOrUndefined, UpdateEsListSchema, + _VersionOrUndefined, } from '../../../common/schemas'; import { getList } from '.'; export interface UpdateListOptions { + _version: _VersionOrUndefined; id: Id; callCluster: LegacyAPICaller; listIndex: string; @@ -30,6 +34,7 @@ export interface UpdateListOptions { } export const updateList = async ({ + _version, id, name, description, @@ -52,12 +57,14 @@ export const updateList = async ({ updated_by: user, }; const response = await callCluster('update', { + ...decodeVersion(_version), body: { doc }, id, index: listIndex, refresh: 'wait_for', }); return { + _version: encodeHitVersion(response), created_at: list.created_at, created_by: list.created_by, description: description ?? list.description, diff --git a/x-pack/plugins/lists/server/services/utils/decode_version.ts b/x-pack/plugins/lists/server/services/utils/decode_version.ts new file mode 100644 index 0000000000000..e5fb9b54bcf97 --- /dev/null +++ b/x-pack/plugins/lists/server/services/utils/decode_version.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +// Similar to the src/core/server/saved_objects/version/decode_version.ts +// with the notable differences in that it is more tolerant and does not throw saved object specific errors +// but rather just returns an empty object if it cannot parse the version or cannot find one. +export const decodeVersion = ( + version: string | undefined +): + | { + ifSeqNo: number; + ifPrimaryTerm: number; + } + | {} => { + if (version != null) { + try { + const decoded = Buffer.from(version, 'base64').toString('utf8'); + const parsed = JSON.parse(decoded); + if (Array.isArray(parsed) && Number.isInteger(parsed[0]) && Number.isInteger(parsed[1])) { + return { + ifPrimaryTerm: parsed[1], + ifSeqNo: parsed[0], + }; + } else { + return {}; + } + } catch (err) { + // do nothing here, this is on purpose and we want to return any empty object when we can't parse. + return {}; + } + } else { + return {}; + } +}; diff --git a/x-pack/plugins/lists/server/services/utils/encode_hit_version.ts b/x-pack/plugins/lists/server/services/utils/encode_hit_version.ts new file mode 100644 index 0000000000000..42dccfbac7340 --- /dev/null +++ b/x-pack/plugins/lists/server/services/utils/encode_hit_version.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/** + * Very similar to the encode_hit_version from saved object system from here: + * src/core/server/saved_objects/version/encode_hit_version.ts + * + * with the most notably change is that it doesn't do any throws but rather just returns undefined + * if _seq_no or _primary_term does not exist. + * @param response The response to encode into a version by using _seq_no and _primary_term + */ +export const encodeHitVersion = (hit: T): string | undefined => { + // Have to do this "as cast" here as these two types aren't included in the SearchResponse hit type + const { _seq_no: seqNo, _primary_term: primaryTerm } = (hit as unknown) as { + _seq_no: number; + _primary_term: number; + }; + + if (seqNo == null || primaryTerm == null) { + return undefined; + } else { + return Buffer.from(JSON.stringify([seqNo, primaryTerm]), 'utf8').toString('base64'); + } +}; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts index bb1ae1d4b9ff3..fb226d91fe395 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts @@ -8,6 +8,8 @@ import { SearchResponse } from 'elasticsearch'; import { ListArraySchema, SearchEsListSchema } from '../../../common/schemas'; +import { encodeHitVersion } from './encode_hit_version'; + export interface TransformElasticToListOptions { response: SearchResponse; } @@ -17,6 +19,7 @@ export const transformElasticToList = ({ }: TransformElasticToListOptions): ListArraySchema => { return response.hits.hits.map((hit) => { return { + _version: encodeHitVersion(hit), id: hit._id, ...hit._source, }; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts index a59b3b383cd2a..26fe15e9106fe 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts @@ -9,6 +9,7 @@ import { SearchResponse } from 'elasticsearch'; import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas'; import { ErrorWithStatusCode } from '../../error_with_status_code'; +import { encodeHitVersion } from './encode_hit_version'; import { findSourceValue } from './find_source_value'; export interface TransformElasticToListItemOptions { @@ -40,6 +41,7 @@ export const transformElasticToListItem = ({ throw new ErrorWithStatusCode(`Was expected ${type} to not be null/undefined`, 400); } else { return { + _version: encodeHitVersion(hit), created_at, created_by, deserializer, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_actions_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_actions_array.ts index c69ae591f5ddc..e6b00aeac0d9c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_actions_array.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_actions_array.ts @@ -10,14 +10,12 @@ import { actions, Actions } from '../common/schemas'; /** * Types the DefaultStringArray as: - * - If null or undefined, then a default action array will be set + * - If undefined, then a default action array will be set */ -export const DefaultActionsArray = new t.Type( +export const DefaultActionsArray = new t.Type( 'DefaultActionsArray', actions.is, (input, context): Either => input == null ? t.success([]) : actions.validate(input, context), t.identity ); - -export type DefaultActionsArrayC = typeof DefaultActionsArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_false.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_false.ts index 0cab6525779a6..5ace191c5f86f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_false.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_false.ts @@ -11,12 +11,10 @@ import { Either } from 'fp-ts/lib/Either'; * Types the DefaultBooleanFalse as: * - If null or undefined, then a default false will be set */ -export const DefaultBooleanFalse = new t.Type( +export const DefaultBooleanFalse = new t.Type( 'DefaultBooleanFalse', t.boolean.is, (input, context): Either => input == null ? t.success(false) : t.boolean.validate(input, context), t.identity ); - -export type DefaultBooleanFalseC = typeof DefaultBooleanFalse; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_true.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_true.ts index 6997652b72636..92167287d23f5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_true.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_boolean_true.ts @@ -11,12 +11,10 @@ import { Either } from 'fp-ts/lib/Either'; * Types the DefaultBooleanTrue as: * - If null or undefined, then a default true will be set */ -export const DefaultBooleanTrue = new t.Type( +export const DefaultBooleanTrue = new t.Type( 'DefaultBooleanTrue', t.boolean.is, (input, context): Either => input == null ? t.success(true) : t.boolean.validate(input, context), t.identity ); - -export type DefaultBooleanTrueC = typeof DefaultBooleanTrue; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_empty_string.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_empty_string.ts index a1103c4aa8d0e..185cccd86313e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_empty_string.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_empty_string.ts @@ -11,12 +11,10 @@ import { Either } from 'fp-ts/lib/Either'; * Types the DefaultEmptyString as: * - If null or undefined, then a default of an empty string "" will be used */ -export const DefaultEmptyString = new t.Type( +export const DefaultEmptyString = new t.Type( 'DefaultEmptyString', t.string.is, (input, context): Either => input == null ? t.success('') : t.string.validate(input, context), t.identity ); - -export type DefaultEmptyStringC = typeof DefaultEmptyString; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_export_file_name.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_export_file_name.ts index 4c7f663e7f46d..12efda77e5435 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_export_file_name.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_export_file_name.ts @@ -11,12 +11,10 @@ import { Either } from 'fp-ts/lib/Either'; * Types the DefaultExportFileName as: * - If null or undefined, then a default of "export.ndjson" will be used */ -export const DefaultExportFileName = new t.Type( +export const DefaultExportFileName = new t.Type( 'DefaultExportFileName', t.string.is, (input, context): Either => input == null ? t.success('export.ndjson') : t.string.validate(input, context), t.identity ); - -export type DefaultExportFileNameC = typeof DefaultExportFileName; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts index b6b432858eb92..a85ea58b26478 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts @@ -11,12 +11,10 @@ import { Either } from 'fp-ts/lib/Either'; * Types the DefaultFromString as: * - If null or undefined, then a default of the string "now-6m" will be used */ -export const DefaultFromString = new t.Type( +export const DefaultFromString = new t.Type( 'DefaultFromString', t.string.is, (input, context): Either => input == null ? t.success('now-6m') : t.string.validate(input, context), t.identity ); - -export type DefaultFromStringC = typeof DefaultFromString; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_interval_string.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_interval_string.ts index 9492374ffe91e..4001af46e7ddf 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_interval_string.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_interval_string.ts @@ -11,12 +11,10 @@ import { Either } from 'fp-ts/lib/Either'; * Types the DefaultIntervalString as: * - If null or undefined, then a default of the string "5m" will be used */ -export const DefaultIntervalString = new t.Type( +export const DefaultIntervalString = new t.Type( 'DefaultIntervalString', t.string.is, (input, context): Either => input == null ? t.success('5m') : t.string.validate(input, context), t.identity ); - -export type DefaultIntervalStringC = typeof DefaultIntervalString; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_language_string.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_language_string.ts index 1e05a46d7273c..afa83c484698f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_language_string.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_language_string.ts @@ -12,12 +12,10 @@ import { language } from '../common/schemas'; * Types the DefaultLanguageString as: * - If null or undefined, then a default of the string "kuery" will be used */ -export const DefaultLanguageString = new t.Type( +export const DefaultLanguageString = new t.Type( 'DefaultLanguageString', t.string.is, (input, context): Either => input == null ? t.success('kuery') : language.validate(input, context), t.identity ); - -export type DefaultLanguageStringC = typeof DefaultLanguageString; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_max_signals_number.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_max_signals_number.ts index d3c48b5522f57..518af32dcf2b4 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_max_signals_number.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_max_signals_number.ts @@ -16,11 +16,7 @@ import { DEFAULT_MAX_SIGNALS } from '../../../constants'; * - greater than 1 * - If undefined then it will use DEFAULT_MAX_SIGNALS (100) as the default */ -export const DefaultMaxSignalsNumber: DefaultMaxSignalsNumberC = new t.Type< - number, - number, - unknown ->( +export const DefaultMaxSignalsNumber = new t.Type( 'DefaultMaxSignals', t.number.is, (input, context): Either => { @@ -28,5 +24,3 @@ export const DefaultMaxSignalsNumber: DefaultMaxSignalsNumberC = new t.Type< }, t.identity ); - -export type DefaultMaxSignalsNumberC = t.Type; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_page.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_page.ts index 96e01d381e34b..f3a997e3cc897 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_page.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_page.ts @@ -14,7 +14,7 @@ import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_ * - If null or undefined, then a default of 1 will be used * - If the number is 0 or less this will not validate as it has to be a positive number greater than zero */ -export const DefaultPage = new t.Type( +export const DefaultPage = new t.Type( 'DefaultPerPage', t.number.is, (input, context): Either => { @@ -28,5 +28,3 @@ export const DefaultPage = new t.Type( }, t.identity ); - -export type DefaultPageC = typeof DefaultPage; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_per_page.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_per_page.ts index b78de8b35cede..72e817b10a600 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_per_page.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_per_page.ts @@ -14,7 +14,7 @@ import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_ * - If null or undefined, then a default of 20 will be used * - If the number is 0 or less this will not validate as it has to be a positive number greater than zero */ -export const DefaultPerPage = new t.Type( +export const DefaultPerPage = new t.Type( 'DefaultPerPage', t.number.is, (input, context): Either => { @@ -28,5 +28,3 @@ export const DefaultPerPage = new t.Type( }, t.identity ); - -export type DefaultPerPageC = typeof DefaultPerPage; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts index ba74045b4e32c..bf88ece913767 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts @@ -13,12 +13,14 @@ import { risk_score_mapping, RiskScoreMapping } from '../common/schemas'; * Types the DefaultStringArray as: * - If null or undefined, then a default risk_score_mapping array will be set */ -export const DefaultRiskScoreMappingArray = new t.Type( +export const DefaultRiskScoreMappingArray = new t.Type< + RiskScoreMapping, + RiskScoreMapping | undefined, + unknown +>( 'DefaultRiskScoreMappingArray', risk_score_mapping.is, (input, context): Either => input == null ? t.success([]) : risk_score_mapping.validate(input, context), t.identity ); - -export type DefaultRiskScoreMappingArrayC = typeof DefaultRiskScoreMappingArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts index 8e68b73148af1..56b0ac1b75982 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts @@ -13,12 +13,14 @@ import { severity_mapping, SeverityMapping } from '../common/schemas'; * Types the DefaultStringArray as: * - If null or undefined, then a default severity_mapping array will be set */ -export const DefaultSeverityMappingArray = new t.Type( +export const DefaultSeverityMappingArray = new t.Type< + SeverityMapping, + SeverityMapping | undefined, + unknown +>( 'DefaultSeverityMappingArray', severity_mapping.is, (input, context): Either => input == null ? t.success([]) : severity_mapping.validate(input, context), t.identity ); - -export type DefaultSeverityMappingArrayC = typeof DefaultSeverityMappingArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_array.ts index a8c53c230acd9..a973bbb37cac7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_array.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_array.ts @@ -9,14 +9,13 @@ import { Either } from 'fp-ts/lib/Either'; /** * Types the DefaultStringArray as: - * - If null or undefined, then a default array will be set + * - If undefined, then a default array will be set + * - If an array is sent in, then the array will be validated to ensure all elements are a string */ -export const DefaultStringArray = new t.Type( +export const DefaultStringArray = new t.Type( 'DefaultStringArray', t.array(t.string).is, (input, context): Either => input == null ? t.success([]) : t.array(t.string).validate(input, context), t.identity ); - -export type DefaultStringArrayC = typeof DefaultStringArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_boolean_false.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_boolean_false.ts index aa070c171d7ea..eba805048fe3a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_boolean_false.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_string_boolean_false.ts @@ -12,7 +12,7 @@ import { Either } from 'fp-ts/lib/Either'; * - If a string this will convert the string to a boolean * - If null or undefined, then a default false will be set */ -export const DefaultStringBooleanFalse = new t.Type( +export const DefaultStringBooleanFalse = new t.Type( 'DefaultStringBooleanFalse', t.boolean.is, (input, context): Either => { diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_threat_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_threat_array.ts index 5499a3c1e3064..3b3c20f003e6e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_threat_array.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_threat_array.ts @@ -12,12 +12,10 @@ import { Threat, threat } from '../common/schemas'; * Types the DefaultThreatArray as: * - If null or undefined, then an empty array will be set */ -export const DefaultThreatArray = new t.Type( +export const DefaultThreatArray = new t.Type( 'DefaultThreatArray', threat.is, (input, context): Either => input == null ? t.success([]) : threat.validate(input, context), t.identity ); - -export type DefaultThreatArrayC = typeof DefaultThreatArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_throttle_null.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_throttle_null.ts index b76a35c0265a0..c4cf9a07ed359 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_throttle_null.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_throttle_null.ts @@ -12,12 +12,10 @@ import { ThrottleOrNull, throttle } from '../common/schemas'; * Types the DefaultThrottleNull as: * - If null or undefined, then a null will be set */ -export const DefaultThrottleNull = new t.Type( +export const DefaultThrottleNull = new t.Type( 'DefaultThreatNull', throttle.is, (input, context): Either => input == null ? t.success(null) : throttle.validate(input, context), t.identity ); - -export type DefaultThrottleNullC = typeof DefaultThrottleNull; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_to_string.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_to_string.ts index 158eedc121c53..6fe247f05e7e6 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_to_string.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_to_string.ts @@ -11,12 +11,10 @@ import { Either } from 'fp-ts/lib/Either'; * Types the DefaultToString as: * - If null or undefined, then a default of the string "now" will be used */ -export const DefaultToString = new t.Type( +export const DefaultToString = new t.Type( 'DefaultToString', t.string.is, (input, context): Either => input == null ? t.success('now') : t.string.validate(input, context), t.identity ); - -export type DefaultToStringC = typeof DefaultToString; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_uuid.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_uuid.ts index 74e32e083cc44..5f1d51ba84369 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_uuid.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_uuid.ts @@ -15,12 +15,10 @@ import { NonEmptyString } from './non_empty_string'; * - If null or undefined, then a default string uuid.v4() will be * created otherwise it will be checked just against an empty string */ -export const DefaultUuid = new t.Type( +export const DefaultUuid = new t.Type( 'DefaultUuid', t.string.is, (input, context): Either => input == null ? t.success(uuid.v4()) : NonEmptyString.validate(input, context), t.identity ); - -export type DefaultUuidC = typeof DefaultUuid; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_version_number.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_version_number.ts index 832c942291c32..bbba7c5b8f3bb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_version_number.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_version_number.ts @@ -12,12 +12,10 @@ import { version, Version } from '../common/schemas'; * Types the DefaultVersionNumber as: * - If null or undefined, then a default of the number 1 will be used */ -export const DefaultVersionNumber = new t.Type( +export const DefaultVersionNumber = new t.Type( 'DefaultVersionNumber', version.is, (input, context): Either => input == null ? t.success(1) : version.validate(input, context), t.identity ); - -export type DefaultVersionNumberC = typeof DefaultVersionNumber; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.test.ts index 2268e47bd1149..59819947ddddf 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.test.ts @@ -9,7 +9,7 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../../test_utils'; -import { DefaultListArray, DefaultListArrayC } from './lists_default_array'; +import { DefaultListArray } from './lists_default_array'; import { getListArrayMock } from './lists.mock'; describe('lists_default_array', () => { @@ -51,7 +51,7 @@ describe('lists_default_array', () => { test('it should not validate an array of non accepted types', () => { // Terrible casting for purpose of tests - const payload = ([1] as unknown) as DefaultListArrayC; + const payload = [1] as unknown; const decoded = DefaultListArray.decode(payload); const message = pipe(decoded, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.ts index ac5666cad23a7..9260d7cbdb2a8 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/lists_default_array.ts @@ -9,13 +9,11 @@ import { Either } from 'fp-ts/lib/Either'; import { ListArray, list } from './lists'; -export type DefaultListArrayC = t.Type; - /** * Types the DefaultListArray as: * - If null or undefined, then a default array of type list will be set */ -export const DefaultListArray: DefaultListArrayC = new t.Type( +export const DefaultListArray = new t.Type( 'DefaultListArray', t.array(list).is, (input, context): Either => diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/only_false_allowed.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/only_false_allowed.ts index b22562e2ab9dc..add926598a137 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/only_false_allowed.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/only_false_allowed.ts @@ -13,7 +13,7 @@ import { Either } from 'fp-ts/lib/Either'; * - If true is sent in then this will return an error * - If false is sent in then this will allow it only false */ -export const OnlyFalseAllowed = new t.Type( +export const OnlyFalseAllowed = new t.Type( 'DefaultBooleanTrue', t.boolean.is, (input, context): Either => { @@ -29,5 +29,3 @@ export const OnlyFalseAllowed = new t.Type( }, t.identity ); - -export type OnlyFalseAllowedC = typeof OnlyFalseAllowed; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer.ts index 298487a3fae98..0dc6e2c700395 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer.ts @@ -22,5 +22,3 @@ export const PositiveInteger = new t.Type( }, t.identity ); - -export type PositiveIntegerC = typeof PositiveInteger; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts index 8aeaf99a19eb1..2aa17c6c68955 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts @@ -22,5 +22,3 @@ export const PositiveIntegerGreaterThanZero = new t.Type( +export const ReferencesDefaultArray = new t.Type( 'referencesWithDefaultArray', t.array(t.string).is, (input, context): Either => input == null ? t.success([]) : t.array(t.string).validate(input, context), t.identity ); - -export type ReferencesDefaultArrayC = typeof ReferencesDefaultArray; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index daa8589613daa..7171d3c6b815e 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -398,11 +398,11 @@ describe('Exception helpers', () => { title: 'OS', }, { - description: 'April 23rd 2020 @ 00:19:13', + description: 'April 20th 2020 @ 15:25:31', title: 'Date created', }, { - description: 'user_name', + description: 'some user', title: 'Created by', }, ]; @@ -417,11 +417,11 @@ describe('Exception helpers', () => { const result = getDescriptionListContent(payload); const expected: DescriptionListItem[] = [ { - description: 'April 23rd 2020 @ 00:19:13', + description: 'April 20th 2020 @ 15:25:31', title: 'Date created', }, { - description: 'user_name', + description: 'some user', title: 'Created by', }, { @@ -440,11 +440,11 @@ describe('Exception helpers', () => { const result = getDescriptionListContent(payload); const expected: DescriptionListItem[] = [ { - description: 'April 23rd 2020 @ 00:19:13', + description: 'April 20th 2020 @ 15:25:31', title: 'Date created', }, { - description: 'user_name', + description: 'some user', title: 'Created by', }, ]; @@ -520,12 +520,12 @@ describe('Exception helpers', () => { const expected = { _tags: ['endpoint', 'process', 'malware', 'os:linux'], comments: [], - description: 'This is a sample endpoint type exception', + description: 'some description', entries: ENTRIES, id: '1', item_id: 'endpoint_list_item', meta: {}, - name: 'Sample Endpoint Exception List', + name: 'some name', namespace_type: 'single', tags: ['user added string for a tag', 'malware'], type: 'simple', diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx index f5b34b7838d25..4fc744c2c9d01 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx @@ -179,7 +179,7 @@ describe('ExceptionDetails', () => { expect(wrapper.find('EuiDescriptionListTitle').at(1).text()).toEqual('Date created'); expect(wrapper.find('EuiDescriptionListDescription').at(1).text()).toEqual( - 'April 23rd 2020 @ 00:19:13' + 'April 20th 2020 @ 15:25:31' ); }); @@ -196,7 +196,7 @@ describe('ExceptionDetails', () => { ); expect(wrapper.find('EuiDescriptionListTitle').at(2).text()).toEqual('Created by'); - expect(wrapper.find('EuiDescriptionListDescription').at(2).text()).toEqual('user_name'); + expect(wrapper.find('EuiDescriptionListDescription').at(2).text()).toEqual('some user'); }); test('it renders the description if one is included on the exception item', () => { @@ -212,8 +212,6 @@ describe('ExceptionDetails', () => { ); expect(wrapper.find('EuiDescriptionListTitle').at(3).text()).toEqual('Comment'); - expect(wrapper.find('EuiDescriptionListDescription').at(3).text()).toEqual( - 'This is a sample endpoint type exception' - ); + expect(wrapper.find('EuiDescriptionListDescription').at(3).text()).toEqual('some description'); }); }); From 8a4daffcfdb90b8ff776ea051266516441c6fda2 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 20 Jul 2020 11:00:59 -0600 Subject: [PATCH 07/15] [SIEM][Detection Engine][Lists] Adds list permissions (#72335) ## Summary * Adds list permissions as a feature control to SIEM. * Separates the controls between two, one of which is `access:lists-all` and the other is `access:lists-read` * Grants SIEM the ability to utilize both depending on which feature mode the space is in. --- .../routes/create_endpoint_list_item_route.ts | 2 +- .../routes/create_endpoint_list_route.ts | 2 +- .../routes/create_exception_list_item_route.ts | 2 +- .../routes/create_exception_list_route.ts | 2 +- .../server/routes/create_list_index_route.ts | 2 +- .../server/routes/create_list_item_route.ts | 2 +- .../lists/server/routes/create_list_route.ts | 2 +- .../routes/delete_endpoint_list_item_route.ts | 2 +- .../routes/delete_exception_list_item_route.ts | 2 +- .../routes/delete_exception_list_route.ts | 2 +- .../server/routes/delete_list_index_route.ts | 2 +- .../server/routes/delete_list_item_route.ts | 2 +- .../lists/server/routes/delete_list_route.ts | 2 +- .../server/routes/export_list_item_route.ts | 2 +- .../routes/find_endpoint_list_item_route.ts | 2 +- .../routes/find_exception_list_item_route.ts | 2 +- .../server/routes/find_exception_list_route.ts | 2 +- .../server/routes/find_list_item_route.ts | 2 +- .../lists/server/routes/find_list_route.ts | 2 +- .../server/routes/import_list_item_route.ts | 2 +- .../server/routes/patch_list_item_route.ts | 2 +- .../lists/server/routes/patch_list_route.ts | 2 +- .../routes/read_endpoint_list_item_route.ts | 2 +- .../routes/read_exception_list_item_route.ts | 2 +- .../server/routes/read_exception_list_route.ts | 2 +- .../server/routes/read_list_index_route.ts | 2 +- .../server/routes/read_list_item_route.ts | 2 +- .../lists/server/routes/read_list_route.ts | 2 +- .../server/routes/read_privileges_route.ts | 2 +- .../routes/update_endpoint_list_item_route.ts | 2 +- .../routes/update_exception_list_item_route.ts | 2 +- .../routes/update_exception_list_route.ts | 2 +- .../server/routes/update_list_item_route.ts | 2 +- .../lists/server/routes/update_list_route.ts | 2 +- .../plugins/security_solution/server/plugin.ts | 18 ++++++++++++++++-- 35 files changed, 50 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts index b6eacc3b7dd04..5ff2a9d9df9f4 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts @@ -21,7 +21,7 @@ export const createEndpointListItemRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: ENDPOINT_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts index cac69ce65623f..b1e589be67cd1 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts @@ -26,7 +26,7 @@ export const createEndpointListRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: ENDPOINT_LIST_URL, validate: false, diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts index c331eeb4bd2d0..e4885c7393bd4 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts @@ -22,7 +22,7 @@ export const createExceptionListItemRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: EXCEPTION_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts index bd29a65c9450a..897d82d6a9ba0 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts @@ -21,7 +21,7 @@ export const createExceptionListRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: EXCEPTION_LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/create_list_index_route.ts b/x-pack/plugins/lists/server/routes/create_list_index_route.ts index 5ec2b36da61b0..1bffdd6bd5b5f 100644 --- a/x-pack/plugins/lists/server/routes/create_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_index_route.ts @@ -17,7 +17,7 @@ export const createListIndexRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_INDEX, validate: false, diff --git a/x-pack/plugins/lists/server/routes/create_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_list_item_route.ts index 8ac5db3c7fd1c..656d6af2c6c9a 100644 --- a/x-pack/plugins/lists/server/routes/create_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_item_route.ts @@ -17,7 +17,7 @@ export const createListItemRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/create_list_route.ts b/x-pack/plugins/lists/server/routes/create_list_route.ts index eee7517523b0f..ff041699054c9 100644 --- a/x-pack/plugins/lists/server/routes/create_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_route.ts @@ -17,7 +17,7 @@ export const createListRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts index b8946c542b27e..2d5028bd9525a 100644 --- a/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts @@ -21,7 +21,7 @@ export const deleteEndpointListItemRoute = (router: IRouter): void => { router.delete( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: ENDPOINT_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts index f363252dada50..06ff051925407 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts @@ -21,7 +21,7 @@ export const deleteExceptionListItemRoute = (router: IRouter): void => { router.delete( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: EXCEPTION_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts index b1bf705dcc5f6..f2bf517f55ae3 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts @@ -21,7 +21,7 @@ export const deleteExceptionListRoute = (router: IRouter): void => { router.delete( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: EXCEPTION_LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts index cb2e16b3602a7..be58d8aeed17d 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts @@ -33,7 +33,7 @@ export const deleteListIndexRoute = (router: IRouter): void => { router.delete( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_INDEX, validate: false, diff --git a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts index bb278ba436725..50313cd1294ae 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts @@ -17,7 +17,7 @@ export const deleteListItemRoute = (router: IRouter): void => { router.delete( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/delete_list_route.ts b/x-pack/plugins/lists/server/routes/delete_list_route.ts index 600e4b00c29ca..4eeb6d8f126ad 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_route.ts @@ -17,7 +17,7 @@ export const deleteListRoute = (router: IRouter): void => { router.delete( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/export_list_item_route.ts b/x-pack/plugins/lists/server/routes/export_list_item_route.ts index 8148c9b1ed824..98167931c4346 100644 --- a/x-pack/plugins/lists/server/routes/export_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/export_list_item_route.ts @@ -18,7 +18,7 @@ export const exportListItemRoute = (router: IRouter): void => { router.post( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: `${LIST_ITEM_URL}/_export`, validate: { diff --git a/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts index 7374ff7dc92ea..9f83761cc501a 100644 --- a/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts @@ -21,7 +21,7 @@ export const findEndpointListItemRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: `${ENDPOINT_LIST_ITEM_URL}/_find`, validate: { diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts index a318d653450c7..270aad85796b2 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts @@ -21,7 +21,7 @@ export const findExceptionListItemRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: `${EXCEPTION_LIST_ITEM_URL}/_find`, validate: { diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts index 97e1de834cd37..c5cae7a1e0bb8 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts @@ -21,7 +21,7 @@ export const findExceptionListRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: `${EXCEPTION_LIST_URL}/_find`, validate: { diff --git a/x-pack/plugins/lists/server/routes/find_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_list_item_route.ts index 8a0e06aa0c7d8..533dc74aa3694 100644 --- a/x-pack/plugins/lists/server/routes/find_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_item_route.ts @@ -22,7 +22,7 @@ export const findListItemRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: `${LIST_ITEM_URL}/_find`, validate: { diff --git a/x-pack/plugins/lists/server/routes/find_list_route.ts b/x-pack/plugins/lists/server/routes/find_list_route.ts index 2fa43c6368b5c..268eb36a5e26e 100644 --- a/x-pack/plugins/lists/server/routes/find_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_route.ts @@ -18,7 +18,7 @@ export const findListRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: `${LIST_URL}/_find`, validate: { diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index 2e629d7516dd1..5e88ca0f2569a 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -26,7 +26,7 @@ export const importListItemRoute = (router: IRouter, config: ConfigType): void = maxBytes: config.maxImportPayloadBytes, parse: false, }, - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: `${LIST_ITEM_URL}/_import`, validate: { diff --git a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts index 9a74beb45bafd..d975e80079ab7 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts @@ -17,7 +17,7 @@ export const patchListItemRoute = (router: IRouter): void => { router.patch( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/patch_list_route.ts b/x-pack/plugins/lists/server/routes/patch_list_route.ts index 06a76559dee9a..681581c6ff6bd 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_route.ts @@ -17,7 +17,7 @@ export const patchListRoute = (router: IRouter): void => { router.patch( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts index 5e7ed901bf0cb..fd932746ce990 100644 --- a/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts @@ -21,7 +21,7 @@ export const readEndpointListItemRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: ENDPOINT_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts index c4e969b27fcf4..fe8256fbda5cd 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts @@ -21,7 +21,7 @@ export const readExceptionListItemRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: EXCEPTION_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts index 6cb91c10aea55..0512876d298d4 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts @@ -21,7 +21,7 @@ export const readExceptionListRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: EXCEPTION_LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/read_list_index_route.ts b/x-pack/plugins/lists/server/routes/read_list_index_route.ts index 4664bed3e7a8b..87a4d85e0d254 100644 --- a/x-pack/plugins/lists/server/routes/read_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_index_route.ts @@ -17,7 +17,7 @@ export const readListIndexRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: LIST_INDEX, validate: false, diff --git a/x-pack/plugins/lists/server/routes/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_list_item_route.ts index 24011d3b50d27..b7cf2b9f7123b 100644 --- a/x-pack/plugins/lists/server/routes/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_item_route.ts @@ -17,7 +17,7 @@ export const readListItemRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/read_list_route.ts b/x-pack/plugins/lists/server/routes/read_list_route.ts index 34924b70fd4df..4bce09ecd3bde 100644 --- a/x-pack/plugins/lists/server/routes/read_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_route.ts @@ -17,7 +17,7 @@ export const readListRoute = (router: IRouter): void => { router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/read_privileges_route.ts b/x-pack/plugins/lists/server/routes/read_privileges_route.ts index 892b6406a28ec..a4ec878613608 100644 --- a/x-pack/plugins/lists/server/routes/read_privileges_route.ts +++ b/x-pack/plugins/lists/server/routes/read_privileges_route.ts @@ -20,7 +20,7 @@ export const readPrivilegesRoute = ( router.get( { options: { - tags: ['access:lists'], + tags: ['access:lists-read'], }, path: LIST_PRIVILEGES_URL, validate: false, diff --git a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts index 8415c64633a06..f717dc0fb3392 100644 --- a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts @@ -21,7 +21,7 @@ export const updateEndpointListItemRoute = (router: IRouter): void => { router.put( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: ENDPOINT_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index 2aa1e016d51ed..293435b3f6202 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -21,7 +21,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => { router.put( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: EXCEPTION_LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts index 331ec064fa663..403a9f6db934f 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -21,7 +21,7 @@ export const updateExceptionListRoute = (router: IRouter): void => { router.put( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: EXCEPTION_LIST_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/update_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_list_item_route.ts index 0f5d11afcda09..d479bc63b64bd 100644 --- a/x-pack/plugins/lists/server/routes/update_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_item_route.ts @@ -17,7 +17,7 @@ export const updateListItemRoute = (router: IRouter): void => { router.put( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_ITEM_URL, validate: { diff --git a/x-pack/plugins/lists/server/routes/update_list_route.ts b/x-pack/plugins/lists/server/routes/update_list_route.ts index 2fae910c1b398..78aed23db13fc 100644 --- a/x-pack/plugins/lists/server/routes/update_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_route.ts @@ -17,7 +17,7 @@ export const updateListRoute = (router: IRouter): void => { router.put( { options: { - tags: ['access:lists'], + tags: ['access:lists-all'], }, path: LIST_URL, validate: { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 17192057d2ad3..22b55c64a1657 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -167,7 +167,14 @@ export class Plugin implements IPlugin Date: Mon, 20 Jul 2020 13:47:04 -0400 Subject: [PATCH 08/15] add index-pattern link when error contains 'click here' text (#72470) --- .../job_config_error_callout.tsx | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx index 945d6654067c0..9b9e1258db503 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx @@ -6,7 +6,7 @@ import React, { FC } from 'react'; -import { EuiCallOut, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiLink, EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -31,6 +31,23 @@ export const JobConfigErrorCallout: FC = ({ jobConfigErrorMessage, title, }) => { + const containsIndexPatternLink = + typeof jobCapsServiceErrorMessage === 'string' && + jobCapsServiceErrorMessage.includes('locate that index-pattern') && + jobCapsServiceErrorMessage.includes('click here to re-create'); + + const message = ( +

{jobConfigErrorMessage ? jobConfigErrorMessage : jobCapsServiceErrorMessage}

+ ); + + const calloutBody = containsIndexPatternLink ? ( + + {message} + + ) : ( + message + ); + return ( @@ -40,7 +57,7 @@ export const JobConfigErrorCallout: FC = ({ color="danger" iconType="cross" > -

{jobConfigErrorMessage ? jobConfigErrorMessage : jobCapsServiceErrorMessage}

+ {calloutBody}
); From 85d8ec8905856d2eb0f0bb22454d8ca3801e3b98 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Mon, 20 Jul 2020 13:18:43 -0500 Subject: [PATCH 09/15] [Metrics UI] Fix Alert Preview Error design (#71005) Co-authored-by: Elastic Machine --- .../common/components/alert_preview.tsx | 58 ++++++++----------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx index 0e0e23ef73a3a..f3136ca155c78 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx +++ b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx @@ -14,13 +14,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiCallOut, - EuiOverlayMask, - EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, + EuiAccordion, EuiCodeBlock, - EuiLink, + EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -61,9 +57,6 @@ export const AlertPreview: React.FC = (props) => { const [previewResult, setPreviewResult] = useState< (AlertPreviewSuccessResponsePayload & Record) | null >(null); - const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); - const onOpenModal = useCallback(() => setIsErrorModalVisible(true), [setIsErrorModalVisible]); - const onCloseModal = useCallback(() => setIsErrorModalVisible(false), [setIsErrorModalVisible]); const onSelectPreviewLookbackInterval = useCallback((e) => { setPreviewLookbackInterval(e.target.value); @@ -271,33 +264,32 @@ export const AlertPreview: React.FC = (props) => { iconType="alert" > {previewError.body && ( - view the error, - }} - /> + <> + + + + + + + + } + > + + {previewError.body.message} + + )} )} - {isErrorModalVisible && ( - - - - - - - - - {previewError.body.message} - - - - )} )} From 88e8c30e61daf0a982a9944469a901dad8fa4118 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 20 Jul 2020 11:21:03 -0700 Subject: [PATCH 10/15] Convert ILM remove_lifecycle_confirm_modal component to TS. (#70382) - Also convert api and api_errors services, and improve typing of http service. - Fix bug where fatalErrors service was improperly consumed in api_errors. - Improve typing in Rollup api_errors service, for consistency. --- .../helpers/setup_environment.ts | 2 ++ .../public/application/services/api.ts | 12 ++++++++---- .../services/{api_errors.js => api_errors.ts} | 10 ++++++---- .../public/application/services/http.ts | 15 ++++++++++----- ...odal.js => remove_lifecycle_confirm_modal.tsx} | 15 ++++++--------- .../rollup/public/crud_app/services/api_errors.ts | 10 ++++++---- 6 files changed, 38 insertions(+), 26 deletions(-) rename x-pack/plugins/index_lifecycle_management/public/application/services/{api_errors.js => api_errors.ts} (73%) rename x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/{remove_lifecycle_confirm_modal.js => remove_lifecycle_confirm_modal.tsx} (95%) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/setup_environment.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/setup_environment.ts index b3205a9523c62..325d8193de5fd 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/setup_environment.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/setup_environment.ts @@ -29,6 +29,8 @@ export const setupEnvironment = () => { ); mockHttpClient.interceptors.response.use(({ data }) => data); + // This expects HttpSetup but we're giving it AxiosInstance. + // @ts-ignore initHttp(mockHttpClient); const { server, httpRequestsMockHelpers } = initHttpRequests(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/api.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/api.ts index 065fb3bcebca7..30c341baa6194 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/api.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/api.ts @@ -5,7 +5,6 @@ */ import { METRIC_TYPE } from '@kbn/analytics'; -import { trackUiMetric } from './ui_metric'; import { UIM_POLICY_DELETE, @@ -15,8 +14,13 @@ import { UIM_INDEX_RETRY_STEP, } from '../constants'; +import { trackUiMetric } from './ui_metric'; import { sendGet, sendPost, sendDelete, useRequest } from './http'; +interface GenericObject { + [key: string]: any; +} + export async function loadNodes() { return await sendGet(`nodes/list`); } @@ -33,7 +37,7 @@ export async function loadPolicies(withIndices: boolean) { return await sendGet('policies', { withIndices }); } -export async function savePolicy(policy: any) { +export async function savePolicy(policy: GenericObject) { return await sendPost(`policies`, policy); } @@ -58,14 +62,14 @@ export const removeLifecycleForIndex = async (indexNames: string[]) => { return response; }; -export const addLifecyclePolicyToIndex = async (body: any) => { +export const addLifecyclePolicyToIndex = async (body: GenericObject) => { const response = await sendPost(`index/add`, body); // Only track successful actions. trackUiMetric(METRIC_TYPE.COUNT, UIM_POLICY_ATTACH_INDEX); return response; }; -export const addLifecyclePolicyToTemplate = async (body: any) => { +export const addLifecyclePolicyToTemplate = async (body: GenericObject) => { const response = await sendPost(`template`, body); // Only track successful actions. trackUiMetric(METRIC_TYPE.COUNT, UIM_POLICY_ATTACH_INDEX_TEMPLATE); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.js b/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.ts similarity index 73% rename from x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.js rename to x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.ts index af107b5cff4b1..7b8d48acced33 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/api_errors.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IHttpFetchError } from 'src/core/public'; import { fatalErrors, toasts } from './notification'; -function createToastConfig(error, errorTitle) { +function createToastConfig(error: IHttpFetchError, errorTitle: string) { if (error && error.body) { + // Error body shape is defined by the API. const { error: errorString, statusCode, message } = error.body; return { @@ -17,7 +19,7 @@ function createToastConfig(error, errorTitle) { } } -export function showApiWarning(error, errorTitle) { +export function showApiWarning(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { @@ -26,10 +28,10 @@ export function showApiWarning(error, errorTitle) { // This error isn't an HTTP error, so let the fatal error screen tell the user something // unexpected happened. - return fatalErrors(error, errorTitle); + return fatalErrors.add(error, errorTitle); } -export function showApiError(error, errorTitle) { +export function showApiError(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts index c54ee15fd69bf..0b5f39a52c13f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts @@ -4,15 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +import { HttpSetup } from 'src/core/public'; import { UseRequestConfig, useRequest as _useRequest, Error, } from '../../../../../../src/plugins/es_ui_shared/public'; -let _httpClient: any; +interface GenericObject { + [key: string]: any; +} + +let _httpClient: HttpSetup; -export function init(httpClient: any): void { +export function init(httpClient: HttpSetup): void { _httpClient = httpClient; } @@ -26,15 +31,15 @@ function getFullPath(path: string): string { return apiPrefix; } -export function sendPost(path: string, payload: any): any { +export function sendPost(path: string, payload: GenericObject) { return _httpClient.post(getFullPath(path), { body: JSON.stringify(payload) }); } -export function sendGet(path: string, query?: any): any { +export function sendGet(path: string, query?: GenericObject): any { return _httpClient.get(getFullPath(path), { query }); } -export function sendDelete(path: string): any { +export function sendDelete(path: string) { return _httpClient.delete(getFullPath(path)); } diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.tsx similarity index 95% rename from x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.js rename to x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.tsx index 048ed44bd58b2..6057522885b1d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.tsx @@ -13,16 +13,13 @@ import { removeLifecycleForIndex } from '../../application/services/api'; import { showApiError } from '../../application/services/api_errors'; import { toasts } from '../../application/services/notification'; -export class RemoveLifecyclePolicyConfirmModal extends Component { - constructor(props) { - super(props); - this.state = { - policies: [], - selectedPolicyName: null, - selectedAlias: null, - }; - } +interface Props { + indexNames: string[]; + closeModal: () => void; + reloadIndices: () => void; +} +export class RemoveLifecyclePolicyConfirmModal extends Component { removePolicy = async () => { const { indexNames, closeModal, reloadIndices } = this.props; diff --git a/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts b/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts index af9e1a16e4cc5..bea21d119e7fd 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts +++ b/x-pack/plugins/rollup/public/crud_app/services/api_errors.ts @@ -4,12 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IHttpFetchError } from 'src/core/public'; import { getNotifications, getFatalErrors } from '../../kibana_services'; -function createToastConfig(error: any, errorTitle: string) { - // Expect an error in the shape provided by http service. +function createToastConfig(error: IHttpFetchError, errorTitle: string) { if (error && error.body) { + // Error body shape is defined by the API. const { error: errorString, statusCode, message } = error.body; + return { title: errorTitle, text: `${statusCode}: ${errorString}. ${message}`, @@ -17,7 +19,7 @@ function createToastConfig(error: any, errorTitle: string) { } } -export function showApiWarning(error: any, errorTitle: string) { +export function showApiWarning(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { @@ -29,7 +31,7 @@ export function showApiWarning(error: any, errorTitle: string) { return getFatalErrors().add(error, errorTitle); } -export function showApiError(error: any, errorTitle: string) { +export function showApiError(error: IHttpFetchError, errorTitle: string) { const toastConfig = createToastConfig(error, errorTitle); if (toastConfig) { From 2771d69c96330465b56f5b7c18530d04d5e00cb8 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 20 Jul 2020 21:32:46 +0300 Subject: [PATCH 11/15] [KP] bump es client to rc2 (#72448) * bump es client to rc2 * update code for new typings --- package.json | 2 +- .../security_solution/common/endpoint/index_data.ts | 2 +- x-pack/test/api_integration/apis/fleet/agents/acks.ts | 2 +- .../test/api_integration/apis/fleet/agents/checkin.ts | 2 +- .../test/api_integration/apis/fleet/agents/enroll.ts | 2 +- .../test/api_integration/apis/fleet/unenroll_agent.ts | 2 +- x-pack/test/api_integration/services/resolver.ts | 2 +- yarn.lock | 11 +++++++++++ 8 files changed, 18 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index ceb3ac4cca937..2f3f95854df04 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "@elastic/apm-rum": "^5.2.0", "@elastic/charts": "19.8.1", "@elastic/datemath": "5.0.3", - "@elastic/elasticsearch": "7.9.0-rc.1", + "@elastic/elasticsearch": "7.9.0-rc.2", "@elastic/ems-client": "7.9.3", "@elastic/eui": "26.3.1", "@elastic/filesaver": "1.1.2", diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts index 00b8f0b057afd..9a61738cd84b4 100644 --- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts @@ -94,6 +94,6 @@ async function indexAlerts( }, [] ); - await client.bulk({ body, refresh: 'true' }); + await client.bulk({ body, refresh: true }); } } diff --git a/x-pack/test/api_integration/apis/fleet/agents/acks.ts b/x-pack/test/api_integration/apis/fleet/agents/acks.ts index e8381aa9d59ea..a040ef20081a8 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/acks.ts @@ -38,7 +38,7 @@ export default function (providerContext: FtrProviderContext) { await esClient.update({ index: '.kibana', id: 'fleet-agents:agent1', - refresh: 'true', + refresh: true, body: { doc: agentDoc, }, diff --git a/x-pack/test/api_integration/apis/fleet/agents/checkin.ts b/x-pack/test/api_integration/apis/fleet/agents/checkin.ts index 8942deafdd83c..70147f602e9c7 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/checkin.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/checkin.ts @@ -38,7 +38,7 @@ export default function (providerContext: FtrProviderContext) { await esClient.update({ index: '.kibana', id: 'fleet-agents:agent1', - refresh: 'true', + refresh: true, body: { doc: agentDoc, }, diff --git a/x-pack/test/api_integration/apis/fleet/agents/enroll.ts b/x-pack/test/api_integration/apis/fleet/agents/enroll.ts index 8a21fbcf24c7d..58440a34457d0 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/enroll.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/enroll.ts @@ -43,7 +43,7 @@ export default function (providerContext: FtrProviderContext) { await esClient.update({ index: '.kibana', id: 'fleet-enrollment-api-keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0', - refresh: 'true', + refresh: true, body: { doc: enrollmentApiKeyDoc, }, diff --git a/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts b/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts index bc6c44e590cc4..bbbce3314e4cc 100644 --- a/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts +++ b/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts @@ -52,7 +52,7 @@ export default function (providerContext: FtrProviderContext) { await esClient.update({ index: '.kibana', id: 'fleet-agents:agent1', - refresh: 'true', + refresh: true, body: { doc: agentDoc, }, diff --git a/x-pack/test/api_integration/services/resolver.ts b/x-pack/test/api_integration/services/resolver.ts index 750d2f702fb84..7f568a2b00314 100644 --- a/x-pack/test/api_integration/services/resolver.ts +++ b/x-pack/test/api_integration/services/resolver.ts @@ -57,7 +57,7 @@ export function ResolverGeneratorProvider({ getService }: FtrProviderContext) { return array; }, []); // force a refresh here otherwise the documents might not be available when the tests search for them - await client.bulk({ body, refresh: 'true' }); + await client.bulk({ body, refresh: true }); allTrees.push(tree); } return { trees: allTrees, eventsIndex, alertsIndex }; diff --git a/yarn.lock b/yarn.lock index 3924655b5e43e..4cc802e328ab8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2183,6 +2183,17 @@ pump "^3.0.0" secure-json-parse "^2.1.0" +"@elastic/elasticsearch@7.9.0-rc.2": + version "7.9.0-rc.2" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.9.0-rc.2.tgz#cbc935f30940a15484b5ec3758c9b1ef119a5e5c" + integrity sha512-1FKCQJVr7s/LasKq6VbrmbWCI0LjoPcnjgmh2vKPzC+yyEEHVoYlmEfR5wBRchK1meATTXZtDhCVF95+Q9kVbA== + dependencies: + debug "^4.1.1" + decompress-response "^4.2.0" + ms "^2.1.1" + pump "^3.0.0" + secure-json-parse "^2.1.0" + "@elastic/ems-client@7.9.3": version "7.9.3" resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-7.9.3.tgz#71b79914f76e347f050ead8474ad65d761e94a8a" From 3ccdd79aa7ff6f03b08de2a21590a92c1a7e759e Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 20 Jul 2020 12:55:52 -0600 Subject: [PATCH 12/15] [SIEM][Detection Engine] Reduces flakiness within the tests using waitFor() and un-skips the tests (#72479) ## Summary * Utilizes the `waitFor` so that the tests are less flaky and more resilient * Unskips a test that was due to a regression within Elastic Search that should be fixed now * https://github.com/elastic/kibana/issues/71867 * https://github.com/elastic/kibana/issues/71814 * https://github.com/elastic/kibana/issues/71612 ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- .../basic/tests/add_prepackaged_rules.ts | 14 ++++++++++++-- .../tests/add_prepackaged_rules.ts | 14 ++++++++++++-- .../security_and_spaces/tests/create_rules_bulk.ts | 3 +-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts index 3340ac49b2d2d..a022b7c79c079 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts @@ -13,6 +13,7 @@ import { deleteAllAlerts, deleteAllTimelines, deleteSignalsIndex, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -20,8 +21,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/71867 - describe.skip('add_prepackaged_rules', () => { + describe('add_prepackaged_rules', () => { describe('validation errors', () => { it('should give an error that the index must exist first if it does not exist before adding prepackaged rules', async () => { const { body } = await supertest @@ -91,6 +91,16 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(200); + // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. + // This is to reduce flakiness where it can for a short period of time try to install the same rule twice. + await waitFor(async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.rules_not_installed === 0; + }); + const { body } = await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts index 7671b1bd49744..40456737b8761 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts @@ -13,6 +13,7 @@ import { deleteAllAlerts, deleteAllTimelines, deleteSignalsIndex, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -20,8 +21,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/71814 - describe.skip('add_prepackaged_rules', () => { + describe('add_prepackaged_rules', () => { describe('validation errors', () => { it('should give an error that the index must exist first if it does not exist before adding prepackaged rules', async () => { const { body } = await supertest @@ -91,6 +91,16 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(200); + // NOTE: I call the GET call until eventually it becomes consistent and that the number of rules to install are zero. + // This is to reduce flakiness where it can for a short period of time try to install the same rule the same rule twice. + await waitFor(async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.rules_not_installed === 0; + }); + const { body } = await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index b59fd1b744e97..52865e43be750 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -29,8 +29,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); - // Failing ES promotion: https://github.com/elastic/kibana/issues/71612 - describe.skip('create_rules_bulk', () => { + describe('create_rules_bulk', () => { describe('validation errors', () => { it('should give a 200 even if the index does not exist as all bulks return a 200 but have an error of 409 bad request in the body', async () => { const { body } = await supertest From 0f964f66916480f2de1f4b633e5afafc08cf62a0 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Mon, 20 Jul 2020 12:14:33 -0700 Subject: [PATCH 13/15] [Ingest Manager] Disable asset facet links (#72158) * Disable asset facets * Fix prop name Co-authored-by: Elastic Machine --- .../epm/components/assets_facet_group.tsx | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx index 24b4baeaa092b..b8fab92e40da8 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import React, { Fragment } from 'react'; import { EuiFacetButton, EuiFacetGroup, @@ -14,8 +14,8 @@ import { EuiTextColor, EuiTitle, } from '@elastic/eui'; -import React, { Fragment } from 'react'; import styled from 'styled-components'; +import { FormattedMessage } from '@kbn/i18n/react'; import { AssetsGroupedByServiceByType, AssetTypeToParts, @@ -43,8 +43,15 @@ const FacetGroup = styled(EuiFacetGroup)` `; const FacetButton = styled(EuiFacetButton)` - padding: '${(props) => props.theme.eui.paddingSizes.xs} 0'; - height: 'unset'; + &&& { + .euiFacetButton__icon, + .euiFacetButton__quantity { + opacity: 1; + } + .euiFacetButton__text { + color: ${(props) => props.theme.eui.euiTextColor}; + } + } `; export function AssetsFacetGroup({ assets }: { assets: AssetsGroupedByServiceByType }) { @@ -70,7 +77,15 @@ export function AssetsFacetGroup({ assets }: { assets: AssetsGroupedByServiceByT -

{ServiceTitleMap[service]} Assets

+

+ +

@@ -83,13 +98,7 @@ export function AssetsFacetGroup({ assets }: { assets: AssetsGroupedByServiceByT const iconType = type in AssetIcons && AssetIcons[type]; const iconNode = iconType ? : ''; return ( - {}} - > + {AssetTitleMap[type]} ); From 4ccf1aed96600382f2ba60bdf631a0d8c7f5e257 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Mon, 20 Jul 2020 15:34:26 -0400 Subject: [PATCH 14/15] [Security Solution][Detections]Exceptions modal bugs (#72471) --- .../exceptions/add_exception_modal/index.tsx | 8 ++++++-- .../exceptions/edit_exception_modal/index.tsx | 10 ++++++++-- .../detections/components/alerts_table/index.tsx | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 79383676266f5..53c53f48f076b 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -21,6 +21,7 @@ import { EuiCallOut, EuiText, } from '@elastic/eui'; +import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; import { alertsIndexPattern } from '../../../../../common/endpoint/constants'; import { ExceptionListItemSchema, @@ -67,6 +68,7 @@ interface AddExceptionModalProps { }; onCancel: () => void; onConfirm: (didCloseAlert: boolean) => void; + alertStatus?: Status; } const Modal = styled(EuiModal)` @@ -105,6 +107,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ alertData, onCancel, onConfirm, + alertStatus, }: AddExceptionModalProps) { const { http } = useKibana().services; const [comment, setComment] = useState(''); @@ -183,7 +186,8 @@ export const AddExceptionModal = memo(function AddExceptionModal({ if (indexPatternLoading === false && isSignalIndexLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || - entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) + entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) || + exceptionItemsToAdd.length === 0 ); } }, [ @@ -335,7 +339,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ - {alertData !== undefined && ( + {alertData !== undefined && alertStatus !== 'closed' && ( - {!isSignalIndexLoading && ( + {(addExceptionIsLoading || indexPatternLoading || isSignalIndexLoading) && ( + + )} + + {!isSignalIndexLoading && !addExceptionIsLoading && !indexPatternLoading && ( <> {i18n.EXCEPTION_BUILDER_INFO} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 30cfe2d02354f..1d4c97d85443f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -458,6 +458,7 @@ export const AlertsTableComponent: React.FC = ({ alertData={addExceptionModalState.alertData} onCancel={onAddExceptionCancel} onConfirm={onAddExceptionConfirm} + alertStatus={filterGroup} /> )} From b9413cf3c816d65c53d5b7fb5f2d0e1300729f3c Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Mon, 20 Jul 2020 15:55:26 -0400 Subject: [PATCH 15/15] [SIEM] [Detections] Fixes faulty circuit breaker (#71999) * removes useSortIds which was leftover from a previous attempt at implementing gap detection mitigation code. This only showed up because I modified the count variable used to determine when we hit maxSignals from utilizing the searchResult hits length to using the count of bulk created items (signals indexed) in this commit 56de45d156be23069815fec17440cf978710451f * removes logs and fixes if statement ordering * adds tests, increases code coverage for search after and bulk create function, updates log statements * update tests after rebase onto master * clean up if statements * fix test data * merge conflicts are hard --- .../signals/__mocks__/es_results.ts | 42 +++- .../signals/build_bulk_body.test.ts | 7 +- .../signals/search_after_bulk_create.test.ts | 207 ++++++++++++++++++ .../signals/search_after_bulk_create.ts | 171 +++++++++------ 4 files changed, 351 insertions(+), 76 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 17e05109b9a87..19fcf65ec0c5e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -56,11 +56,10 @@ export const sampleRuleAlertParams = ( exceptionsList: getListArrayMock(), }); -export const sampleDocNoSortId = (someUuid: string = sampleIdGuid): SignalSourceHit => ({ +export const sampleDocNoSortIdNoVersion = (someUuid: string = sampleIdGuid): SignalSourceHit => ({ _index: 'myFakeSignalIndex', _type: 'doc', _score: 100, - _version: 1, _id: someUuid, _source: { someKey: 'someValue', @@ -68,18 +67,26 @@ export const sampleDocNoSortId = (someUuid: string = sampleIdGuid): SignalSource }, }); -export const sampleDocNoSortIdNoVersion = (someUuid: string = sampleIdGuid): SignalSourceHit => ({ +export const sampleDocWithSortId = ( + someUuid: string = sampleIdGuid, + ip?: string +): SignalSourceHit => ({ _index: 'myFakeSignalIndex', _type: 'doc', _score: 100, + _version: 1, _id: someUuid, _source: { someKey: 'someValue', '@timestamp': '2020-04-20T21:27:45+0000', + source: { + ip: ip ?? '127.0.0.1', + }, }, + sort: ['1234567891111'], }); -export const sampleDocWithSortId = ( +export const sampleDocNoSortId = ( someUuid: string = sampleIdGuid, ip?: string ): SignalSourceHit => ({ @@ -95,7 +102,7 @@ export const sampleDocWithSortId = ( ip: ip ?? '127.0.0.1', }, }, - sort: ['1234567891111'], + sort: [], }); export const sampleEmptyDocSearchResults = (): SignalSearchResponse => ({ @@ -116,6 +123,8 @@ export const sampleEmptyDocSearchResults = (): SignalSearchResponse => ({ export const sampleDocWithAncestors = (): SignalSearchResponse => { const sampleDoc = sampleDocNoSortId(); + delete sampleDoc.sort; + delete sampleDoc._source.source; sampleDoc._source.signal = { parent: { rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', @@ -317,6 +326,29 @@ export const repeatedSearchResultsWithSortId = ( }, }); +export const repeatedSearchResultsWithNoSortId = ( + total: number, + pageSize: number, + guids: string[], + ips?: string[] +) => ({ + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, + }, + hits: { + total, + max_score: 100, + hits: Array.from({ length: pageSize }).map((x, index) => ({ + ...sampleDocNoSortId(guids[index], ips ? ips[index] : '127.0.0.1'), + })), + }, +}); + export const sampleDocSearchResultsWithSortId = ( someUuid: string = sampleIdGuid ): SignalSearchResponse => ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index e840ae96cf3c1..fe2e0f2d96fd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -21,8 +21,10 @@ describe('buildBulkBody', () => { test('bulk body builds well-defined body', () => { const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + delete doc._source.source; const fakeSignalSourceHit = buildBulkBody({ - doc: sampleDocNoSortId(), + doc, ruleParams: sampleParams, id: sampleRuleGuid, name: 'rule-name', @@ -107,6 +109,7 @@ describe('buildBulkBody', () => { test('bulk body builds original_event if it exists on the event to begin with', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); + delete doc._source.source; doc._source.event = { action: 'socket_opened', module: 'system', @@ -208,6 +211,7 @@ describe('buildBulkBody', () => { test('bulk body builds original_event if it exists on the event to begin with but no kind information', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); + delete doc._source.source; doc._source.event = { action: 'socket_opened', module: 'system', @@ -307,6 +311,7 @@ describe('buildBulkBody', () => { test('bulk body builds original_event if it exists on the event to begin with with only kind information', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); + delete doc._source.source; doc._source.event = { kind: 'event', }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 17935f64d5e14..3312191c3b41b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -11,6 +11,7 @@ import { sampleRuleGuid, mockLogger, repeatedSearchResultsWithSortId, + repeatedSearchResultsWithNoSortId, sampleDocSearchResultsNoSortIdNoHits, } from './__mocks__/es_results'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; @@ -356,6 +357,212 @@ describe('searchAfterAndBulkCreate', () => { expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); }); + test('should return success when all search results are in the allowlist and with sortId present', async () => { + listClient.getListItemByValues = jest + .fn() + .mockResolvedValue([{ value: '1.1.1.1' }, { value: '2.2.2.2' }, { value: '3.3.3.3' }]); + const sampleParams = sampleRuleAlertParams(30); + mockService.callCluster + .mockResolvedValueOnce( + repeatedSearchResultsWithSortId(4, 4, someGuids.slice(0, 3), [ + '1.1.1.1', + '2.2.2.2', + '2.2.2.2', + '2.2.2.2', + ]) + ) + .mockResolvedValueOnce(sampleDocSearchResultsNoSortIdNoHits()); + + const exceptionItem = getExceptionListItemSchemaMock(); + exceptionItem.entries = [ + { + field: 'source.ip', + operator: 'included', + type: 'list', + list: { + id: 'ci-badguys.txt', + type: 'ip', + }, + }, + ]; + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ + ruleParams: sampleParams, + gap: null, + previousStartedAt: new Date(), + listClient, + exceptionsList: [exceptionItem], + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + inputIndexPattern, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + actions: [], + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + refresh: false, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + buildRuleMessage, + }); + expect(success).toEqual(true); + expect(mockService.callCluster).toHaveBeenCalledTimes(2); + expect(createdSignalsCount).toEqual(0); // should not create any signals because all events were in the allowlist + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); + }); + + test('should return success when all search results are in the allowlist and no sortId present', async () => { + listClient.getListItemByValues = jest + .fn() + .mockResolvedValue([{ value: '1.1.1.1' }, { value: '2.2.2.2' }, { value: '3.3.3.3' }]); + const sampleParams = sampleRuleAlertParams(30); + mockService.callCluster.mockResolvedValueOnce( + repeatedSearchResultsWithNoSortId(4, 4, someGuids.slice(0, 3), [ + '1.1.1.1', + '2.2.2.2', + '2.2.2.2', + '2.2.2.2', + ]) + ); + + const exceptionItem = getExceptionListItemSchemaMock(); + exceptionItem.entries = [ + { + field: 'source.ip', + operator: 'included', + type: 'list', + list: { + id: 'ci-badguys.txt', + type: 'ip', + }, + }, + ]; + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ + ruleParams: sampleParams, + gap: null, + previousStartedAt: new Date(), + listClient, + exceptionsList: [exceptionItem], + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + inputIndexPattern, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + actions: [], + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + refresh: false, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + buildRuleMessage, + }); + expect(success).toEqual(true); + expect(mockService.callCluster).toHaveBeenCalledTimes(1); + expect(createdSignalsCount).toEqual(0); // should not create any signals because all events were in the allowlist + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); + // I don't like testing log statements since logs change but this is the best + // way I can think of to ensure this section is getting hit with this test case. + expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[7][0]).toContain( + 'sortIds was empty on searchResult' + ); + }); + + test('should return success when no sortId present but search results are in the allowlist', async () => { + const sampleParams = sampleRuleAlertParams(30); + mockService.callCluster + .mockResolvedValueOnce(repeatedSearchResultsWithNoSortId(4, 4, someGuids.slice(0, 3))) + .mockResolvedValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + { + create: { + status: 201, + }, + }, + { + create: { + status: 201, + }, + }, + { + create: { + status: 201, + }, + }, + { + create: { + status: 201, + }, + }, + ], + }); + + const exceptionItem = getExceptionListItemSchemaMock(); + exceptionItem.entries = [ + { + field: 'source.ip', + operator: 'included', + type: 'list', + list: { + id: 'ci-badguys.txt', + type: 'ip', + }, + }, + ]; + const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ + ruleParams: sampleParams, + gap: null, + previousStartedAt: new Date(), + listClient, + exceptionsList: [exceptionItem], + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + inputIndexPattern, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + actions: [], + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + refresh: false, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + buildRuleMessage, + }); + expect(success).toEqual(true); + expect(mockService.callCluster).toHaveBeenCalledTimes(2); + expect(createdSignalsCount).toEqual(4); // should not create any signals because all events were in the allowlist + expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000')); + // I don't like testing log statements since logs change but this is the best + // way I can think of to ensure this section is getting hit with this test case. + expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[12][0]).toContain( + 'sortIds was empty on filteredEvents' + ); + }); + test('should return success when no exceptions list provided', async () => { const sampleParams = sampleRuleAlertParams(30); mockService.callCluster diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index 2a0e39cbbf237..e90e5996877f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -90,23 +90,12 @@ export const searchAfterAndBulkCreate = async ({ createdSignalsCount: 0, }; - let sortId; // tells us where to start our next search_after query - let searchResultSize = 0; + // sortId tells us where to start our next consecutive search_after query + let sortId; - /* - The purpose of `maxResults` is to ensure we do not perform - extra search_after's. This will be reset on each - iteration, although it really only matters for the first - iteration of the loop. - e.g. if maxSignals = 100 but our search result only yields - 27 documents, there is no point in performing another search - since we know there are no more events that match our rule, - and thus, no more signals we could possibly generate. - However, if maxSignals = 500 and our search yields a total - of 3050 results we don't want to make 3050 signals, - we only want 500. So maxResults will help us control how - many times we perform a search_after - */ + // signalsCreatedCount keeps track of how many signals we have created, + // to ensure we don't exceed maxSignals + let signalsCreatedCount = 0; const totalToFromTuples = getSignalTimeTuples({ logger, @@ -118,7 +107,6 @@ export const searchAfterAndBulkCreate = async ({ interval, buildRuleMessage, }); - const useSortIds = totalToFromTuples.length <= 1; logger.debug(buildRuleMessage(`totalToFromTuples: ${totalToFromTuples.length}`)); while (totalToFromTuples.length > 0) { const tuple = totalToFromTuples.pop(); @@ -127,16 +115,18 @@ export const searchAfterAndBulkCreate = async ({ toReturn.success = false; return toReturn; } - searchResultSize = 0; - while (searchResultSize < tuple.maxSignals) { + signalsCreatedCount = 0; + while (signalsCreatedCount < tuple.maxSignals) { try { logger.debug(buildRuleMessage(`sortIds: ${sortId}`)); + + // perform search_after with optionally undefined sortId const { searchResult, searchDuration, }: { searchResult: SignalSearchResponse; searchDuration: string } = await singleSearchAfter( { - searchAfterSortId: useSortIds ? sortId : undefined, + searchAfterSortId: sortId, index: inputIndexPattern, from: tuple.from.toISOString(), to: tuple.to.toISOString(), @@ -149,6 +139,7 @@ export const searchAfterAndBulkCreate = async ({ ); toReturn.searchAfterTimes.push(searchDuration); + // determine if there are any candidate signals to be processed const totalHits = typeof searchResult.hits.total === 'number' ? searchResult.hits.total @@ -157,7 +148,23 @@ export const searchAfterAndBulkCreate = async ({ logger.debug( buildRuleMessage(`searchResult.hit.hits.length: ${searchResult.hits.hits.length}`) ); - if (totalHits === 0) { + + // search results yielded zero hits so exit + // with search_after, these two values can be different when + // searching with the last sortId of a consecutive search_after + // yields zero hits, but there were hits using the previous + // sortIds. + // e.g. totalHits was 156, index 50 of 100 results, do another search-after + // this time with a new sortId, index 22 of the remainding 56, get another sortId + // search with that sortId, total is still 156 but the hits.hits array is empty. + if (totalHits === 0 || searchResult.hits.hits.length === 0) { + logger.debug( + buildRuleMessage( + `${ + totalHits === 0 ? 'totalHits' : 'searchResult.hits.hits.length' + } was 0, exiting and moving on to next tuple` + ) + ); toReturn.success = true; break; } @@ -167,10 +174,10 @@ export const searchAfterAndBulkCreate = async ({ searchResult.hits.hits[searchResult.hits.hits.length - 1]?._source['@timestamp'] ) : null; - searchResultSize += searchResult.hits.hits.length; // filter out the search results that match with the values found in the list. - // the resulting set are valid signals that are not on the allowlist. + // the resulting set are signals to be indexed, given they are not duplicates + // of signals already present in the signals index. const filteredEvents: SignalSearchResponse = listClient != null ? await filterEventsAgainstList({ @@ -181,55 +188,79 @@ export const searchAfterAndBulkCreate = async ({ buildRuleMessage, }) : searchResult; - if (filteredEvents.hits.total === 0 || filteredEvents.hits.hits.length === 0) { - // everything in the events were allowed, so no need to generate signals - toReturn.success = true; - break; - } - const { - bulkCreateDuration: bulkDuration, - createdItemsCount: createdCount, - } = await singleBulkCreate({ - filteredEvents, - ruleParams, - services, - logger, - id, - signalsIndex, - actions, - name, - createdAt, - createdBy, - updatedAt, - updatedBy, - interval, - enabled, - refresh, - tags, - throttle, - }); - logger.debug(buildRuleMessage(`created ${createdCount} signals`)); - toReturn.createdSignalsCount += createdCount; - if (bulkDuration) { - toReturn.bulkCreateTimes.push(bulkDuration); - } + // only bulk create if there are filteredEvents leftover + // if there isn't anything after going through the value list filter + // skip the call to bulk create and proceed to the next search_after, + // if there is a sort id to continue the search_after with. + if (filteredEvents.hits.hits.length !== 0) { + // make sure we are not going to create more signals than maxSignals allows + if (signalsCreatedCount + filteredEvents.hits.hits.length > tuple.maxSignals) { + filteredEvents.hits.hits = filteredEvents.hits.hits.slice( + 0, + tuple.maxSignals - signalsCreatedCount + ); + } + const { + bulkCreateDuration: bulkDuration, + createdItemsCount: createdCount, + } = await singleBulkCreate({ + filteredEvents, + ruleParams, + services, + logger, + id, + signalsIndex, + actions, + name, + createdAt, + createdBy, + updatedAt, + updatedBy, + interval, + enabled, + refresh, + tags, + throttle, + }); + logger.debug(buildRuleMessage(`created ${createdCount} signals`)); + toReturn.createdSignalsCount += createdCount; + signalsCreatedCount += createdCount; + logger.debug(buildRuleMessage(`signalsCreatedCount: ${signalsCreatedCount}`)); + if (bulkDuration) { + toReturn.bulkCreateTimes.push(bulkDuration); + } - logger.debug( - buildRuleMessage(`filteredEvents.hits.hits: ${filteredEvents.hits.hits.length}`) - ); - if (useSortIds && filteredEvents.hits.hits[0].sort == null) { - logger.debug(buildRuleMessage('sortIds was empty on search')); - toReturn.success = true; - break; - } else if ( - useSortIds && - filteredEvents.hits.hits !== null && - filteredEvents.hits.hits[0].sort !== null - ) { - sortId = filteredEvents.hits.hits[0].sort - ? filteredEvents.hits.hits[0].sort[0] - : undefined; + logger.debug( + buildRuleMessage(`filteredEvents.hits.hits: ${filteredEvents.hits.hits.length}`) + ); + + if ( + filteredEvents.hits.hits[0].sort != null && + filteredEvents.hits.hits[0].sort.length !== 0 + ) { + sortId = filteredEvents.hits.hits[0].sort + ? filteredEvents.hits.hits[0].sort[0] + : undefined; + } else { + logger.debug(buildRuleMessage('sortIds was empty on filteredEvents')); + toReturn.success = true; + break; + } + } else { + // we are guaranteed to have searchResult hits at this point + // because we check before if the totalHits or + // searchResult.hits.hits.length is 0 + if ( + searchResult.hits.hits[0].sort != null && + searchResult.hits.hits[0].sort.length !== 0 + ) { + sortId = searchResult.hits.hits[0].sort ? searchResult.hits.hits[0].sort[0] : undefined; + } else { + logger.debug(buildRuleMessage('sortIds was empty on searchResult')); + toReturn.success = true; + break; + } } } catch (exc) { logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`));