diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/index.ts index 2c71afd9e066e..3d3c41aed5a72 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/request/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/index.ts @@ -31,6 +31,7 @@ export * from './read_exception_list_item_schema'; export * from './read_exception_list_schema'; export * from './read_list_item_schema'; export * from './read_list_schema'; +export * from './summary_exception_list_schema'; export * from './update_endpoint_list_item_schema'; export * from './update_exception_list_item_schema'; export * from './update_exception_list_item_validation'; diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.mock.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.mock.ts new file mode 100644 index 0000000000000..384f093d0884a --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.mock.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ID, LIST_ID, NAMESPACE_TYPE } from '../../constants/index.mock'; + +import { SummaryExceptionListSchema } from '.'; + +export const getSummaryExceptionListSchemaMock = (): SummaryExceptionListSchema => ({ + id: ID, + list_id: LIST_ID, + namespace_type: NAMESPACE_TYPE, +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.test.ts new file mode 100644 index 0000000000000..ade015b0d26bf --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.test.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { left } from 'fp-ts/lib/Either'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { getSummaryExceptionListSchemaMock } from './index.mock'; +import { SummaryExceptionListSchema, summaryExceptionListSchema } from '.'; + +describe('summary_exception_list_schema', () => { + test('it should validate a typical exception list request', () => { + const payload = getSummaryExceptionListSchemaMock(); + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "id"', () => { + const payload = getSummaryExceptionListSchemaMock(); + delete payload.id; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "list_id"', () => { + const payload = getSummaryExceptionListSchemaMock(); + delete payload.list_id; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "namespace_type" but default to "single"', () => { + const payload = getSummaryExceptionListSchemaMock(); + delete payload.namespace_type; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(getSummaryExceptionListSchemaMock()); + }); + + test('it should accept an undefined for "id", "list_id", "namespace_type" but default "namespace_type" to "single"', () => { + const payload = getSummaryExceptionListSchemaMock(); + delete payload.id; + delete payload.namespace_type; + delete payload.list_id; + const output = getSummaryExceptionListSchemaMock(); + delete output.id; + delete output.list_id; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(output); + }); + + test('it should accept an undefined for "id", "list_id"', () => { + const payload = getSummaryExceptionListSchemaMock(); + delete payload.id; + delete payload.list_id; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "id", "namespace_type" but default "namespace_type" to "single"', () => { + const payload = getSummaryExceptionListSchemaMock(); + delete payload.id; + delete payload.namespace_type; + const output = getSummaryExceptionListSchemaMock(); + delete output.id; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(output); + }); + + test('it should accept an undefined for "list_id", "namespace_type" but default "namespace_type" to "single"', () => { + const payload = getSummaryExceptionListSchemaMock(); + delete payload.namespace_type; + delete payload.list_id; + const output = getSummaryExceptionListSchemaMock(); + delete output.list_id; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(output); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: SummaryExceptionListSchema & { + extraKey?: string; + } = getSummaryExceptionListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = summaryExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = foldLeftRight(checked); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.ts new file mode 100644 index 0000000000000..990091882df7b --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/request/summary_exception_list_schema/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as t from 'io-ts'; + +import { NamespaceType } from '../../common/default_namespace'; +import { RequiredKeepUndefined } from '../../common/required_keep_undefined'; +import { id } from '../../common/id'; +import { list_id } from '../../common/list_id'; +import { namespace_type } from '../../common/namespace_type'; + +export const summaryExceptionListSchema = t.exact( + t.partial({ + id, + list_id, + namespace_type, // defaults to 'single' if not set during decode + }) +); + +export type SummaryExceptionListSchema = t.OutputOf; + +// This type is used after a decode since some things are defaults after a decode. +export type SummaryExceptionListSchemaDecoded = Omit< + RequiredKeepUndefined>, + 'namespace_type' +> & { + namespace_type: NamespaceType; +}; diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.mock.ts b/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.mock.ts new file mode 100644 index 0000000000000..6b7ce27d07194 --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.mock.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExceptionListSummarySchema } from '.'; + +export const getListSummaryResponseMock = (): ExceptionListSummarySchema => ({ + windows: 0, + linux: 1, + macos: 2, + total: 3, +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.test.ts b/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.test.ts new file mode 100644 index 0000000000000..ea086f427451d --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { getListSummaryResponseMock } from './index.mock'; +import { ExceptionListSummarySchema, exceptionListSummarySchema } from '.'; + +describe('list_summary_schema', () => { + test('it should validate a typical list summary response', () => { + const payload = getListSummaryResponseMock(); + const decoded = exceptionListSummarySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "windows"', () => { + const payload = getListSummaryResponseMock(); + // @ts-expect-error + delete payload.windows; + const decoded = exceptionListSummarySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "windows"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "linux"', () => { + const payload = getListSummaryResponseMock(); + // @ts-expect-error + delete payload.linux; + const decoded = exceptionListSummarySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "linux"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "macos"', () => { + const payload = getListSummaryResponseMock(); + // @ts-expect-error + delete payload.macos; + const decoded = exceptionListSummarySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "macos"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "total"', () => { + const payload = getListSummaryResponseMock(); + // @ts-expect-error + delete payload.total; + const decoded = exceptionListSummarySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "total"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ExceptionListSummarySchema & { + extraKey?: string; + } = getListSummaryResponseMock(); + payload.extraKey = 'some new value'; + const decoded = exceptionListSummarySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.ts new file mode 100644 index 0000000000000..4c0cc8301dbf7 --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-list-types/src/response/exception_list_summary_schema/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; +import * as t from 'io-ts'; + +export const exceptionListSummarySchema = t.exact( + t.type({ + windows: PositiveInteger, + linux: PositiveInteger, + macos: PositiveInteger, + total: PositiveInteger, + }) +); + +export type ExceptionListSummarySchema = t.TypeOf; diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/response/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/response/index.ts index 005e753ccf1b3..dc29bdf16ab48 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/response/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/response/index.ts @@ -16,5 +16,6 @@ export * from './found_list_item_schema'; export * from './found_list_schema'; export * from './list_item_schema'; export * from './list_schema'; +export * from './exception_list_summary_schema'; export * from './list_item_index_exist_schema'; export * from './search_list_item_schema'; diff --git a/x-pack/plugins/lists/server/routes/index.ts b/x-pack/plugins/lists/server/routes/index.ts index aa6749d179971..f8d4deea344b2 100644 --- a/x-pack/plugins/lists/server/routes/index.ts +++ b/x-pack/plugins/lists/server/routes/index.ts @@ -36,6 +36,7 @@ export * from './read_list_index_route'; export * from './read_list_item_route'; export * from './read_list_route'; export * from './read_privileges_route'; +export * from './summary_exception_list_route'; export * from './update_endpoint_list_item_route'; export * from './update_exception_list_item_route'; export * from './update_exception_list_route'; diff --git a/x-pack/plugins/lists/server/routes/init_routes.ts b/x-pack/plugins/lists/server/routes/init_routes.ts index 430dad953a32f..2511596ca8463 100644 --- a/x-pack/plugins/lists/server/routes/init_routes.ts +++ b/x-pack/plugins/lists/server/routes/init_routes.ts @@ -39,6 +39,7 @@ import { readListItemRoute, readListRoute, readPrivilegesRoute, + summaryExceptionListRoute, updateEndpointListItemRoute, updateExceptionListItemRoute, updateExceptionListRoute, @@ -95,4 +96,7 @@ export const initRoutes = (router: ListsPluginRouter, config: ConfigType): void updateEndpointListItemRoute(router); deleteEndpointListItemRoute(router); findEndpointListItemRoute(router); + + // exception list items summary + summaryExceptionListRoute(router); }; diff --git a/x-pack/plugins/lists/server/routes/summary_exception_list_route.ts b/x-pack/plugins/lists/server/routes/summary_exception_list_route.ts new file mode 100644 index 0000000000000..0db189fb70759 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/summary_exception_list_route.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { validate } from '@kbn/securitysolution-io-ts-utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { + SummaryExceptionListSchemaDecoded, + exceptionListSummarySchema, + summaryExceptionListSchema, +} from '@kbn/securitysolution-io-ts-list-types'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; + +import type { ListsPluginRouter } from '../types'; + +import { + buildRouteValidation, + buildSiemResponse, + getErrorMessageExceptionList, + getExceptionListClient, +} from './utils'; + +export const summaryExceptionListRoute = (router: ListsPluginRouter): void => { + router.get( + { + options: { + tags: ['access:lists-summary'], + }, + path: `${EXCEPTION_LIST_URL}/summary`, + validate: { + query: buildRouteValidation< + typeof summaryExceptionListSchema, + SummaryExceptionListSchemaDecoded + >(summaryExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { id, list_id: listId, namespace_type: namespaceType } = request.query; + const exceptionLists = getExceptionListClient(context); + if (id != null || listId != null) { + const exceptionListSummary = await exceptionLists.getExceptionListSummary({ + id, + listId, + namespaceType, + }); + if (exceptionListSummary == null) { + return siemResponse.error({ + body: getErrorMessageExceptionList({ id, listId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionListSummary, exceptionListSummarySchema); + if (errors != null) { + return response.ok({ body: exceptionListSummary }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } else { + return siemResponse.error({ body: 'id or list_id required', statusCode: 400 }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/scripts/summary_exception_list.sh b/x-pack/plugins/lists/server/scripts/summary_exception_list.sh new file mode 100755 index 0000000000000..54daeee7cb387 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/summary_exception_list.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +set -e +./check_env_variables.sh + + +LIST_ID=${1:-endpoint_list} +NAMESPACE_TYPE=${2-agnostic} + +# First, post a exception list and two list items for the example to work +# ./post_exception_list.sh ./exception_lists/new/exception_list_agnostic.json +# ./post_exception_list_item.sh ./exception_lists/new/exception_list_item_agnostic.json + +# Retrieve exception list stats by os +# Example: ./summary_exception_list.sh endpoint_list agnostic +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/summary?list_id=${LIST_ID}&namespace_type=${NAMESPACE_TYPE}" | jq . 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 803cd04c1d1b4..4ccff2dd000b9 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 @@ -9,6 +9,7 @@ import { SavedObjectsClientContract } from 'kibana/server'; import type { ExceptionListItemSchema, ExceptionListSchema, + ExceptionListSummarySchema, FoundExceptionListItemSchema, FoundExceptionListSchema, } from '@kbn/securitysolution-io-ts-list-types'; @@ -31,11 +32,13 @@ import { GetEndpointListItemOptions, GetExceptionListItemOptions, GetExceptionListOptions, + GetExceptionListSummaryOptions, UpdateEndpointListItemOptions, UpdateExceptionListItemOptions, UpdateExceptionListOptions, } from './exception_list_client_types'; import { getExceptionList } from './get_exception_list'; +import { getExceptionListSummary } from './get_exception_list_summary'; import { createExceptionList } from './create_exception_list'; import { getExceptionListItem } from './get_exception_list_item'; import { createExceptionListItem } from './create_exception_list_item'; @@ -72,6 +75,15 @@ export class ExceptionListClient { return getExceptionList({ id, listId, namespaceType, savedObjectsClient }); }; + public getExceptionListSummary = async ({ + listId, + id, + namespaceType, + }: GetExceptionListSummaryOptions): Promise => { + const { savedObjectsClient } = this; + return getExceptionListSummary({ id, listId, namespaceType, savedObjectsClient }); + }; + public getExceptionListItem = async ({ itemId, id, 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 cbbf7f1513444..b734d3a7b1a3b 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 @@ -56,6 +56,12 @@ export interface GetExceptionListOptions { namespaceType: NamespaceType; } +export interface GetExceptionListSummaryOptions { + listId: ListIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; +} + export interface CreateExceptionListOptions { listId: ListId; namespaceType: NamespaceType; diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_summary.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_summary.ts new file mode 100644 index 0000000000000..f5722ea26ccf7 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_summary.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + ExceptionListSummarySchema, + IdOrUndefined, + ListIdOrUndefined, + NamespaceType, +} from '@kbn/securitysolution-io-ts-list-types'; +import { getSavedObjectType } from '@kbn/securitysolution-list-utils'; + +import { + SavedObjectsClientContract, + SavedObjectsErrorHelpers, +} from '../../../../../../src/core/server/'; +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; + +interface GetExceptionListSummaryOptions { + id: IdOrUndefined; + listId: ListIdOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; +} + +interface ByOsAggBucketType { + key: string; + doc_count: number; +} +interface ByOsAggType { + by_os: { + buckets: ByOsAggBucketType[]; + }; +} + +export const getExceptionListSummary = async ({ + id, + listId, + savedObjectsClient, + namespaceType, +}: GetExceptionListSummaryOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + let finalListId: string = listId ?? ''; + + // If id and no listId, get the list by id to use the list_id for the find below + if (listId === null && id != null) { + try { + const savedObject = await savedObjectsClient.get(savedObjectType, id); + finalListId = savedObject.attributes.list_id; + } catch (err) { + if (SavedObjectsErrorHelpers.isNotFoundError(err)) { + return null; + } else { + throw err; + } + } + } + + const savedObject = await savedObjectsClient.find({ + aggs: { + by_os: { + terms: { + field: `${savedObjectType}.attributes.os_types`, + }, + }, + }, + filter: `${savedObjectType}.attributes.list_type: item`, + perPage: 0, + search: finalListId, + searchFields: ['list_id'], + sortField: 'tie_breaker_id', + sortOrder: 'desc', + type: savedObjectType, + }); + + if (!savedObject.aggregations) { + return null; + } + + const summary: ExceptionListSummarySchema = savedObject.aggregations.by_os.buckets.reduce( + (acc, item: ByOsAggBucketType) => ({ + ...acc, + [item.key]: item.doc_count, + total: acc.total + item.doc_count, + }), + { linux: 0, macos: 0, total: 0, windows: 0 } + ); + + return summary; +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/index.ts b/x-pack/plugins/lists/server/services/exception_lists/index.ts index 21041b9d5d9bd..e6a6dd7ef8c3c 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/index.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/index.ts @@ -15,5 +15,6 @@ export * from './find_exception_list_item'; export * from './find_exception_list_items'; export * from './get_exception_list'; export * from './get_exception_list_item'; +export * from './get_exception_list_summary'; export * from './update_exception_list'; export * from './update_exception_list_item'; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index dd0ff540cb4af..c084dd8ca7668 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -1095,3 +1095,13 @@ export interface GetAgentSummaryResponse { versions_count: { [key: string]: number }; }; } + +/** + * REST API response for retrieving exception summary + */ +export interface GetExceptionSummaryResponse { + total: number; + windows: number; + macos: number; + linux: number; +} diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts index 8863acaafbf5a..05729c6d1c14d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts @@ -11,6 +11,7 @@ import type { ExceptionListItemSchema, CreateExceptionListItemSchema, UpdateExceptionListItemSchema, + ExceptionListSummarySchema, } from '@kbn/securitysolution-io-ts-list-types'; import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants'; @@ -102,4 +103,16 @@ export class EventFiltersHttpService implements EventFiltersService { }, }); } + + async getSummary(): Promise { + return (await this.httpWrapper()).get( + `${EXCEPTION_LIST_URL}/summary`, + { + query: { + list_id: ENDPOINT_EVENT_FILTERS_LIST_ID, + namespace_type: 'agnostic', + }, + } + ); + } } diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts index b55a32a937c45..5229e4078eb0d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts @@ -29,6 +29,7 @@ const createEventFiltersServiceMock = (): jest.Mocked => ({ getOne: jest.fn(), updateOne: jest.fn(), deleteOne: jest.fn(), + getSummary: jest.fn(), }); const createStoreSetup = (eventFiltersService: EventFiltersService) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts index be6689b7e5b57..3bcf6a3369302 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts @@ -10,6 +10,7 @@ import type { CreateExceptionListItemSchema, ExceptionListItemSchema, UpdateExceptionListItemSchema, + ExceptionListSummarySchema, } from '@kbn/securitysolution-io-ts-list-types'; import { AsyncResourceState } from '../../state/async_resource_state'; import { Immutable } from '../../../../common/endpoint/types'; @@ -49,6 +50,7 @@ export interface EventFiltersService { getOne(id: string): Promise; updateOne(exception: Immutable): Promise; deleteOne(id: string): Promise; + getSummary(): Promise; } export interface EventFiltersListPageData { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/trusted_app_items_summary.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/exception_items_summary.tsx similarity index 52% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/trusted_app_items_summary.tsx rename to x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/exception_items_summary.tsx index fae65def7e2f6..f42304ffb89ae 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/trusted_app_items_summary.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/exception_items_summary.tsx @@ -6,56 +6,45 @@ */ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import React, { FC, memo, useEffect, useState } from 'react'; -import { CoreStart } from 'kibana/public'; +import React, { FC, memo } from 'react'; import { i18n } from '@kbn/i18n'; -import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; -import { TrustedAppsHttpService } from '../../../../../trusted_apps/service'; -import { GetTrustedAppsSummaryResponse } from '../../../../../../../../common/endpoint/types'; +import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types'; -const SUMMARY_KEYS: Readonly> = [ +const SUMMARY_KEYS: Readonly> = [ 'windows', 'macos', 'linux', 'total', ]; -const SUMMARY_LABELS: Readonly<{ [key in keyof GetTrustedAppsSummaryResponse]: string }> = { +const SUMMARY_LABELS: Readonly<{ [key in keyof GetExceptionSummaryResponse]: string }> = { windows: i18n.translate( - 'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.windows', + 'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.windows', { defaultMessage: 'Windows' } ), linux: i18n.translate( - 'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.linux', + 'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.linux', { defaultMessage: 'Linux' } ), macos: i18n.translate( - 'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.macos', + 'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.macos', { defaultMessage: 'Mac' } ), total: i18n.translate( - 'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.total', + 'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.total', { defaultMessage: 'Total' } ), }; const CSS_BOLD: Readonly = { fontWeight: 'bold' }; -export const TrustedAppItemsSummary = memo(() => { - const { - services: { http }, - } = useKibana(); - const [stats, setStats] = useState(); - const [trustedAppsApi] = useState(() => new TrustedAppsHttpService(http)); - - useEffect(() => { - trustedAppsApi.getTrustedAppsSummary().then((response) => { - setStats(response); - }); - }, [trustedAppsApi]); +interface ExceptionItemsSummaryProps { + stats: GetExceptionSummaryResponse | undefined; +} +export const ExceptionItemsSummary = memo(({ stats }) => { return ( - + {SUMMARY_KEYS.map((stat) => { return ( @@ -73,18 +62,13 @@ export const TrustedAppItemsSummary = memo(() => { ); }); -TrustedAppItemsSummary.displayName = 'TrustedAppItemsSummary'; +ExceptionItemsSummary.displayName = 'ExceptionItemsSummary'; const SummaryStat: FC<{ value: number; color?: EuiBadgeProps['color'] }> = memo( ({ children, value, color, ...commonProps }) => { return ( - + {children} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx new file mode 100644 index 0000000000000..6f368a89eb5f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useMemo, useState, useEffect } from 'react'; +import { ApplicationStart, CoreStart } from 'kibana/public'; +import { EuiPanel, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + PackageCustomExtensionComponentProps, + pagePathGetters, +} from '../../../../../../../../../fleet/public'; +import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; +import { getEventFiltersListPath } from '../../../../../../common/routing'; +import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types'; +import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common'; +import { MANAGEMENT_APP_ID } from '../../../../../../common/constants'; +import { useToasts } from '../../../../../../../common/lib/kibana'; +import { LinkWithIcon } from './link_with_icon'; +import { ExceptionItemsSummary } from './exception_items_summary'; +import { EventFiltersHttpService } from '../../../../../event_filters/service'; +import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components'; + +export const FleetEventFiltersCard = memo(({ pkgkey }) => { + const { + services: { + application: { getUrlForApp }, + http, + }, + } = useKibana(); + const toasts = useToasts(); + const [stats, setStats] = useState(); + const eventFiltersListUrlPath = getEventFiltersListPath(); + const eventFiltersApi = useMemo(() => new EventFiltersHttpService(http), [http]); + + useEffect(() => { + const fetchStats = async () => { + try { + const summary = await eventFiltersApi.getSummary(); + setStats(summary); + } catch (error) { + toasts.addDanger( + i18n.translate( + 'xpack.securitySolution.endpoint.fleetCustomExtension.eventFiltersSummaryError', + { + defaultMessage: 'There was an error trying to fetch event filters stats: "{error}"', + values: { error }, + } + ) + ); + } + }; + fetchStats(); + }, [eventFiltersApi, toasts]); + + const eventFiltersRouteState = useMemo(() => { + const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`; + return { + backButtonLabel: i18n.translate( + 'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel', + { defaultMessage: 'Back to Endpoint Integration' } + ), + onBackButtonNavigateTo: [ + FLEET_PLUGIN_ID, + { + path: fleetPackageCustomUrlPath, + }, + ], + backButtonUrl: getUrlForApp(FLEET_PLUGIN_ID, { + path: fleetPackageCustomUrlPath, + }), + }; + }, [getUrlForApp, pkgkey]); + + return ( + + + + +

+ +

+
+
+ + + + + <> + + + + + +
+
+ ); +}); + +FleetEventFiltersCard.displayName = 'FleetEventFiltersCard'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx index fe6f82e632f73..ec1479643999a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; -import { ApplicationStart } from 'kibana/public'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; +import React, { memo, useMemo, useState, useEffect } from 'react'; +import { ApplicationStart, CoreStart } from 'kibana/public'; +import { EuiPanel, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -16,19 +16,48 @@ import { } from '../../../../../../../../../fleet/public'; import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; import { getTrustedAppsListPath } from '../../../../../../common/routing'; -import { TrustedAppsListPageRouteState } from '../../../../../../../../common/endpoint/types'; +import { + TrustedAppsListPageRouteState, + GetExceptionSummaryResponse, +} from '../../../../../../../../common/endpoint/types'; import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common'; import { MANAGEMENT_APP_ID } from '../../../../../../common/constants'; +import { useToasts } from '../../../../../../../common/lib/kibana'; import { LinkWithIcon } from './link_with_icon'; -import { TrustedAppItemsSummary } from './trusted_app_items_summary'; +import { ExceptionItemsSummary } from './exception_items_summary'; +import { TrustedAppsHttpService } from '../../../../../trusted_apps/service'; +import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components'; export const FleetTrustedAppsCard = memo(({ pkgkey }) => { const { services: { application: { getUrlForApp }, + http, }, - } = useKibana<{ application: ApplicationStart }>(); + } = useKibana(); + const toasts = useToasts(); + const [stats, setStats] = useState(); + const trustedAppsApi = useMemo(() => new TrustedAppsHttpService(http), [http]); + useEffect(() => { + const fetchStats = async () => { + try { + const response = await trustedAppsApi.getTrustedAppsSummary(); + setStats(response); + } catch (error) { + toasts.addDanger( + i18n.translate( + 'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsSummaryError', + { + defaultMessage: 'There was an error trying to fetch trusted apps stats: "{error}"', + values: { error }, + } + ) + ); + } + }; + fetchStats(); + }, [toasts, trustedAppsApi]); const trustedAppsListUrlPath = getTrustedAppsListPath(); const trustedAppRouteState = useMemo(() => { @@ -52,8 +81,8 @@ export const FleetTrustedAppsCard = memo(( return ( - - + +

(( />

-
- - - - - + + + + + + <> (( defaultMessage="Manage trusted applications" /> - - -
+ + +
); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/styled_components.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/styled_components.tsx new file mode 100644 index 0000000000000..8791f7fa87283 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/styled_components.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import styled from 'styled-components'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export const StyledEuiFlexGridGroup = styled(EuiFlexGroup)` + display: grid; + grid-template-columns: 25% 45% 30%; + grid-template-areas: 'title summary link'; +`; + +export const StyledEuiFlexGridItem = styled(EuiFlexItem)<{ + gridArea: string; + alignItems?: string; +}>` + grid-area: ${({ gridArea }) => gridArea}; + align-items: ${({ alignItems }) => alignItems ?? 'center'}; + margin: 0px; + padding: 12px; +`; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx index c127a60d84ccf..094f1131d7034 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx @@ -5,15 +5,19 @@ * 2.0. */ +import { EuiSpacer } from '@elastic/eui'; import React, { memo } from 'react'; import { PackageCustomExtensionComponentProps } from '../../../../../../../../fleet/public'; import { FleetTrustedAppsCard } from './components/fleet_trusted_apps_card'; +import { FleetEventFiltersCard } from './components/fleet_event_filters_card'; export const EndpointPackageCustomExtension = memo( (props) => { return (
+ +
); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d829c8eb22a98..72b6667476dc6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20463,10 +20463,6 @@ "xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失敗} other {不明}}", "xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel": "エンドポイント統合に戻る", "xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel": "信頼できるアプリケーションを管理", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.linux": "Linux", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.macos": "Mac", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.total": "合計", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.windows": "Windows", "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsLabel": "信頼できるアプリケーション", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration": "推奨のデフォルト値で統合が保存されます。後からこれを変更するには、エージェントポリシー内で Endpoint Security 統合を編集します。", "xpack.securitySolution.endpoint.ingestToastMessage": "Fleetが設定中に失敗しました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a7cd8b5fe8d51..66c86eae7d7c6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20763,10 +20763,6 @@ "xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失败} other {未知}}", "xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel": "返回至终端集成", "xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel": "管理受信任的应用程序", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.linux": "Linux", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.macos": "Mac", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.total": "合计", - "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.windows": "Windows", "xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsLabel": "受信任的应用程序", "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration": "我们将使用建议的默认值保存您的集成。稍后,您可以通过在代理策略中编辑 Endpoint Security 集成对其进行更改。", "xpack.securitySolution.endpoint.ingestToastMessage": "Fleet 在设置期间失败。", diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts index 61ca693936643..89a1183da6790 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts @@ -35,5 +35,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./find_exception_lists')); loadTestFile(require.resolve('./find_exception_list_items')); loadTestFile(require.resolve('./read_list_privileges')); + loadTestFile(require.resolve('./summary_exception_lists')); }); }; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/summary_exception_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/summary_exception_lists.ts new file mode 100644 index 0000000000000..13ba7da4c7aaa --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/summary_exception_lists.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { ExceptionListSummarySchema } from '@kbn/securitysolution-io-ts-list-types'; +import { EXCEPTION_LIST_URL, EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { LIST_ID } from '../../../../plugins/lists/common/constants.mock'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { getCreateExceptionListMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_item_schema.mock'; +import { createListsIndex, deleteListsIndex, deleteAllExceptions } from '../../utils'; + +interface SummaryResponseType { + body: ExceptionListSummarySchema; +} +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('summary_exception_lists', () => { + describe('summary exception lists', () => { + beforeEach(async () => { + await createListsIndex(supertest); + }); + afterEach(async () => { + await deleteListsIndex(supertest); + await deleteAllExceptions(es); + }); + + it('should give a validation error if the list_id and the id are not supplied', async () => { + const { body } = await supertest + .get(`${EXCEPTION_LIST_URL}/summary`) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(400); + + expect(body).to.eql({ + message: 'id or list_id required', + status_code: 400, + }); + }); + + it('should return init summary when there are no items created', async () => { + const { body }: SummaryResponseType = await supertest + .get(`${EXCEPTION_LIST_URL}/summary?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + const expected: ExceptionListSummarySchema = { + linux: 0, + macos: 0, + total: 0, + windows: 0, + }; + expect(body).to.eql(expected); + }); + + it('should return right summary when there are items created', async () => { + await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + const item = getCreateExceptionListItemMinimalSchemaMock(); + + for (const os of ['windows', 'linux', 'macos']) { + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ ...item, os_types: [os], item_id: `${item.item_id}-${os}` }) + .expect(200); + } + + const { body }: SummaryResponseType = await supertest + .get(`${EXCEPTION_LIST_URL}/summary?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + const expected: ExceptionListSummarySchema = { + linux: 1, + macos: 1, + total: 3, + windows: 1, + }; + expect(body).to.eql(expected); + }); + }); + }); +};