From 9d1522eab7282db627d00b8df8985c21051ad4ab Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Fri, 5 Apr 2024 16:20:44 +0530 Subject: [PATCH 01/15] Add thehive case connector --- .../common/thehive/constants.ts | 34 ++ .../stack_connectors/common/thehive/schema.ts | 179 +++++++ .../stack_connectors/common/thehive/types.ts | 38 ++ .../public/connector_types/index.ts | 2 + .../thehive/connector.test.tsx | 138 +++++ .../connector_types/thehive/connector.tsx | 41 ++ .../connector_types/thehive/constants.ts | 110 ++++ .../public/connector_types/thehive/index.ts | 8 + .../public/connector_types/thehive/logo.tsx | 23 + .../connector_types/thehive/params.test.tsx | 186 +++++++ .../public/connector_types/thehive/params.tsx | 483 ++++++++++++++++++ .../connector_types/thehive/thehive.test.tsx | 137 +++++ .../connector_types/thehive/thehive.tsx | 79 +++ .../connector_types/thehive/translations.ts | 134 +++++ .../public/connector_types/thehive/types.ts | 19 + .../server/connector_types/index.ts | 2 + .../connector_types/thehive/index.test.ts | 33 ++ .../server/connector_types/thehive/index.ts | 39 ++ .../connector_types/thehive/thehive.test.ts | 421 +++++++++++++++ .../server/connector_types/thehive/thehive.ts | 145 ++++++ .../stack_connectors/server/plugin.test.ts | 9 +- 21 files changed, 2259 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/stack_connectors/common/thehive/constants.ts create mode 100644 x-pack/plugins/stack_connectors/common/thehive/schema.ts create mode 100644 x-pack/plugins/stack_connectors/common/thehive/types.ts create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/index.ts create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts diff --git a/x-pack/plugins/stack_connectors/common/thehive/constants.ts b/x-pack/plugins/stack_connectors/common/thehive/constants.ts new file mode 100644 index 00000000000000..62afe84b801f4f --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/thehive/constants.ts @@ -0,0 +1,34 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const THEHIVE_TITLE = i18n.translate( + 'xpack.stackConnectors.components.thehive.connectorTypeTitle', + { + defaultMessage: 'TheHive', + } +); +export const THEHIVE_CONNECTOR_ID = '.thehive'; + +export enum SUB_ACTION { + PUSH_TO_SERVICE = 'pushToService', + CREATE_ALERT = 'createAlert', +} +export enum TheHiveSeverity { + LOW = 1, + MEDIUM = 2, + HIGH = 3, + CRITICAL = 4, +} +export enum TheHiveTLP { + CLEAR = 0, + GREEN = 1, + AMBER = 2, + AMBER_STRICT = 3, + RED = 4, +} diff --git a/x-pack/plugins/stack_connectors/common/thehive/schema.ts b/x-pack/plugins/stack_connectors/common/thehive/schema.ts new file mode 100644 index 00000000000000..cea8629bd4c8d8 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/thehive/schema.ts @@ -0,0 +1,179 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { TheHiveSeverity, TheHiveTLP, SUB_ACTION } from './constants'; + +export const TheHiveConfigSchema = schema.object({ + url: schema.string(), + organisation: schema.nullable(schema.string()), +}); + +export const TheHiveSecretsSchema = schema.object({ + api_key: schema.string() +}); + +export const ExecutorSubActionPushParamsSchema = schema.object({ + incident: schema.object({ + title: schema.string(), + description: schema.string(), + externalId: schema.nullable(schema.string()), + severity: schema.nullable(schema.number({ defaultValue: TheHiveSeverity.MEDIUM })), + tlp: schema.nullable(schema.number({ defaultValue: TheHiveTLP.AMBER })), + tags: schema.nullable(schema.arrayOf(schema.string())), + }), + comments: schema.nullable( + schema.arrayOf( + schema.object({ + comment: schema.string(), + commentId: schema.string(), + }) + ) + ), +}); + +export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ + externalId: schema.string(), +}); + +export const ExecutorSubActionCreateAlertParamsSchema = schema.object({ + title: schema.string(), + description: schema.string(), + type: schema.string(), + source: schema.string(), + sourceRef: schema.string(), + severity: schema.nullable(schema.number({ defaultValue: TheHiveSeverity.MEDIUM })), + tlp: schema.nullable(schema.number({ defaultValue: TheHiveTLP.AMBER })), + tags: schema.nullable(schema.arrayOf(schema.string())), +}); + +export const ExecutorParamsSchema = schema.oneOf([ + schema.object({ + subAction: schema.literal(SUB_ACTION.PUSH_TO_SERVICE), + subActionParams: ExecutorSubActionPushParamsSchema, + }), + schema.object({ + subAction: schema.literal(SUB_ACTION.CREATE_ALERT), + subActionParams: ExecutorSubActionCreateAlertParamsSchema, + }), +]); + + +export const TheHiveIncidentResponseSchema = schema.object( + { + _id: schema.string(), + _type: schema.string(), + _createdBy: schema.string(), + _updatedBy: schema.nullable(schema.string()), + _createdAt: schema.number(), + _updatedAt: schema.nullable(schema.number()), + number: schema.number(), + title: schema.string(), + description: schema.string(), + severity: schema.number(), + severityLabel: schema.string(), + startDate: schema.number(), + endDate: schema.nullable(schema.number()), + tags: schema.nullable(schema.arrayOf(schema.string())), + flag: schema.boolean(), + tlp: schema.number(), + tlpLabel: schema.string(), + pap: schema.number(), + papLabel: schema.string(), + status: schema.string(), + stage: schema.string(), + summary: schema.nullable(schema.string()), + impactStatus: schema.nullable(schema.string()), + assignee: schema.nullable(schema.string()), + customFields: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), + userPermissions: schema.nullable(schema.arrayOf(schema.string())), + extraData: schema.object({}, { unknowns: 'allow' }), + newDate: schema.number(), + inProgressDate: schema.nullable(schema.number()), + closedDate: schema.nullable(schema.number()), + alertDate: schema.nullable(schema.number()), + alertNewDate: schema.nullable(schema.number()), + alertInProgressDate: schema.nullable(schema.number()), + alertImportedDate: schema.nullable(schema.number()), + timeToDetect: schema.number(), + timeToTriage: schema.nullable(schema.number()), + timeToQualify: schema.nullable(schema.number()), + timeToAcknowledge: schema.nullable(schema.number()), + timeToResolve: schema.nullable(schema.number()), + handlingDuration: schema.nullable(schema.number()), + }, + { unknowns: 'ignore' } +); + +export const TheHiveUpdateIncidentResponseSchema = schema.any(); + +export const TheHiveAddCommentResponseSchema = schema.object( + { + _id: schema.string(), + _type: schema.string(), + createdBy: schema.string(), + createdAt: schema.number(), + updatedAt: schema.nullable(schema.number()), + updatedBy: schema.nullable(schema.string()), + message: schema.string(), + isEdited: schema.boolean(), + extraData: schema.object({}, { unknowns: 'allow' }), + }, + { unknowns: 'ignore' } +); + +export const TheHiveCreateAlertResponseSchema = schema.object( + { + _id: schema.string(), + _type: schema.string(), + _createdBy: schema.string(), + _updatedBy: schema.nullable(schema.string()), + _createdAt: schema.number(), + _updatedAt: schema.nullable(schema.number()), + type: schema.string(), + source: schema.string(), + sourceRef: schema.string(), + externalLink: schema.nullable(schema.string()), + title: schema.string(), + description: schema.string(), + severity: schema.number(), + severityLabel: schema.string(), + date: schema.number(), + tags: schema.nullable(schema.arrayOf(schema.string())), + tlp: schema.number(), + tlpLabel: schema.string(), + pap: schema.number(), + papLabel: schema.string(), + follow: schema.nullable(schema.boolean()), + customFields: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), + caseTemplate: schema.nullable(schema.string()), + observableCount: schema.number(), + caseId: schema.nullable(schema.string()), + status: schema.string(), + stage: schema.string(), + assignee: schema.nullable(schema.string()), + summary: schema.nullable(schema.string()), + extraData: schema.object({}, { unknowns: 'allow' }), + newDate: schema.number(), + inProgressDate: schema.nullable(schema.number()), + closedDate: schema.nullable(schema.number()), + importedDate: schema.nullable(schema.number()), + timeToDetect: schema.number(), + timeToTriage: schema.nullable(schema.number()), + timeToQualify: schema.nullable(schema.number()), + timeToAcknowledge: schema.nullable(schema.number()), + }, + { unknowns: 'ignore' } +); + +export const TheHiveFailureResponseSchema = schema.object( + { + type: schema.number(), + message: schema.string(), + }, + { unknowns: 'allow' } +); diff --git a/x-pack/plugins/stack_connectors/common/thehive/types.ts b/x-pack/plugins/stack_connectors/common/thehive/types.ts new file mode 100644 index 00000000000000..7d13af9d94a4f1 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/thehive/types.ts @@ -0,0 +1,38 @@ +/* + * 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 { TypeOf } from '@kbn/config-schema'; +import { + TheHiveConfigSchema, + TheHiveSecretsSchema, + ExecutorParamsSchema, + ExecutorSubActionPushParamsSchema, + ExecutorSubActionCreateAlertParamsSchema, + TheHiveFailureResponseSchema, +} from './schema'; + +export type TheHiveConfig = TypeOf; +export type TheHiveSecrets = TypeOf; + +export type ExecutorParams = TypeOf; +export type ExecutorSubActionPushParams = TypeOf; +export type ExecutorSubActionCreateAlertParams = TypeOf; + +export type TheHiveFailureResponse = TypeOf; + +export interface ExternalServiceIncidentResponse { + id: string; + title: string; + url: string; + pushedDate: string; +} + +export interface ExternalServiceCommentResponse { + commentId: string; + pushedDate: string; + externalCommentId?: string; +} diff --git a/x-pack/plugins/stack_connectors/public/connector_types/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/index.ts index a2297dac9d6bff..422c6bcaf43b35 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/index.ts @@ -31,6 +31,7 @@ import { getXmattersConnectorType } from './xmatters'; import { getD3SecurityConnectorType } from './d3security'; import { ExperimentalFeaturesService } from '../common/experimental_features_service'; import { getSentinelOneConnectorType } from './sentinelone'; +import { getTheHiveConnectorType } from './thehive'; export interface RegistrationServices { validateEmailAddresses: ( @@ -68,6 +69,7 @@ export function registerConnectorTypes({ connectorTypeRegistry.register(getTorqConnectorType()); connectorTypeRegistry.register(getTinesConnectorType()); connectorTypeRegistry.register(getD3SecurityConnectorType()); + connectorTypeRegistry.register(getTheHiveConnectorType()); if (ExperimentalFeaturesService.get().sentinelOneConnectorOn) { connectorTypeRegistry.register(getSentinelOneConnectorType()); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx new file mode 100644 index 00000000000000..de5a40a2e78a42 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx @@ -0,0 +1,138 @@ +/* + * 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 from 'react'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import TheHiveConnectorFields from './connector'; +import { ConnectorFormTestProvider } from '../lib/test_utils'; +import { act, render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); + +describe('TheHiveActionConnectorFields renders', () => { + test('TheHive connector fields are rendered', () => { + const actionConnector = { + actionTypeId: '.thehive', + name: 'thehive', + config: { + url: 'https://test.com', + }, + secrets: { + api_key: 'api_key', + }, + isDeprecated: false, + }; + + const wrapper = mountWithIntl( + + { }} + /> + + ); + + expect(wrapper.find('[data-test-subj="config.url-input"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="secrets.api_key-input"]').length > 0).toBeTruthy(); + }); + + describe('Validation', () => { + const onSubmit = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const tests: Array<[string, string]> = [ + ['config.url-input', 'not-valid'], + ['secrets.api_key-input', ''], + ]; + + it('connector validation succeeds when connector config is valid', async () => { + const actionConnector = { + actionTypeId: '.thehive', + name: 'thehive', + config: { + url: 'https://test.com', + }, + secrets: { + api_key: 'api_key', + }, + isDeprecated: false, + }; + + const { getByTestId } = render( + + { }} + /> + + ); + + await act(async () => { + userEvent.click(getByTestId('form-test-provide-submit')); + }); + + waitFor(() => { + expect(onSubmit).toBeCalledWith({ + data: { + actionTypeId: '.thehive', + name: 'thehive', + config: { + url: 'https://test.com', + }, + secrets: { + api_key: 'api_key', + }, + isDeprecated: false, + }, + isValid: true, + }); + }); + }); + + it.each(tests)('validates correctly %p', async (field, value) => { + const actionConnector = { + actionTypeId: '.thehive', + name: 'thehive', + config: { + url: 'https://test.com', + }, + secrets: { + api_key: 'api_key', + }, + isDeprecated: false, + }; + + const res = render( + + { }} + /> + + ); + + await act(async () => { + await userEvent.type(res.getByTestId(field), `{selectall}{backspace}${value}`, { + delay: 10, + }); + }); + + await act(async () => { + userEvent.click(res.getByTestId('form-test-provide-submit')); + }); + + expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx new file mode 100644 index 00000000000000..064108864281c7 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx @@ -0,0 +1,41 @@ +/* + * 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 from 'react'; +import { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + ConfigFieldSchema, + SimpleConnectorForm, + SecretsFieldSchema, +} from '@kbn/triggers-actions-ui-plugin/public'; + +import { URL_LABEL, API_KEY_LABEL, ORGANISATION_LABEL } from './translations'; + +const configFormSchema: ConfigFieldSchema[] = [ + { id: 'organisation', label: ORGANISATION_LABEL, isRequired: false, helpText: `By default, the user's default organization will be considered.` }, + { id: 'url', label: URL_LABEL, isUrlField: true }, +]; + +const secretsFormSchema: SecretsFieldSchema[] = [ + { id: 'api_key', label: API_KEY_LABEL, isPasswordField: true }, +]; + +const TheHiveConnectorFields: React.FC = ({ readOnly, isEdit }) => { + return ( + <> + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { TheHiveConnectorFields as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts new file mode 100644 index 00000000000000..d6d38f47a1497a --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts @@ -0,0 +1,110 @@ +import { i18n } from '@kbn/i18n'; +import { TheHiveSeverity, TheHiveTLP, SUB_ACTION } from '../../../common/thehive/constants'; + +export const eventActionOptions = [ + { + value: SUB_ACTION.PUSH_TO_SERVICE, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectCreateCaseOptionLabel', + { + defaultMessage: 'Create Case', + } + ), + }, + { + value: SUB_ACTION.CREATE_ALERT, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectCreateAlertOptionLabel', + { + defaultMessage: 'Create Alert', + } + ), + } +]; + +export const severityOptions = [ + { + value: TheHiveSeverity.LOW, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityLowOptionLabel', + { + defaultMessage: 'LOW', + } + ) + }, + { + value: TheHiveSeverity.MEDIUM, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityMediumOptionLabel', + { + defaultMessage: 'MEDIUM', + } + ), + }, + { + value: TheHiveSeverity.HIGH, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityHighOptionLabel', + { + defaultMessage: 'HIGH', + } + ), + }, + { + value: TheHiveSeverity.CRITICAL, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityCriticalOptionLabel', + { + defaultMessage: 'CRITICAL', + } + ), + }, +]; + +export const tlpOptions = [ + { + value: TheHiveTLP.CLEAR, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpClearOptionLabel', + { + defaultMessage: 'CLEAR', + } + ), + }, + { + value: TheHiveTLP.GREEN, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpGreenOptionLabel', + { + defaultMessage: 'GREEN', + } + ), + }, + { + value: TheHiveTLP.AMBER, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpAmberOptionLabel', + { + defaultMessage: 'AMBER', + } + ), + }, + { + value: TheHiveTLP.AMBER_STRICT, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpAmberStrictOptionLabel', + { + defaultMessage: 'AMBER+STRICT', + } + ), + }, + { + value: TheHiveTLP.RED, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpRedOptionLabel', + { + defaultMessage: 'RED', + } + ), + } +]; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/index.ts new file mode 100644 index 00000000000000..9a98fce201e583 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { getConnectorType as getTheHiveConnectorType } from './thehive'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx new file mode 100644 index 00000000000000..ca7a41bb61e374 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx @@ -0,0 +1,23 @@ +/* + * 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 from 'react'; +import { LogoProps } from '../types'; + +const Logo = (props: LogoProps) => ( + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export { Logo as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx new file mode 100644 index 00000000000000..592e12ed92f9d6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx @@ -0,0 +1,186 @@ +/* + * 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 from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; +import TheHiveParamsFields from './params'; +import { SUB_ACTION } from '../../../common/thehive/constants'; +import { ExecutorParams, ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; + +describe('SubAction: pushToService', () => { + describe('TheHiveParamsFields renders', () => { + const subActionParams: ExecutorSubActionPushParams = { + incident: { + title: 'title {test}', + description: 'test description', + tlp: 2, + severity: 2, + tags: ["test1"], + externalId: null + }, + comments: [], + }; + const actionParams: ExecutorParams = { + subAction: SUB_ACTION.PUSH_TO_SERVICE, + subActionParams + }; + const connector: ActionConnector = { + secrets: {}, + config: {}, + id: 'test', + actionTypeId: '.test', + name: 'Test', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false as const, + }; + + const editAction = jest.fn(); + const defaultProps = { + actionConnector: connector, + actionParams, + editAction, + errors: { 'subActionParams.incident.title': [] }, + index: 0, + messageVariables: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('all Params fields is rendered', () => { + const wrapper = mountWithIntl(); + + expect(wrapper.find('[data-test-subj="eventActionSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="title-row"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="description-row"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="eventTags"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="eventSeveritySelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="eventTlpSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="comment"]').length > 0).toBeTruthy(); + + expect(wrapper.find('[data-test-subj="eventActionSelect"]').first().prop('value')).toStrictEqual(SUB_ACTION.PUSH_TO_SERVICE); + expect(wrapper.find('[data-test-subj="eventSeveritySelect"]').first().prop('value')).toStrictEqual(2); + expect(wrapper.find('[data-test-subj="eventTlpSelect"]').first().prop('value')).toStrictEqual(2); + + }); + + it('calls editAction function with the correct arguments', () => { + const { getByTestId } = render(); + const eventActionEl = getByTestId('eventActionSelect'); + + fireEvent.change(eventActionEl, { target: { value: SUB_ACTION.PUSH_TO_SERVICE } }); + expect(editAction).toHaveBeenCalledWith( + 'subActionParams', + { + incident: { + tlp: 2, + severity: 2, + tags: [], + }, + comments: [], + }, + 0 + ); + }); + + it('handles the case when subAction is undefined', () => { + const newProps = { + ...defaultProps, + actionParams: { + ...actionParams, + subAction: undefined, + }, + }; + render( + + ); + expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.PUSH_TO_SERVICE, 0); + }); + }) +}); + +describe('SubAction: createAlert', () => { + describe('TheHiveParamsFields renders', () => { + const subActionParams: ExecutorSubActionCreateAlertParams = { + title: 'title {test}', + description: 'description test', + tlp: 2, + severity: 2, + tags: ["test1"], + source: 'source test', + type: 'sourceType test', + sourceRef: 'sourceRef test' + } + const actionParams: ExecutorParams = { + subAction: SUB_ACTION.CREATE_ALERT, + subActionParams + }; + const connector: ActionConnector = { + secrets: {}, + config: {}, + id: 'test', + actionTypeId: '.test', + name: 'Test', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false as const, + }; + + const editAction = jest.fn(); + const defaultProps = { + actionConnector: connector, + actionParams, + editAction, + errors: { 'subActionParams.incident.title': [] }, + index: 0, + messageVariables: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('all Params fields is rendered', () => { + const wrapper = mountWithIntl(); + + expect(wrapper.find('[data-test-subj="eventActionSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-title-row"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-description-row"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-eventTags"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-eventSeveritySelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-eventTlpSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-type-row"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-source-row"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alert-sourceRef-row"]').length > 0).toBeTruthy(); + + expect(wrapper.find('[data-test-subj="eventActionSelect"]').first().prop('value')).toStrictEqual(SUB_ACTION.CREATE_ALERT); + expect(wrapper.find('[data-test-subj="alert-eventSeveritySelect"]').first().prop('value')).toStrictEqual(2); + expect(wrapper.find('[data-test-subj="alert-eventTlpSelect"]').first().prop('value')).toStrictEqual(2); + }); + + it('calls editAction function with the correct arguments', () => { + const { getByTestId } = render(); + const eventActionEl = getByTestId('eventActionSelect'); + + fireEvent.change(eventActionEl, { target: { value: SUB_ACTION.CREATE_ALERT } }); + expect(editAction).toHaveBeenCalledWith( + 'subActionParams', + { + tlp: 2, + severity: 2, + tags: [], + }, + 0 + ); + }); + + }) +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx new file mode 100644 index 00000000000000..4e9b92295ae256 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx @@ -0,0 +1,483 @@ +/* + * 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, { useState, useCallback, useEffect, useMemo, useRef } from 'react'; +import { ActionParamsProps, TextAreaWithMessageVariables, TextFieldWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { SUB_ACTION } from '../../../common/thehive/constants'; +import { eventActionOptions, severityOptions, tlpOptions } from './constants'; +import { ExecutorParams, ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; +import * as translations from './translations'; +import { + EuiFormRow, + EuiSelect, + EuiText, + EuiComboBox, +} from '@elastic/eui'; + +const TheHiveParamsFields: React.FunctionComponent> = ({ + actionConnector, + actionParams, + editAction, + index, + errors, + messageVariables +}) => { + const [eventAction, setEventAction] = useState(actionParams.subAction ?? SUB_ACTION.PUSH_TO_SERVICE); + const [selectedOptions, setSelected] = useState>([]); + const [isInvalid, setInvalid] = useState(false); + const [severity, setSeverity] = useState(severityOptions[1].value); + const [tlp, setTlp] = useState(tlpOptions[2].value); + const actionConnectorRef = useRef(actionConnector?.id ?? ''); + + useEffect(() => { + if (actionConnector != null && actionConnectorRef.current !== actionConnector.id) { + actionConnectorRef.current = actionConnector.id; + editAction( + 'subActionParams', + { + incident: { + tlp: 2, + severity: 2, + tags: [] + }, + comments: [], + }, + index + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [actionConnector]); + + useEffect(() => { + if (!actionParams.subAction) { + editAction('subAction', SUB_ACTION.PUSH_TO_SERVICE, index); + } + if (!actionParams.subActionParams) { + editAction( + 'subActionParams', + { + incident: { + tlp: 2, + severity: 2, + tags: [] + }, + comments: [], + }, + index + ); + } + + }, [actionParams]); + + useEffect(() => { + editAction('subAction', eventAction, index); + setSelected([]); + setInvalid(false); + setSeverity(severityOptions[1].value); + setTlp(tlpOptions[2].value); + }, [eventAction]); + + const setEventActionType = (eventActionType: SUB_ACTION) => { + const subActionParams = + eventActionType === SUB_ACTION.CREATE_ALERT + ? { + tlp: 2, + severity: 2, + tags: [], + } + : { + incident: { + tlp: 2, + severity: 2, + tags: [], + }, + comments: [], + }; + + setEventAction(eventActionType); + editAction('subActionParams', subActionParams, index); + }; + + const { incident, comments } = useMemo( + () => + actionParams.subActionParams as ExecutorSubActionPushParams ?? + ({ + incident: { + tlp: 2, + severity: 2, + tags: [] + }, + comments: [], + } as unknown as ExecutorSubActionPushParams), + [actionParams.subActionParams] + ); + + const alert = useMemo( + () => + actionParams.subActionParams as ExecutorSubActionCreateAlertParams ?? + ({ + tlp: 2, + severity: 2, + tags: [], + } as unknown as ExecutorSubActionCreateAlertParams), + [actionParams.subActionParams] + ); + + const editSubActionProperty = useCallback( + (key: string, value: any) => { + const newProps = + key !== 'comments' + ? { + incident: { ...incident, [key]: value }, + comments, + } + : { incident, [key]: value }; + editAction('subActionParams', newProps, index); + }, + [comments, editAction, incident, index] + ); + + const editComment = useCallback( + (key, value) => { + editSubActionProperty(key, [{ commentId: '1', comment: value }]); + }, + [editSubActionProperty] + ); + + const onCreateOption = (searchValue: string) => { + setSelected([...selectedOptions, { label: searchValue }]); + + if (eventAction === SUB_ACTION.PUSH_TO_SERVICE) { + editSubActionProperty('tags', [...incident.tags ?? [], searchValue]) + } else { + editAction('subActionParams', { ...alert, tags: [...alert.tags ?? [], searchValue] }, index); + } + }; + + const onSearchChange = (searchValue: string) => { + if (!searchValue) { + setInvalid(false); + return; + } + }; + + const onChange = (selectedOptions: Array<{ label: string }>) => { + setSelected(selectedOptions); + if (eventAction === SUB_ACTION.PUSH_TO_SERVICE) { + editSubActionProperty('tags', selectedOptions.map((option) => option.label)); + } else { + editAction('subActionParams', { ...alert, tags: selectedOptions.map((option) => option.label) }, index); + } + } + + return ( + <> + + setEventActionType(e.target.value as SUB_ACTION)} + /> + + {eventAction === SUB_ACTION.PUSH_TO_SERVICE ? + <> + 0 && + incident.title !== undefined + } + label={translations.TITLE_LABEL} + labelAppend={ + + Required + + } + > + + + 0 && + incident.description !== undefined + } + label={translations.DESCRIPTION_LABEL} + labelAppend={ + + Required + + } + > + + + + { + editSubActionProperty('severity', parseInt(e.target.value)) + setSeverity(parseInt(e.target.value)); + }} + /> + + + { + editSubActionProperty('tlp', parseInt(e.target.value)); + setTlp(parseInt(e.target.value)); + }} + /> + + + + + 0 ? comments[0].comment : undefined} + label={translations.COMMENTS_LABEL} + /> + + : + <> + 0 && + alert.title !== undefined + } + label={translations.TITLE_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'title'} + inputTargetValue={alert.title ?? undefined} + errors={errors['createAlertParam.title'] as string[]} + /> + + 0 && + alert.description !== undefined + } + label={translations.DESCRIPTION_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'description'} + inputTargetValue={alert.description ?? undefined} + errors={errors['createAlertParam.description'] as string[]} + /> + + 0 && + alert.type !== undefined + } + label={translations.TYPE_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'type'} + inputTargetValue={alert.type ?? undefined} + errors={(errors['createAlertParam.type'] ?? []) as string[]} + /> + + 0 && + alert.source !== undefined + } + label={translations.SOURCE_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'source'} + inputTargetValue={alert.source ?? undefined} + errors={(errors['createAlertParam.source'] ?? []) as string[]} + /> + + 0 && + alert.sourceRef !== undefined + } + label={translations.SOURCE_REF_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + messageVariables={messageVariables} + paramsProperty={'sourceRef'} + inputTargetValue={alert.sourceRef ?? undefined} + errors={(errors['createAlertParam.sourceRef'] ?? []) as string[]} + /> + + + { + editAction('subActionParams', { ...alert, severity: parseInt(e.target.value) }, index); + setSeverity(parseInt(e.target.value)); + }} + /> + + + { + editAction('subActionParams', { ...alert, tlp: parseInt(e.target.value) }, index); + setTlp(parseInt(e.target.value)); + }} + /> + + + + + + } + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { TheHiveParamsFields as default }; \ No newline at end of file diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx new file mode 100644 index 00000000000000..999b94ae34f685 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx @@ -0,0 +1,137 @@ +/* + * 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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '..'; +import { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { experimentalFeaturesMock, registrationServicesMock } from '../../mocks'; +import { SUB_ACTION } from '../../../common/thehive/constants'; +import { ExperimentalFeaturesService } from '../../common/experimental_features_service'; + +const CONNECTOR_TYPE_ID = '.thehive'; +let connectorTypeModel: ConnectorTypeModel; +beforeAll(() => { + const connectorTypeRegistry = new TypeRegistry(); + ExperimentalFeaturesService.init({ experimentalFeatures: experimentalFeaturesMock }); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID); + }); +}); + +describe('thehive pushToService action params validation', () => { + test('pushToService action params validation succeeds when action params is valid', async () => { + const actionParams = { + subAction: SUB_ACTION.PUSH_TO_SERVICE, + subActionParams: { + incident: { + title: 'title {test}', + description: 'test description', + } + }, + comments: [] + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'pushToServiceParam.incident.title': [], + 'pushToServiceParam.incident.description': [], + 'createAlertParam.title': [], + 'createAlertParam.description': [], + 'createAlertParam.type': [], + 'createAlertParam.source': [], + 'createAlertParam.sourceRef': [], + }, + }); + }); + + test('pushToService action params validation fails when Required fields is not valid', async () => { + const actionParams = { + subAction: SUB_ACTION.PUSH_TO_SERVICE, + subActionParams: { + incident: { + title: '', + description: '', + } + }, + comments: [] + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'pushToServiceParam.incident.title': ['Title is required.'], + 'pushToServiceParam.incident.description': ['Description is required.'], + 'createAlertParam.title': [], + 'createAlertParam.description': [], + 'createAlertParam.type': [], + 'createAlertParam.source': [], + 'createAlertParam.sourceRef': [], + }, + }); + }); +}); + +describe('thehive createAlert action params validation', () => { + test('createAlert action params validation succeeds when action params is valid', async () => { + const actionParams = { + subAction: SUB_ACTION.CREATE_ALERT, + subActionParams: { + title: 'some title {test}', + description: 'some description {test}', + type: 'type test', + source: 'source test', + sourceRef: 'source reference test', + }, + comments: [] + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'pushToServiceParam.incident.title': [], + 'pushToServiceParam.incident.description': [], + 'createAlertParam.title': [], + 'createAlertParam.description': [], + 'createAlertParam.type': [], + 'createAlertParam.source': [], + 'createAlertParam.sourceRef': [], + }, + }); + }); + + test('params validation fails when Required fields is not valid', async () => { + const actionParams = { + subAction: SUB_ACTION.CREATE_ALERT, + subActionParams: { + title: '', + description: '', + type: '', + source: '', + sourceRef: '', + }, + comments: [] + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'pushToServiceParam.incident.title': [], + 'pushToServiceParam.incident.description': [], + 'createAlertParam.title': ['Title is required.'], + 'createAlertParam.description': ['Description is required.'], + 'createAlertParam.type': ['Type is required.'], + 'createAlertParam.source': ['Source is required.'], + 'createAlertParam.sourceRef': ['Source Reference is required.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx new file mode 100644 index 00000000000000..121e30bf8cf0ae --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx @@ -0,0 +1,79 @@ +/* + * 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 { lazy } from 'react'; +import { i18n } from '@kbn/i18n'; +import { GenericValidationResult } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { TheHiveConnector } from './types'; +import { THEHIVE_CONNECTOR_ID, SUB_ACTION, THEHIVE_TITLE } from '../../../common/thehive/constants'; +import { ExecutorParams, ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; + +export function getConnectorType(): TheHiveConnector { + return { + id: THEHIVE_CONNECTOR_ID, + iconClass: lazy(() => import('./logo')), + selectMessage: i18n.translate('xpack.stackConnectors.components.thehive.descriptionText', { + defaultMessage: 'Create Case and Alert in Hive', + }), + actionTypeTitle: THEHIVE_TITLE, + validateParams: async ( + actionParams: ExecutorParams + ): Promise> => { + const translations = await import('./translations'); + + const errors = { + 'pushToServiceParam.incident.title': new Array(), + 'pushToServiceParam.incident.description': new Array(), + 'createAlertParam.title': new Array(), + 'createAlertParam.description': new Array(), + 'createAlertParam.type': new Array(), + 'createAlertParam.source': new Array(), + 'createAlertParam.sourceRef': new Array(), + }; + + const validationResult = { + errors, + }; + + const { subAction, subActionParams } = actionParams; + if (subAction === SUB_ACTION.PUSH_TO_SERVICE) { + const pushToServiceParam = subActionParams as ExecutorSubActionPushParams; + if (pushToServiceParam && pushToServiceParam.incident) { + if (!pushToServiceParam.incident.title?.length) { + errors['pushToServiceParam.incident.title'].push(translations.TITLE_REQUIRED); + } + if (!pushToServiceParam.incident.description?.length) { + errors['pushToServiceParam.incident.description'].push(translations.DESCRIPTION_REQUIRED); + } + } + } else if (subAction === SUB_ACTION.CREATE_ALERT) { + const createAlertParam = subActionParams as ExecutorSubActionCreateAlertParams; + if (createAlertParam) { + if (!createAlertParam.title?.length) { + errors['createAlertParam.title'].push(translations.TITLE_REQUIRED); + } + if (!createAlertParam.description?.length) { + errors['createAlertParam.description'].push(translations.DESCRIPTION_REQUIRED); + } + if (!createAlertParam.type?.length) { + errors['createAlertParam.type'].push(translations.TYPE_REQUIRED); + } + if (!createAlertParam.source?.length) { + errors['createAlertParam.source'].push(translations.SOURCE_REQUIRED); + } + if (!createAlertParam.sourceRef?.length) { + errors['createAlertParam.sourceRef'].push(translations.SOURCE_REF_REQUIRED); + } + } + } + + return validationResult; + }, + actionConnectorFields: lazy(() => import('./connector')), + actionParamsFields: lazy(() => import('./params')), + }; +} diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts new file mode 100644 index 00000000000000..66c3059ed7b95c --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts @@ -0,0 +1,134 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const URL_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.urlFieldLabel', + { + defaultMessage: 'URL', + } +); + +export const ORGANISATION_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.organisationFieldLabel', + { + defaultMessage: 'Organisation', + } +); + +export const API_KEY_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.apiKeyFieldLabel', + { + defaultMessage: 'API Key', + } +); + +export const EVENT_ACTION_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.eventActionSelectFieldLabel', + { + defaultMessage: 'Event Action', + } +); + +export const TITLE_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.titleFieldLabel', + { + defaultMessage: 'Title', + } +); + +export const DESCRIPTION_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.descriptionFieldLabel', + { + defaultMessage: 'Description', + } +); + +export const TLP_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.tlpSelectFieldLabel', + { + defaultMessage: 'TLP', + } +); + +export const SEVERITY_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.severitySelectFieldLabel', + { + defaultMessage: 'Severity', + } +); + +export const TAGS_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.TagsMultiSelectFieldLabel', + { + defaultMessage: 'Tags', + } +); + +export const COMMENTS_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.commentsTextAreaFieldLabel', + { + defaultMessage: 'Additional comments', + } +); + +export const TYPE_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.typeFieldLabel', + { + defaultMessage: 'Type', + } +); + +export const SOURCE_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.sourceFieldLabel', + { + defaultMessage: 'Source', + } +); + +export const SOURCE_REF_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.sourceRefFieldLabel', + { + defaultMessage: 'Source Reference', + } +); + +export const TITLE_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.thehive.requiredTitleText', + { + defaultMessage: 'Title is required.', + } +); + +export const DESCRIPTION_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.thehive.requiredDescriptionText', + { + defaultMessage: 'Description is required.', + } +) + +export const TYPE_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.thehive.requiredTypeText', + { + defaultMessage: 'Type is required.', + } +); + +export const SOURCE_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.thehive.requiredSourceText', + { + defaultMessage: 'Source is required.', + } +); + +export const SOURCE_REF_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.thehive.requiredSourceRefText', + { + defaultMessage: 'Source Reference is required.', + } +); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts new file mode 100644 index 00000000000000..098d22a9eb85c6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts @@ -0,0 +1,19 @@ +/* + * 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 { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TheHiveConfig, + TheHiveSecrets, + ExecutorParams, +} from '../../../common/thehive/types'; + +export type TheHiveConnector = ConnectorTypeModel< + TheHiveConfig, + TheHiveSecrets, + ExecutorParams +>; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/index.ts index 540f2746516b59..b059879fc14f3a 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/index.ts @@ -28,6 +28,7 @@ import { getConnectorType as getWebhookConnectorType } from './webhook'; import { getConnectorType as getXmattersConnectorType } from './xmatters'; import { getConnectorType as getTeamsConnectorType } from './teams'; import { getConnectorType as getD3SecurityConnectorType } from './d3security'; +import { getConnectorType as getTheHiveConnectorType } from './thehive'; import { getOpsgenieConnectorType } from './opsgenie'; import type { ActionParamsType as ServiceNowITSMActionParams } from './servicenow_itsm'; import type { ActionParamsType as ServiceNowSIRActionParams } from './servicenow_sir'; @@ -108,6 +109,7 @@ export function registerConnectorTypes({ actions.registerSubActionConnectorType(getOpenAIConnectorType()); actions.registerSubActionConnectorType(getBedrockConnectorType()); actions.registerSubActionConnectorType(getD3SecurityConnectorType()); + actions.registerSubActionConnectorType(getTheHiveConnectorType()); if (experimentalFeatures.sentinelOneConnectorOn) { actions.registerSubActionConnectorType(getSentinelOneConnectorType()); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.ts new file mode 100644 index 00000000000000..bb0138790a723e --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; +import { TheHiveConnectorType, getConnectorType } from '.'; + +jest.mock('axios'); +jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { + const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); + return { + ...originalUtils, + request: jest.fn(), + patch: jest.fn(), + }; +}); + +axios.create = jest.fn(() => axios); + +let connectorType: TheHiveConnectorType; + +describe('TheHive Connector', () => { + beforeEach(() => { + connectorType = getConnectorType(); + }); + test('exposes the connector as `TheHive` with id `.thehive`', () => { + expect(connectorType.id).toEqual('.thehive'); + expect(connectorType.name).toEqual('TheHive'); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts new file mode 100644 index 00000000000000..2992ea3ecc4229 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts @@ -0,0 +1,39 @@ +/* + * 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 { + SubActionConnectorType, + ValidatorType, +} from '@kbn/actions-plugin/server/sub_action_framework/types'; +import { + AlertingConnectorFeatureId, + SecurityConnectorFeatureId, + CasesConnectorFeatureId, + UptimeConnectorFeatureId, +} from '@kbn/actions-plugin/common/types'; +import { urlAllowListValidator } from '@kbn/actions-plugin/server'; +import { TheHiveConnector } from './thehive'; +import { TheHiveConfigSchema, TheHiveSecretsSchema } from '../../../common/thehive/schema'; +import { THEHIVE_CONNECTOR_ID, THEHIVE_TITLE } from '../../../common/thehive/constants'; +import { TheHiveConfig, TheHiveSecrets } from '../../../common/thehive/types'; + +export type TheHiveConnectorType = SubActionConnectorType; + +export function getConnectorType(): TheHiveConnectorType { + return { + id: THEHIVE_CONNECTOR_ID, + minimumLicenseRequired: 'gold', + name: THEHIVE_TITLE, + getService: (params) => new TheHiveConnector(params), + supportedFeatureIds: [AlertingConnectorFeatureId, SecurityConnectorFeatureId, CasesConnectorFeatureId, UptimeConnectorFeatureId], + schema: { + config: TheHiveConfigSchema, + secrets: TheHiveSecretsSchema, + }, + validators: [{ type: ValidatorType.CONFIG, validator: urlAllowListValidator('url') }], + }; +} diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts new file mode 100644 index 00000000000000..840f7d0b6343bb --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts @@ -0,0 +1,421 @@ +/* + * 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 { TheHiveConnector } from './thehive'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { THEHIVE_CONNECTOR_ID } from '../../../common/thehive/constants'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { + TheHiveIncidentResponseSchema, + TheHiveUpdateIncidentResponseSchema, + TheHiveAddCommentResponseSchema, + TheHiveCreateAlertResponseSchema, +} from '../../../common/thehive/schema'; +import type { ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; + +const mockTime = new Date('2024-04-03T09:10:30.000'); + +describe('TheHiveConnector', () => { + + const connector = new TheHiveConnector({ + configurationUtilities: actionsConfigMock.create(), + connector: { id: '1', type: THEHIVE_CONNECTOR_ID }, + config: { url: 'https://example.com', organisation: null }, + secrets: { api_key: 'test123' }, + logger: loggingSystemMock.createLogger(), + services: actionsMock.createServices(), + }); + + let mockRequest: jest.Mock; + let mockError: jest.Mock; + + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(mockTime); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(() => { + mockError = jest.fn().mockImplementation(() => { + throw new Error('API Error'); + }); + jest.clearAllMocks(); + }); + + describe('createIncident', () => { + const mockResponse = { + data: { + _id: '~172064', + _type: 'Case', + _createdBy: 'user1@thehive.local', + _createdAt: 1712128153041, + number: 67, + title: 'title', + description: 'description', + severity: 1, + severityLabel: 'LOW', + startDate: 1712128153029, + tags: [ + 'tag1', + 'tag2' + ], + flag: false, + tlp: 2, + tlpLabel: 'AMBER', + pap: 2, + papLabel: 'AMBER', + status: 'New', + stage: 'New', + assignee: 'user1@thehive.local', + customFields: [], + userPermissions: [ + 'manageCase/create', + 'manageAlert/update', + 'manageProcedure', + 'managePage', + 'manageObservable', + 'manageCase/delete', + 'manageAlert/create', + 'manageCaseReport', + 'manageAlert/delete', + 'accessTheHiveFS', + 'manageKnowledgeBase', + 'manageAction', + 'manageShare', + 'manageAnalyse', + 'manageFunction/invoke', + 'manageTask', + 'manageCase/merge', + 'manageCustomEvent', + 'manageAlert/import', + 'manageCase/changeOwnership', + 'manageComment', + 'manageAlert/reopen', + 'manageCase/update', + 'manageCase/reopen' + ], + extraData: {}, + newDate: 1712128153029, + timeToDetect: 0 + } + }; + + beforeEach(() => { + mockRequest = jest.fn().mockResolvedValue(mockResponse); + // @ts-ignore + connector.request = mockRequest; + jest.clearAllMocks(); + }); + + const incident: ExecutorSubActionPushParams["incident"] = { + title: 'title', + description: 'description', + severity: 1, + tlp: 2, + tags: ['tag1', 'tag2'], + externalId: null, + }; + + it('TheHive API call is successful with correct parameters', async () => { + const response = await connector.createIncident(incident); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: 'https://example.com/api/v1/case', + method: 'post', + responseSchema: TheHiveIncidentResponseSchema, + data: incident, + headers: { + Authorization: 'Bearer test123', + 'X-Organisation': null, + }, + }); + expect(response).toEqual({ + id: '~172064', + url: 'https://example.com/cases/~172064/details', + title: 'title', + pushedDate: '2024-04-03T07:09:13.041Z' + }); + }); + + it('errors during API calls are properly handled', async () => { + // @ts-ignore + connector.request = mockError; + + await expect(connector.createIncident(incident)).rejects.toThrow('API Error'); + }); + }); + + describe('updateIncident', () => { + const mockResponse = { + data: null + }; + + beforeEach(() => { + mockRequest = jest.fn().mockResolvedValue(mockResponse); + // @ts-ignore + connector.request = mockRequest; + jest.clearAllMocks(); + }); + + const incident: ExecutorSubActionPushParams["incident"] = { + title: 'new title', + description: 'new description', + severity: 3, + tlp: 1, + tags: ['tag3'], + externalId: null, + }; + + it('TheHive API call is successful with correct parameters', async () => { + const response = await connector.updateIncident({ incidentId: '~172064', incident }); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: 'https://example.com/api/v1/case/~172064', + method: 'patch', + responseSchema: TheHiveUpdateIncidentResponseSchema, + data: incident, + headers: { + Authorization: 'Bearer test123', + 'X-Organisation': null, + }, + }); + expect(response).toEqual({ + id: '~172064', + url: 'https://example.com/cases/~172064/details', + title: 'new title', + pushedDate: mockTime.toISOString() + }); + }); + + it('errors during API calls are properly handled', async () => { + // @ts-ignore + connector.request = mockError; + + await expect(connector.updateIncident({ incidentId: '~172064', incident })).rejects.toThrow('API Error'); + }); + }) + + describe('addComment', () => { + const mockResponse = { + data: { + _id: '~41156688', + _type: 'Comment', + createdBy: 'user1@thehive.local', + createdAt: 1712158280100, + message: 'test comment', + isEdited: false, + extraData: {} + } + }; + + beforeEach(() => { + mockRequest = jest.fn().mockResolvedValue(mockResponse); + // @ts-ignore + connector.request = mockRequest; + jest.clearAllMocks(); + }); + + it('TheHive API call is successful with correct parameters', async () => { + const response = await connector.addComment({ incidentId: '~172064', comment: 'test comment' }); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: 'https://example.com/api/v1/case/~172064/comment', + method: 'post', + responseSchema: TheHiveAddCommentResponseSchema, + data: { message: 'test comment' }, + headers: { + Authorization: 'Bearer test123', + 'X-Organisation': null, + }, + }); + expect(response).toEqual({ + commentId: '~41156688', + externalCommentId: '~41156688', + pushedDate: '2024-04-03T15:31:20.100Z' + }); + }); + + it('errors during API calls are properly handled', async () => { + // @ts-ignore + connector.request = mockError; + + await expect(connector.addComment({ incidentId: '~172064', comment: 'test comment' })).rejects.toThrow('API Error'); + }); + }); + + describe('getIncident', () => { + const mockResponse = { + data: { + _id: '~172064', + _type: 'Case', + _createdBy: 'user1@thehive.local', + _createdAt: 1712128153041, + number: 67, + title: 'title', + description: 'description', + severity: 1, + severityLabel: 'LOW', + startDate: 1712128153029, + tags: [ + 'tag1', + 'tag2' + ], + flag: false, + tlp: 2, + tlpLabel: 'AMBER', + pap: 2, + papLabel: 'AMBER', + status: 'New', + stage: 'New', + assignee: 'user1@thehive.local', + customFields: [], + userPermissions: [ + 'manageCase/create', + 'manageAlert/update', + 'manageProcedure', + 'managePage', + 'manageObservable', + 'manageCase/delete', + 'manageAlert/create', + 'manageCaseReport', + 'manageAlert/delete', + 'accessTheHiveFS', + 'manageKnowledgeBase', + 'manageAction', + 'manageShare', + 'manageAnalyse', + 'manageFunction/invoke', + 'manageTask', + 'manageCase/merge', + 'manageCustomEvent', + 'manageAlert/import', + 'manageCase/changeOwnership', + 'manageComment', + 'manageAlert/reopen', + 'manageCase/update', + 'manageCase/reopen' + ], + extraData: {}, + newDate: 1712128153029, + timeToDetect: 0 + } + }; + + beforeEach(() => { + mockRequest = jest.fn().mockResolvedValue(mockResponse); + // @ts-ignore + connector.request = mockRequest; + jest.clearAllMocks(); + }); + + it('TheHive API call is successful with correct parameters', async () => { + const response = await connector.getIncident({ id: '~172064' }); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: 'https://example.com/api/v1/case/~172064', + responseSchema: TheHiveIncidentResponseSchema, + headers: { + Authorization: 'Bearer test123', + 'X-Organisation': null, + }, + }); + expect(response).toEqual({ + id: '~172064', + url: 'https://example.com/cases/~172064/details', + title: 'title', + pushedDate: mockTime.toISOString() + }); + }); + + it('errors during API calls are properly handled', async () => { + // @ts-ignore + connector.request = mockError; + + await expect(connector.getIncident({ id: '~172064' })).rejects.toThrow('API Error'); + }); + }); + + describe('createAlert', () => { + const mockResponse = { + data: { + _id: '~41128088', + _type: 'Alert', + _createdBy: 'user1@thehive.local', + _createdAt: 1712161128982, + type: 'alert type', + source: 'alert source', + sourceRef: 'test123', + title: 'title', + description: 'description', + severity: 1, + severityLabel: 'LOW', + date: 1712161128964, + tags: [ + 'tag1', + 'tag2' + ], + tlp: 2, + tlpLabel: 'AMBER', + pap: 2, + papLabel: 'AMBER', + follow: true, + customFields: [], + observableCount: 0, + status: 'New', + stage: 'New', + extraData: {}, + newDate: 1712161128967, + timeToDetect: 0 + } + }; + + beforeEach(() => { + mockRequest = jest.fn().mockResolvedValue(mockResponse); + // @ts-ignore + connector.request = mockRequest; + jest.clearAllMocks(); + }); + + const alert: ExecutorSubActionCreateAlertParams = { + title: 'title', + description: 'description', + type: 'alert type', + source: 'alert source', + sourceRef: 'test123', + severity: 1, + tlp: 2, + tags: ['tag1', 'tag2'], + }; + + it('TheHive API call is successful with correct parameters', async () => { + await connector.createAlert(alert); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: 'https://example.com/api/v1/alert', + method: 'post', + responseSchema: TheHiveCreateAlertResponseSchema, + data: alert, + headers: { + Authorization: 'Bearer test123', + 'X-Organisation': null, + }, + }); + }); + + it('errors during API calls are properly handled', async () => { + // @ts-ignore + connector.request = mockError; + + await expect(connector.createAlert(alert)).rejects.toThrow('API Error'); + }); + }); + +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts new file mode 100644 index 00000000000000..3da5fdbc5bd5dd --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts @@ -0,0 +1,145 @@ +/* + * 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 { ServiceParams, CaseConnector } from '@kbn/actions-plugin/server'; +import type { AxiosError } from 'axios'; +import { SUB_ACTION } from '../../../common/thehive/constants'; +import { + TheHiveIncidentResponseSchema, + TheHiveUpdateIncidentResponseSchema, + TheHiveAddCommentResponseSchema, + TheHiveCreateAlertResponseSchema, + ExecutorSubActionCreateAlertParamsSchema, +} from '../../../common/thehive/schema'; +import type { + TheHiveConfig, + TheHiveSecrets, + ExecutorSubActionPushParams, + ExecutorSubActionCreateAlertParams, + TheHiveFailureResponse, + ExternalServiceIncidentResponse, + ExternalServiceCommentResponse, +} from '../../../common/thehive/types'; + +export const API_VERSION = 'v1'; + +export class TheHiveConnector extends CaseConnector { + private url: string; + private api_key: string; + private organisation: string | null; + private urlWithoutTrailingSlash: string; + + constructor(params: ServiceParams) { + super(params); + + this.registerSubAction({ + name: SUB_ACTION.CREATE_ALERT, + method: 'createAlert', + schema: ExecutorSubActionCreateAlertParamsSchema, + }); + + this.url = this.config.url; + this.organisation = this.config.organisation; + this.api_key = this.secrets.api_key; + this.urlWithoutTrailingSlash = this.url.endsWith('/') ? this.url.slice(0, -1) : this.url; + } + + protected getResponseErrorMessage(error: AxiosError): string { + if (!error.response?.status) { + return 'Unknown API Error'; + } + return `API Error: ${error.response?.data?.type} - ${error.response?.data?.message}`; + } + + public async createIncident(incident: ExecutorSubActionPushParams["incident"]): Promise { + const res = await this.request({ + method: 'post', + url: `${this.url}/api/${API_VERSION}/case`, + data: incident, + headers: { Authorization: `Bearer ${this.api_key}`, 'X-Organisation': this.organisation }, + responseSchema: TheHiveIncidentResponseSchema, + }); + + return { + id: res.data._id, + title: res.data.title, + url: `${this.urlWithoutTrailingSlash}/cases/${res.data._id}/details`, + pushedDate: new Date(res.data._createdAt).toISOString(), + }; + } + + public async addComment({ + incidentId, + comment, + }: { + incidentId: string; + comment: string; + }): Promise { + const res = await this.request({ + method: 'post', + url: `${this.url}/api/${API_VERSION}/case/${incidentId}/comment`, + data: { message: comment }, + headers: { Authorization: `Bearer ${this.api_key}`, 'X-Organisation': this.organisation }, + responseSchema: TheHiveAddCommentResponseSchema, + }); + + return { + commentId: res.data._id, + externalCommentId: res.data._id, + pushedDate: new Date(res.data.createdAt).toISOString(), + }; + } + + public async updateIncident({ + incidentId, + incident, + }: { + incidentId: string; + incident: ExecutorSubActionPushParams["incident"]; + }): Promise { + await this.request({ + method: 'patch', + url: `${this.url}/api/${API_VERSION}/case/${incidentId}`, + data: incident, + headers: { Authorization: `Bearer ${this.api_key}`, 'X-Organisation': this.organisation }, + responseSchema: TheHiveUpdateIncidentResponseSchema, + }); + + return { + id: incidentId, + title: incident.title, + url: `${this.urlWithoutTrailingSlash}/cases/${incidentId}/details`, + pushedDate: new Date().toISOString(), + }; + } + + public async getIncident({ id }: { id: string }): Promise { + const res = await this.request({ + url: `${this.url}/api/${API_VERSION}/case/${id}`, + headers: { Authorization: `Bearer ${this.api_key}`, 'X-Organisation': this.organisation }, + responseSchema: TheHiveIncidentResponseSchema, + }); + + return { + id: res.data._id, + title: res.data.title, + url: `${this.urlWithoutTrailingSlash}/cases/${res.data._id}/details`, + pushedDate: new Date().toISOString(), + }; + } + + public async createAlert(alert: ExecutorSubActionCreateAlertParams) { + await this.request({ + method: 'post', + url: `${this.url}/api/${API_VERSION}/alert`, + data: alert, + headers: { Authorization: `Bearer ${this.api_key}`, 'X-Organisation': this.organisation }, + responseSchema: TheHiveCreateAlertResponseSchema, + }); + } + +} \ No newline at end of file diff --git a/x-pack/plugins/stack_connectors/server/plugin.test.ts b/x-pack/plugins/stack_connectors/server/plugin.test.ts index f0f86aac7f5b7d..7efbccfd73bcd9 100644 --- a/x-pack/plugins/stack_connectors/server/plugin.test.ts +++ b/x-pack/plugins/stack_connectors/server/plugin.test.ts @@ -138,7 +138,7 @@ describe('Stack Connectors Plugin', () => { name: 'Torq', }) ); - expect(actionsSetup.registerSubActionConnectorType).toHaveBeenCalledTimes(6); + expect(actionsSetup.registerSubActionConnectorType).toHaveBeenCalledTimes(7); expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith( 1, expect.objectContaining({ @@ -174,6 +174,13 @@ describe('Stack Connectors Plugin', () => { name: 'D3 Security', }) ); + expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith( + 6, + expect.objectContaining({ + id: '.thehive', + name: 'TheHive', + }) + ); }); }); }); From b5d816491d5283d08b52430107dcc00d4cb65842 Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Wed, 17 Apr 2024 22:23:08 +0530 Subject: [PATCH 02/15] resolve review comments --- .../stack_connectors/common/thehive/schema.ts | 9 +- .../thehive/connector.test.tsx | 61 +-- .../connector_types/thehive/connector.tsx | 6 +- .../connector_types/thehive/params.test.tsx | 251 ++++------- .../public/connector_types/thehive/params.tsx | 400 ++---------------- .../thehive/params_alert.test.tsx | 70 +++ .../connector_types/thehive/params_alert.tsx | 230 ++++++++++ .../thehive/params_case.test.tsx | 69 +++ .../connector_types/thehive/params_case.tsx | 182 ++++++++ .../connector_types/thehive/thehive.tsx | 2 +- .../connector_types/thehive/translations.ts | 7 + .../connector_types/thehive/index.test.ts | 13 - .../server/connector_types/thehive/index.ts | 3 +- .../connector_types/thehive/thehive.test.ts | 2 +- .../server/connector_types/thehive/thehive.ts | 18 +- 15 files changed, 708 insertions(+), 615 deletions(-) create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx diff --git a/x-pack/plugins/stack_connectors/common/thehive/schema.ts b/x-pack/plugins/stack_connectors/common/thehive/schema.ts index cea8629bd4c8d8..63225536c83c69 100644 --- a/x-pack/plugins/stack_connectors/common/thehive/schema.ts +++ b/x-pack/plugins/stack_connectors/common/thehive/schema.ts @@ -14,7 +14,7 @@ export const TheHiveConfigSchema = schema.object({ }); export const TheHiveSecretsSchema = schema.object({ - api_key: schema.string() + apiKey: schema.string() }); export const ExecutorSubActionPushParamsSchema = schema.object({ @@ -62,7 +62,6 @@ export const ExecutorParamsSchema = schema.oneOf([ }), ]); - export const TheHiveIncidentResponseSchema = schema.object( { _id: schema.string(), @@ -89,7 +88,7 @@ export const TheHiveIncidentResponseSchema = schema.object( summary: schema.nullable(schema.string()), impactStatus: schema.nullable(schema.string()), assignee: schema.nullable(schema.string()), - customFields: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), + customFields: schema.nullable(schema.arrayOf(schema.recordOf(schema.string(), schema.any()))), userPermissions: schema.nullable(schema.arrayOf(schema.string())), extraData: schema.object({}, { unknowns: 'allow' }), newDate: schema.number(), @@ -109,7 +108,7 @@ export const TheHiveIncidentResponseSchema = schema.object( { unknowns: 'ignore' } ); -export const TheHiveUpdateIncidentResponseSchema = schema.any(); +export const TheHiveUpdateIncidentResponseSchema = schema.never(); export const TheHiveAddCommentResponseSchema = schema.object( { @@ -175,5 +174,5 @@ export const TheHiveFailureResponseSchema = schema.object( type: schema.number(), message: schema.string(), }, - { unknowns: 'allow' } + { unknowns: 'ignore' } ); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx index de5a40a2e78a42..e6ec40d4b13d58 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx @@ -6,7 +6,6 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; import TheHiveConnectorFields from './connector'; import { ConnectorFormTestProvider } from '../lib/test_utils'; import { act, render, waitFor } from '@testing-library/react'; @@ -15,20 +14,20 @@ import userEvent from '@testing-library/user-event'; jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); describe('TheHiveActionConnectorFields renders', () => { - test('TheHive connector fields are rendered', () => { - const actionConnector = { - actionTypeId: '.thehive', - name: 'thehive', - config: { - url: 'https://test.com', - }, - secrets: { - api_key: 'api_key', - }, - isDeprecated: false, - }; - - const wrapper = mountWithIntl( + const actionConnector = { + actionTypeId: '.thehive', + name: 'thehive', + config: { + url: 'https://test.com', + }, + secrets: { + apiKey: 'apiKey', + }, + isDeprecated: false, + }; + + it('TheHive connector fields are rendered', () => { + const { getByTestId } = render( { ); - expect(wrapper.find('[data-test-subj="config.url-input"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="secrets.api_key-input"]').length > 0).toBeTruthy(); + expect(getByTestId('config.url-input')).toBeInTheDocument(); + expect(getByTestId('secrets.apiKey-input')).toBeInTheDocument(); }); describe('Validation', () => { @@ -51,22 +50,10 @@ describe('TheHiveActionConnectorFields renders', () => { const tests: Array<[string, string]> = [ ['config.url-input', 'not-valid'], - ['secrets.api_key-input', ''], + ['secrets.apiKey-input', ''], ]; it('connector validation succeeds when connector config is valid', async () => { - const actionConnector = { - actionTypeId: '.thehive', - name: 'thehive', - config: { - url: 'https://test.com', - }, - secrets: { - api_key: 'api_key', - }, - isDeprecated: false, - }; - const { getByTestId } = render( { url: 'https://test.com', }, secrets: { - api_key: 'api_key', + apiKey: 'apiKey', }, isDeprecated: false, }, @@ -100,18 +87,6 @@ describe('TheHiveActionConnectorFields renders', () => { }); it.each(tests)('validates correctly %p', async (field, value) => { - const actionConnector = { - actionTypeId: '.thehive', - name: 'thehive', - config: { - url: 'https://test.com', - }, - secrets: { - api_key: 'api_key', - }, - isDeprecated: false, - }; - const res = render( = ({ readOnly, isEdit }) => { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx index 592e12ed92f9d6..d1720371294630 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx @@ -10,177 +10,98 @@ import { fireEvent, render } from '@testing-library/react'; import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import TheHiveParamsFields from './params'; import { SUB_ACTION } from '../../../common/thehive/constants'; -import { ExecutorParams, ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { ExecutorParams, ExecutorSubActionPushParams } from '../../../common/thehive/types'; -describe('SubAction: pushToService', () => { - describe('TheHiveParamsFields renders', () => { - const subActionParams: ExecutorSubActionPushParams = { - incident: { - title: 'title {test}', - description: 'test description', - tlp: 2, - severity: 2, - tags: ["test1"], - externalId: null - }, - comments: [], - }; - const actionParams: ExecutorParams = { - subAction: SUB_ACTION.PUSH_TO_SERVICE, - subActionParams - }; - const connector: ActionConnector = { - secrets: {}, - config: {}, - id: 'test', - actionTypeId: '.test', - name: 'Test', - isPreconfigured: false, - isDeprecated: false, - isSystemAction: false as const, - }; - - const editAction = jest.fn(); - const defaultProps = { - actionConnector: connector, - actionParams, - editAction, - errors: { 'subActionParams.incident.title': [] }, - index: 0, - messageVariables: [], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('all Params fields is rendered', () => { - const wrapper = mountWithIntl(); - - expect(wrapper.find('[data-test-subj="eventActionSelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="title-row"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="description-row"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="eventTags"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="eventSeveritySelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="eventTlpSelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="comment"]').length > 0).toBeTruthy(); - - expect(wrapper.find('[data-test-subj="eventActionSelect"]').first().prop('value')).toStrictEqual(SUB_ACTION.PUSH_TO_SERVICE); - expect(wrapper.find('[data-test-subj="eventSeveritySelect"]').first().prop('value')).toStrictEqual(2); - expect(wrapper.find('[data-test-subj="eventTlpSelect"]').first().prop('value')).toStrictEqual(2); - - }); - - it('calls editAction function with the correct arguments', () => { - const { getByTestId } = render(); - const eventActionEl = getByTestId('eventActionSelect'); - - fireEvent.change(eventActionEl, { target: { value: SUB_ACTION.PUSH_TO_SERVICE } }); - expect(editAction).toHaveBeenCalledWith( - 'subActionParams', - { - incident: { - tlp: 2, - severity: 2, - tags: [], - }, - comments: [], - }, - 0 - ); - }); - - it('handles the case when subAction is undefined', () => { - const newProps = { - ...defaultProps, - actionParams: { - ...actionParams, - subAction: undefined, - }, - }; - render( - - ); - expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.PUSH_TO_SERVICE, 0); - }); - }) -}); - -describe('SubAction: createAlert', () => { - describe('TheHiveParamsFields renders', () => { - const subActionParams: ExecutorSubActionCreateAlertParams = { +describe('TheHiveParamsFields renders', () => { + const subActionParams: ExecutorSubActionPushParams = { + incident: { title: 'title {test}', - description: 'description test', + description: 'test description', tlp: 2, severity: 2, tags: ["test1"], - source: 'source test', - type: 'sourceType test', - sourceRef: 'sourceRef test' - } - const actionParams: ExecutorParams = { - subAction: SUB_ACTION.CREATE_ALERT, - subActionParams - }; - const connector: ActionConnector = { - secrets: {}, - config: {}, - id: 'test', - actionTypeId: '.test', - name: 'Test', - isPreconfigured: false, - isDeprecated: false, - isSystemAction: false as const, - }; - - const editAction = jest.fn(); - const defaultProps = { - actionConnector: connector, - actionParams, - editAction, - errors: { 'subActionParams.incident.title': [] }, - index: 0, - messageVariables: [], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('all Params fields is rendered', () => { - const wrapper = mountWithIntl(); - - expect(wrapper.find('[data-test-subj="eventActionSelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-title-row"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-description-row"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-eventTags"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-eventSeveritySelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-eventTlpSelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-type-row"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-source-row"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alert-sourceRef-row"]').length > 0).toBeTruthy(); - - expect(wrapper.find('[data-test-subj="eventActionSelect"]').first().prop('value')).toStrictEqual(SUB_ACTION.CREATE_ALERT); - expect(wrapper.find('[data-test-subj="alert-eventSeveritySelect"]').first().prop('value')).toStrictEqual(2); - expect(wrapper.find('[data-test-subj="alert-eventTlpSelect"]').first().prop('value')).toStrictEqual(2); - }); - - it('calls editAction function with the correct arguments', () => { - const { getByTestId } = render(); - const eventActionEl = getByTestId('eventActionSelect'); - - fireEvent.change(eventActionEl, { target: { value: SUB_ACTION.CREATE_ALERT } }); - expect(editAction).toHaveBeenCalledWith( - 'subActionParams', - { + externalId: null + }, + comments: [], + }; + const actionParams: ExecutorParams = { + subAction: SUB_ACTION.PUSH_TO_SERVICE, + subActionParams + }; + const connector: ActionConnector = { + secrets: {}, + config: {}, + id: 'test', + actionTypeId: '.test', + name: 'Test', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false as const, + }; + + const editAction = jest.fn(); + const defaultProps = { + actionConnector: connector, + actionParams, + editAction, + errors: { 'subActionParams.incident.title': [] }, + index: 0, + messageVariables: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('all Params fields is rendered', () => { + const { getByTestId } = render(); + + expect(getByTestId('eventActionSelect')).toBeInTheDocument(); + expect(getByTestId('eventActionSelect')).toHaveValue(SUB_ACTION.PUSH_TO_SERVICE); + }); + + it('calls editAction function with the correct arguments', () => { + const { getByTestId } = render(); + const eventActionEl = getByTestId('eventActionSelect'); + + fireEvent.change(eventActionEl, { target: { value: SUB_ACTION.CREATE_ALERT } }); + expect(editAction).toHaveBeenCalledWith( + 'subActionParams', + { + tlp: 2, + severity: 2, + tags: [], + sourceRef: '{{alert.uuid}}', + }, + 0 + ); + + fireEvent.change(eventActionEl, { target: { value: SUB_ACTION.PUSH_TO_SERVICE } }); + expect(editAction).toHaveBeenCalledWith( + 'subActionParams', + { + incident: { tlp: 2, severity: 2, tags: [], }, - 0 - ); - }); - - }) -}); + comments: [], + }, + 0 + ); + }); + + it('handles the case when subAction is undefined', () => { + const newProps = { + ...defaultProps, + actionParams: { + ...actionParams, + subAction: undefined, + }, + }; + render( + + ); + expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.PUSH_TO_SERVICE, 0); + }); +}) diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx index 4e9b92295ae256..1830fe1700a504 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx @@ -5,18 +5,15 @@ * 2.0. */ -import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'; -import { ActionParamsProps, TextAreaWithMessageVariables, TextFieldWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; +import { ActionParamsProps, ActionConnectorMode } from '@kbn/triggers-actions-ui-plugin/public'; +import { EuiFormRow, EuiSelect, } from '@elastic/eui'; +import { eventActionOptions } from './constants'; import { SUB_ACTION } from '../../../common/thehive/constants'; -import { eventActionOptions, severityOptions, tlpOptions } from './constants'; -import { ExecutorParams, ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; +import { ExecutorParams } from '../../../common/thehive/types'; +import { TheHiveParamsAlertFields } from './params_alert'; +import { TheHiveParamsCaseFields } from './params_case'; import * as translations from './translations'; -import { - EuiFormRow, - EuiSelect, - EuiText, - EuiComboBox, -} from '@elastic/eui'; const TheHiveParamsFields: React.FunctionComponent> = ({ actionConnector, @@ -24,14 +21,12 @@ const TheHiveParamsFields: React.FunctionComponent { const [eventAction, setEventAction] = useState(actionParams.subAction ?? SUB_ACTION.PUSH_TO_SERVICE); - const [selectedOptions, setSelected] = useState>([]); - const [isInvalid, setInvalid] = useState(false); - const [severity, setSeverity] = useState(severityOptions[1].value); - const [tlp, setTlp] = useState(tlpOptions[2].value); const actionConnectorRef = useRef(actionConnector?.id ?? ''); + const isTest = useMemo(() => executionMode === ActionConnectorMode.Test, [executionMode]); useEffect(() => { if (actionConnector != null && actionConnectorRef.current !== actionConnector.id) { @@ -75,10 +70,6 @@ const TheHiveParamsFields: React.FunctionComponent { editAction('subAction', eventAction, index); - setSelected([]); - setInvalid(false); - setSeverity(severityOptions[1].value); - setTlp(tlpOptions[2].value); }, [eventAction]); const setEventActionType = (eventActionType: SUB_ACTION) => { @@ -88,6 +79,7 @@ const TheHiveParamsFields: React.FunctionComponent - actionParams.subActionParams as ExecutorSubActionPushParams ?? - ({ - incident: { - tlp: 2, - severity: 2, - tags: [] - }, - comments: [], - } as unknown as ExecutorSubActionPushParams), - [actionParams.subActionParams] - ); - - const alert = useMemo( - () => - actionParams.subActionParams as ExecutorSubActionCreateAlertParams ?? - ({ - tlp: 2, - severity: 2, - tags: [], - } as unknown as ExecutorSubActionCreateAlertParams), - [actionParams.subActionParams] - ); - - const editSubActionProperty = useCallback( - (key: string, value: any) => { - const newProps = - key !== 'comments' - ? { - incident: { ...incident, [key]: value }, - comments, - } - : { incident, [key]: value }; - editAction('subActionParams', newProps, index); - }, - [comments, editAction, incident, index] - ); - - const editComment = useCallback( - (key, value) => { - editSubActionProperty(key, [{ commentId: '1', comment: value }]); - }, - [editSubActionProperty] - ); - - const onCreateOption = (searchValue: string) => { - setSelected([...selectedOptions, { label: searchValue }]); - - if (eventAction === SUB_ACTION.PUSH_TO_SERVICE) { - editSubActionProperty('tags', [...incident.tags ?? [], searchValue]) - } else { - editAction('subActionParams', { ...alert, tags: [...alert.tags ?? [], searchValue] }, index); - } - }; - - const onSearchChange = (searchValue: string) => { - if (!searchValue) { - setInvalid(false); - return; - } - }; - - const onChange = (selectedOptions: Array<{ label: string }>) => { - setSelected(selectedOptions); - if (eventAction === SUB_ACTION.PUSH_TO_SERVICE) { - editSubActionProperty('tags', selectedOptions.map((option) => option.label)); - } else { - editAction('subActionParams', { ...alert, tags: selectedOptions.map((option) => option.label) }, index); - } - } - return ( <> {eventAction === SUB_ACTION.PUSH_TO_SERVICE ? - <> - 0 && - incident.title !== undefined - } - label={translations.TITLE_LABEL} - labelAppend={ - - Required - - } - > - - - 0 && - incident.description !== undefined - } - label={translations.DESCRIPTION_LABEL} - labelAppend={ - - Required - - } - > - - - - { - editSubActionProperty('severity', parseInt(e.target.value)) - setSeverity(parseInt(e.target.value)); - }} - /> - - - { - editSubActionProperty('tlp', parseInt(e.target.value)); - setTlp(parseInt(e.target.value)); - }} - /> - - - - - 0 ? comments[0].comment : undefined} - label={translations.COMMENTS_LABEL} - /> - + : - <> - 0 && - alert.title !== undefined - } - label={translations.TITLE_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'title'} - inputTargetValue={alert.title ?? undefined} - errors={errors['createAlertParam.title'] as string[]} - /> - - 0 && - alert.description !== undefined - } - label={translations.DESCRIPTION_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'description'} - inputTargetValue={alert.description ?? undefined} - errors={errors['createAlertParam.description'] as string[]} - /> - - 0 && - alert.type !== undefined - } - label={translations.TYPE_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'type'} - inputTargetValue={alert.type ?? undefined} - errors={(errors['createAlertParam.type'] ?? []) as string[]} - /> - - 0 && - alert.source !== undefined - } - label={translations.SOURCE_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'source'} - inputTargetValue={alert.source ?? undefined} - errors={(errors['createAlertParam.source'] ?? []) as string[]} - /> - - 0 && - alert.sourceRef !== undefined - } - label={translations.SOURCE_REF_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - messageVariables={messageVariables} - paramsProperty={'sourceRef'} - inputTargetValue={alert.sourceRef ?? undefined} - errors={(errors['createAlertParam.sourceRef'] ?? []) as string[]} - /> - - - { - editAction('subActionParams', { ...alert, severity: parseInt(e.target.value) }, index); - setSeverity(parseInt(e.target.value)); - }} - /> - - - { - editAction('subActionParams', { ...alert, tlp: parseInt(e.target.value) }, index); - setTlp(parseInt(e.target.value)); - }} - /> - - - - - + } ); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx new file mode 100644 index 00000000000000..4f371b68fb778b --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx @@ -0,0 +1,70 @@ +/* + * 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 from 'react'; +import { render } from '@testing-library/react'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { TheHiveParamsAlertFields } from './params_alert'; +import { SUB_ACTION } from '../../../common/thehive/constants'; +import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; + +describe('TheHiveParamsFields renders', () => { + const subActionParams: ExecutorSubActionCreateAlertParams = { + title: 'title {test}', + description: 'description test', + tlp: 2, + severity: 2, + tags: ["test1"], + source: 'source test', + type: 'sourceType test', + sourceRef: 'sourceRef test' + } + const actionParams: ExecutorParams = { + subAction: SUB_ACTION.CREATE_ALERT, + subActionParams + }; + const connector: ActionConnector = { + secrets: {}, + config: {}, + id: 'test', + actionTypeId: '.test', + name: 'Test', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false as const, + }; + + const editAction = jest.fn(); + const defaultProps = { + actionConnector: connector, + actionParams, + editAction, + errors: { 'subActionParams.incident.title': [] }, + index: 0, + messageVariables: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('all Params fields is rendered', () => { + const { getByTestId } = render(); + + expect(getByTestId('alert-title-row')).toBeInTheDocument(); + expect(getByTestId('alert-description-row')).toBeInTheDocument(); + expect(getByTestId('alert-eventTags')).toBeInTheDocument(); + expect(getByTestId('alert-eventSeveritySelect')).toBeInTheDocument(); + expect(getByTestId('alert-eventTlpSelect')).toBeInTheDocument(); + expect(getByTestId('alert-type-row')).toBeInTheDocument(); + expect(getByTestId('alert-source-row')).toBeInTheDocument(); + expect(getByTestId('alert-sourceRef-row')).toBeInTheDocument(); + + expect(getByTestId('alert-eventSeveritySelect')).toHaveValue('2'); + expect(getByTestId('alert-eventTlpSelect')).toHaveValue('2'); + }); +}) diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx new file mode 100644 index 00000000000000..13031e88813510 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx @@ -0,0 +1,230 @@ +/* + * 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, { useState, useMemo } from 'react'; +import { TextFieldWithMessageVariables, ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { severityOptions, tlpOptions } from './constants'; +import * as translations from './translations'; +import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '@kbn/stack-connectors-plugin/common/thehive/types'; +import { + EuiFormRow, + EuiSelect, + EuiText, + EuiComboBox, +} from '@elastic/eui'; + +export const TheHiveParamsAlertFields: React.FC> = ({ + actionParams, + editAction, + index, + errors, + messageVariables +}) => { + const [severity, setSeverity] = useState(severityOptions[1].value); + const [tlp, setTlp] = useState(tlpOptions[2].value); + const [selectedOptions, setSelected] = useState>([]); + + const alert = useMemo( + () => + actionParams.subActionParams as ExecutorSubActionCreateAlertParams ?? + ({ + tlp: 2, + severity: 2, + tags: [], + } as unknown as ExecutorSubActionCreateAlertParams), + [actionParams.subActionParams] + ); + + const onCreateOption = (searchValue: string) => { + setSelected([...selectedOptions, { label: searchValue }]); + editAction('subActionParams', { ...alert, tags: [...alert.tags ?? [], searchValue] }, index); + }; + + const onChange = (selectedOptions: Array<{ label: string }>) => { + setSelected(selectedOptions); + editAction('subActionParams', { ...alert, tags: selectedOptions.map((option) => option.label) }, index); + } + + return ( + <> + 0 && + alert.title !== undefined + } + label={translations.TITLE_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'title'} + inputTargetValue={alert.title ?? undefined} + errors={errors['createAlertParam.title'] as string[]} + /> + + 0 && + alert.description !== undefined + } + label={translations.DESCRIPTION_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'description'} + inputTargetValue={alert.description ?? undefined} + errors={errors['createAlertParam.description'] as string[]} + /> + + 0 && + alert.type !== undefined + } + label={translations.TYPE_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'type'} + inputTargetValue={alert.type ?? undefined} + errors={(errors['createAlertParam.type'] ?? []) as string[]} + /> + + 0 && + alert.source !== undefined + } + label={translations.SOURCE_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'source'} + inputTargetValue={alert.source ?? undefined} + errors={(errors['createAlertParam.source'] ?? []) as string[]} + /> + + 0 && + alert.sourceRef !== undefined + } + label={translations.SOURCE_REF_LABEL} + labelAppend={ + + Required + + } + > + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + messageVariables={messageVariables} + paramsProperty={'sourceRef'} + inputTargetValue={alert.sourceRef ?? undefined} + errors={(errors['createAlertParam.sourceRef'] ?? []) as string[]} + /> + + + { + editAction('subActionParams', { ...alert, severity: parseInt(e.target.value) }, index); + setSeverity(parseInt(e.target.value)); + }} + /> + + + { + editAction('subActionParams', { ...alert, tlp: parseInt(e.target.value) }, index); + setTlp(parseInt(e.target.value)); + }} + /> + + + + + + ) +} \ No newline at end of file diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx new file mode 100644 index 00000000000000..d46e4794c215b0 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx @@ -0,0 +1,69 @@ +/* + * 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 from 'react'; +import { render } from '@testing-library/react'; +import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; +import TheHiveParamsFields from './params'; +import { SUB_ACTION } from '../../../common/thehive/constants'; +import { ExecutorParams, ExecutorSubActionPushParams } from '../../../common/thehive/types'; + +describe('TheHiveParamsFields renders', () => { + const subActionParams: ExecutorSubActionPushParams = { + incident: { + title: 'title {test}', + description: 'test description', + tlp: 2, + severity: 2, + tags: ["test1"], + externalId: null + }, + comments: [], + }; + const actionParams: ExecutorParams = { + subAction: SUB_ACTION.PUSH_TO_SERVICE, + subActionParams + }; + const connector: ActionConnector = { + secrets: {}, + config: {}, + id: 'test', + actionTypeId: '.test', + name: 'Test', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false as const, + }; + + const editAction = jest.fn(); + const defaultProps = { + actionConnector: connector, + actionParams, + editAction, + errors: { 'subActionParams.incident.title': [] }, + index: 0, + messageVariables: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('all Params fields is rendered', () => { + const { getByTestId } = render(); + + expect(getByTestId('title-row')).toBeInTheDocument(); + expect(getByTestId('description-row')).toBeInTheDocument(); + expect(getByTestId('eventTags')).toBeInTheDocument(); + expect(getByTestId('eventSeveritySelect')).toBeInTheDocument(); + expect(getByTestId('eventTlpSelect')).toBeInTheDocument(); + expect(getByTestId('commentsTextArea')).toBeInTheDocument(); + + expect(getByTestId('eventSeveritySelect')).toHaveValue('2'); + expect(getByTestId('eventTlpSelect')).toHaveValue('2'); + }); +}) \ No newline at end of file diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx new file mode 100644 index 00000000000000..96ba0b13c7f597 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx @@ -0,0 +1,182 @@ +/* + * 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, { useState, useMemo, useCallback } from 'react'; +import { TextFieldWithMessageVariables, TextAreaWithMessageVariables, ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { severityOptions, tlpOptions } from './constants'; +import * as translations from './translations'; +import { ExecutorParams, ExecutorSubActionPushParams } from '@kbn/stack-connectors-plugin/common/thehive/types'; +import { + EuiFormRow, + EuiSelect, + EuiText, + EuiComboBox, +} from '@elastic/eui'; + +export const TheHiveParamsCaseFields: React.FC> = ({ + actionParams, + editAction, + index, + errors, + messageVariables, +}) => { + const [severity, setSeverity] = useState(severityOptions[1].value); + const [tlp, setTlp] = useState(tlpOptions[2].value); + const [selectedOptions, setSelected] = useState>([]); + + const { incident, comments } = useMemo( + () => + actionParams.subActionParams as ExecutorSubActionPushParams ?? + ({ + incident: { + tlp: 2, + severity: 2, + tags: [] + }, + comments: [], + } as unknown as ExecutorSubActionPushParams), + [actionParams.subActionParams] + ); + + const editSubActionProperty = useCallback( + (key: string, value: any) => { + const newProps = + key !== 'comments' + ? { + incident: { ...incident, [key]: value }, + comments, + } + : { incident, [key]: value }; + editAction('subActionParams', newProps, index); + }, + [comments, editAction, incident, index] + ); + + const editComment = useCallback( + (key, value) => { + editSubActionProperty(key, [{ commentId: '1', comment: value }]); + }, + [editSubActionProperty] + ); + + const onCreateOption = (searchValue: string) => { + setSelected([...selectedOptions, { label: searchValue }]); + editSubActionProperty('tags', [...incident.tags ?? [], searchValue]) + }; + + const onChange = (selectedOptions: Array<{ label: string }>) => { + setSelected(selectedOptions); + editSubActionProperty('tags', selectedOptions.map((option) => option.label)); + } + + return ( + <> + 0 && + incident.title !== undefined + } + label={translations.TITLE_LABEL} + labelAppend={ + + Required + + } + > + + + 0 && + incident.description !== undefined + } + label={translations.DESCRIPTION_LABEL} + labelAppend={ + + Required + + } + > + + + + { + editSubActionProperty('severity', parseInt(e.target.value)) + setSeverity(parseInt(e.target.value)); + }} + /> + + + { + editSubActionProperty('tlp', parseInt(e.target.value)); + setTlp(parseInt(e.target.value)); + }} + /> + + + + + 0 ? comments[0].comment : undefined} + label={translations.COMMENTS_LABEL} + /> + + ) +} \ No newline at end of file diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx index 121e30bf8cf0ae..56a7ad64de4985 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx @@ -17,7 +17,7 @@ export function getConnectorType(): TheHiveConnector { id: THEHIVE_CONNECTOR_ID, iconClass: lazy(() => import('./logo')), selectMessage: i18n.translate('xpack.stackConnectors.components.thehive.descriptionText', { - defaultMessage: 'Create Case and Alert in Hive', + defaultMessage: 'Create cases and alerts in TheHive', }), actionTypeTitle: THEHIVE_TITLE, validateParams: async ( diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts index 66c3059ed7b95c..ab3a2ebc9aab8c 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts @@ -21,6 +21,13 @@ export const ORGANISATION_LABEL = i18n.translate( } ); +export const ORGANISATION_HELP_TEXT = i18n.translate( + 'xpack.stackConnectors.components.thehive.organisationFieldHelpText', + { + defaultMessage: `By default, the user's default organization will be considered.`, + } +); + export const API_KEY_LABEL = i18n.translate( 'xpack.stackConnectors.components.thehive.apiKeyFieldLabel', { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.ts index bb0138790a723e..86176462ab6d29 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.test.ts @@ -5,21 +5,8 @@ * 2.0. */ -import axios from 'axios'; import { TheHiveConnectorType, getConnectorType } from '.'; -jest.mock('axios'); -jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { - const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); - return { - ...originalUtils, - request: jest.fn(), - patch: jest.fn(), - }; -}); - -axios.create = jest.fn(() => axios); - let connectorType: TheHiveConnectorType; describe('TheHive Connector', () => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts index 2992ea3ecc4229..a1dcfe48767fff 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts @@ -12,7 +12,6 @@ import { import { AlertingConnectorFeatureId, SecurityConnectorFeatureId, - CasesConnectorFeatureId, UptimeConnectorFeatureId, } from '@kbn/actions-plugin/common/types'; import { urlAllowListValidator } from '@kbn/actions-plugin/server'; @@ -29,7 +28,7 @@ export function getConnectorType(): TheHiveConnectorType { minimumLicenseRequired: 'gold', name: THEHIVE_TITLE, getService: (params) => new TheHiveConnector(params), - supportedFeatureIds: [AlertingConnectorFeatureId, SecurityConnectorFeatureId, CasesConnectorFeatureId, UptimeConnectorFeatureId], + supportedFeatureIds: [AlertingConnectorFeatureId, SecurityConnectorFeatureId, UptimeConnectorFeatureId], schema: { config: TheHiveConfigSchema, secrets: TheHiveSecretsSchema, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts index 840f7d0b6343bb..e7b35faa9ba352 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts @@ -26,7 +26,7 @@ describe('TheHiveConnector', () => { configurationUtilities: actionsConfigMock.create(), connector: { id: '1', type: THEHIVE_CONNECTOR_ID }, config: { url: 'https://example.com', organisation: null }, - secrets: { api_key: 'test123' }, + secrets: { apiKey: 'test123' }, logger: loggingSystemMock.createLogger(), services: actionsMock.createServices(), }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts index 3da5fdbc5bd5dd..d1d28f41cd7bfa 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts @@ -29,7 +29,7 @@ export const API_VERSION = 'v1'; export class TheHiveConnector extends CaseConnector { private url: string; - private api_key: string; + private apiKey: string; private organisation: string | null; private urlWithoutTrailingSlash: string; @@ -44,10 +44,14 @@ export class TheHiveConnector extends CaseConnector): string { if (!error.response?.status) { return 'Unknown API Error'; @@ -60,7 +64,7 @@ export class TheHiveConnector extends CaseConnector { const res = await this.request({ url: `${this.url}/api/${API_VERSION}/case/${id}`, - headers: { Authorization: `Bearer ${this.api_key}`, 'X-Organisation': this.organisation }, + headers: this.getAuthHeaders(), responseSchema: TheHiveIncidentResponseSchema, }); @@ -137,7 +141,7 @@ export class TheHiveConnector extends CaseConnector Date: Fri, 19 Apr 2024 06:21:32 +0000 Subject: [PATCH 03/15] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/stack_connectors/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json index 045d5e54b461b0..e00e76a26d883b 100644 --- a/x-pack/plugins/stack_connectors/tsconfig.json +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -40,6 +40,7 @@ "@kbn/cases-components", "@kbn/code-editor-mock", "@kbn/utility-types", + "@kbn/stack-connectors-plugin", ], "exclude": [ "target/**/*", From 4f2cc462993c983eceb5667971bfae9569efccb7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Apr 2024 07:02:40 +0000 Subject: [PATCH 04/15] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../stack_connectors/common/thehive/schema.ts | 2 +- .../stack_connectors/common/thehive/types.ts | 4 +- .../thehive/connector.test.tsx | 6 +- .../connector_types/thehive/connector.tsx | 14 +- .../connector_types/thehive/constants.ts | 202 +++++++++--------- .../public/connector_types/thehive/logo.tsx | 31 ++- .../connector_types/thehive/params.test.tsx | 12 +- .../public/connector_types/thehive/params.tsx | 46 ++-- .../thehive/params_alert.test.tsx | 10 +- .../connector_types/thehive/params_alert.tsx | 46 ++-- .../thehive/params_case.test.tsx | 8 +- .../connector_types/thehive/params_case.tsx | 56 +++-- .../connector_types/thehive/thehive.test.tsx | 12 +- .../connector_types/thehive/thehive.tsx | 10 +- .../connector_types/thehive/translations.ts | 11 +- .../public/connector_types/thehive/types.ts | 12 +- .../server/connector_types/thehive/index.ts | 6 +- .../connector_types/thehive/thehive.test.ts | 71 +++--- .../server/connector_types/thehive/thehive.ts | 9 +- 19 files changed, 291 insertions(+), 277 deletions(-) diff --git a/x-pack/plugins/stack_connectors/common/thehive/schema.ts b/x-pack/plugins/stack_connectors/common/thehive/schema.ts index 63225536c83c69..6002f43555adf7 100644 --- a/x-pack/plugins/stack_connectors/common/thehive/schema.ts +++ b/x-pack/plugins/stack_connectors/common/thehive/schema.ts @@ -14,7 +14,7 @@ export const TheHiveConfigSchema = schema.object({ }); export const TheHiveSecretsSchema = schema.object({ - apiKey: schema.string() + apiKey: schema.string(), }); export const ExecutorSubActionPushParamsSchema = schema.object({ diff --git a/x-pack/plugins/stack_connectors/common/thehive/types.ts b/x-pack/plugins/stack_connectors/common/thehive/types.ts index 7d13af9d94a4f1..096116db29e2e1 100644 --- a/x-pack/plugins/stack_connectors/common/thehive/types.ts +++ b/x-pack/plugins/stack_connectors/common/thehive/types.ts @@ -20,7 +20,9 @@ export type TheHiveSecrets = TypeOf; export type ExecutorParams = TypeOf; export type ExecutorSubActionPushParams = TypeOf; -export type ExecutorSubActionCreateAlertParams = TypeOf; +export type ExecutorSubActionCreateAlertParams = TypeOf< + typeof ExecutorSubActionCreateAlertParamsSchema +>; export type TheHiveFailureResponse = TypeOf; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx index e6ec40d4b13d58..7b61456b093d77 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.test.tsx @@ -32,7 +32,7 @@ describe('TheHiveActionConnectorFields renders', () => { { }} + registerPreSubmitValidator={() => {}} /> ); @@ -59,7 +59,7 @@ describe('TheHiveActionConnectorFields renders', () => { { }} + registerPreSubmitValidator={() => {}} /> ); @@ -92,7 +92,7 @@ describe('TheHiveActionConnectorFields renders', () => { { }} + registerPreSubmitValidator={() => {}} /> ); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx index 1bd586b459940b..01ab1803c00ea8 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/connector.tsx @@ -13,10 +13,20 @@ import { SecretsFieldSchema, } from '@kbn/triggers-actions-ui-plugin/public'; -import { URL_LABEL, API_KEY_LABEL, ORGANISATION_LABEL, ORGANISATION_HELP_TEXT } from './translations'; +import { + URL_LABEL, + API_KEY_LABEL, + ORGANISATION_LABEL, + ORGANISATION_HELP_TEXT, +} from './translations'; const configFormSchema: ConfigFieldSchema[] = [ - { id: 'organisation', label: ORGANISATION_LABEL, isRequired: false, helpText: ORGANISATION_HELP_TEXT }, + { + id: 'organisation', + label: ORGANISATION_LABEL, + isRequired: false, + helpText: ORGANISATION_HELP_TEXT, + }, { id: 'url', label: URL_LABEL, isUrlField: true }, ]; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts index d6d38f47a1497a..b94fd5e4ad4bec 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/constants.ts @@ -1,110 +1,114 @@ +/* + * 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 { i18n } from '@kbn/i18n'; import { TheHiveSeverity, TheHiveTLP, SUB_ACTION } from '../../../common/thehive/constants'; export const eventActionOptions = [ - { - value: SUB_ACTION.PUSH_TO_SERVICE, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectCreateCaseOptionLabel', - { - defaultMessage: 'Create Case', - } - ), - }, - { - value: SUB_ACTION.CREATE_ALERT, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectCreateAlertOptionLabel', - { - defaultMessage: 'Create Alert', - } - ), - } + { + value: SUB_ACTION.PUSH_TO_SERVICE, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectCreateCaseOptionLabel', + { + defaultMessage: 'Create Case', + } + ), + }, + { + value: SUB_ACTION.CREATE_ALERT, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectCreateAlertOptionLabel', + { + defaultMessage: 'Create Alert', + } + ), + }, ]; export const severityOptions = [ - { - value: TheHiveSeverity.LOW, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectSeverityLowOptionLabel', - { - defaultMessage: 'LOW', - } - ) - }, - { - value: TheHiveSeverity.MEDIUM, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectSeverityMediumOptionLabel', - { - defaultMessage: 'MEDIUM', - } - ), - }, - { - value: TheHiveSeverity.HIGH, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectSeverityHighOptionLabel', - { - defaultMessage: 'HIGH', - } - ), - }, - { - value: TheHiveSeverity.CRITICAL, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectSeverityCriticalOptionLabel', - { - defaultMessage: 'CRITICAL', - } - ), - }, + { + value: TheHiveSeverity.LOW, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityLowOptionLabel', + { + defaultMessage: 'LOW', + } + ), + }, + { + value: TheHiveSeverity.MEDIUM, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityMediumOptionLabel', + { + defaultMessage: 'MEDIUM', + } + ), + }, + { + value: TheHiveSeverity.HIGH, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityHighOptionLabel', + { + defaultMessage: 'HIGH', + } + ), + }, + { + value: TheHiveSeverity.CRITICAL, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectSeverityCriticalOptionLabel', + { + defaultMessage: 'CRITICAL', + } + ), + }, ]; export const tlpOptions = [ - { - value: TheHiveTLP.CLEAR, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectTlpClearOptionLabel', - { - defaultMessage: 'CLEAR', - } - ), - }, - { - value: TheHiveTLP.GREEN, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectTlpGreenOptionLabel', - { - defaultMessage: 'GREEN', - } - ), - }, - { - value: TheHiveTLP.AMBER, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectTlpAmberOptionLabel', - { - defaultMessage: 'AMBER', - } - ), - }, - { - value: TheHiveTLP.AMBER_STRICT, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectTlpAmberStrictOptionLabel', - { - defaultMessage: 'AMBER+STRICT', - } - ), - }, - { - value: TheHiveTLP.RED, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectTlpRedOptionLabel', - { - defaultMessage: 'RED', - } - ), - } + { + value: TheHiveTLP.CLEAR, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpClearOptionLabel', + { + defaultMessage: 'CLEAR', + } + ), + }, + { + value: TheHiveTLP.GREEN, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpGreenOptionLabel', + { + defaultMessage: 'GREEN', + } + ), + }, + { + value: TheHiveTLP.AMBER, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpAmberOptionLabel', + { + defaultMessage: 'AMBER', + } + ), + }, + { + value: TheHiveTLP.AMBER_STRICT, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTlpAmberStrictOptionLabel', + { + defaultMessage: 'AMBER+STRICT', + } + ), + }, + { + value: TheHiveTLP.RED, + text: i18n.translate('xpack.stackConnectors.components.thehive.eventSelectTlpRedOptionLabel', { + defaultMessage: 'RED', + }), + }, ]; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx index ca7a41bb61e374..45025e1f240419 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/logo.tsx @@ -9,14 +9,31 @@ import React from 'react'; import { LogoProps } from '../types'; const Logo = (props: LogoProps) => ( - - - - - + + + + + - ); // eslint-disable-next-line import/no-default-export diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx index d1720371294630..d69080938fc26b 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.test.tsx @@ -19,14 +19,14 @@ describe('TheHiveParamsFields renders', () => { description: 'test description', tlp: 2, severity: 2, - tags: ["test1"], - externalId: null + tags: ['test1'], + externalId: null, }, comments: [], }; const actionParams: ExecutorParams = { subAction: SUB_ACTION.PUSH_TO_SERVICE, - subActionParams + subActionParams, }; const connector: ActionConnector = { secrets: {}, @@ -99,9 +99,7 @@ describe('TheHiveParamsFields renders', () => { subAction: undefined, }, }; - render( - - ); + render(); expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.PUSH_TO_SERVICE, 0); }); -}) +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx index 1830fe1700a504..cade42f66a4c83 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx @@ -7,7 +7,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { ActionParamsProps, ActionConnectorMode } from '@kbn/triggers-actions-ui-plugin/public'; -import { EuiFormRow, EuiSelect, } from '@elastic/eui'; +import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { eventActionOptions } from './constants'; import { SUB_ACTION } from '../../../common/thehive/constants'; import { ExecutorParams } from '../../../common/thehive/types'; @@ -22,9 +22,11 @@ const TheHiveParamsFields: React.FunctionComponent { - const [eventAction, setEventAction] = useState(actionParams.subAction ?? SUB_ACTION.PUSH_TO_SERVICE); + const [eventAction, setEventAction] = useState( + actionParams.subAction ?? SUB_ACTION.PUSH_TO_SERVICE + ); const actionConnectorRef = useRef(actionConnector?.id ?? ''); const isTest = useMemo(() => executionMode === ActionConnectorMode.Test, [executionMode]); @@ -37,7 +39,7 @@ const TheHiveParamsFields: React.FunctionComponent { @@ -76,19 +77,19 @@ const TheHiveParamsFields: React.FunctionComponent - + setEventActionType(e.target.value as SUB_ACTION)} /> - {eventAction === SUB_ACTION.PUSH_TO_SERVICE ? + {eventAction === SUB_ACTION.PUSH_TO_SERVICE ? ( - : + ) : ( - } + )} ); }; // eslint-disable-next-line import/no-default-export -export { TheHiveParamsFields as default }; \ No newline at end of file +export { TheHiveParamsFields as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx index 4f371b68fb778b..53092d28c21e2b 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx @@ -18,14 +18,14 @@ describe('TheHiveParamsFields renders', () => { description: 'description test', tlp: 2, severity: 2, - tags: ["test1"], + tags: ['test1'], source: 'source test', type: 'sourceType test', - sourceRef: 'sourceRef test' - } + sourceRef: 'sourceRef test', + }; const actionParams: ExecutorParams = { subAction: SUB_ACTION.CREATE_ALERT, - subActionParams + subActionParams, }; const connector: ActionConnector = { secrets: {}, @@ -67,4 +67,4 @@ describe('TheHiveParamsFields renders', () => { expect(getByTestId('alert-eventSeveritySelect')).toHaveValue('2'); expect(getByTestId('alert-eventTlpSelect')).toHaveValue('2'); }); -}) +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx index 13031e88813510..1ffdd93f5e0cad 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx @@ -6,23 +6,21 @@ */ import React, { useState, useMemo } from 'react'; -import { TextFieldWithMessageVariables, ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextFieldWithMessageVariables, + ActionParamsProps, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { EuiFormRow, EuiSelect, EuiText, EuiComboBox } from '@elastic/eui'; +import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; import { severityOptions, tlpOptions } from './constants'; import * as translations from './translations'; -import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '@kbn/stack-connectors-plugin/common/thehive/types'; -import { - EuiFormRow, - EuiSelect, - EuiText, - EuiComboBox, -} from '@elastic/eui'; export const TheHiveParamsAlertFields: React.FC> = ({ actionParams, editAction, index, errors, - messageVariables + messageVariables, }) => { const [severity, setSeverity] = useState(severityOptions[1].value); const [tlp, setTlp] = useState(tlpOptions[2].value); @@ -30,7 +28,7 @@ export const TheHiveParamsAlertFields: React.FC - actionParams.subActionParams as ExecutorSubActionCreateAlertParams ?? + (actionParams.subActionParams as ExecutorSubActionCreateAlertParams) ?? ({ tlp: 2, severity: 2, @@ -41,13 +39,17 @@ export const TheHiveParamsAlertFields: React.FC { setSelected([...selectedOptions, { label: searchValue }]); - editAction('subActionParams', { ...alert, tags: [...alert.tags ?? [], searchValue] }, index); + editAction('subActionParams', { ...alert, tags: [...(alert.tags ?? []), searchValue] }, index); }; const onChange = (selectedOptions: Array<{ label: string }>) => { setSelected(selectedOptions); - editAction('subActionParams', { ...alert, tags: selectedOptions.map((option) => option.label) }, index); - } + editAction( + 'subActionParams', + { ...alert, tags: selectedOptions.map((option) => option.label) }, + index + ); + }; return ( <> @@ -182,10 +184,7 @@ export const TheHiveParamsAlertFields: React.FC - + - + - + - ) -} \ No newline at end of file + ); +}; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx index d46e4794c215b0..a619e41906a80c 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.test.tsx @@ -19,14 +19,14 @@ describe('TheHiveParamsFields renders', () => { description: 'test description', tlp: 2, severity: 2, - tags: ["test1"], - externalId: null + tags: ['test1'], + externalId: null, }, comments: [], }; const actionParams: ExecutorParams = { subAction: SUB_ACTION.PUSH_TO_SERVICE, - subActionParams + subActionParams, }; const connector: ActionConnector = { secrets: {}, @@ -66,4 +66,4 @@ describe('TheHiveParamsFields renders', () => { expect(getByTestId('eventSeveritySelect')).toHaveValue('2'); expect(getByTestId('eventTlpSelect')).toHaveValue('2'); }); -}) \ No newline at end of file +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx index 96ba0b13c7f597..1f818ea7c4966c 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx @@ -6,16 +6,15 @@ */ import React, { useState, useMemo, useCallback } from 'react'; -import { TextFieldWithMessageVariables, TextAreaWithMessageVariables, ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { + TextFieldWithMessageVariables, + TextAreaWithMessageVariables, + ActionParamsProps, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { EuiFormRow, EuiSelect, EuiText, EuiComboBox } from '@elastic/eui'; +import { ExecutorParams, ExecutorSubActionPushParams } from '../../../common/thehive/types'; import { severityOptions, tlpOptions } from './constants'; import * as translations from './translations'; -import { ExecutorParams, ExecutorSubActionPushParams } from '@kbn/stack-connectors-plugin/common/thehive/types'; -import { - EuiFormRow, - EuiSelect, - EuiText, - EuiComboBox, -} from '@elastic/eui'; export const TheHiveParamsCaseFields: React.FC> = ({ actionParams, @@ -30,12 +29,12 @@ export const TheHiveParamsCaseFields: React.FC const { incident, comments } = useMemo( () => - actionParams.subActionParams as ExecutorSubActionPushParams ?? + (actionParams.subActionParams as ExecutorSubActionPushParams) ?? ({ incident: { tlp: 2, severity: 2, - tags: [] + tags: [], }, comments: [], } as unknown as ExecutorSubActionPushParams), @@ -47,9 +46,9 @@ export const TheHiveParamsCaseFields: React.FC const newProps = key !== 'comments' ? { - incident: { ...incident, [key]: value }, - comments, - } + incident: { ...incident, [key]: value }, + comments, + } : { incident, [key]: value }; editAction('subActionParams', newProps, index); }, @@ -65,13 +64,16 @@ export const TheHiveParamsCaseFields: React.FC const onCreateOption = (searchValue: string) => { setSelected([...selectedOptions, { label: searchValue }]); - editSubActionProperty('tags', [...incident.tags ?? [], searchValue]) + editSubActionProperty('tags', [...(incident.tags ?? []), searchValue]); }; const onChange = (selectedOptions: Array<{ label: string }>) => { setSelected(selectedOptions); - editSubActionProperty('tags', selectedOptions.map((option) => option.label)); - } + editSubActionProperty( + 'tags', + selectedOptions.map((option) => option.label) + ); + }; return ( <> @@ -123,26 +125,19 @@ export const TheHiveParamsCaseFields: React.FC errors={errors['pushToServiceParam.incident.description'] as string[]} /> - + { - editSubActionProperty('severity', parseInt(e.target.value)) + editSubActionProperty('severity', parseInt(e.target.value)); setSeverity(parseInt(e.target.value)); }} /> - + }} /> - + label={translations.COMMENTS_LABEL} /> - ) -} \ No newline at end of file + ); +}; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx index 999b94ae34f685..3a6788a8bf55d1 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.test.tsx @@ -38,9 +38,9 @@ describe('thehive pushToService action params validation', () => { incident: { title: 'title {test}', description: 'test description', - } + }, }, - comments: [] + comments: [], }; expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ @@ -63,9 +63,9 @@ describe('thehive pushToService action params validation', () => { incident: { title: '', description: '', - } + }, }, - comments: [] + comments: [], }; expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ @@ -93,7 +93,7 @@ describe('thehive createAlert action params validation', () => { source: 'source test', sourceRef: 'source reference test', }, - comments: [] + comments: [], }; expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ @@ -119,7 +119,7 @@ describe('thehive createAlert action params validation', () => { source: '', sourceRef: '', }, - comments: [] + comments: [], }; expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx index 56a7ad64de4985..d52d2783e707b6 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx @@ -10,7 +10,11 @@ import { i18n } from '@kbn/i18n'; import { GenericValidationResult } from '@kbn/triggers-actions-ui-plugin/public/types'; import { TheHiveConnector } from './types'; import { THEHIVE_CONNECTOR_ID, SUB_ACTION, THEHIVE_TITLE } from '../../../common/thehive/constants'; -import { ExecutorParams, ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; +import { + ExecutorParams, + ExecutorSubActionPushParams, + ExecutorSubActionCreateAlertParams, +} from '../../../common/thehive/types'; export function getConnectorType(): TheHiveConnector { return { @@ -47,7 +51,9 @@ export function getConnectorType(): TheHiveConnector { errors['pushToServiceParam.incident.title'].push(translations.TITLE_REQUIRED); } if (!pushToServiceParam.incident.description?.length) { - errors['pushToServiceParam.incident.description'].push(translations.DESCRIPTION_REQUIRED); + errors['pushToServiceParam.incident.description'].push( + translations.DESCRIPTION_REQUIRED + ); } } } else if (subAction === SUB_ACTION.CREATE_ALERT) { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts index ab3a2ebc9aab8c..e6570f91dd6bb3 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/translations.ts @@ -7,12 +7,9 @@ import { i18n } from '@kbn/i18n'; -export const URL_LABEL = i18n.translate( - 'xpack.stackConnectors.components.thehive.urlFieldLabel', - { - defaultMessage: 'URL', - } -); +export const URL_LABEL = i18n.translate('xpack.stackConnectors.components.thehive.urlFieldLabel', { + defaultMessage: 'URL', +}); export const ORGANISATION_LABEL = i18n.translate( 'xpack.stackConnectors.components.thehive.organisationFieldLabel', @@ -117,7 +114,7 @@ export const DESCRIPTION_REQUIRED = i18n.translate( { defaultMessage: 'Description is required.', } -) +); export const TYPE_REQUIRED = i18n.translate( 'xpack.stackConnectors.components.thehive.requiredTypeText', diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts index 098d22a9eb85c6..0724b5bf2b9d32 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/types.ts @@ -6,14 +6,6 @@ */ import { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; -import { - TheHiveConfig, - TheHiveSecrets, - ExecutorParams, -} from '../../../common/thehive/types'; +import { TheHiveConfig, TheHiveSecrets, ExecutorParams } from '../../../common/thehive/types'; -export type TheHiveConnector = ConnectorTypeModel< - TheHiveConfig, - TheHiveSecrets, - ExecutorParams ->; +export type TheHiveConnector = ConnectorTypeModel; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts index a1dcfe48767fff..1758ef48922d9f 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts @@ -28,7 +28,11 @@ export function getConnectorType(): TheHiveConnectorType { minimumLicenseRequired: 'gold', name: THEHIVE_TITLE, getService: (params) => new TheHiveConnector(params), - supportedFeatureIds: [AlertingConnectorFeatureId, SecurityConnectorFeatureId, UptimeConnectorFeatureId], + supportedFeatureIds: [ + AlertingConnectorFeatureId, + SecurityConnectorFeatureId, + UptimeConnectorFeatureId, + ], schema: { config: TheHiveConfigSchema, secrets: TheHiveSecretsSchema, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts index e7b35faa9ba352..c2b35ebd3cc1fc 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts @@ -16,12 +16,14 @@ import { TheHiveAddCommentResponseSchema, TheHiveCreateAlertResponseSchema, } from '../../../common/thehive/schema'; -import type { ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; +import type { + ExecutorSubActionPushParams, + ExecutorSubActionCreateAlertParams, +} from '../../../common/thehive/types'; const mockTime = new Date('2024-04-03T09:10:30.000'); describe('TheHiveConnector', () => { - const connector = new TheHiveConnector({ configurationUtilities: actionsConfigMock.create(), connector: { id: '1', type: THEHIVE_CONNECTOR_ID }, @@ -63,10 +65,7 @@ describe('TheHiveConnector', () => { severity: 1, severityLabel: 'LOW', startDate: 1712128153029, - tags: [ - 'tag1', - 'tag2' - ], + tags: ['tag1', 'tag2'], flag: false, tlp: 2, tlpLabel: 'AMBER', @@ -100,12 +99,12 @@ describe('TheHiveConnector', () => { 'manageComment', 'manageAlert/reopen', 'manageCase/update', - 'manageCase/reopen' + 'manageCase/reopen', ], extraData: {}, newDate: 1712128153029, - timeToDetect: 0 - } + timeToDetect: 0, + }, }; beforeEach(() => { @@ -115,7 +114,7 @@ describe('TheHiveConnector', () => { jest.clearAllMocks(); }); - const incident: ExecutorSubActionPushParams["incident"] = { + const incident: ExecutorSubActionPushParams['incident'] = { title: 'title', description: 'description', severity: 1, @@ -141,7 +140,7 @@ describe('TheHiveConnector', () => { id: '~172064', url: 'https://example.com/cases/~172064/details', title: 'title', - pushedDate: '2024-04-03T07:09:13.041Z' + pushedDate: '2024-04-03T07:09:13.041Z', }); }); @@ -155,7 +154,7 @@ describe('TheHiveConnector', () => { describe('updateIncident', () => { const mockResponse = { - data: null + data: null, }; beforeEach(() => { @@ -165,7 +164,7 @@ describe('TheHiveConnector', () => { jest.clearAllMocks(); }); - const incident: ExecutorSubActionPushParams["incident"] = { + const incident: ExecutorSubActionPushParams['incident'] = { title: 'new title', description: 'new description', severity: 3, @@ -191,7 +190,7 @@ describe('TheHiveConnector', () => { id: '~172064', url: 'https://example.com/cases/~172064/details', title: 'new title', - pushedDate: mockTime.toISOString() + pushedDate: mockTime.toISOString(), }); }); @@ -199,9 +198,11 @@ describe('TheHiveConnector', () => { // @ts-ignore connector.request = mockError; - await expect(connector.updateIncident({ incidentId: '~172064', incident })).rejects.toThrow('API Error'); + await expect(connector.updateIncident({ incidentId: '~172064', incident })).rejects.toThrow( + 'API Error' + ); }); - }) + }); describe('addComment', () => { const mockResponse = { @@ -212,8 +213,8 @@ describe('TheHiveConnector', () => { createdAt: 1712158280100, message: 'test comment', isEdited: false, - extraData: {} - } + extraData: {}, + }, }; beforeEach(() => { @@ -224,7 +225,10 @@ describe('TheHiveConnector', () => { }); it('TheHive API call is successful with correct parameters', async () => { - const response = await connector.addComment({ incidentId: '~172064', comment: 'test comment' }); + const response = await connector.addComment({ + incidentId: '~172064', + comment: 'test comment', + }); expect(mockRequest).toBeCalledTimes(1); expect(mockRequest).toHaveBeenCalledWith({ url: 'https://example.com/api/v1/case/~172064/comment', @@ -239,7 +243,7 @@ describe('TheHiveConnector', () => { expect(response).toEqual({ commentId: '~41156688', externalCommentId: '~41156688', - pushedDate: '2024-04-03T15:31:20.100Z' + pushedDate: '2024-04-03T15:31:20.100Z', }); }); @@ -247,7 +251,9 @@ describe('TheHiveConnector', () => { // @ts-ignore connector.request = mockError; - await expect(connector.addComment({ incidentId: '~172064', comment: 'test comment' })).rejects.toThrow('API Error'); + await expect( + connector.addComment({ incidentId: '~172064', comment: 'test comment' }) + ).rejects.toThrow('API Error'); }); }); @@ -264,10 +270,7 @@ describe('TheHiveConnector', () => { severity: 1, severityLabel: 'LOW', startDate: 1712128153029, - tags: [ - 'tag1', - 'tag2' - ], + tags: ['tag1', 'tag2'], flag: false, tlp: 2, tlpLabel: 'AMBER', @@ -301,12 +304,12 @@ describe('TheHiveConnector', () => { 'manageComment', 'manageAlert/reopen', 'manageCase/update', - 'manageCase/reopen' + 'manageCase/reopen', ], extraData: {}, newDate: 1712128153029, - timeToDetect: 0 - } + timeToDetect: 0, + }, }; beforeEach(() => { @@ -331,7 +334,7 @@ describe('TheHiveConnector', () => { id: '~172064', url: 'https://example.com/cases/~172064/details', title: 'title', - pushedDate: mockTime.toISOString() + pushedDate: mockTime.toISOString(), }); }); @@ -358,10 +361,7 @@ describe('TheHiveConnector', () => { severity: 1, severityLabel: 'LOW', date: 1712161128964, - tags: [ - 'tag1', - 'tag2' - ], + tags: ['tag1', 'tag2'], tlp: 2, tlpLabel: 'AMBER', pap: 2, @@ -373,8 +373,8 @@ describe('TheHiveConnector', () => { stage: 'New', extraData: {}, newDate: 1712161128967, - timeToDetect: 0 - } + timeToDetect: 0, + }, }; beforeEach(() => { @@ -417,5 +417,4 @@ describe('TheHiveConnector', () => { await expect(connector.createAlert(alert)).rejects.toThrow('API Error'); }); }); - }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts index d1d28f41cd7bfa..efe139c4b5e2b5 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts @@ -59,7 +59,9 @@ export class TheHiveConnector extends CaseConnector { + public async createIncident( + incident: ExecutorSubActionPushParams['incident'] + ): Promise { const res = await this.request({ method: 'post', url: `${this.url}/api/${API_VERSION}/case`, @@ -103,7 +105,7 @@ export class TheHiveConnector extends CaseConnector { await this.request({ method: 'patch', @@ -145,5 +147,4 @@ export class TheHiveConnector extends CaseConnector Date: Fri, 19 Apr 2024 19:17:22 +0530 Subject: [PATCH 05/15] Add documentation and functional test --- docs/management/action-types.asciidoc | 4 + .../connectors/action-types/thehive.asciidoc | 79 +++++ .../connectors/images/thehive-connector.png | Bin 0 -> 58661 bytes .../images/thehive-params-alert-test.png | Bin 0 -> 41263 bytes .../images/thehive-params-case-test.png | Bin 0 -> 36289 bytes docs/management/connectors/index.asciidoc | 1 + docs/settings/alert-action-settings.asciidoc | 2 +- x-pack/plugins/stack_connectors/tsconfig.json | 1 - .../alerting_api_integration/common/config.ts | 1 + .../server/thehive_simulation.ts | 104 ++++++ .../tests/actions/connector_types/thehive.ts | 332 ++++++++++++++++++ .../group2/tests/actions/index.ts | 1 + .../check_registered_connector_types.ts | 1 + .../check_registered_task_types.ts | 1 + 14 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 docs/management/connectors/action-types/thehive.asciidoc create mode 100644 docs/management/connectors/images/thehive-connector.png create mode 100644 docs/management/connectors/images/thehive-params-alert-test.png create mode 100644 docs/management/connectors/images/thehive-params-case-test.png create mode 100644 x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts diff --git a/docs/management/action-types.asciidoc b/docs/management/action-types.asciidoc index ae9a887481406c..fc6c4665885bf6 100644 --- a/docs/management/action-types.asciidoc +++ b/docs/management/action-types.asciidoc @@ -75,6 +75,10 @@ a| <> | Create an incident in {swimlane}. +a| <> + +| Create cases and alerts in {thehive}. + a| <> | Send events to a Tines Story. diff --git a/docs/management/connectors/action-types/thehive.asciidoc b/docs/management/connectors/action-types/thehive.asciidoc new file mode 100644 index 00000000000000..7552fba920cc34 --- /dev/null +++ b/docs/management/connectors/action-types/thehive.asciidoc @@ -0,0 +1,79 @@ +[[thehive-action-type]] +== TheHive connector and action +++++ +TheHive +++++ +:frontmatter-description: Add a connector that can create cases and alerts in TheHive. +:frontmatter-tags-products: [kibana] +:frontmatter-tags-content-type: [how-to] +:frontmatter-tags-user-goals: [configure] + +TheHive connector uses the https://docs.strangebee.com/thehive/api-docs/[TheHive (v1) REST API] to create cases and alerts. + +[float] +[[define-thehive-ui]] +=== Create connectors in {kib} + +You can create connectors in *{stack-manage-app} > {connectors-ui}* +or as needed when you're creating a rule. For example: + +[role="screenshot"] +image::management/connectors/images/thehive-connector.png[TheHive connector] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +[float] +[[thehive-connector-configuration]] +==== Connector configuration + +TheHive connectors have the following configuration properties: + +Name:: The name of the connector. +Organisation:: Organisation name in which user intends to create cases or alerts. +URL:: TheHive instance URL. +API Key:: TheHive API key for authentication. + +[float] +[[TheHive-action-configuration]] +=== Test connectors + +You can test connectors for creating a case or an alert with the <> or +as you're creating or editing the connector in {kib}. For example: + +[role="screenshot"] +image::management/connectors/images/thehive-params-case-test.png[TheHive case params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +[role="screenshot"] +image::management/connectors/images/thehive-params-alert-test.png[TheHive alert params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +TheHive actions have the following configuration properties. + +EventAction:: Action that will be performed in thehive. Supported actions are Create Case (default) and Create Alert. +Title:: Title of the incident. +Description:: The details about the incident. +Severity:: Severity of the incident. This can be one of `LOW`, `MEDIUM`(default), `HIGH` or `CRITICAL`. +TLP:: Traffic Light Protocol designation for the incident. This can be one of `CLEAR`, `GREEN`, `AMBER`(default), `AMBER+STRICT` or `RED`. +Tags:: The keywords or tags about the incident. +Additional comments:: Additional information about the Case. +Type:: Type of the Alert. +Source:: Source of the Alert. +Source Reference:: Source reference of the Alert. + +[float] +[[thehive-connector-networking-configuration]] +=== Connector networking configuration + +Use the <> to customize connector networking configurations, such as proxies, certificates, or TLS settings. You can set configurations that apply to all your connectors or use `xpack.actions.customHostSettings` to set per-host configurations. + +[float] +[[configure-thehive]] +=== Configure TheHive + +To generate an API Key in TheHive: + +1. Log in to your TheHive instance. +2. Open profile tab and select the settings. +3. Go to *API Key*. +4. Click *Create* if no API key has been created previously; otherwise, you can view the API key by clicking on *Reveal*. +5. Copy the *API key* value to configure the connector in {kib}. \ No newline at end of file diff --git a/docs/management/connectors/images/thehive-connector.png b/docs/management/connectors/images/thehive-connector.png new file mode 100644 index 0000000000000000000000000000000000000000..592307023172eb51869d15d89c8673a6a5210153 GIT binary patch literal 58661 zcmb@ucQ~8x+dtl^Z(6iel@@KSqNFuz7cI40d#CnDiP|ySYOBNEdz3`gp0OfSTYD1( zp+$&@lpu)lyY>Bfp1;1w@%!WXIF1~-6S?kdp4WAruk-b~KRnUXU}WHAIC0_xqvqp> z1}9FOMggy@|Ih*7oV)OH9e6wGZJ?ogqOAWq0r+snSw&an#EHt7^ZT}EfzR}wkIlVL zoM37{ex2-qesMT)VxdvV9G*YEcsX@*^VIRh^p!mJxs7A|~P7 zS6i3~`PnPGVH*P~c<^d}JMoj+f7)r3g_T-pg0{rQC=-Odmqcp{v*9C3yPJ?|9g>CA zY7u(}@1&jbx#K3cIwp&P`O8}l%YqNaI))2gs7bgF{f2Mgr+Cs@r*cENBuEAq|Lv`5 zq<5$8gKPa6N}W8yn=#%4vOKuZsoq0#KXWIfuS1LOkS6c^`9l))N; znEEpQ&XL#_d8dozVyrf{u3;!UzOrb(Gc^X@prN~eF2i18=?wdtGK>g0{?gum) zt;lxVg0efRWd6sGA18KpzW>B&Ff}!0`TF(ihs@}Ak2>UnLk{@v-fdMtPv7Fr=Mxjd zgz_U4l$4aVi)*guVV>$~>?$fK@CgfR1%@7!?r+l`2$`0Z1kX^;yH4MBR<}3}-2bJv z$};MVyx(eKVNQsOw8ylAepll;?qva;vIa!$Li{P+|VQMRAkYqHqKGFoN&at>NCF-ybv)8j2N~Wie}0Q>7q4z5`EC+skYVTx z*Q5l%n5;2aKcDLwCuO&Jh?w@r^cN?yc8!ff$2u)I* zl?eUDCmXy|0V^dEhZ_b;OB7 z={Iz;B_v`p$B%^V)^(pMn5<)xly>S>X1T)cVIv<6*@NU87bg!_wu{CK>T_3Hsf%r2 zT~TJHF<*}*j5v30EQ$=!Y&u%F6$5qDqYgXvf<|@Fl<27f1NcXLN358&oMzjpv%Nk^ zlY7MRauJ!CQT31PB2IML-iVM+c2OIW za9ujEcVgl5kDdahWHD=x0on1c1i^%+nIq4Ys@bDKdDySdKe`yN$>>)zh18%L)HZy<-UMFCp9)%CFO;y#QL1SUydEDP zzv^F5|HOQAy}yZvFGtR|J5=N2X2*l^d(Y->82T+ySLWLycyyHl{=^>id&lhh&r-Fr z@4ITf8&~hpLkgCI(QTni<|$qmwiNaUi}l|*_AR3bRH*~CC4xAUxjDMu--?;3KL=#J z)N5Xy&@>%tx+1v*IX(z-VH9Kab0_!KXt=7dqmx11=5kt_G0IyKwSG((*~E2Lfo>Yl z_T5?sj-jVX$~%AT2}e^W40{N|*zxfOC;x-JD7&+=G~Ee#?J8pw>H#fG9S|pI3-Mzp0wc)d%14E5QKWY1) zcd9|r7QZ&7pHJ|iw~s{IA!?AaprZ-X{!CeW{e~KdT8`x_%&NY+qj5BK#p}a|$)>WZ z#?Y+t9!I~>tt%G1>e2CzJth-|K)+tr{DU&(aOdP=;}BA&l0@^>nWRF(YRMJtHH4$C zpSsS_xEkLnKf)NXV|32bGDWY-v+emL+CSpC6eVnyh${f3RXTf5-(= z-Xf*^vTto|^O|c_3F;~t`t$WB#&)LBx5}R!N;B5FJyLgve06{BH>?yM0b-W%VmEBcAfh~!`DIHr{U=+l+TZ}XoJkxUl$M63A~ zo%ZRYmc!u-JMe=VFl_e`Y&)$GXuDLOpni507@l;94cK4TSm=|nsa`@{2b0J|&&rM` zko2t%HVw0~CoRn|=)5|kF4~qPL0glDYNd(8`+{eFCW~5rpPK3^k-2~Sw(hJG$-jg= z0&CP!Cx&%2{_IHsIY$>Aj*ZJ%6M#{6lA4=YYto=&=DfkI7=Wg4#-h_Koq&C-_0?cd zQjv8rf}^>^9R86_XYR%ONG!JHho$lxS ze3EGj_!n^Mm2m|oC68b7^Er{vXwvEJ;&xV+Q3SA0KQb->VPXyumdccMx!7E5f6nk) zQA~_dAXrt+JEN^Y`uTm!)7ae514$~MNnp-zd|&@NT%XAah2t$qxYR-lqV;Njmt<bz!{yGvTJNl_-JQkHf1y;0aT8TY!vkCJZFe=^;TYRbBB%E1|d&0kH6ody~z zN=m?BqR%;No7t2&y(~FIk?65cmU$_#Ts+&xMxfW>QE;$T`XLGnsy4}@Ge9|^B$-L< zgk0M@0s=onlai&Gecz-4yA<8#fY*I*L_X0i3eo;Csn}t*B?P~Vm4X;*?2M)N0F6vt zl0|{LVIf-3%0wGnGfYQzGp{yX?xW{k)nySIol~d0Cd&<$cZHvResiGZFkUuO)TX2e z0u6ZgL&c72h&?*$B`3;)7bQj^gK&;@g?$d%E|w zYW4gF7bWBo=8B;}U+R@E1;%~VTI?>mDR7{cLBTW8SElr4e>MJ^N)=&zlOYYg_scEI zkkRcR(3cNw$aotlb7J9)Dp^X0rW**$;i}M}6|B5eSqj=bC`ut%60{zDyev&E^fhbp z$l|!%GxXdn10fB?-0}UB*Ha0`MoyUf=wQ+V3PIXBK0fbw>4|gpOpVGG`9b{0(?OXd zNnT7md=NJ?lr|tJLL49Om!dW*gc|}&Y|idtV7cwdlP6u%lelHQ9tH;o_vNbeGrJ3a zwf%W7HI{Nitt{8}ky>f~EF9PP_*|0LX62aE2%8Avf<=KC;9!VZTQFR?6PY^6SDj-Ui#>u{D~L_vn3o)#`LMW@`|v{M$#kzkv`+y( zh)hc7w5dgG04_(OJh{>1IvNqqX*Pbkfr3F^aeZ$XL`0Tn=Qp`P5` zC@x=I7tD@B&cZ;xvHYmMXx!nl*7f;KvBj%(=taCF(su9b(oun7iadPkfH?!bhy9go ziPc;bb|&`}Be}e|SBVr*VEQ}_@J7F3riLnh%sHQRrjF zEGE%(8U2x($zNWpDPkk@`OF4AxkRffpR}TZUAhAJ&eIv=u|`W!d*M z)`yBbRMw7Ro|S16LDjii_O#B1B^71|?`NLOvMRD_sKP5D`#GgcKsVDO8L~!b@Sf=) zeNzr8uhbeeJWFxw4r8(M!Gtz0h$_nJlSSC3ZIKde%_Svq-c)Px6|(Jc<5*OY?XDYO zClyXnE8;G5P?NbZ%ydgv8ekG=>d}u(Q|HF)6q{h&^O4<{(* zzph4dzQ(~}iz^PdK1)aENQj{8ppEY0yeo;v>sIW||HS^1&|_@l|A7t=zY<0=5-$n} zO5;FXnoqYASLttiMx0>_>JR;kC$t)04s54Ve{tm(QfHJ z{k;cW@NYQkiE0v7g{t=2$TsXgrN8Y{eufP;ot^G$As=ivAO3sM)M~?M*-`)RO|8gf zAC*wQXf15|*g;Qu%mA$N_ZH zzbcTVF6;THVtN|!c4x&l%M zV`PRPDpw*1O@JK!-oPS{VoPQ#9KK%EGB6EmfP2Vda&lB1B=D+uprz-Nl)qkS9 z-ZL|8L4HzgV>;nK7rQUJ%2Q;V|1o&C%W`8Oe`aAQVl6oa7ozDw^r@yt5oJ7TQ(Br? zSXmc02@-fvnQ~lQ9ME!AHMNB#cJ2g>jC5_GLk?!buNhvudwn_IFrtpd;jQ)oF%>~6 zcOdyixg{-RjproA^MAx1M-y{p&4FgCOJZ=TDs0IlsHWk}<@z1~eDN=?OoIVt~?MGF9MYhS}|98HOAK+2sYS%3Yn3aFa+g<3F@UxOx%Yx(-h-H zQqYQHAOCQ3W7t>h5O~7(E<*#PpRXb77HuvLA>jU=Em7)Xiin|>*AkPou1ZAIbwOSY z{n)dgl0T&&k(Wgb%oG#{4#g^G879=;#UwpF(utp>B&WW#L>SSFC0M61=JM3h|*RSwiC8y-M*dbOVN_ z-V-LV)H7t?p*!+KSs5Jkfs&%h&(Gf#?^wKuRDRd9;9Bk2{<9SGLP)Hw?UhqYcemf> zRVE%;n|CpL0BaJ|u~Q>S$575w%KB;Cw3 zf-{qS5srsJy?h+^++NEnV=S z`(iM!h{r0#Jx{A@{!i>l_v{N(=gY_S+fMEBg|#SbH}&gh9D=|NCM)BmLNHbDDMQ2i z(JDppKO6>QtH+)9wgmb3x?legamY2kf{OMdPlA4YV!LOHy%*M1WsH|FE7xO`=u1mh zv9ud4Gas`$9epr7@Yu*GGWckojNiL$Qd4ojL~OPkUj(6=ao5RBBD~ z{h@c*m&rNLjU)` zfn%LQHxKN(-(?Eo0(KXWOAF1Nowq4xo1M0V@jc#wHa0fHHBS{Ok1Po(OARK^MnWSk z+DE^q7yeQ!40ArqNQ#+27<+f6$i(QRDyRXoEg<#PSo)Q7N5A4QvGAnH#m$7e(hO}O zkvA?chX|ZsycahlD3WtEZj+mZBzSfTdeMaaU^?7P&v*s4eN#H+A$5DB~5sJbi8 z5o#OrF*=!t#webLB1QG-pgWR^Wje2mJRuyq)Ym^r^FeX0cIfZvd$`~_6A7DXcSYXx zCvsxe8LCdHIZdT0LMvs<0(B&<4*$%b5P2V%HgEWiK^_|>r=aldPEN~yBe-z}U3$Z& z)a{Y$P)!~iH>#KUa)a9IzL-nv^z`Bb*c7g&ZL^!SH#IJp#|-S{Mng-k-XiHk+GUjqH_trGir zA~8sBd2HSy^6UtuEK9R?>a9$Am5)`nrr`&){O`ZdCWDz)}^Rd$6+})?56iW{Kr7y70Eb6}{PB$q z>C-jXvOjPR$LvJZRTO6T$b6{_So(UyHVB>gCq(&i?}v2^`;k?A(`q3Hoz1zNR$)w4 z0>6@7)IxVyvDD&5Fh|tKkI8A?SF}ccZuPPgdY4mdL$1pO8zq~A56KNglaXdaNRnhH zVD%;RZTbpcIrLZd&RRg^YSr~2TvBE(Ip2eKRTs16<pv!A6;sfA|rndx$2K&wmzHT*Osu0{=|^kn=jkoZR;XJU_jw?KqDoA&MYd zUz2opCGWZzl1_*uCRLnTd)r-5o5)(yI#r><)leG(flR$EjkQow^*)!eWRXxjHPlMk zpfqNEbdu*}sJ)_l+SwQaW8~x9$PQbvWvwx&Gd9>Tvn_mCi`ukRgTQK^b3XHED1uKW zmuWy=P*>%jsXqN&^vcH3%_k>l@225=S%)>%y;2HQ##XXf-&^$>BS5_0yl2<_;5zf# ztfs|DK_1C*>&nk>zt74=zdg@Z(LXNDy4N@zm>`RGYx~T`;%;3SV@Vw4w6!j@5wZAm zEuA(!5Q}LX0N3dyp!)Z=u5()O#Lj{jKXp~GxbZ8#(8)p@5?Lu zncp-=!j*sC--fg!N{2tYp7!qDlpExEzS(iLqjH2t=JTz5-Q}n%Owhd?Uf{&Ff|Zd+ zGBWvo(a=a$$nsg}(}Dw-zI%|$?{CRzSW9;nF59<@ooc5^{Nl-8=kq)GZU#m?VkjJ-u3@1{N}v^9a;p8x%u zf0#ikZ3sk_t8#A-tQ~&S{?R10LZEZ#g8IZ5qCKPTIr5a7tcX!I*WhEwrP<&`Awv`2 zJaJeMEd4>Alv@*ZO{Qm|>UCs`@`M~yG|7dO{m%4U>B%yq%97QYput4z_Lz5R4d;{7 zZxeA#Z<5YnY?|YoZ{rQJP;pBg@PVdwx3@Pmxnl^KlumS1o8-|AD zNOzQ3z=N^#&))ipQg&Uwj14vUf@=hxAWJ(01f((hX7M&A<)`6!rDj(&&N4GG+~pMq zfh&Km#tid-Bz2sPV{4Z;gH$Q>{xC1#CP-8DokHLEOwBnbGmMr_?&|0!YdHBUi@d8 zxU9GX^P^+pzE6a+7;U}~$wX|*^hHWP)zgct0B5nm9NyPPpWZHxTFj+fI+P*B!Uy1Xdnj%sVPgsJ}!JdUiRaA9&tOVm*P9`<)!qOydvZ~`S zC-U4HYG(9d==zra#&;ch*T5vP8Z1>2)Czw9$&;tBT1=yNN8QG3FKlC& z$df^0MqqLnjD?y4njIToDwMzN@z->TUlN z&!%Jtz80x0;TY}DFx_7j$aPzv(VFAUL8K%U`y^eeNKj4V5E6|})}ZMVnOV)@%C}rf zw-!H(UREFp{NNb!L^;g}!1jJnhp%5Foh}(K=e;h%^`vacnVd-pI3z^qe(8%m{Kv4# z#Mm5ZTP+J@GPX1_u~LSi)AgwXmjHVyV-jOTaVqgLGm7gbkIa6)&8zvMW7T;{>pjjx zxhFE>OiBR1V@AYLP2u95(vu^2IQF@C{-p$|iM~wK;F`mDw3xM6w(swoD(p}aDjvQ^Fl6he5!hLf0J}!yu43wWaJ5) z{3*Ii!N{prr_p@|nbUIgT`~!SFAWLhc1YKhuk(h`q&bpFrQ_N6q}uo9Ut?}l_Ye`m z2fOmaEi+Mwh7u<6#HRuB-U3#8hjJ6b`JI;KN}|fty=m-WAwt>3FOz)V!B2^+X0mC! z1J&Sues{Z=x;V~*PNd=jY*bYZ()$r9NyIrjm#8n&9b@xHgottg@bJ~mS&6e3Nc9d7 z9ULsx9p1W%P!erMobeF&X&Y#5D+~pRBBL+VvU)&CBIRVXVr`YH>hF5WOjfV^G?A zYtF;bl_7{MA`G95$aGB|In3rPv$LDmp=g!XLY|Z-)4yI>0CKXpU6+xeg717It0QTc zcsE94^)?L}Hd)x%s?OXNHS!m43Rs-Q{N*wIc8xx z6D^%Eru3E2Ud)ud;1XS|8}9p3j1kS5J~cg|vsU?fI*(UlMAF@NP<%OPEBUFMSpuE% z%s@M4WAfK+uzH6|Pq0!GbyecP&Jy|DEqz7U{Zgzs$;YHDe04Yk2E%&;arfd_IqDcbP-SHv$vSmwYUOcvs(V!lr6KVIrs8$55k zr~C^LEm?n#i9x%H=h>PYWW(bz9jenyiXWf^P2L# zOQP>~1Lx)1$%<&nY06Zn*WBPGLvKzNmOIy^J?vK)}7RM5?U)h#R`Ci7X8_an0>Zkr}my*oZ2U5Zg<7)?1{$MSM;8K>>3VR!V`@n2n#AsW;Z+h`Q*h^Xq>YYQA~hlZdZ|0x|f8=pAnEH0qJ} z7k-KezlIao*qL{u)?(oy*m4B$;ct-qszq&{pP@?Ztf$ z(-4-2aUd*L0x{>KpL(fI9^nPZ99YE^mqC%Ld3BVRqgMJQ_pQ+SdBMKBAKsBC&z1klK*92vKpMEKx(ig40pqbSaK9zzjI-mA@~d)PD{eE?F!Gpet~8IZmzQt? z#SWRg9CGwI+U~Y+U3J9Uh%r;!`tw>g-0E{c3Dzfvvk-vD8ST?!i;uMec@IPDZvJjpCdr^8rHH z3{7Wn%XJ>!D3{g(ZhwIBG`2_;$HSE+7813ueI1`PTfP6aLQJEp- z9LMo;^)LvkE%7oXoQ|}aFXQafw=(A-oIiCM?w>c5F+z@^3tjTgr>2+Llz!gLQ!Br8 zFr=bkQD#+?IXzXIP;}G3Swk6O-r)Gui3T62Es={&@Atl70S^P=|8?#Mcx4N}YF6FE zFG$i)O&O|cwTK?VpjCxVe3KW<023a%ZlU3`{pU~JH_x9TyjiXr=Dcf{QSL7R~E~zrwWa z?F3|4QXGD~txHQ1hl?eoI-Dw^EPwCcoFF{VPZQ18P3Gm@GI^k6E`0Q373y?-Nj==$ z596B`(*54vGR^uNJIgh8=WarRIps+*oh1^ z<=gyfXql7ry}muZeC49|r<=}QUF#-yv)$4KgZ8e(DJ-I^m)#f)8E;z z*g2Ce|1y%v%h#*NkmboAZhGOp|HUI;xaQb6xAEJg6ugmOfjN9piH!s&OAqxZ0 zqs&Pe)5Crz8-smK%6Jblmo4DhW2+r4*;*36Y{M4EhZw9KUUu32qXJ5euVV1@_GO@r zCtA0bKaOpFO*O3k-XmDPb7m}l=e-o7%iC^x@??+#it_l-jkm-gFzw>?Cv$r18z9$o z(VWS}QMSo;!`GjbcpDg|ZEaS9nFk&iUi-@;st>gu8YEuy^?CE~`YKdW+cL5B8?!~s z8USsIbd1m1(iJcW@72_ry}mx{i7XQG!?Op5zex-h(dLyuMvYHQ&$2;qtGT*yyiA8~ zirP6S#omxWV}tz{^tS_$0lQc3mH%kB+^5uO!`Aq=H?4rqJ*qxN_)nZLtwG4FOx*r( z|4;RSuqb+3m)}24pJckE#WDCbV{3K#Hv~P~oD|B&T>dQRm?(E*F3{(PTj2b+6!TZe z93ahJRNG8n$R}o}yTYsF6`oesh5;4C!LqaIepiGjPm~`QIWb-=VlonBXXvjPIpd7h z>KPFPcb_o%ZM%lN=Iy55eZdmfefr1L1((_f{i}O}*@0z)Rp5`%$7-~{1gE(`??hrC}PG?n-E@O>|S^^lx7hEA4v)aHEJIb*^)n``09E$~N}SzqF_w-bw<-@Pw?>$B=8 zd%Ucx^-pz9Ru9OZRb&)jo6TQH=*c+DJMc>i!!hBZPMdzmn=e2=8ewzAUH5iFydmvQ zhbEc0>_WfGe2M;^ZGPL?FXWlNVhZ>%YnS!51jQ<4WO0PDAyYV^Fu3?61i9Q>->zrDf6Iz{`$|LoSa=$q0sJ=D9hLQMkUi*c0CE4$XA|ERl5B94VU z$3?$wtaH3WdjrpuxDPu;5(27j{lUBWyD|3__0RfU8e7@fx0HyB!9Zo6T`sst;%m&G z-`H=mKs^uiZUDb~kbs9UFCAN|?5n{HHGtm5+pZBE=bRm6%pJDz2%y*8ko8%vP^4o0 zm;YRXEyp=KELS|mZ&l6$uipNVND8uEf$HU;o znJ06u!~&(qy>eI+c}+(1t8wWqM$XZssEoQ!6jfP_RZuqpQaZKSa&T^8hN>(T#^tq3 z(}sUsgnRyajrV={@J-~db93f>*l=@yq*yt5geoW?&>asKaUkOkoNV@dzqW-j$sFfm zPju41!D-hVwW{o{5Xo>gT86_Qk8%%XK=G!&;*fO<4et$GDot4|%n3`_x+XoT4Zg<0 z!m=>eriL$9b8+#2k+a4Ne2cHG3Yq8k5NVqS1jm~GiV9CkJ?C|W3NXNhxm~E2b#2vu zZNiCLR!HdH(|3#t+qXA2JnQOlN0E0dxjnxL@{k6-WFCjsA2uuoun3 z4iHq7juIET7oqxW>wo^l#>2Hz@n}K4%*4GdVk*I6s^Qr$mfgdM7V+TkS0`(OViNB> zwNh1m-51Ol%oDa(yU>$jYEojw3qC4O>CHku(OMR7Q`w+?c`;nj5TW-b#ZLCVfXqKjrE#~XD z#M84DBBU!Zq~@J&ORtqt4%mKWL;=wn*|mA(q&XXk&e$+6HF&z#MAc(`zD1s~hE}Vl zhPgHP$oH%7AH>j4TOjn1buub}b39>bzCw!X$8;iGmQmB;JhBk;B?t!V37^S{m2dK6)u(BkhA-*O zva=7Xl-JsL&x?(RKbx)DQgzXQxg*#$+YEwE}C?sV5svMZg z15Agf6hI3eN2eTkFCZTkE&^X+T-BFE2yV;|8>O`}1^wy*I1Ze951= zCvdMvux||T#z?wWtCNR_pWC>gwYwuVP7PjghAQ_dCK{LBUN@%1ypLgW25t)^;U80J zi!(^DRP9hlsh2Phmn~~3tK0S`&H0Hm5yK}$P zy7(PEIZeo*prZM3g@Vv&)Nh1VI}fB}HEmC5J4AV?A&m<>Z&C&~Am*F(uI?KMUCYIZ zO8f2MTM$f1!Z2JrozoKx2D9R%0@!Q26Ie~kt;Apyd_qu@Z;IME=FV(ud6MkXaD|NB z@(tIqnq*1HlSdxJK`s11RuZSG;Gw*_O%Oo{MlMY$pdI*kwxEI>#=0Fh22s{J6>Fnq zZ_U*YHb$uUJRfXExdJZ_ikdE2WNL09Q_OohjF`bG1~5h!I(S;7M|=+U)L5};+%Q}VYgpqqplyL0>>W+!R`U6tEo_i)F*b@vH0*n)3AF7}7PYhR z1cGC_p**!|%;_8B%9y6Lx^bz6d3?#mDk!*n?kWFA_Jlfj56PQ`5OD2>b$|iPf9saP zty|S$;L&R(4^~{dOSF>i+;r?ttWsUM64VVBF>FkH^X^^2c2qQw%7OQoqz`f*r{zrg zumfUQ^6ztUa$q4sk~(4*_w_kt+;1)l8^voS6{llyy>13e{pmNWo%Is(@&&rEqURP4 zV$g|T+TpXa&DAQf1FZ3IS%@=-l2cXU9b@&CQTH3+5it@(Ze`zWpVi<9l!FE5M`#np z#=P1!CwOm+D(<)W&t#<{T%YtBR_!?Sx&iUiuMdAPTrkO47#QNGDa<9eB7-)Tce4uE6vjy9icq2ZQJp6cu&n4sIu&SH%ZuZw@{?I{~n$U~4TC1#MSZy5@r2Qyh;hllI5j^TSPu9clU ztY8}@VoDuBan($#v(9IcTn5E@A%>qy4os!2vXw%*ZGPWz#}>cZ`H7Lye!MpAh3|D0 zvTn>wylrkF4aGhK@eh|g0{Gl-U7%1N?->VU6kQbDqBb4T;2lDQwW+?$9dWB&RnOKg@78z7 zJAtc+HEi4ZIkNU>t-RAVgj@qGvy33H+B-HilmY<|&!QGHz4AEhLH%`;QZwNuzvn60 zIbkl}ezneq)*(a_N;71z+L`eDJH!Xi@-+_mp{TtQSn$qTF(RfUhYTN=lOK6iY5*B4 z^)4EJ+7f)$0J4ojHUF4ZhU4WW;94n3=bDsVx%2X13qyiADVB0RdLwc-M5?N0v!G<* zsPvjx|Ji`27Lp|bWYli_>TC+iiF|C9@LQWUrxrJ~$QD_3Fxygu0@;a-aLr*e^eRV0*8hghwd^5A*eD4=bsb%fmeBC8r*fld4 zk#19Z=~C#YoKhZQ?pWL{Kr0gOsLh_`EpoH;RbP@wiVupaB5^;FqBj1b;eK5cWITFQ zoWOjYz47c*&&Tga%Phg^vx8b|jkM4-x!qaGzKW6a^4@aooNFbk{?DGpD(ro)-XJG7 zh!fCn<3(;fpkv(mk$CKoda?mW$MDpm&)O(1GQI@KFl?d6SSy!0LWG9i82f6RjL^N7 z?g+traRR{hj$E`3*n(tOjHCg6@H5x29$f?n89sPe%6oL!@y5j(i4!aG&jA;_n5VodumB{xFwM}>PrGj-pnDav)+X<^0Iu@e(hB-;9x_;8{#-fHvzkq<43)@X4uPex9>k>xP03s z{Sy#e$s7-J(atvPLrooZNP}CF_i7CHK{Wt#@L^|k6=_#k#{joCkes`#L*!WyycH(e8fV8>76JyFYxy48!k^FMR*Nx}8}g znX5$E%opDouyqRJ0)~I;9BQxIn*;YvNTkt+a2rN>*PZ3B%m z^NJSySF}rBX=j*2MwV9Jb!=Yg&*5bgHi@(Qp*>+}U{oA892P)Y@eW^QEX8)Z`%jC4 z6b>pzzgk(@1S6VUv6d+OF70}Xu(5i&BvfcX!4DmIReA#8jmZsSh)`-Q!BDV!i3ct6 zmVqrfxfF}_2Hm-Rdty4?6%$!hbl@rP*J!PkC@jj1n=;3R5&2zlRkf~kS#f|zv~9gM zx+rFj^s5~$w!tb$yIX+(DMkf-FE9l!iv#%Nk-9(lN0e$me9#ow%qq_*5KD=Tb$M;? zrrf7p;Wfrdpv;$V93a7|oc5&I;#xPtxjuuy?%J;ELq-M$78Kk-y=L14vg)I_l;88{ z;los=o|7jh$=5P3F%AfYD`kmhVE(l1Zfk6G_Y9<4P*%;*q}YSijUnvtV`4p>#gYxV zA*g|{J2Jm;wNg$%4tqfde$pC(uwGQ9k?PT&4<2lX{ZnL<_x|8G{H(t}x{1Hb7{VzB zlDr!$Rw#v*y%aDq;=9r)!O*PLgK!*m6hGgrwPbz!kFd4gc#`2OQ@CWfqNOw=hJpvh z18}GI?uH|&$dsKiST4R&u0xNah^c4?cnYISew$_yi_f)Vc~n(MLrb0YoHtud~u#VERI8sj)BC)`p;~;Pa&s{sf7S+DgqR% zC1QHKLCt(G*4H1KoEm&7C8w**x@mDsW;Qv5MxD~OnYqQwyEsJ-?E3y8;V^e!N%%2YStA?;c;Lu&&XfAoNkgyegnHm={eyz(X=~9r#lU0rmfMy9ZTV;?RC}qgo z42bo1Lt1&neI_2$pJz@1qJ@uuYi>Rs$x-dl_WEtSa#$#bbnu%C+zJoBs}&e^#s6Mh zbab;7G<#J82yVq;-I4?vrQC^Yv4pgjSG`Z|E61I<3U5y`0MVg~AqeXX6&Jujbp^^B*E2 zBCad?^}Nv_YTX>^`?98<>pQ}y8>5O03ws6*_IjopDBnBA1#73H zsY?SX`iES~ZP(G$4QeI3j~u&(9fzB!`uKD2#NpZ*2fif(Pw{}XPqR(+EFUym0dc%x zpO|B^VhOBeCcjjs(`}D$MQy6~!+uuF7e?=-nIk>xZeXY_T}S;^+lji_Nh&wX({QC|#|@SQ&LqCUIvc0Jb$Ivsj+TTVU|xBZ5GQQ9iiy;}d&I zHzp|hy3#GhBWmgM=^D+SQCy&#ZEXTOOI@sPJZIw`eHs9-y?_4??4ZxTWd5F(+t^pv zQ~u>R<#e9YD;BiUHj`Pj1HoXj8CE=r*`ZHx7|2!>_gc6uQjwGVi}z5P_CoEGlhBce|F?FzKtzNF zUktZ|bc4&fc$djt>7IzU5rXKQ4M>;29}2Yl=U00d1XCtK_U|3<0N|@W$*iT+Beim+ zu62M*)54}?xRj#+6K?>d3x$&@;IIQl-L#t3MrJPgne1j~GH&!B>>vuM%OT;F=VfUt z1p|b@Ivhp->SR6}&a9jsythdhr4n0~Y2&4qInyPNibw5>Xd9DD;5086SWWNVG((e9 z2mt4Gkx@raH(2!*Gp=8`;)FN`uv%;T`9lIrNe-@wPNkPHZDpJ?9(;ht#>oQk_u=YM zZdLY6VjrTSq(+2|zurg^F%=(BQ&ZDrB>+;p$HBpwX$XqjScz>qf$rQ{&9q_oLT{P` z%cV=MbbVS{T8WI;q_t*q!tODPJWdCMdm+~q1C7$9U2oxVHcnhr|0Yu)0J(~L>(XR6< zdbnvoAK@o+@8-R&2|yzJ=&RY{Aa$ubsYl`C6;ZqHd>YHuR4<*a`)+Bq$!0Wxqfumz zE0gya~i%R}}~fA__xcUc=&gM`K1yyX>R(`j62cqycTkh_r-Pix)&< zt&HlEpurQS1pumIH*elFKq@$Y`Hq+j7{JI#KqSlP;}m``EdhQL!;HF- zQNc8z=7z0J7WU0|334Z#TSM!+jhFH7QkEooxT?G7DqhSB7+(8Yn@b?vFocy11`FKL z`Nn&zS5lOQ@3Sm;*7myS7uBFIQ+8_9=?I6;*(6SBG%D}sUcU3BH9JF*a|d9_rsTHX zfc<$zGmKHAM)1UYP-lXMh{qns%i<_))pEM_CUT1*gPHBhv05iad3X9vr_5}iDK3P3 z`dB_R49JLB1E9}g;fh|Ae@23lrsX`xFxPNp2yOIfBIHu;m| z2GFZQGP7RAC6;IvAVnZdg1S26V`%=(IvIyK!P~p0;?Tp_qxd$rwr@Y2_y_*!Jd$tom;2Z&4=~ePs_51{gB5p&du4H4vubQ@kM68Fai2YGA<1O;Wx z7u6C`Vf$UzPb*%LdpS{Dj&@BqI`%PM%6m?R1d1erKkBLQ)Y zG?mx~XhdD<4!V0lT0Pe+It6n=#r&sXF(o$DCQ57TuSUzk<2EDkt`tytGj+`pkweV# zpAqS^sELPBuB5uV)$Qb#=LN z(0*e(qdem;UVUP%m3ML|dgATo73IK~iPqK|Bju93SgPqcD+VSe=@4tsxl-9Nl~Ukg z3gWBUK`pyj^!LNz|9$d?uemDJzyP!MI&p0AZ`twe;+vF)eI?Y37av$SxImRJ=;Kpk z`M&>sgomd-H>SO3%5Xmz#eX*?W@j%D-s^cY<*m((R)9mv!-qdVpmUJ0<^F`f&%|ie zH7_z1k+-*F?d82aX!`o}!FRQhicj@-Hs3w^uH|5qxxlo}4(1sz3_7m4HHnoy=eF}V zJHETErmC;^SBp||wCSoo*0P{5e^>(kc6P78{E0-KlUm>VvfuH8*Vq!CrAx5>=U?!& zC)IgQ4^3ZaE=R@aOUnN%J8E&29*tZUE~7`$e-H{cIljw@GDte36mR24?f5So!k?a| z|NYWL+GjD!yXZgp+yBF}Oni0U{eQf@cUY5MyY}h(Sa?v8M-fpJEEJ_HRl10D0@6E( zbOHn^QbQ~VDpI8Ph(Hh_L`vvQQ7IvG66w8$q688l1ZD-_y}xgcnQu?o$Bh4}kleYm z?zOIRp1*6nA%xQY^0WiMTy$jnJernPpy0{Oc19xS%A(IOuy##t5>he{DzJXRZ*WP@;SUX&XM?<9j7IVK!kM6 zl>#IF_GC+U=S1dXSt1<$LDLJ72b-91TpQhv7lYX{s_lV+nPXjR zHX(U-`%lHaC+*5Yx3$}m%9yjl=FP$had9`u9K!KnpGX~Uf;EwNcr^BSG-A1vHs(5B zH#H$GTHmDkI_-vQ8CL0uSX71OZZ6Zy2TDv-fzP8e6boAq+g>4KAln<3b3K-`FQSqy zaTF<%Lf~RtqM*gyzYZT>4amzM`Mp+$bX;$2FqJjKeH?`5g*C`cFQgbokXIvLN5#sJ ze(yzIcG^t+-)0(uQCbZ3MH9>Qy^V`((zA+V}pd6kQy1n5XbRqJ@YLm7dzart=YxdF- zdHsIN2cXH*p4~um{53sCDE;BmRgf%0uNiJ|0c}NF>u11b5WF?~dxgBOm90?Y)cf9P zLluL>*ARVKz^c6x5}%;<{{0Le@9i0d`2LdRo(wTNZdv&98|E_V1fnXj{I0sU_b28C z31|FU8~Fg8B}2Y^e^T<93l{{M=t`O&O^bxpj&n$_+ckzJ)o*u28Wuc;fOEm>E3s80 z?9(&rwYYx@@8@JP%X0#kXrPts`TB5+vIB{9CL1xO0;oRu9)A5RY9~Dr}^eVi6dZT9V>eT z5|Q;Li@xeds_k5t5|?WGqd?;T*0{t6f^2EhN2{vJ?J4_?#3g5&8lP>eR`;s+9a|rSROShC%eU<-` zD=38Y$a{_Q0*=7-JexXc!}>sjLWiS?a2jhHz?|gSHprnKmjocgeDsO(v)LhQi4VWM z*2cP<2bgNh%ifLOGI?Cm=f&{r{o7{ zM*)ue)7@W{p7MENbUFU>a{X}f*48a-oXz_3H9(P(%#jqLS>$zPqR%st){bV`~>yL5kcp! zrvzzIh;mg|QZ`R(johP+JYk#4zT-PAyr4abf*=L_mj@C7xI$e!Nl;~yu?>pG7pbmG zh2XBSD03QeRZztAT|c;$6$3ky@U2_7Odk}bpqM?>>YVRq?8_Nf-8H^bVoebedSZFi ziV3Z)t8I}Y0mmF0ioPgaP@)PAWaU=4=8v94`(+b&-EaE##1-ZQf>iJfJG-ZT!>8hv zNP;>O6I0sfhL}sT?(Skz@1)Dd6D-)~-?-wbAUV%3W%oHT+wG#y&dki59IF&Ma(sYg zwsT3M%pZ5Erqroi@^j6-S7!`y!wZ1IGi=nQsA2hC+({b;s{X429Jc&IG>p@5rGMmT z&wICYag#zrt;XH=k<80!Lluvr{P%=(3gh`!1q2qfiP+C_u^OkcxJv6(8fw$_%)zZV zG`Z}DkFaH_v2006W3*u|Vac9U?F45}qsu#%P^#a39;SuH8jGA9UFZ=Rui=Nb80%6? zK~ur&YUo1+lAq{`?!}T#IX3B@O?%%%Ol#glsyjaxuV?^JNumE>*te>&UWlB3jYi|n zPKa#aWL!gXaj(_w7cX9BY4W4 zb%E{mx8_5KhjUw1N;m1w<=PoYC}e(n4Y4w}z1fhWL*3SlvW~8TkfH-WjI#Ex)^WT|Y}YXWmfK-Pn$@HU!&g zcZWDca}KV^UYowkXfN4b%K z4|65JIu(P#7=JWZF-6;EpfYR6nb}Zn0Qkgfw^wgi?fox{Ca(lVnHOtCYrba}4(Nwg1QtG;qCbL=zr7eY1k?|_ytnUf%*l^*UIZ84j9mb6#c-!z z1G{#`Qb|l^hRKVlv&n!e)xoN~D@>B{NX*M~1e)Q*iDM_@^_8|aGVi3@$Y~n48-AP1 zfQ9I&3Ei0Y=lr1wW6G04(ZlFtM?_hiL#QhW0pm;d>Ox(@-lS&B-dnfIobkaDPZE-4 z!A3*3oCqYTrilmD7M2dQel&rigQg0fBb+MM-YhPDqfswQ+8b2mlG2sn$tN$!^-0%%J#dQEI)a_5-4ArX$EJ_yoiKx9qpe0ti_VVHn zY1FP!$3u*+ec_plkzY?i;bHNhbI3OcHGW&SwjA$#hu?+sCpUcdUrYP6D?xkTWEjm7 zi(%FV#cmxjB_70{)R#>jLgaYK!WPjs(Z>nrSPO1Ovmr8cw?x>g@{5NCF~3SRn1)XQ z44=f$*_LBJ?q&y1pI-X)i=%K(_QBD)fC)Gc ztDjFH4jL5Ijg6Rq^wGgre{OMcalqGqc~*eTZL=Q&R3NO1-mw*RHP|4A_TvdOMZ?E= zFec>=6qgCpuh_qzdbW+j^=QkR7F$!->P-qot9-n;FI}1IA1xZpIiTmGPCmAO*hP|2 zl&2)J2k)LaGY!G#m7iGE&=zskW~m&X7sz!;8mjH@$K8b*Z^4{NK3k9Q9h9=#H)|;| z!K=D7ap=S(@QSOuF(Q4@gs)#6WglNNe9ggJT3~IP!ncV^Vf&LAd9V8VFV1lbgCG0v z;R;1V79o0i=k)biPm`1xFV^he)e*tZQ`_dm+Q7}tJw3N1@QPXMKNP##fAW#`WNNJ-saRGKi;yX z=&1~ETX*&nC$Rkpii#Rs!xolr$=njqijUVkpf^s&_$4}XRcJ#WW3|eKpn(jd)}>wn7Slgl&qFV3Z)k^um)J+ZLg zX}tcZ&(5Y>p+$M^=s;yOsJ&V#D7H@$pXF-Ac7S36e~~FVxlx(I*vW=`8J~M{dAcmI za45L<{kOh3O3B8~MHsFc(TdBgA?J0+D(e7HTRbSTSigV&ekC6RKYZ*IsK_V_+wHH* zgOVM#z_vkLXnnw~AHOLx9;(5BlcBb^x3$(rYxpE3jql#Q+bPL-n`+J);Z3iJQdOmu zUxh0_N{!Mb-GmvH4)v24uVI=fWz6~@dcS{+h5H)F1}X9-p7!?k`~%3_K^y6=e`Q(( z@$lq!Za!yX)dC4z8tN&vK&ioTZDl_XKueq%k{?hFIKUF5L?0V^ir5vHxvnb-u@9mD zL=P0&=x8SkUyZ&fC7BbjgOC7=Q5ygwzYK7@{~WGxD=%bT101(B$)JzSHu=R=Miqa{ zU1T=$a@aNb$7}J|)Q;<{E5YZ8c8C-p^V9xzoSpaf?H|Gg*wlU6e2A>W(K>4&@ax7j zB`FtgfPD9!psBDJkQ_S8VqyDX+jA)spsr8vl*UqOtcS!4M~ojmdL-_-gV>-`;h?hJ z;Zy%~fr3X#GC#=5H;8{Uv$ibwjlUiC`#uiS`1Uj+_6*>o{Mw+zQ^)r;fudGe z%H25SzL0T&82L?F`L}*tznmW5HyjREeDK7Ie&x%d0v6fE$dji4{-zYP)7}b!V4j2K zMWTR>wq=zUA3j4Sfsy{sm+!|Qujnh2pD}9!5rLG{w*-Fuw>EVFIy2u{_-M~Po;~{ua(@?8UY&>7h0NOb=uCMQ z46D-S2VJC6i!SR4Rv9NdS9F~UJ=ip8!^IqU!fD+<9)T3+5ukl~pymb}>m1MyAN?jj z!f6y8K)1>_#dC^3oW0>|T{j*Sc~;0&64z=@-QVW0t-QM%JcNZ->84Anok3qI#$aOu zA}Lj=90Tl%DE=@)l^+U!_1 z(U~J<+oks8O~Jl(BvH-?5YTpF-Ad#{i?u;BTk(E=%N!`FauwCI3?oR#UXr~wvAZr* zjt5Qnc2(AK*k4#LGkms0x8yfb+xpa#mv;(Q#%t7q;rr2PGNE5J%aMvrySn25V=A%w zl&Q8o#uKPFN9r6ynw+~bJ>3I1L^2(cXzh|%yf*Zq)TnYF{c4)D)3eT9*vHM^i*SKB z~B*p#(Lh8h(y-T1^PZvtyf0Gcy zHG=EPs+;YZQl-Q*wiWL%GbjJFV^`481DsPC!1>hrt$`(Mesh}d~D1;p*z@fIXX8;n_c2 zA29PBf|?-pIpI1N}D}Ms!mf)*C|r|4IR6vC}|(jGf533k`?UL-90z-E>|dG}-XegMan2*01X~@Q)J@1$;#NxzRRuNIEhLZ;9Iz@8e2bzO(BSFdCXbP0k$+g~7Uka}p6Sv;6 ziz7oQZURQAinQdnD{E$@)87nZt<=5h8p=n5p-gOTNcb9`+vx1ELX(20p&LsasP7gu zL-`IGfQ06)8POvkpyqMQxpvJ=ucPBu7g8DN_I>poJO>}#v|A3Q>BHWPuln|7&>aEw5zQfSCp>wZ%#p70I&-N)+r+&M@Nv_7 zv$!hSA3Za|yTvP(m7fartJ(1bsx#wrof%H)<8sOs5xv_K1xttJch4ohM4RT&WuMNnlps zQQz}a-(iV;dDGs2$zhpB7DJt@@EVgtC1XOjeNfCR-ys?3C*?!Ms!$8;Hs+&2LoK$X zTM_=!NyaVjoJ~4pVBkfMtGAvJ&R9vWz zQCFNtK5`CP<=MrFDW_fLtza!b_2!`F&G_&|!xdg40CkqhEQ1WAib3oSnbT&6yzEDP(pREq`ZX+s)zG^_I?+(Snh`Hj;!G8~_tV%CA*OI7 zl$1rq&op|mYOO0Nzlzk*?q9MRp72)6^7f^29>NYW<7t6)hq$l$tIwGXBTNy+sWPyq3cGDH&QCr?&E5XKT$H_^AU-FU!MO*T zas_VZxLrNuYy)R%mz0gjOZQ(QlNDl%^$ql^<-^-5*{b)9-n=SaPi zcTH>B-VyS(29R{9xCk}Jy_CL1*ek4kRTn4zcvK>X6 z6#cZV6O98K%QXLB=T;t}OSIL{abk&;7;6Ax^rZJ728J9M_L;j;9`VqVQBgZ|Xz0;5 zAhA-GdqT{BHMzKZA_J!lYN*+DeBX~(fYbPjLu+(OUEMez591N^+S&m4E^A<5Anx&76O=_84-ysMn6-r` zPoF!k)^7bt8J`G&HR_&f{a-4Bk{)d?-nyU=_)<4b95OvU4S@Bq5y~q6fII=~(#^fJ zy(J^=(xq^KP&+iq$!&Tbzx@$^ve=?dFFSPEq8%xQ1i}>+U?xcgRtbZ>UFe7K@Ni9} z?SvL!(yr#)=&-5)e-{Y$UowdAThsKOqF7K;xEr=YZkTOPXdQ3{A*{}wJ5PoVwwq?D zf2S9Oz`YF|%xC4tqa}x%2PiEg#ge4NLDH1?0=bz@Q*4z`%9dT9yJW1xM!N z$KwG@GkM5&UYow-Ki4zW0-8iuF35R=&vtQL(}=m0fcPGh@Ygok>a+NOhr{PDY8yCA zQyTU9*Vc>&=&R(#QiplCX>AfvJ>jY*DDha+zr`2N%9JO7dFg$X%Q(M#C>Z62IXq%08%P2W#O-Q85p;OKJ=CU z<>P1D#1aa06B?EG1?^2;MO~7jdl*W}Llz5QUxo)V?cUPll=iri1zUCff>Z_PD!Qji zm9jD>^CNPjy!GynJBNYaOaM^4H#(v zMi5A0J7xqLXq(tCg;kPXqivh3@X60>{GFg!YQnFdEof9sT`J?uv_p`&E^Je86q*#F z*lqPSfe`35lHChB@WJcua+wouu3AcwaC{nQQxe}b-zkTSSS0}s33&mK`#&F?}*76`DbSiWGW$1g7fF8L5nm37{-`5e>6p{8|B2Oz&U8+P=n8OCHicXV2b1TaMlM_WE3sfKfqOIx$Vc zMeFXv_w0sjtNHghr9Z&NNmEny=_W$xqk8(ZbL(SuX5)cmE+C-?AhZarqKj7P{GFVc zFzlBPDJGSO?|VWGv|V1EWKdZP|0*1P4-Jp3@*ETOClPL8Yd-?)Y2UjrxY5Y!jo39P zI$cWF;`#GudAdu6{Qqd&yO+Mr_#{tG)CuPh`}n}@&K58V<9f5uTj-yDOg{opK5w}XBS{?#ie@W{x;J)*_?D;vI@+KD2Vx;*?Ws| zF^3#ub<$-Px{-FAQXW@;{p%L|F;~U0!1-cXpkfI%I#Kl8EWZGsBlV9v*;&KLse!l- znwB7Z$G^Rm@1#*5k6GN01BApB=W$`TM6 z`cLMAu**^XM%eBvm)RM8r4-~2CE-BA6Skn}MyM$CTj~>NNr%8;83Z(+9KWS4h?9Kf z-{~vU?tE+BF)ubG^a^mONcL}GBB=jEU(+Q2LOQm@ytKZax~JhhyRa$uc=5{RdTLBszzCPk zT;#ZNM}oeBD68`3Asz{ahUm_z4NZddcTp5Rfrp0&%qMAkGah=?@*dh|i%QkE2?Ghz z#yHdEwBA(z`#19GFR&=n46TKG2y+{xv`W{9OiOO|^XDJzp$eP`RA`!nP}I!r#YxLi z#K9ySpK*5PhN1k!>J7j9ihL&ERLv0rdhTmG+s2GOouP~E12qPS-9aY}c-Q&wVhAtw zfPQM1GIGc_|6gc#z(I)Bk~q-MA1bk9RJPsK{n%3mH{CBED2XJXD^AyEb!GVE!qscG-C-Dv%`ri=mi0Ul*&nc!bKJ=ls zPABFDhU!J);IAc_MU6q^xT`#!Gd|Usq=T5pI>-#Z$O6h-vA8Lp5=LL_koQ~=uhq&z z3Vu)3-tdauyTBkP%n+IBau}#AW2CxIunXT9s~r>Zw48Wg3uc0J02Q@U->lo2%g7r* zg)NvR9}qV=c}2pGOTk<7*uIo?rRUhyoSdsZ6Ad;Xh8jM@&3$#BhB8C_`vSOMy=wPa zxtDiV?94;`n)*K>7zBKiM~?k=uEW4UmNL6pD8UG3YWDZ)@~zVhO+TJ;sf!hY6b$8OH-vff_Kc^EQO7TxZxk z4fC}%KR?@+ux6Nv4ozcaxo8j{fKs?8L8U7QMc*E2< z6do#WbMQvZaO*92aKDbOZlV;FpdB?20ae^kit*cb@9q>@6(oBMRoVcJl`a}6!807U zUz^Nh``%fV88ed){0ftMB{e-o|B@CK)@@uLG)tF+X3XdGgW{9!O|S9#(otXH>c&!m zV!r=~)!eHB+eEj17cJ}1`i6r?;7MKG{9O#?1O+I50>*<)&WZe9&(w&G9cM=%l*>C6 zsiRjDu1CM^MeGY?$&Fv5@6h={czKTF&Dft6d)(SLhe#xvX(!*fN|N?aC?HVU-sYnC z%~e+m`wM{la)zIm1IVG?tc=p9Xif`VSqxu)(;st17w9xzJpa!Xkch^^L#W^e6sHj? zSEI{<`b{s0JBHu8cTd~U5Rmih^lXFYJV9cSW8pkn{c6J?R&4HPPRjug0#v*5L3C;h zPy*8Z;+Fq;M{}x-7EYPo$BP6^5N-D80q@gHi_HzTXJ-PO20sq^7}bgP6@P&cCL$A~`th9Dp3Wf#rPf!`X%g}9ddkd?-1O#cK{%3Vov&K4d_<4^Z zZs;DLJyG$+nX=YT5*8KJt8ajJ)evDoJPkx>+7BMwaZmL}NmT~q8KPjA48eL-$*=m#=yAq->fA0i6<>C;K& z0M5t5hiofmjDLMXO8QT6;^eyLV_{LH-hXTS#$EXbJU(3LH30`beYc(u2qL?}za1bz z07m71(ls)?HFhAY`PpXv>g5|Y1e9rOYR=QiE5(D0@fMRl+yY?ld~9}^Gw}Igy0u(f z67DWW)!O>~J@r-{59?<`LE?Y*`s?^g%b}$Ok_c!sekBt9;X(gXcMpWv2Mcsk`^x|A zp=>p?$!fK)fB-~GQ@5hv z>i0bS2-A>nnHWF6Dd2j5qxX)2&!Qf#R#~thHbH2yCMG84?VX~PU_DW_$Hb(>L|!2? zSR6}6mDXU#T)xU!IwfN>UHa~pAv z4Gq0oPA(v7MzLh%oepVQEZQB7K6O)B<8;q5zQ$E;B94gS$5&-D7Wu5pmfvgF|E(w;+CU+F1NTH_@Hb+hF(`1P`#vccQ9j0Gqj z6TD7@3-qP5Y?r6*?aKC(v@k_)=52J!e7OBC`!17HO#r}LhYY(0&grD-zIlArc%S7? z*#4`a-5&B`0n3WGH`@Wr?9BgIvhl_XqrF^(x0a2&eOA4JFr-e)x!cO4srC~%4=%`u zrC*W{>&t!?#eVuUpOZF63PB`eK9FU0O_9m5&zOZHm~Kr6^FC$)HTEVeJWRk3$iD@fSo+QNp@2Kz_0t7_k`H9P`f0=SeT2u@G;;k5 zoZ}mIB@PiAKFim@Gn7961d8@_OR%uf!2&SMx)~I)GKOdaGzE04N^Eoz4!s{g*PXN9_S%Gm51Yqj zw1&B8v#lbovTJrul3AOA_{`AJRk+ z`x5N~h8&{CJ(u+JTdk^;N9D$YJ;130YQQyp4!KfKt{0cN%96w9rKJ|2G9JkhThLcX zBxYr_L0{d1l%)WV!De}vBgP4G;?p-Gv#X2>x-?x~UwVwzj}_}6GTVxUcT~SuTp**N zZn?4)+Nn!r9cQiqlC=}5(q<#p-?m^+hM2iN{q1-Z5`%Huk@K2HpqQOq*ZwJoE1K@( zEw2EU`7ONNRz0!EW=u|*3D0^VSdW!URuK4cQH`xb+c8NFV?dHMALubss`1ebojR|u zti*KmScfpw%JLYCcrZjaONqGBQp;kcJA^$q@@TipV?>}7ba)+q-zEpOIV1@oO_Z`Q z#KLT-;(YDOSqzp2zLE>UtBWoV=U3F`Ir|Z@m-dh41vL6%x~M^eba|)M{->%M zoeQorC{IGj2^6>T0)hu#KUY|Fjlt3Ww}=14iSGq>?3bc7~DK1xRs@O%Ml54L}$e4f`S4>KsbUsCaV zXGaqN>+z~hclPAGQ#-2~cPl*z_bnySVhP1@AMEx9_6|p)#iB=aZ$9o3Bg!c@JXAZ8 z;azB7WcZ@&Oq|N!{wOleoef&2uoD&;ZB>~e)`W(6JVa6V9m1KpwN>|I2ujNwKOcL- z;?9$FmZ-|7ZYrP4E5M_`)0~Rh-fT~BF{En6h9JY9Lw*E)7~$f7xhw4&F1u&Rro6KL z%k*95_2`QO$+g&z9f3l5q?7 zoCpBcm5qO|%h-)((E{ZcDPh0e-U&pm8P)sSet*+XI3& z?T{|FK+J1b2^q6L-#Tp2-n~VwyMPd`*tV|s(g`L-by9J22|07FJEb-AFpEHvP@)d` z#l%(k_GS)o*^cLXT2SrUH$YZ;X}|_57-!~(2jOdU@)fpbE*g-NpEnW}d7hoIPw$!7 z92sa_0E~E+EX?L_=nrJogS4USoKW!0S3lAB$r<@d*F zzpcoDv=$5m-Flencgt>XQ>UV;DrI{kufkaiYVK}|@tBwI7xULi!MtT!uY-7b^qQwj ze-s*9TN8ez<Z7f z@bTA#Mz>{PdN(IK z%4(!4xq!Q&d*C0p=)_|=t=x?xm%z59@7H8TfQ_bep4v=(+_Zm6!VVhjkNjv>==yAT zynzWHd(zWVT8c&qq=w`f(WIx7K0lEgzHA1~?8>fsY&ayR9MkxEK`075MKykxLf}ow z;DVPocB1rC#b!)|%x)6zrn3JnzVm1@v?2ln##*2#R^3G<@(n-*-tH`PZ@H#tV`+vN zir8Yxwh1u|OVrZSP9Bk;@aV{G1y=du=QkC19+(A9zYWPZjTY=H3*srXtBNSx^6h~x z004gaoxF97DfU^hO~B*E=WEZsR4?c2oRd+}t>bb%@eHia?%ZsBMyL^-*^dzAuzGs) z_6^OoDy9C~J*(nof2-Dv;-BTsq1h&;xIz`vSiGQ+HwhcoDQ9LG0T7x#KYI+y-&Bfz zFmLP(vNEuK$?c#>i0O{YJ`A36JxR$D^9Eb)YU)apM7PqfCpGf{BMW_Lwi%oXTfqZ! z8B&EVT@8d^?jlQwat=)?UwXa=ZWsGkex-Bom5&>9%n*A01QDCC{Yw*Sl2n=Oy&Lzw zb^nD6cd~OdDa%MFOaOVcbwQRc72Jr>2XyvgxRopATPZBKb5jB+?!BeF-4+M$JkDSr z-OR~ZZtHar4<0>h1buI3U1ThX+~G6J`0e&`P%L0+>5d$y;ns=z=Y$7I!nOknM6tLX zHG`(FP?7$!Bq)Anpzx-wJ-jfg{mNH&w8_-nZN6Jqdk0>+ODM7=S+b$#Y7BRG;YJ$C zgK;@_yC#Y3*D8)_;u=B+<;oE|W(80Q)z*~7;yzSuZ3g&G-@b@4(*~7`?JwvNg5|~1 z##*SW?e&9i%pN3oH-E!O>(WpNH@{>4ep!hVp$xiO?SS;Z z;Ay$ESd^MnmREpn9PYGG?dz01ftM2$#mIDMGY})U@@fU?03P_ z)~e>=&;ZmpNi;uKpw%%j%lbp+&fz#|J|cfdaN}0%dAiJ9Xu#I48@_Ze!$)A#da*RX zIx6_&=9UskPW}PBu|n#}-tFmXg0#n|bJ!8wA`@GK^p*mLVBq7Ql5`nugl1oN@>kj$5{tHK3Js!Nz&GCd^t zd#$Y=@|66-?PbU=C znHKXLuDXv4{W$5&?9d`6@jlf6ANpM1Ui;La9V|s%7{>U{D(sFi1 zar6wINX1w zF1G%;m%Oa3fR3`TYxIgvx1Y=cv>BN@i)Rf^zg(MX6Vu)EVo!0fto8f1bW(d7nSuM9 z)b?>(gou4Tp8WJ*|01M((j;3#)qEnuQ~w34m{-Zb$u`Ex_kVsa`8!*d%~@ZoaC_i$ zRAShp{$cEyZ&7V6V{4k^YEmTy_5GTnr`!iA>-GA*Hwu)VG3VKde+n!UH=j?srfQcp zezg^l`;?N5d5r$Ju;M_=utE`*Z!anp?4Ma4y=1-AEV1M8wWwQJ5Lv(fT2?G71uZ?L zI)P~wrB{}T%aw5pp|=P4-(GH{KU3KL0T)M1i5v0Ebv_#Jli?avs*d5G9}B{V1KnC9j2TM7#NK7-jRSToyqD83Re5z0c8g=oO@=Fg&@NCy5PP*21zc?{3LpRE* zs_~_c55Nh-rs&>~ef$EXd}$uC>~dZYIE>SyQ5NKe0Do;?h;_%~OTbXGp0yNK6y>)3 zQJH&D)~4|xx)8wIT^09thKQdFH`6;7HA+`{9Eyo45~~hHQ#MR(ee*wtcjf8WWCIaY zb_kbbhXJ%`lPTioOHCu8BKzT91W~1m>-_mt%c9_`d^%^P@Y%}rNp@cm{d93$@x+u* zXv8H_=gLHX;@Zil&JXUmEKvcF_P|qW)-+0bAPI!~=J)QopYIWy9NyJuiYq%8e}<6K z_U5dpadYTYmb7nU;oDwI2v@OYMzWlwn|W*Kt#L2bwf8vsFQTi&o%`z8cf<9T8R8o1 z>KR0ruJDYf4=-(=`1vcLptD&7Ow>Uwl}isPA{Q5b3~-9li|m!k`Kz^=(qFxlyO`Xi z(ezTf#BSuIdhS)7r9+Yv^xMrR1(ULm{ms%5tXsjFO#uEph5l?IXI?Y(obTpRzu4L^ z(@n{MPZu0<{ViZjyp2apR(2B~j@VY9NXraBB#Q30#`SJ2{4C`^v>3uueA6>sA5~;; zG#qK%k}^}j)JS(Z_El^97f+p2uYsGtyj(h3w8*JPsXOM~JNtWwyW;1H)PN*N!*$~>$$LcfpE_Y#BdZEkCy`z9tq==C0Mjn0R+NA3_^bX0ybyJfmrjJUxWf**_ z!W5!$(KMLVm1}kPp=!UlJ%cZ*d8Cp=Xe``%Y$pBNj;iO~d zcYAP+MW`5RdN>#Oh|Hx@84P`8`P|Fj%352j?Z9!HY=hwKCEaJ57IZxtM5ng>k|k_k zhVMOJ84vM7+A30mtuGN#N`6P2JPGUeHsxFH#GB6zWGnL#TV$&O=cE&R)$dd$LMvC# zo!RLz=)rz_W((``*^$WWqOKPbel9EBUe6FM;GxL73Pm>Ur+Jhx>SAWTOCnK{=8LYV z9X0RyOttx_epTw2mwOy{UDu*l(A zJsQEy=!wPzV;5j>^D-22g2p_;fwT*Lghn?ZPmlP^}dnP3zAX;L{84l z-?3HC+G4Uo@K>aF_)Kucc0cvaY5N6Re2>i()T6T= zdb#CF{j!0rKS(z$m^P0dQmA8Bdm>MJAJ_|CL>wndEbNse;V`-%py-Y{hqp-7JAvF7|L(FvC!>scmBt( ze)P{Ky>ywvlv+g%^!u4)8P1bL*s!~L)zke>`m)l*W52Olgxm&2T!?oNt6JhP)`gVig1~G2OZ>ks zlvGv8d|tT=CWKI+gERSPCCTEYtvqEtSBP_D229rzwN@Y7HB!?f5>+Ae{M7AyWC%+Y z=bjvxA60M0WZ6$H^VtgC#_d=yFX&{jO^nvQ0MpL4GQlRR9!tz~D=&uH>}RD4oj!fq z`Tq5>O7en#nG~#KE6sJhqOqd13;Vd+dz^`>b)n()7R>$@N#bL!JZZZR@mw!c!HIxY zw!Pg%HB?pwxedn-wIY0LJtk(Qcx0fvdVF5fD*E;wyqEm5JKW7#8H)hT;wM5uyrk}3 zv(sodnp5-5rLg@hi)Bnf)p>)RZ$dmfFKn8;-TH0lf5ER>))h(xaVBQcPTC22DxnV_ zURuQL@JU~HTg@kYbTjJQ#HW|*Q0<^WQo6T~eR+gxwTdn5>h=<`dOTi4%fpd^@Ev>yflxM9EfpiBolte^Nj4>fG(BG!QV=?9mz3 z&$DKb#X&LG;mdmKErnI$!7Di>Zg-opdeD~@L1Z4*QGbiWCrT89ZQf+vwBc^tKV@Wu z5jnm&{t0y9y{HGg4(rvAK=gWYjry4f!qyYAws@n)x1fQ&6{|&>|W_z{8KW&!(7cT)? zeh_W#O5`8A%UmF%dk3x%+$8q&Kfv$*LN1na(veZCo|>APURn}RQBj!&+KO(5w&CsK zqN~IhZ~+Jx;N$_=q992B?p;uuS2yp6gX(v)?m>U!3Xf>vlA`bY8wJ{^&c+ajBB)Rk zveO>|CI&+dY|#+xx$Ltu{Of}?HkqNV1c;ypi!gxN@ga_lNx+=V?0!+ zF2iZj?z!VYTh0L%sy(4#mMj+10UG_Qe1AUEry}vqd9kJlh6Q?_0>;(v z8o-(Ye>fhSM2w&2(a>r*2FZ6`-SOE!JceKFVz^flZ`h9h=W9>r0C*}JfCt>LwRMSl zPY*N`wz{zo$#8TJEHcCJ1BDR8w}|K9B^mAsVt2Pbm>GQo=w9L(WGf>zKumF@e~$(( z1U`w0&#go=Fsl>4Ia2Ev5A@OL)Pv6u$MvTU{k^qS!v{c9t|C~H7$OAFbOQDo)kZpn z2{>9_E9|tjFsuumilJML4S-1vwCOUbRCW=|d!{9pJR|5im}_rmFswp1^|r;AmKLDj zRW0)v@*$g-JEIeC7+5m^1L%WyED7LlKS$ek%FnQ<_AU*jjj@(FE{RK^1AjdPhVj^r z!_)~Ebyp?-$iV^A5Vnt;}2#g{69d@ zp@leuYpl*NUZ@)Lzhi&E_W1evv<%OD=3xBi=-YyV$iSfB*BDI7k@<~V2pfEUj*Rd% z(=T5f_?m|;5XvxMj;VOxq{znj@kv{SduQ0K0ZWCBSz0OT60 zlvr&kf}}IoGk)Kj-lzLv=)T1JqH4!YXUMPN$b)P4hWC2};)$Db{h}>$+T|B2{@fg! z{)0}OXvmqZpFcI1W^gy~j2Ag1bg_1WfHiJ9_*Vamr~mFjgRR-~|JY0%kNSs=O$Z1* z!El9OWZ}=Be{1Q2UfHB&TxO=vIA~I@FlGT98h!N#$HpA>GO78H9bjdDDsaS)E7HKM z@(YHQ9s;0QTUuai!Re0JUE*LqaUun9KfwC0^qde?3<(p1q)W$J10U5ec+wqrf47&p zblU=+Z$)5G7Cm|jbbf)C=KS^?@oAaglFz@18+Q~G6m$<|$obcTNso8H8u#sL85z+5 zR02ngz>A0~^hjmK7JZATDI2yM&LC*`0DG0#+tTMur`n&WsOAAYaal+=w}3@`^^wRU zBfj1FQ(@m;4~jq9kQ9g$L(ZPUdv>?~=*?|){24dZhnYCU0Nl;uGg^ncpE)aMIptrn z7;9a*bRvstAKRTDj1@e?$?1MDY}vcU+>XcxjF5Mq(N_dgh$c4uA=3j|X?qjGucD%= z02r+0J7nW?Br@&FvIhf`@;2eimlnuNw0U8sBHuTHh&+&k#S>ngkpAr5JMFZw>5FOW zZrT;qNkMBG9j=EGy@Ix}0dtd6JR8l2shaJEe6w^`B_P#8r8Os~h5rupRiLaYKG2ti zE>~RG*gb)<`Ycj4>f6m#X5XUD@ZJ_X0jypo6d)>5Z|v<)Z}dE;rZw$Y7P#C~-3?IQ zy#Z?=zXce)&P*h^mma#8FXPxs$@1B!2V~H9f97^O{Y0wvUy3bG<|(43W;vAZH~)+I z@)}{U!kHu)JOT z;OYi&UJS<4Cu-}v9K-s1w}R+{9C?0A@rD>LPat~?KgmU@lJq2)1EzEyuyi@sl3OMw z^Z|TLcx7iEXqH(J-an zs%w2XJdkF11+jOjL7j0w9b?kRPiD&cs_*PmA{8jV$s5C@^^T3XB17msEPvGkvB7bK z!nM(dgXv(Dlo^K_1izlw-ruP@A>%M_U0%O8=u{E(XkCfksbFKO`nH^*08anBDTTR6 zfW`aF;j{s^Kv8WLq?E_N^7yG;=+Jnsmbe%r|^UBCJj?&AA<&S1-gz9QK)G2yVU|FM#T@ zcm~s`>s8!Lb0}#URI=*+GBm4%oaJ^ubfaBkb}$7ACP#F89#iloC=I zW)!R&(}tJ9YL_GDI+EUMCv~)U0H!hNKNemNy-db){F)Thq`cr@>B%ruOi_Q!z$JVm z!`s;O{bV37m6w0j0CEN#@ z;cB?!9z7@W;Rr7lOuPTr#@MYfN3} zUZoz}wLuENk$4RCodNbKeJz9P2P$MLLjsOrd_@*cCmGnCAtS@ZKewulNbf4HY&6K- z+33*e)^6DG?<4`E!z=yURTj08kp+RY-)*Ohba$TLHO0Lhgm-q8_UwIsOW3+!Z+`2~ z+@S#9!N_s8R()+3p@DHYm8*h=5Y1D=LWe-a$la z2$2#>LQxU00V(Mm=OH zxk8Q)i%mrO!=BW~1saU}*$QASi*5327A1bxbbeN|XXIGk_1?AltZQEKzc`KPWWO6UN^32GwwTR+QhP3ru0;19<$GD!G{Omxuu+ zykJ6Y!Y2b1Zc#*3BoljZ>l-X1CE-b%vgPB)BTDpPAY&>vT54(AxD(JsfO6j4hyFwf9Msufvrb9IT{`tBaw(OI00`V1U zzNAuQyoO>zN!i%C zMUjz3!19Cy(1q#>^Af;oGdyFgGdium&R4X#4}WqE$5#;D4RvxVp-lsWTxjWPQbZuB zC2|fGHJ$7bhCSxwMkowq)n%A;3RzwM_x_aTS=WBXNKYNT&%#itWwE`@PP4A(>n`cr zLhn%+G{sa~ZjWbrlr>O!Oov@W0@LNw} zqHj^zlCwoJ*15&IL#x=nc}^btk5@;h&)Xr}Mw9!C0T9bzCOVFS9BeZH6A1qkd&D)Z z#I~?t^2zbZ?#v*EG*p9*L&&0}AMpx0@qJX^%a_NL{FM7gSEzpFsklEaD)biRa(@M# zx8^iI&XF!SPSvd4p=&scrF>>D`Bmz$Vgn0;%g9yj*9)K9MA&GdTF#y|F7R@4Gf6uJ zdWsz`EX5aHo)QkVAdn^}CL^}39RpT$B`KeqH(k|Iho6@MEs~^6K(#u?%?sKqr!wli;iclXSW!+)(RgfC>S3GY z_V)V194P=xPiJjL4*XMiJDH(flWVgnL-Ka+dkiG{ClE3LO2cWT`Ual@=5i>V9fw)|SYobbTdES=8$K7-UfH%g6HKN* zw0T>cqjB}>)xO*-G#~7$&>w%i=hyQ4=*))c7H$X@{xgeC_}U;fo=*P`2wJ&ECO;n5 znNgBcQm%I!+!NXvtfpmMx#cg{2?+hus*C zr-c3k_rRb0?<1JQW1kUybr$50ipW~Y0M|V7xi_k!B{lZ{*FgSXg?#@u*#EP@ap?1u zls`^$b0-7faDe^D%g5&;^z+R;Rr3p8z)dUugl3qM!{&ezwd-($0VUW{%H{$1S@6P! z#Nxtm?g)EWEHA91K*n#*3r?k=fEjf$hz1^M0;^0^IpwMWNgn{0r6I+Q2E5dg+9M7= zO1JJ2RHHbB#oYk!sCn18Ryx3B{_qoUVp^zQU-;%_bYx#on>Hd^J&;E-BI$nLk8v># z4GqBOp&sz|0p#7UQj_9O{C4R{-45fRHGX{DN>&npe!raiCJDeLsxnWec>Ht!zQGGq z4L&8s*WI0E{R=FAQMMUE4zsR$`oY@aXkn`*3XBDuyfQ?|VPxF`!z}UO^_LH;A9tG6 z=il+c7GOr^yR-ED7Wib{-WDbo0@1ZeOG}Gh%ra0Pa6No)t?=}Fph_UI5*7wnn5f(V z#Fk|qE?==&Yyx;_`mdh?dL=O0ZSA8Auz>js5J`QAgN0lU=i<8t_+7jQu*GT1TKDe* z^V2~O0O4vwt2~eUq&j(P|NToqW}SZ8_x_KMre9xux@=SBS*+dpIRA|#DRHcs;kmYr zw*i%#zwJCvi`3@fY3C@?Zg~0opOb%TKRb5nmyfr-zZ`^;Ga~;!HvaUU%wB8AR>u$5{tFxCj9#ZN9rj*qV_H-+(ceH0UhU>tMo`E4B`Q_ZF9V)F> zGL)esh37Sh_TKskd2_@W%3<*Z^iKKM=G>1TSD^!!6hjOSkhpsKqW3leVq%A!&#oDf z2|ZvxWL9OMrl^rKFDKW9o>RXwd-1aF7#M<)Bz+2OuSuJFyZAkMZGtx$vv z4()xE-YvSvxzlM5+ex&XYFU=UlUD_Ji@-O^tq^+ya5@bTyN|hv-Xlf}X5R4mgh;km zX#Oq9zQ9BD8PSJ_>}YqvJuDp^j9h7;aW}D;%%+`j{L(zpg+--70u24|Q%q`HIIKq! z-s$1mzc(P+EP1kdV5|vBV=UL3+JpU|6gRRT4%aq@9o*}~^bKGKhy&8M;-*9TliqE!-k40m@qnM-j*;c4=YoBmSLDdZTuC539+#`F$WSgW1kOlJQ#l!7S>(k@ zZHBbv_zf=7Gc{QKrn~DKr>R3ZXt=keb&VOoBu`Uf+lDgQq~)GY$JK_ixY+d4N^=_< zs;9bRh{rpAg?BhmWosD?fqV9_1JdXN<&Go09wCoM{4QQrCw3a=sKX{%oTn2~WvQLIDOS ziEPRadUM*i8|9t@REsydVge>>7qNllcv!>VqM}VV#tZa8Q0lnxe3s%+jP&*EzJ1Oe z8Jyqix+bC^0$kv3)A>vySrQA39uksRoFyl8qncaAYxxE?F>$%rUj29CkKh2$?5@pT ziLeU5j-laMFeUNs=1@eqS(a(Mxs}8y@JlFs0k$}RLiI-?O#qljAVuU=KCeHNK}((nx9miUC)O7nrC zcMH2Q_lw>481N6M22Zf#tO z3ERI9p{7-1N|p`9CUyu8c@g~gPOeZSuU?&;+CZA4Ceu<6Gbl#9IGY7j#tnS{2E<8Z zxkqSNU)Wo=v7PlqXjnd>N2^Vu+HgCaJQ{(RG;sWnH*p%I@HoJ;0$t3dLHuB51n7?} zv0lP;TF_CyL0YO?mY;li-6CU&Ua**T5cCDHJN*MfURg2aB3KI*p<+&P2WABPTpK+j zk3L*LF`pZ3{7S#i%Q-S$?|=;_hxVy0RSCL*?7`qlusY~y3g_eYxo(9$R~PWmCD|5f z%E{rSI_GY#3USEpi%6;86+A}1;$9NwAu*xa@!ap~8eEOW9BE{k-ST5Koa!Z(z?q(y}k$9PVH#OnLTQ>xxtKpgK*DN$97$ zHo6WeJe&B_bt-J0j@#OfRSx_d$2mXTD71 zI-+O?`+F&2tY#v#Bc^Q_RM}PxI6^>rhsMd>sEo%G4hiA%doX7F9rKB-^G!=`$xSR5 z@JW+o`+W{Pd{gp@w_p3g7ml#9yu82h#>qToYbXtoO`r(9!G~wUL(&$mzF7jYD=P0L zsf4cG*MN2~HCDy!_ctUk^A0&}YHQr}mn`jk8k+|Xcj5O6EHvqRo5xq?y}jIw>$>Mk zKE@mRWL%$^8`Mpf4E&{kOJ*;gL;7hYk$?xl`IgB+?UFqKM;J6vg+*JKTN5b~iJ% zwkLpn&=vqf{EU@)g=mW8>;tMo#qf8M>?<5On+a&|@Gm_iA_&w>Z(v(MxZvrEtJ+Qb zP2au8x4yPK4yK6CB$b!%Gb$^Fi^A2)FG|`pXh`*^#us1>X|rQU`eI?Q>CbGzv$j#$ zoTVPJ9Q>4*wh54>KEpu_SFdj~6yUQabW>s)WMUkf+*m%bPcECPTPJ zRSE10Z|maJx}2S*UM7y9?+k}?ad2o%%S?#=s0*Aws_)i;Wb6EGn^{-PXqO}ZoB^LT z%@lDT;`@$VDzl-X`GEf&c}CFsCl|VX@gM5p^nW<=K>kfrv_kJ`LV}e9H4ih8VL^LO z&i?%2=lXvwd{+M>iCX_}^%VKvHEqbh9F6~@2T+$~TE5#DThy7V1b78@R9ABX)hR1c z<k9Srr9%x51E32X?PJxmz_AY7sStz>BS^N3Jhi{l^hrRDJ*%Mt5 z*#6=;@ZlV%j^)<3Zu@>zRCoj+5O#NX+RWnOKL%O;We1=?o12)-L`gJJ zlP#%j^j3-xbBc8m$hF=#)KKZg{TREqHHU#MRg0O~*_A~L@8v2(mln{6sis>OftegL zFfitS-oJ9M^Y^KzY*bpjf~A0h_ol;Cu6b$F;h^BNp|7}*d^bEt$#0}KEObF0b^Q2o z7`51~_UZ6twe{5pB4SxHUBqd$`|x8nV@=Gz?-g-#xO;Z0c5HJ|R*u>@41i=%aBl!; z!Q{4RSwOF=q9G5WVfkqDNgy(kM?3P<`E}2 ze~ufT)KL#_7_HeF$r#nhGLG-kzaZ2)zwdn50f#HcG%L$R^i!410YhFY)Wi_FGHHy4 zXO%(PGMbtTP|5&>0Gs#iz!d4K&%=u!5@-&WZdt9#3=T**vA3Yje{0$faCjVysh97p z+@F`9T1Ox@*WV7(a!wu;84s$1lR!fh4jRm_R?^rx8Ou#|ZJA1XDW z4sS!&y04uBPc|4M`sbzW8Xgp81eDBR4tbEzn)1gq=`A~pE146Dq#)p?p{77J{O&w< zu!)rD7OL2Xxc2DtGe77-X!z5(c`dc_)3X|*k-X`2H0)8}R4{@R4k?pra(}NB*me9%}>6WSgK3{3+QIh+rvCFRJ;78 z1M#lk!(KT&yoR&7Ju}r2&Tz@_)^x2>@-bQ$N6XJ7A*NH8~afN~OmsQ`Cd17VTT z0dmlUb>84<<>9rdNmtIn`udJyUPpLwW9ka$_SvjOFg2&!Fz9*fSrg}KaQ!}0Gf@KfCvV)?3Dn9@F`GFA;<*+++I3gV=^UFAv zGf8$PHKr!DrXkxR{9qO}u4u3rfzAbIqb(Ba0+=W2}WT}Rv|!8Qskjj6z1J8X-az&{aZL49Wp zKLMf>j=k$=(Q=3FrKeDa$*RcTJ=_raUuV#NYMwr_UdsiC6*`5Um9aJJ7lg(lWfJPin{<&JF*E-o(K4NcLt zx)PS=I0dm;$r`?T+ z%+0aaL-4*=&HlrNgZS(_yuhtNX1)$GzP*&*oGcPU-;6G32}urE8qXOY9}f-qi1M2^ zzz)^PSbv==n6TRCYr!dw{w~8X8c?mRVgah zDEB@ovj)B?wCTL#wk)$P)FF`_Gc6GwTt1+3aE|TqCOK3bkL2 z@0;x40>b!68T~s>IhkQw`FPV_?^-xUD)v5Hq0;Lv~>Ao6)I$hY2zxS!S-+$bX1_`!Of3M`UZm1r z{^w2x+4Amw@x`RG8#f^DpRtSv4C_cf%mAAJU?=Peb`n~1?+v>i zU%sOH<;jZ8j{nb7-7YWXZHUH?tq`I)TYRdGQG^fX<&mW9j~_D###SXWA7$#-pBxVA zt6~T+*u~B4$;-zAqn1yeEKl_bn9=@eiDv{83FbdYgVnqLvJL)U31I%e5JdeSxQD|y z?TV;G<*2~n%Hg&rpCeg7x#oYn+W22O=)ZmYKf~4k_5j>rN7A!!C{{G{E9PrVYlI=@ zDhC4x4Lt=5TgBvt#lf>ven98TtJNzJ{;sBUA_2ijw07LT7wUdh`^c?psCSigPC;vJ z@WPaXUBOAQvZ3*bR8O+1R$)D8scX;*WnSug21+TGWQ6lVExevI2soPZ$ztVhcEHbF z;44-y3^ZH!F($oIzOHfCS)=a*%zt#K!iV&rC?r?3EvuL^H&ZxxTrJU#0TFX(q`(Z1 ze!+e6)8$-B&EB3qZhDF$Fy8%G-u;1_Nbqd>Cn&$7x~)LRWgPv>3iT{4MuWfHv0xY* zP=rlI%U7WkD-;sEM!&A5_Fj#{eeP2!HN|n@&Y4CDr^>wZ&THqbxBR5Yzn`S&7quI1 zmYwB5(2mOtOUT4?(Qd{U-;9$QKI&OfDSS&Oqow**Rqs5x!#;NBAv+|}wKz2__|C`e z^QuQKwGhsrwJBuP4gG1gg9&S!X4HdtZ#8}Kz%5*SL~}#;X!F2Oa+``isQ$uY_h4Az zi;)acv!^JFRSx?bTq+upRVltzec3YC&3m+SP!IP?9Dy#TR(%T5SD+~>_3c9ink5|y zoAnx1KPHuj8}w~ySxhGq7a|*NLHfF(1>_o^T;XccJLvfq( z!YsRjvm0kduQqlJ3kH>gcUEp$S7G1|wwG;-97{E03~UR#naVhF4m(G`YD-*I-BM0)0|ShvYT&hicAOMitaY1pF=}4c`%##U(l( zMY&%r_~H7&^GD1b_iI_d{iUlon*>4a+>{lzTy&K?tQ84;0X$7SbMJUie!?ro#*+Cy@fSV3k`zye9In`q7TcL@lB-oG6_H3>BYr8k*t zu}tlE2o3O;X2 zc!Pqmn|%EHbIS6|f8?7B4`R`-O@q9Y;?VhHA}hTkUjwm>XQA{KNl$Wdq>cP4$vtu2 zv;E*AbuG|hfsz1CLzrjhoI0iD4GJcX)-T%lpcv+ z*%))z>Iy)4W9?oy_jQg1_2ZnCzVnZ`7sl|ny3k@Io%ly8Bl*o$ z>|GL>)tB>QGjdBUS4Lds8KYMUExmuYOh*AoBm1)MRy*)ky#BRP;oMBgqZ^!!b`qyr z+%hgJ*P3^Z;o)d84cmAPUA*Ixy4h4+&%j9hVkb3l|7Vyw6 z$;vdmV%ZkHAAM_#A0x^KT4bt|EetI;y~<9R#){J=haG({Z}#BOxcU$F1HP2<0|y9t zIW$R0t6uwFu`SICelp7xN24O6qPlDHe!Qf)REj&?|8A(5pBmHP4E>thR(_Jo>Jt3KCqTr|sOfGwDHQn@bU zvP-{BUK<*O?_4@6vEC2B@O5sshk6d~+MUZNHik%zXcUZx<`1P0rj@4JFQ&V|ex#!K zEn9RO??>9K+DBcC?xv;Jk+!%Pmtfh>jU)S(5i-79wV7JM&t+VtNopAQoSnc~w^rVB z*8n@_kpLvi*1rsPlDRz9t*0w*t6&%&+?N#E2^L5x`xu(!Xq@N_8mtc3^|578K^*;c zH;B;Y(L%J+hjQy0H-@PGh*L-O;--&Sa)GL z13KP9w@7~dAsU-=AUx_xa+4t0eJ}*{lr*k;wa+f0aUMR#lJcqz07q^>D1!tiX1!s( zM6Ow{eZ7vUt5;D#xF5>}h2`;h&6imewF^!DHFR}XvtBx+^{S;l(R`v%bAn!C>VsyY z4qn==NRB@Bp@dckj*#X{Lxsq9PWq@9+lijM<$AYO;l})rnZ)O`G`7&j5$cPEqKIa5 zQ~}r1vGP!ISev<9pnJ*o!W%c6#fs-*3xm9YTbaklJqA3<_sA8uZg1q>t#-=MQf<`a zmL}#43C_r6e=|TU`@F7zf%5|_)p=IT9y+{skarBJ^scP@Q84lXH=%rxZ>F}tbS{R` ziL?fZo8p)&3xYQo8_>ND@pJ&%I8kOK(HvTe-P2dlGsQv9Fco$-C&?5 zWha32m0#+dfKB!v49Jrj(nDf`{xY8}sU64IHket48f9y_PQbq=cz)@}DTi-rx*R*; zGR}2LDWr`=aJ1DM2tKZr9JD)@4{;>KAtv;=%0V5 z!-<+xFd5#0{7KZ^@?yT{@!0ulW?!Fem(00PY?1EeDbgUO{7Fiw*o3m)V{+tvB;SChB%{=zf_9Jtd}-J z>yz1(R!vUa^D*%!IvG$y`VAmIZsTl`5>x-UTF6mUgy~V4Bl@RxCkNIaSM-}2SnB1Z zoBY*b?Cc0*quzpEtW(rPzr`9K4A^jRtg{JhjW4`<-v9iYH=Enq9E={Ctp9#P2*j>h z%h2Z2S<@Fpv9IjF`!?C{0`8^NC{n^FrD9O?NpVZ+eEE4?)zt4cYg!9`Gk4W@`N#2V ze7)7+EzD@WYZ1Kx)B6fMz4eQW3++u|fydHH{L6ZE3B3E)6n(`UBg<4-VtK=UXFW~nG*)=vcRBQr%B`PX12 zLKC5m9hsr5MZHGG;#miPYttIK2IouktQaZGqwXe{h@=hrvSO^~&$0*yOw%y)I7wy* z_JrDMNWsKK&VKkC`8fnn(+0-6i3Ao_snByawWQ8NEN`9AoZ#Z=6kQlCm_wQ%6K&QS ztW(RYCtTjp$1fX!Jm*=XXJV|V82n0iyhYJL(HXMrUzv(q=XzzDZMBjXc8}kGie$)Irr@VNy%=`a);Yl(p=B?IRjX)goAE5a+(;iYGylU~7;Vlm zE5A}YZkXW02LoAmN|(#rXRPGlAxpV%@1;Wg;0?zFAfxQBl{Ga>T6XK=p`<~7Yq9D}AW0^8ZPn>JO3G)6O@-{F!is0+Q(c7D_G=*)t+v#tFg(&xaBM840SWptrw9dV518EcS3nzPVC00 z?6cfQ(K`Izb&Ct;K+>j1ZPr_XqU7_vOU*^_#c>Z;`wO{kaW}fFRO-`n@Y3F&+GF~C zijQ$#8Zn-4wAr=dtgFm&a3L3eqOS3cRHMDBSCHZFl<0p4X4YU;gAD`KCW`a|V=8@B z=d`JcOa?yJ)sZh4S5oXP5IUj`ic0y6G^o zZ2L}c$e^m)^6vvz{*v&F~0yE2qL@ z>qfV~>&Ecyp~EVUF$hOW2&!$CSoCe@$)mQF=b-b<;AN<-f1}VrK#VmxR2V7W^mA%P ztgLzKH;=NUjyyYBwBoyS(%j6ZB7>5slHkDZX>4jTlvpm!UHtMf0$qi{b&Bc{Z!ZhQ zNeWd9zTgUN-CZ z-+9h2P_7q$%RJ#^tmCutqPXFqh)$bneC>RlHuASQ4%Y@g{^l?Hd@ZhhH>$Lo)zG|_ zrI7>56H#n%8kI+%^a8{0tRDDOn$=lrj^s_N5(G;vNs!+1O*v>RyY4%--A8(c#60N> zJ#hN`&9?Hj=-;9Nx<(pIOIN=;_`d5A>?<$J@04A3{(dGVOj>d^2wF0*gV)mk?d?br z2a!(V%K3(WrQ@0bZY(EdpEf^eYV*(4Lg?Svu2t}C>5w?s6ZD*HtUgil0GUkGFt}7? zS9<;n{2T$aVE&E&2}rN+?Ub9_kHjVdA|^6==ogy@G_#|_f>4%WZemJCX*Z_nw*#MK zHP$9m33v!lP+P$IwJ=U(7Sp5P&fIO*Bc}qzmEZmK#y5h8&ps!VH#`tIp&{Lr3L`wT zus|9xW@h`hjGuRfrRBU6tke54y7-R7ubz#$!#H8Pv;7-T8g>IxdisVB|D=lNpq$F% zQRG|eItEd$?Quse7f)~d%gh0ttp``f*4ZL#{;a!qZzXYSKA>5*z23DS*RIdNeo+;- ztey%~OuCBClnzKpQ^T)rE%&l)Yd=;hTc(C9nI4rm_UXCW$E|b;s#tNBcpy*e&WWx| zX)n8!X}2K1eBm#uW@TSRom#o&Bsn^sxU%6{E4cNFnp#Bc2duq$_e5{Bt55aU z+WRq(S&2yF_>_9NBXSXWD8{JQ(5S-=HFk|_PaJqDbANHXt|P$tB!O- zM=MKN9ZTQG%Bnv`5Zf=cy`JJv-5AK+aEpnp{1bwfNG?_?%m_@H=pMjf0yIjT8Z8OD z5%Z-nD%YY(Y$39|Qr0~h-i_$obAHNz`R-D7zkmvTzQv*gbQD>}o_RsxI+CMA9pG29 zksp20kB&FALc_LLaOp+b1`gXYKYhYul8&DBqlt*P}(%P z+jTiuwb~#Tvz-8r9zotu^<6d}9m6QM&9n&X;;LL+LmYG{4&mG;A^wR9wp)*<_Uem! z!o=}je4ivHozc=d=(Lnare&uq#JyWx^AX&y6o2^|W_(g(&^Ai#%jlTA4k7`3C5e43<)q0i4Z zeNB?wToVNMcRom6fvJHFxrdS(Pu1Zsvh#vn3eVAn0&&L##2Q^h|~31 z%!K)Cj9fnct)UC3-B~kcqh*MLSuUa1GqS-z{%kl*Wy-ZOW#+s;mXGISB-8r!8c(U? zyO&lBxdX{t39Df*MKZ|xm4WYuMpGq5nZw1d0wMWMl?9;oG3tQ-NeiI6QpTmQ3iZ-+ zpz-rOg{k=)g+yOs@zR^NDf6{D^qZ}Ez+@s?=mQa^X+P*o0Wz;AGq)d#kq;6Lf=2aZ zl}+O%PQ-^ULvW`kA=V(St>)`Br~&2SbPZn0v9w!=?49T0eTeut?qh#IT6JPdCO-&_ zJhV=lZb1Mq1rDezVckQ;gMHueX(U;z=NxrES=)TY?W=I z>&dgtqVw@r<<(ce8{g-w#Gx6vwp4n{c07AU}03^QCfG+ z>N_IUwoFIY&))yYC4uE`wJV^AMXHsjS3bwbZG;s9 z<&x7fW=3XaNSm6pZ(~13Kkp}e{Lrk@uU=C&F3%=D*~+mt-=9%mXd(6P^&Meu@3!v4 z7Q5m{D^22ra^4KbEBY()Ue1vBb-yA)@4?*_Haj)(;rZRwy1o*L1NB{Pjmo2`>?E3c zkbZ+ba>0`nzt*X0uZp~FtQNn?un(=Je*Y!y=H=uC{org5KdbZ;o3(zjEjud=wcK+&=4{=^`nV^lZ~IC^3R9L%Lp!Q${NH!u z)%$8+TmgLiO1|xUO$d$QjuWO5j(o2EdzCZn!XiHBbrZ^~sF~zA6SFoNe-ScXk=2$` zP_zD-k1@dHrR9ySDd*ZdGxy%#tHZ7eYfDtSm+tQ*20D5nwUlvoPKNz*?SD!cthH;K zRm+S`u_pNPF)k~{;;(m`|6js0+tU?3-;GOP?X`jE7 za$cXubX*Xq3OvK(`Qp&v#p zg?`irkDbt$L)2>f&QIM@*tYVmwzlxQqbS0DWhedz12?YgVLa7L%Mo&VXkX=PD0Ug_ zEI+!J-1HaVzK-aw_!UF8$}57khkAumnxq}huPlLgAAJN9?AFxgW*BhOIsJ2T>+7-9 z(d!1rb-2BP%NQ=bDn}&V!u_aGLhb|);u!Gq>vT{cpQoo<2KfWm&~*9n)gndIfRI-HZh7vDasx}S>XbSH~mDw2xQ#t1=tTaT0KNct}nCvxOj0>nB8-Q$j$}4 z8^Pc~Y`@IctkKb9A1=qFJeJb45u+Av?YoO@2iK+21NobWLL4ljx>{?j0#kpxwbw0( z>(IxWe6z=d&wZI*8QzfhA<4w>u`@EsK~I}OS_BKfn*s&xYJms0aNcX*UN=qbX(;0Y zYJBqUOYHPkR79luI7*$_e83m}B8tgn+Pmojn?@#zE~RAGKF$#mMj2VQG}(#s*a)sP z+c70uE&cpHIFxC-rV2KV!23HNOPFTb`pE?(O>phFk`=O8pSw>M2``U+otjlt>hwa! zC5el;w65&B70eMq8QJg0z8!mKSy0k8m$%GX%{PWI%la(1*PheFCKS%*R59zyxx0&+i}f$ zCC?iqRSM_bEGSdo_KGx-@$~J0yLSidd4T;5y;6mOfV%&UCEt{duL;&o`;oXNSPMSb zw5NZaz+vk5U3sjG_<~dPqR_Bk5swXDWKHH;Ie*HAVJa!DR+DcltTP?T&wLowbT+h|8U{`^zk)n?1WJzX9b7Ekq@`c}<&qzl z)cs3Nn8#Nbl=8yjJM5>7aLqXY$bfg9cKFt4BqJ zqNAOCrd-Y=*ViB8i8M^d=oP1t)MbBDpV*2p1^ao!e)Fcr>@bh7#c<`eTj%;7z36dq z5W9RAj;fdpj${TaMkAyL6*2Q_&6!HJ3U4jh3=0!zYrZ8SKc;0IC#zZNDIf@+}6=H?V_yA zhc2Jui11{3mboiP;uEkONO&J3l4(ctM z6XCfDstri3RKK3_*BsCq$#7Gx_>S4lU1&Q+fp?P^g;iUl@NeixRcx0!mHa zSZ7NBJ+b>B6y7Zrtz@XzYiZVf5k^mALsd;PMEU(2C)%#`Zf<|YHTl7&HMd`7-n}cC zYcz&*wq(z4UUiWkFmi;q&BTkQLm!SrB)(Y6WP^j#rhraw-rR=vxtn~q%)YdBk1IKX z%q9AM)1x3PU?@|&R8JrsGK-pM8t<>CBSeEz+1vSN3zwC`BmGI^wh#J3(kHw-`df7S z1K&*4g<|APiM4n&)*}70&O*_Lqn~i@rAq1~>tgOLp1r#n70M@rLynKBXmMH)W#s2; z1Aled-G!pjS*+95liY=A>4s^kp+V`^WVhtS-C&9`ZF69^TWO1Xc}zWa;(Y~Y39?Y0 z=g5*F+MY!{w{xSnWOp`1{svsX!nI;zPPweavNDraTS7mr@Bff^#m9o2CBrk%TXO5V z$y#;u!9eKtg^A4HJ9u0)7ynWeFN@7M7&v;Jo8QVvlydmJ98WKu%=Hji*`P|O zdux!bMDAaUeWfnr;#*sd2@On0p|75RvkjoPUr}sKbG)L_38f81X2vVsZ<)?*G&89Q z)4S}BTAzq49-h)5I;jtxI@{2rX9C_4`UM!^>O3;V``A)}I@OKYsxLPTu6Ea*<_zvs z(Vqy?pW?oc>Vv-=%*ja_em;gJ3Z1+j4VYs__!kl3ZCsC4o=A@}48dkvSp&N>HKFXA zqX@eCprggB`55%entqa;JV|ISRK)3x@-$pC!&{h(x_u^J?{4bc4UV0V$J}vjePkTF zrHicNGS?ta3oDwJJfiU`X$|GKD#Xa)g}!b)HEHwdQ#uZ|jq}o1tIlxcDqS?(djv_4 zcqaVpv@qh~+l2k^nw++m3dF+k8moR`M~++)Me)G6*A0_yLu(jG;1^qOPP;UwOQ!#+lmC|w_Vm^Nefo-$`o7huLqXpvkLN#Cx~>1M0OCIc{r?9JFoz_6=fuBx zv7XT@C2qVwAj6ZK40kq8z8@JWE~Tn@`oo7er#YBi`%f({TwFj(BIE+$nG^Uo=A|mP zxSC;eAAaY{j~D2oeJOZN9sg5w^`}JS_ZK#tXlv1DPcHHX4^9Il!%w)8V+q2&M1%}u ztcyXC-5!>Iby0S!1kvKFQ+JR2!xP3 z9~37TU`qr$AiMxED80JF8`O*e8uQsvK$8TbfA6f$ud8oFNAq@HtosSE(=?XI!3Wqx zI_rWVenbzRSV^y+RLhs2EHtIja>8&=ID+Vq=Humr050<91=QFFo~M7WA@6ky{qyrX z|GP?y|Lu!8Y*yi$#XF6pq3`uB@K*J7Muw=gp5??qhBDgOjdKwmMNj~(=&yCubgIB9 zOc$f0hmioHYSV4Dzr@YOe`s-J6##^egxzX#m3p_~)CH`)W>f7E?QGnxN zt9?xmf*LI!x+62SM)UmXP)!#tM?G_I|IEw8~plocu(QMwet9kG!VHTB8Q;mlk(GOa_*eNM^u| z9O1Ia;6$sudCOQo5G}Rth=MVsH2B^w}L%?OPkc9-hhX zi^3fPb2A0QGAh&ppA|^imrTB3M`LDc&XrH3gU66P0)E>|G6%kO^Psyc3W;<=_;%L^ zB(iGg_~Z%6b2z$&bP1i~z&8#_zLcva7BJSUNVf2BXjPO4?&ev<+5Pw5IfZXpi*y1n z1&J;)qU&W9#K&pei7-&V59oybW}S0$p%0tSkD{OgD6f%w#%CGkCN?GCICvL^eS{ZK zkk*mb* zI`hHMwZ_b}8>E*L!E>_t=k>BVxw!-B#k~sPeb}1j`A51K8Bqz<>w4$0a}9$n3kgr| zFXz5SmQRKHe(9DPm;Po~`Hf=)N86so{au#>54E;P+V^%isJBk4yeMgwn{uK;DX{7{ zFIfmH^9_wyU@a@sr6}Xne4E+Ah?=W#$OpTQ8`QgWb1@c&hSAKYo;*CuoPxp5iY~Cb zQ0U(4A+FZh{DIkN*ww%*Y)m;1N8Mv%=jDo`VT%@OlD)w5&%n1UVmTY<186y$?@+M7E;+Te zZ`!+FBs+VrUm+n8e?Usms8^6B&1PuJ&YmeT@J64|srPjgTd3TFd~<|C6Dzrc7P-g` zY5o4CT=FO@C~Q4>7Ym0pyZlx`my~YHmOJlHLl}{f;E(*>UrU~g-xUZjywQFdC9QgG zh#T6|%FXxV`N*OqbKDCJyeP8iy zqD+{%l&^BaK2O$g>C?V_lLvJ~gkV#3m)#s7`%CR}_0;&1zt7)>rQCMUPr-|&8mZ3g zaU?#Pa|1c(_61VOcv2AuoG})xX5foSa!g;(hh+^pl&8r->7^xLq$?pk%T03;s76n6 z{z18`x1a_MEcb%iD1xa;i`Yhwrro9erme~6o_`1~r-2U0ClAc$lO!2~j`oGZP;{^ARGUqy7T`^!rbF{cU#+7p>@kFapl(4Ec|k>iA=3< zHM8cRnyL8(Ep-wIek){&=+vw$%f47jU`lBzieh1af?rlmEfq-$5CqIGS&4vY$`pZN z_wMWuxc8iU?(d)ToX_Wn=XpNw=X_r8=UFj)8pr56)Nxn|(5@f$J)A_}xecz_mR*g2 z+Jq-u?TR$dFkJ#DOhfGEu%E5S%P5s3Ve}S8&M)<1N|jLqg@rm29spTp`o6mCr@A6} zqE)<6`Rl|cm_S(&3|eC4jQApVPojmQW7_J%BC;5QqE`hU0=}D}g&(^KUufAe%n2@V zc2=}7m6Fwjlp(2PenV*~<92 z$=`lEXLaHvUtly=1ION}4B%_*ghHbu=w_p7Ai$ZIq89>!e%_B1F!Zlc*Om<3>xVoJ zf@2mgwJ&r+Xn3Q(gqJ`qb5UYDxG=#ncaiZ5F*!dutv`&HqjlRMX3eB`aygJkZQngD zXO_GCjY28ifs#5xd|N0~{o5VNyGNqL6Wd4X+gaP2wEs}-iHqcUUkd9%wmugw50_7u z`B|<8+R5)?@-^$mV5eb#cS<~z5ty~r{&>)##l5{y?BN!`r(9|iuA--nZ2L9{96Y>HqZ{HFJ{X!1`AoS~;Lq_bBQ{X&B7lzWWHy2q z3OihtQ)8!ermKqRT?e{tH`Ooc!!h$n1K*`z;+ys>d@}19Ufsmcr@L9ZvtM$b%V{y7 zR2;kpnEEyM$*ix6|Au0+`V_eP_B>YCcTzQw85LnY$gY8svRKrfJ#Bfn@ZnbEi-yM; zVWF^H(Wk<48W)nJjGK&aP^b3{v~5_sfi*q-cU8K%U~$w8)6O-|J*4yB)UnHt$!RY} zUfHBVT5WCG_eSwj2qT1^ho@y1T5d zD+4=w6nsgMEk@E@sCj|z$y>R2TIYyIs}w6kt?dD#8-l4X0YUjT46Kkr8bsWZPK-CB z=kK)cdfIXgAIA*iTTuC&=O3j-X|Q@NhtqcpgDxGADp>vM@yv7^kMreizy)py*a&K^ z1&3oA&LqeBH@-TUIZg&6*PVNL%c<}BJ1C~XPH8GsByZuCa8sY8h7gDZ|nS9L~J1)PLq-;ma)wI zy$X74L@Cp?yN#7tf` zVZ=#JcVLgjE?~&S!4t;Clzu@#t;@9h#m3eenc}hl&TCz&1l;IcG~l6pH8p>V*e#^J z(E+&~_#`<^Ch+7RwO;NGKFx|sQ{vk=)5|IY{6`^S%%Stw>j9(+DOR13hpF2P$}H*E ziG^)pSAcSEJnINOr!IYVDYm0MrshU!9`gxDc&!^@k^-ZA`Ya>G!34uYtp8g!tY*D6 z3szG@DA#J`TRp^nV9q6|#d(}fxr!HGgzL;jqmK%RnYMjuy3u5%sw6}DT5qVRh&k*U zqdgbRvgzRW0EaH~Crq}Sae0maJ=L5{P)^cdLpL3*3$tM%-iF-s%B({RLv-Y}(62N0 zP-78NvVCl*dd4pK2F{!{>JF_tLmyXltc#|k9$I$vwfWWl^9Xr>!&E|#S;qpN36&Fn z;+3HtXfj)8rQGK3%y-s5f0?iLfUhOq;{Frt5$mc4u<58IX1-ze^Cx#p9?ZIJNQl8%^xMB>kqP{y$|o z2jm&GfB)L7OMl4#4dG1tn!WDXJuqxM&bz0QT1yOYeUFty9y#0|B6oJ~8IGtPok>q| zoAeq&m$aRi`Jh=^2lZsVtJ_mlln+`rzpJUyju}QsbM|WwfA`5?>~<-7XSbN literal 0 HcmV?d00001 diff --git a/docs/management/connectors/images/thehive-params-alert-test.png b/docs/management/connectors/images/thehive-params-alert-test.png new file mode 100644 index 0000000000000000000000000000000000000000..bee5b8c30c1ef3c6c26f34fd59044cb49475069f GIT binary patch literal 41263 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 Tl}!)enrY-@l&_>q8~FYo^}mKD literal 0 HcmV?d00001 diff --git a/docs/management/connectors/index.asciidoc b/docs/management/connectors/index.asciidoc index 1acba0863b07d1..de5d10cca1721e 100644 --- a/docs/management/connectors/index.asciidoc +++ b/docs/management/connectors/index.asciidoc @@ -15,6 +15,7 @@ include::action-types/servicenow-sir.asciidoc[leveloffset=+1] include::action-types/servicenow-itom.asciidoc[leveloffset=+1] include::action-types/swimlane.asciidoc[leveloffset=+1] include::action-types/slack.asciidoc[leveloffset=+1] +include::action-types/thehive.asciidoc[leveloffset=+1] include::action-types/tines.asciidoc[leveloffset=+1] include::action-types/torq.asciidoc[leveloffset=+1] include::action-types/webhook.asciidoc[leveloffset=+1] diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index 7bd1a09396d013..93ff818c4b1ebe 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -138,7 +138,7 @@ WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions. Default: true. `xpack.actions.enabledActionTypes` {ess-icon}:: -A list of action types that are enabled. It defaults to `["*"]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.opsgenie`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.tines`, `.torq`, `.xmatters`, `.gen-ai`, `.bedrock`, `.d3security`, and `.webhook`. An empty list `[]` will disable all action types. +A list of action types that are enabled. It defaults to `["*"]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.opsgenie`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.thehive`, `.tines`, `.torq`, `.xmatters`, `.gen-ai`, `.bedrock`, `.d3security`, and `.webhook`. An empty list `[]` will disable all action types. + Disabled action types will not appear as an option when creating new connectors, but existing connectors and actions of that type will remain in {kib} and will not function. diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json index e00e76a26d883b..045d5e54b461b0 100644 --- a/x-pack/plugins/stack_connectors/tsconfig.json +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -40,7 +40,6 @@ "@kbn/cases-components", "@kbn/code-editor-mock", "@kbn/utility-types", - "@kbn/stack-connectors-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 46a3119ab0d222..6eaec102414bc6 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -56,6 +56,7 @@ const enabledActionTypes = [ CROWDSTRIKE_CONNECTOR_ID, '.slack', '.slack_api', + '.thehive', '.tines', '.webhook', '.xmatters', diff --git a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts new file mode 100644 index 00000000000000..fc2ca869ac5f1b --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts @@ -0,0 +1,104 @@ +/* + * 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 http from 'http'; + +import { ProxyArgs, Simulator } from './simulator'; + +export class TheHiveSimulator extends Simulator { + private readonly returnError: boolean; + + constructor({ returnError = false, proxy }: { returnError?: boolean; proxy?: ProxyArgs }) { + super(proxy); + + this.returnError = returnError; + } + + public async handler( + request: http.IncomingMessage, + response: http.ServerResponse, + data: Record + ) { + if (this.returnError) { + return TheHiveSimulator.sendErrorResponse(response); + } + + return TheHiveSimulator.sendResponse(response); + } + + private static sendResponse(response: http.ServerResponse) { + response.statusCode = 201; + response.setHeader('Content-Type', 'application/json'); + response.end(JSON.stringify(theHiveSuccessResponse, null, 4)); + } + + private static sendErrorResponse(response: http.ServerResponse) { + response.statusCode = 400; + response.setHeader('Content-Type', 'application/json'); + response.end(JSON.stringify(theHiveFailedResponse, null, 4)); + } +} + +export const theHiveSuccessResponse = { + _id: '~172064', + _type: 'Case', + _createdBy: 'user1@thehive.local', + _createdAt: 1712128153041, + number: 67, + title: 'title', + description: 'description', + severity: 1, + severityLabel: 'LOW', + startDate: 1712128153029, + tags: [ + 'tag1', + 'tag2' + ], + flag: false, + tlp: 2, + tlpLabel: 'AMBER', + pap: 2, + papLabel: 'AMBER', + status: 'New', + stage: 'New', + assignee: 'user1@thehive.local', + customFields: [], + userPermissions: [ + 'manageCase/create', + 'manageAlert/update', + 'manageProcedure', + 'managePage', + 'manageObservable', + 'manageCase/delete', + 'manageAlert/create', + 'manageCaseReport', + 'manageAlert/delete', + 'accessTheHiveFS', + 'manageKnowledgeBase', + 'manageAction', + 'manageShare', + 'manageAnalyse', + 'manageFunction/invoke', + 'manageTask', + 'manageCase/merge', + 'manageCustomEvent', + 'manageAlert/import', + 'manageCase/changeOwnership', + 'manageComment', + 'manageAlert/reopen', + 'manageCase/update', + 'manageCase/reopen' + ], + extraData: {}, + newDate: 1712128153029, + timeToDetect: 0 +}; + +export const theHiveFailedResponse = { + type: 'BadRequest', + message: 'Invalid json' +}; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts new file mode 100644 index 00000000000000..a7f779afbfac5d --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts @@ -0,0 +1,332 @@ +/* + * 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 { + TheHiveSimulator, +} from '@kbn/actions-simulators-plugin/server/thehive_simulation'; +import { TaskErrorSource } from '@kbn/task-manager-plugin/common'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; + +const connectorTypeId = '.thehive'; +const name = 'TheHive action'; +const secrets = { + apiKey: 'token12345', +}; + +// eslint-disable-next-line import/no-default-export +export default function theHiveTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const configService = getService('config'); + + const createConnector = async (url: string) => { + const { body } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: { url, organisation: null }, + secrets, + }) + .expect(200); + + return body.id; + }; + + describe('TheHive', () => { + describe('action creation', () => { + const simulator = new TheHiveSimulator({ + returnError: false, + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + const config = { url: '', organisation: null }; + + before(async () => { + config.url = await simulator.start(); + }); + + after(() => { + simulator.close(); + }); + + it('should return 200 when creating the connector without organisation', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config, + secrets, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + is_preconfigured: false, + is_system_action: false, + is_deprecated: false, + name, + connector_type_id: connectorTypeId, + is_missing_secrets: false, + config, + }); + }); + + it('should return 200 when creating the connector with the organisation', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: { ...config, organisation: 'test-organisation' }, + secrets, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + is_preconfigured: false, + is_system_action: false, + is_deprecated: false, + name, + connector_type_id: connectorTypeId, + is_missing_secrets: false, + config: { ...config, organisation: 'test-organisation' }, + }); + }); + + it('should return 400 Bad Request when creating the connector without the url', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: {}, + secrets, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type config: [url]: expected value of type [string] but got [undefined]', + }); + }); + }); + + it('should return 400 Bad Request when creating the connector with a url that is not allowed', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: { + url: 'http://thehive.mynonexistent.com', + }, + secrets, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type config: error validating url: target url "http://thehive.mynonexistent.com" is not added to the Kibana config xpack.actions.allowedHosts', + }); + }); + }); + + it('should return 400 Bad Request when creating the connector without secrets', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type secrets: [apiKey]: expected value of type [string] but got [undefined]', + }); + }); + }); + }); + + describe('executor', () => { + describe('validation', () => { + const simulator = new TheHiveSimulator({ + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + let theHiveActionId: string; + + before(async () => { + const url = await simulator.start(); + theHiveActionId = await createConnector(url); + }); + + after(() => { + simulator.close(); + }); + + it('should fail when the params is empty', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: {}, + }); + expect(200); + + expect(body).to.eql({ + status: 'error', + connector_id: theHiveActionId, + message: + 'error validating action params: [subAction]: expected value of type [string] but got [undefined]', + retry: false, + errorSource: TaskErrorSource.FRAMEWORK, + }); + }); + + it('should fail when the subAction is invalid', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { subAction: 'invalidAction' }, + }) + .expect(200); + + expect(body).to.eql({ + connector_id: theHiveActionId, + status: 'error', + retry: true, + message: 'an error occurred while running the action', + errorSource: TaskErrorSource.FRAMEWORK, + service_message: `Sub action "invalidAction" is not registered. Connector id: ${theHiveActionId}. Connector name: TheHive. Connector type: .thehive`, + }); + }); + }); + + describe('execution', () => { + describe('successful response simulator', () => { + const simulator = new TheHiveSimulator({ + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + let url: string; + let theHiveActionId: string; + + before(async () => { + url = await simulator.start(); + theHiveActionId = await createConnector(url); + }); + + after(() => { + simulator.close(); + }); + + it('should send a formatted JSON object', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + title: 'title', + description: 'description', + tlp: 2, + severity: 1, + tags: ['tag1', 'tag2'], + }, + comments: [], + }, + }, + }) + .expect(200); + + expect(simulator.requestData).to.eql({ + title: 'title', + description: 'description', + tlp: 2, + severity: 1, + tags: ['tag1', 'tag2'], + }); + + expect(body).to.eql({ + status: 'ok', + connector_id: theHiveActionId, + data: { + id: '~172064', + title: 'title', + url: `${url}/cases/~172064/details`, + pushedDate: new Date(1712128153041).toISOString(), + }, + }); + }); + }); + + describe('error response simulator', () => { + const simulator = new TheHiveSimulator({ + returnError: true, + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + + let theHiveActionId: string; + + before(async () => { + const url = await simulator.start(); + theHiveActionId = await createConnector(url); + }); + + after(() => { + simulator.close(); + }); + + it('should return a failure when error happens', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: {}, + }) + .expect(200); + + expect(body).to.eql({ + status: 'error', + connector_id: theHiveActionId, + message: + 'error validating action params: [subAction]: expected value of type [string] but got [undefined]', + retry: false, + errorSource: TaskErrorSource.FRAMEWORK, + }); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts index ab79a189839e23..2f773c7bbf43bf 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts @@ -43,6 +43,7 @@ export default function connectorsTests({ loadTestFile, getService }: FtrProvide loadTestFile(require.resolve('./connector_types/torq')); loadTestFile(require.resolve('./connector_types/openai')); loadTestFile(require.resolve('./connector_types/d3security')); + loadTestFile(require.resolve('./connector_types/thehive')); loadTestFile(require.resolve('./connector_types/bedrock')); loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts index 87a258cd022e4f..d7f95b6264a276 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts @@ -46,6 +46,7 @@ export default function createRegisteredConnectorTypeTests({ getService }: FtrPr '.observability-ai-assistant', '.resilient', '.teams', + '.thehive', '.tines', '.torq', '.opsgenie', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index 6790888311fadc..ca2c95d728c8e2 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -73,6 +73,7 @@ export default function ({ getService }: FtrProviderContext) { 'actions:.slack_api', 'actions:.swimlane', 'actions:.teams', + 'actions:.thehive', 'actions:.tines', 'actions:.torq', 'actions:.webhook', From 86c87fddef0ba0acba566ac6b41bd66fef71fb67 Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Tue, 23 Apr 2024 19:24:01 +0530 Subject: [PATCH 06/15] update minimumLicenseRequired to platinum --- .../stack_connectors/server/connector_types/thehive/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts index 1758ef48922d9f..50becfe1e29968 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/index.ts @@ -25,7 +25,7 @@ export type TheHiveConnectorType = SubActionConnectorType new TheHiveConnector(params), supportedFeatureIds: [ From d6b7ae3e0b849127f9216380cf293c1faa5820eb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:50:04 +0000 Subject: [PATCH 07/15] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../server/thehive_simulation.ts | 155 +++-- .../tests/actions/connector_types/thehive.ts | 590 +++++++++--------- 2 files changed, 370 insertions(+), 375 deletions(-) diff --git a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts index fc2ca869ac5f1b..fc6ff59cc24989 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/thehive_simulation.ts @@ -10,95 +10,92 @@ import http from 'http'; import { ProxyArgs, Simulator } from './simulator'; export class TheHiveSimulator extends Simulator { - private readonly returnError: boolean; + private readonly returnError: boolean; - constructor({ returnError = false, proxy }: { returnError?: boolean; proxy?: ProxyArgs }) { - super(proxy); + constructor({ returnError = false, proxy }: { returnError?: boolean; proxy?: ProxyArgs }) { + super(proxy); - this.returnError = returnError; - } - - public async handler( - request: http.IncomingMessage, - response: http.ServerResponse, - data: Record - ) { - if (this.returnError) { - return TheHiveSimulator.sendErrorResponse(response); - } + this.returnError = returnError; + } - return TheHiveSimulator.sendResponse(response); + public async handler( + request: http.IncomingMessage, + response: http.ServerResponse, + data: Record + ) { + if (this.returnError) { + return TheHiveSimulator.sendErrorResponse(response); } - private static sendResponse(response: http.ServerResponse) { - response.statusCode = 201; - response.setHeader('Content-Type', 'application/json'); - response.end(JSON.stringify(theHiveSuccessResponse, null, 4)); - } + return TheHiveSimulator.sendResponse(response); + } - private static sendErrorResponse(response: http.ServerResponse) { - response.statusCode = 400; - response.setHeader('Content-Type', 'application/json'); - response.end(JSON.stringify(theHiveFailedResponse, null, 4)); - } + private static sendResponse(response: http.ServerResponse) { + response.statusCode = 201; + response.setHeader('Content-Type', 'application/json'); + response.end(JSON.stringify(theHiveSuccessResponse, null, 4)); + } + + private static sendErrorResponse(response: http.ServerResponse) { + response.statusCode = 400; + response.setHeader('Content-Type', 'application/json'); + response.end(JSON.stringify(theHiveFailedResponse, null, 4)); + } } export const theHiveSuccessResponse = { - _id: '~172064', - _type: 'Case', - _createdBy: 'user1@thehive.local', - _createdAt: 1712128153041, - number: 67, - title: 'title', - description: 'description', - severity: 1, - severityLabel: 'LOW', - startDate: 1712128153029, - tags: [ - 'tag1', - 'tag2' - ], - flag: false, - tlp: 2, - tlpLabel: 'AMBER', - pap: 2, - papLabel: 'AMBER', - status: 'New', - stage: 'New', - assignee: 'user1@thehive.local', - customFields: [], - userPermissions: [ - 'manageCase/create', - 'manageAlert/update', - 'manageProcedure', - 'managePage', - 'manageObservable', - 'manageCase/delete', - 'manageAlert/create', - 'manageCaseReport', - 'manageAlert/delete', - 'accessTheHiveFS', - 'manageKnowledgeBase', - 'manageAction', - 'manageShare', - 'manageAnalyse', - 'manageFunction/invoke', - 'manageTask', - 'manageCase/merge', - 'manageCustomEvent', - 'manageAlert/import', - 'manageCase/changeOwnership', - 'manageComment', - 'manageAlert/reopen', - 'manageCase/update', - 'manageCase/reopen' - ], - extraData: {}, - newDate: 1712128153029, - timeToDetect: 0 + _id: '~172064', + _type: 'Case', + _createdBy: 'user1@thehive.local', + _createdAt: 1712128153041, + number: 67, + title: 'title', + description: 'description', + severity: 1, + severityLabel: 'LOW', + startDate: 1712128153029, + tags: ['tag1', 'tag2'], + flag: false, + tlp: 2, + tlpLabel: 'AMBER', + pap: 2, + papLabel: 'AMBER', + status: 'New', + stage: 'New', + assignee: 'user1@thehive.local', + customFields: [], + userPermissions: [ + 'manageCase/create', + 'manageAlert/update', + 'manageProcedure', + 'managePage', + 'manageObservable', + 'manageCase/delete', + 'manageAlert/create', + 'manageCaseReport', + 'manageAlert/delete', + 'accessTheHiveFS', + 'manageKnowledgeBase', + 'manageAction', + 'manageShare', + 'manageAnalyse', + 'manageFunction/invoke', + 'manageTask', + 'manageCase/merge', + 'manageCustomEvent', + 'manageAlert/import', + 'manageCase/changeOwnership', + 'manageComment', + 'manageAlert/reopen', + 'manageCase/update', + 'manageCase/reopen', + ], + extraData: {}, + newDate: 1712128153029, + timeToDetect: 0, }; export const theHiveFailedResponse = { - type: 'BadRequest', - message: 'Invalid json' + type: 'BadRequest', + message: 'Invalid json', }; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts index a7f779afbfac5d..e1bf048606bc11 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/thehive.ts @@ -7,326 +7,324 @@ import expect from '@kbn/expect'; -import { - TheHiveSimulator, -} from '@kbn/actions-simulators-plugin/server/thehive_simulation'; +import { TheHiveSimulator } from '@kbn/actions-simulators-plugin/server/thehive_simulation'; import { TaskErrorSource } from '@kbn/task-manager-plugin/common'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; const connectorTypeId = '.thehive'; const name = 'TheHive action'; const secrets = { - apiKey: 'token12345', + apiKey: 'token12345', }; // eslint-disable-next-line import/no-default-export export default function theHiveTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const configService = getService('config'); - - const createConnector = async (url: string) => { - const { body } = await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name, - connector_type_id: connectorTypeId, - config: { url, organisation: null }, - secrets, - }) - .expect(200); - - return body.id; - }; - - describe('TheHive', () => { - describe('action creation', () => { - const simulator = new TheHiveSimulator({ - returnError: false, - proxy: { - config: configService.get('kbnTestServer.serverArgs'), - }, - }); - const config = { url: '', organisation: null }; - - before(async () => { - config.url = await simulator.start(); + const supertest = getService('supertest'); + const configService = getService('config'); + + const createConnector = async (url: string) => { + const { body } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: { url, organisation: null }, + secrets, + }) + .expect(200); + + return body.id; + }; + + describe('TheHive', () => { + describe('action creation', () => { + const simulator = new TheHiveSimulator({ + returnError: false, + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + const config = { url: '', organisation: null }; + + before(async () => { + config.url = await simulator.start(); + }); + + after(() => { + simulator.close(); + }); + + it('should return 200 when creating the connector without organisation', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config, + secrets, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + is_preconfigured: false, + is_system_action: false, + is_deprecated: false, + name, + connector_type_id: connectorTypeId, + is_missing_secrets: false, + config, + }); + }); + + it('should return 200 when creating the connector with the organisation', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: { ...config, organisation: 'test-organisation' }, + secrets, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + is_preconfigured: false, + is_system_action: false, + is_deprecated: false, + name, + connector_type_id: connectorTypeId, + is_missing_secrets: false, + config: { ...config, organisation: 'test-organisation' }, + }); + }); + + it('should return 400 Bad Request when creating the connector without the url', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: {}, + secrets, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type config: [url]: expected value of type [string] but got [undefined]', }); - - after(() => { - simulator.close(); + }); + }); + + it('should return 400 Bad Request when creating the connector with a url that is not allowed', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config: { + url: 'http://thehive.mynonexistent.com', + }, + secrets, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type config: error validating url: target url "http://thehive.mynonexistent.com" is not added to the Kibana config xpack.actions.allowedHosts', }); - - it('should return 200 when creating the connector without organisation', async () => { - const { body: createdAction } = await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name, - connector_type_id: connectorTypeId, - config, - secrets, - }) - .expect(200); - - expect(createdAction).to.eql({ - id: createdAction.id, - is_preconfigured: false, - is_system_action: false, - is_deprecated: false, - name, - connector_type_id: connectorTypeId, - is_missing_secrets: false, - config, - }); + }); + }); + + it('should return 400 Bad Request when creating the connector without secrets', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name, + connector_type_id: connectorTypeId, + config, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type secrets: [apiKey]: expected value of type [string] but got [undefined]', }); + }); + }); + }); - it('should return 200 when creating the connector with the organisation', async () => { - const { body: createdAction } = await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name, - connector_type_id: connectorTypeId, - config: { ...config, organisation: 'test-organisation' }, - secrets, - }) - .expect(200); - - expect(createdAction).to.eql({ - id: createdAction.id, - is_preconfigured: false, - is_system_action: false, - is_deprecated: false, - name, - connector_type_id: connectorTypeId, - is_missing_secrets: false, - config: { ...config, organisation: 'test-organisation' }, - }); - }); + describe('executor', () => { + describe('validation', () => { + const simulator = new TheHiveSimulator({ + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + let theHiveActionId: string; - it('should return 400 Bad Request when creating the connector without the url', async () => { - await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name, - connector_type_id: connectorTypeId, - config: {}, - secrets, - }) - .expect(400) - .then((resp: any) => { - expect(resp.body).to.eql({ - statusCode: 400, - error: 'Bad Request', - message: - 'error validating action type config: [url]: expected value of type [string] but got [undefined]', - }); - }); - }); + before(async () => { + const url = await simulator.start(); + theHiveActionId = await createConnector(url); + }); - it('should return 400 Bad Request when creating the connector with a url that is not allowed', async () => { - await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name, - connector_type_id: connectorTypeId, - config: { - url: 'http://thehive.mynonexistent.com', - }, - secrets, - }) - .expect(400) - .then((resp: any) => { - expect(resp.body).to.eql({ - statusCode: 400, - error: 'Bad Request', - message: - 'error validating action type config: error validating url: target url "http://thehive.mynonexistent.com" is not added to the Kibana config xpack.actions.allowedHosts', - }); - }); - }); + after(() => { + simulator.close(); + }); - it('should return 400 Bad Request when creating the connector without secrets', async () => { - await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name, - connector_type_id: connectorTypeId, - config, - }) - .expect(400) - .then((resp: any) => { - expect(resp.body).to.eql({ - statusCode: 400, - error: 'Bad Request', - message: - 'error validating action type secrets: [apiKey]: expected value of type [string] but got [undefined]', - }); - }); + it('should fail when the params is empty', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: {}, }); + expect(200); + + expect(body).to.eql({ + status: 'error', + connector_id: theHiveActionId, + message: + 'error validating action params: [subAction]: expected value of type [string] but got [undefined]', + retry: false, + errorSource: TaskErrorSource.FRAMEWORK, + }); }); - describe('executor', () => { - describe('validation', () => { - const simulator = new TheHiveSimulator({ - proxy: { - config: configService.get('kbnTestServer.serverArgs'), + it('should fail when the subAction is invalid', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { subAction: 'invalidAction' }, + }) + .expect(200); + + expect(body).to.eql({ + connector_id: theHiveActionId, + status: 'error', + retry: true, + message: 'an error occurred while running the action', + errorSource: TaskErrorSource.FRAMEWORK, + service_message: `Sub action "invalidAction" is not registered. Connector id: ${theHiveActionId}. Connector name: TheHive. Connector type: .thehive`, + }); + }); + }); + + describe('execution', () => { + describe('successful response simulator', () => { + const simulator = new TheHiveSimulator({ + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + let url: string; + let theHiveActionId: string; + + before(async () => { + url = await simulator.start(); + theHiveActionId = await createConnector(url); + }); + + after(() => { + simulator.close(); + }); + + it('should send a formatted JSON object', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + title: 'title', + description: 'description', + tlp: 2, + severity: 1, + tags: ['tag1', 'tag2'], }, - }); - let theHiveActionId: string; - - before(async () => { - const url = await simulator.start(); - theHiveActionId = await createConnector(url); - }); - - after(() => { - simulator.close(); - }); - - it('should fail when the params is empty', async () => { - const { body } = await supertest - .post(`/api/actions/connector/${theHiveActionId}/_execute`) - .set('kbn-xsrf', 'foo') - .send({ - params: {}, - }); - expect(200); - - expect(body).to.eql({ - status: 'error', - connector_id: theHiveActionId, - message: - 'error validating action params: [subAction]: expected value of type [string] but got [undefined]', - retry: false, - errorSource: TaskErrorSource.FRAMEWORK, - }); - }); - - it('should fail when the subAction is invalid', async () => { - const { body } = await supertest - .post(`/api/actions/connector/${theHiveActionId}/_execute`) - .set('kbn-xsrf', 'foo') - .send({ - params: { subAction: 'invalidAction' }, - }) - .expect(200); - - expect(body).to.eql({ - connector_id: theHiveActionId, - status: 'error', - retry: true, - message: 'an error occurred while running the action', - errorSource: TaskErrorSource.FRAMEWORK, - service_message: `Sub action "invalidAction" is not registered. Connector id: ${theHiveActionId}. Connector name: TheHive. Connector type: .thehive`, - }); - }); + comments: [], + }, + }, + }) + .expect(200); + + expect(simulator.requestData).to.eql({ + title: 'title', + description: 'description', + tlp: 2, + severity: 1, + tags: ['tag1', 'tag2'], + }); + + expect(body).to.eql({ + status: 'ok', + connector_id: theHiveActionId, + data: { + id: '~172064', + title: 'title', + url: `${url}/cases/~172064/details`, + pushedDate: new Date(1712128153041).toISOString(), + }, }); + }); + }); - describe('execution', () => { - describe('successful response simulator', () => { - const simulator = new TheHiveSimulator({ - proxy: { - config: configService.get('kbnTestServer.serverArgs'), - }, - }); - let url: string; - let theHiveActionId: string; - - before(async () => { - url = await simulator.start(); - theHiveActionId = await createConnector(url); - }); - - after(() => { - simulator.close(); - }); - - it('should send a formatted JSON object', async () => { - const { body } = await supertest - .post(`/api/actions/connector/${theHiveActionId}/_execute`) - .set('kbn-xsrf', 'foo') - .send({ - params: { - subAction: 'pushToService', - subActionParams: { - incident: { - title: 'title', - description: 'description', - tlp: 2, - severity: 1, - tags: ['tag1', 'tag2'], - }, - comments: [], - }, - }, - }) - .expect(200); - - expect(simulator.requestData).to.eql({ - title: 'title', - description: 'description', - tlp: 2, - severity: 1, - tags: ['tag1', 'tag2'], - }); - - expect(body).to.eql({ - status: 'ok', - connector_id: theHiveActionId, - data: { - id: '~172064', - title: 'title', - url: `${url}/cases/~172064/details`, - pushedDate: new Date(1712128153041).toISOString(), - }, - }); - }); - }); - - describe('error response simulator', () => { - const simulator = new TheHiveSimulator({ - returnError: true, - proxy: { - config: configService.get('kbnTestServer.serverArgs'), - }, - }); - - let theHiveActionId: string; - - before(async () => { - const url = await simulator.start(); - theHiveActionId = await createConnector(url); - }); - - after(() => { - simulator.close(); - }); - - it('should return a failure when error happens', async () => { - const { body } = await supertest - .post(`/api/actions/connector/${theHiveActionId}/_execute`) - .set('kbn-xsrf', 'foo') - .send({ - params: {}, - }) - .expect(200); - - expect(body).to.eql({ - status: 'error', - connector_id: theHiveActionId, - message: - 'error validating action params: [subAction]: expected value of type [string] but got [undefined]', - retry: false, - errorSource: TaskErrorSource.FRAMEWORK, - }); - }); - }); + describe('error response simulator', () => { + const simulator = new TheHiveSimulator({ + returnError: true, + proxy: { + config: configService.get('kbnTestServer.serverArgs'), + }, + }); + + let theHiveActionId: string; + + before(async () => { + const url = await simulator.start(); + theHiveActionId = await createConnector(url); + }); + + after(() => { + simulator.close(); + }); + + it('should return a failure when error happens', async () => { + const { body } = await supertest + .post(`/api/actions/connector/${theHiveActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: {}, + }) + .expect(200); + + expect(body).to.eql({ + status: 'error', + connector_id: theHiveActionId, + message: + 'error validating action params: [subAction]: expected value of type [string] but got [undefined]', + retry: false, + errorSource: TaskErrorSource.FRAMEWORK, }); + }); }); + }); }); + }); } From f576b5f2fe4ef763796bbecaa4ed1472b3081202 Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Mon, 6 May 2024 19:23:13 +0530 Subject: [PATCH 08/15] Refactor UI and update server code to align with CaseConnector framework changes --- .../connectors/action-types/thehive.asciidoc | 2 +- .../images/thehive-params-alert-test.png | Bin 41263 -> 29603 bytes .../images/thehive-params-case-test.png | Bin 36289 -> 28068 bytes .../stack_connectors/common/thehive/schema.ts | 10 +- .../stack_connectors/common/thehive/types.ts | 9 +- .../thehive/params_alert.test.tsx | 20 +- .../connector_types/thehive/params_alert.tsx | 250 ++++++++---------- .../thehive/params_case.test.tsx | 14 +- .../connector_types/thehive/params_case.tsx | 98 +++---- .../connector_types/thehive/translations.ts | 10 +- .../server/connector_types/index.ts | 2 +- .../server/connector_types/thehive/index.ts | 8 +- .../connector_types/thehive/thehive.test.ts | 45 ++-- .../server/connector_types/thehive/thehive.ts | 50 ++-- 14 files changed, 226 insertions(+), 292 deletions(-) diff --git a/docs/management/connectors/action-types/thehive.asciidoc b/docs/management/connectors/action-types/thehive.asciidoc index 7552fba920cc34..9ee6a2e4325c13 100644 --- a/docs/management/connectors/action-types/thehive.asciidoc +++ b/docs/management/connectors/action-types/thehive.asciidoc @@ -49,7 +49,7 @@ image::management/connectors/images/thehive-params-alert-test.png[TheHive alert TheHive actions have the following configuration properties. -EventAction:: Action that will be performed in thehive. Supported actions are Create Case (default) and Create Alert. +Event Action:: Action that will be performed in thehive. Supported actions are Create Case (default) and Create Alert. Title:: Title of the incident. Description:: The details about the incident. Severity:: Severity of the incident. This can be one of `LOW`, `MEDIUM`(default), `HIGH` or `CRITICAL`. diff --git a/docs/management/connectors/images/thehive-params-alert-test.png b/docs/management/connectors/images/thehive-params-alert-test.png index bee5b8c30c1ef3c6c26f34fd59044cb49475069f..c72eff5e31054527bfd119dc21ed443bc82b7cbd 100644 GIT binary patch literal 29603 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 RV$H7(9}N7k{)3aB{1cmw??nIr literal 41263 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 JnR}zZ{5#@Hsl)&P literal 36289 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 Tl}!)enrY-@l&_>q8~FYo^}mKD diff --git a/x-pack/plugins/stack_connectors/common/thehive/schema.ts b/x-pack/plugins/stack_connectors/common/thehive/schema.ts index 6002f43555adf7..e880ca900591a0 100644 --- a/x-pack/plugins/stack_connectors/common/thehive/schema.ts +++ b/x-pack/plugins/stack_connectors/common/thehive/schema.ts @@ -36,6 +36,14 @@ export const ExecutorSubActionPushParamsSchema = schema.object({ ), }); +export const PushToServiceIncidentSchema = { + title: schema.string(), + description: schema.string(), + severity: schema.nullable(schema.number()), + tlp: schema.nullable(schema.number()), + tags: schema.nullable(schema.arrayOf(schema.string())), +}; + export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ externalId: schema.string(), }); @@ -108,7 +116,7 @@ export const TheHiveIncidentResponseSchema = schema.object( { unknowns: 'ignore' } ); -export const TheHiveUpdateIncidentResponseSchema = schema.never(); +export const TheHiveUpdateIncidentResponseSchema = schema.any(); export const TheHiveAddCommentResponseSchema = schema.object( { diff --git a/x-pack/plugins/stack_connectors/common/thehive/types.ts b/x-pack/plugins/stack_connectors/common/thehive/types.ts index 096116db29e2e1..b67820ac77e5ef 100644 --- a/x-pack/plugins/stack_connectors/common/thehive/types.ts +++ b/x-pack/plugins/stack_connectors/common/thehive/types.ts @@ -13,6 +13,7 @@ import { ExecutorSubActionPushParamsSchema, ExecutorSubActionCreateAlertParamsSchema, TheHiveFailureResponseSchema, + TheHiveIncidentResponseSchema, } from './schema'; export type TheHiveConfig = TypeOf; @@ -33,8 +34,6 @@ export interface ExternalServiceIncidentResponse { pushedDate: string; } -export interface ExternalServiceCommentResponse { - commentId: string; - pushedDate: string; - externalCommentId?: string; -} +export type Incident = Omit; + +export type GetIncidentResponse = TypeOf; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx index 53092d28c21e2b..138595bd526908 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.test.tsx @@ -55,16 +55,16 @@ describe('TheHiveParamsFields renders', () => { it('all Params fields is rendered', () => { const { getByTestId } = render(); - expect(getByTestId('alert-title-row')).toBeInTheDocument(); - expect(getByTestId('alert-description-row')).toBeInTheDocument(); - expect(getByTestId('alert-eventTags')).toBeInTheDocument(); - expect(getByTestId('alert-eventSeveritySelect')).toBeInTheDocument(); - expect(getByTestId('alert-eventTlpSelect')).toBeInTheDocument(); - expect(getByTestId('alert-type-row')).toBeInTheDocument(); - expect(getByTestId('alert-source-row')).toBeInTheDocument(); - expect(getByTestId('alert-sourceRef-row')).toBeInTheDocument(); + expect(getByTestId('titleInput')).toBeInTheDocument(); + expect(getByTestId('descriptionTextArea')).toBeInTheDocument(); + expect(getByTestId('tagsInput')).toBeInTheDocument(); + expect(getByTestId('severitySelectInput')).toBeInTheDocument(); + expect(getByTestId('tlpSelectInput')).toBeInTheDocument(); + expect(getByTestId('typeInput')).toBeInTheDocument(); + expect(getByTestId('sourceInput')).toBeInTheDocument(); + expect(getByTestId('sourceRefInput')).toBeInTheDocument(); - expect(getByTestId('alert-eventSeveritySelect')).toHaveValue('2'); - expect(getByTestId('alert-eventTlpSelect')).toHaveValue('2'); + expect(getByTestId('severitySelectInput')).toHaveValue('2'); + expect(getByTestId('tlpSelectInput')).toHaveValue('2'); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx index 1ffdd93f5e0cad..69fa1097d291ae 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx @@ -8,9 +8,10 @@ import React, { useState, useMemo } from 'react'; import { TextFieldWithMessageVariables, + TextAreaWithMessageVariables, ActionParamsProps, } from '@kbn/triggers-actions-ui-plugin/public'; -import { EuiFormRow, EuiSelect, EuiText, EuiComboBox } from '@elastic/eui'; +import { EuiFormRow, EuiSelect, EuiComboBox } from '@elastic/eui'; import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; import { severityOptions, tlpOptions } from './constants'; import * as translations from './translations'; @@ -42,177 +43,142 @@ export const TheHiveParamsAlertFields: React.FC) => { - setSelected(selectedOptions); + const onChange = (selectedOptionList: Array<{ label: string }>) => { + setSelected(selectedOptionList); editAction( 'subActionParams', - { ...alert, tags: selectedOptions.map((option) => option.label) }, + { ...alert, tags: selectedOptionList.map((option) => option.label) }, index ); }; return ( <> - 0 && - alert.title !== undefined - } - label={translations.TITLE_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'title'} - inputTargetValue={alert.title ?? undefined} - errors={errors['createAlertParam.title'] as string[]} - /> - - 0 && - alert.description !== undefined - } + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + messageVariables={messageVariables} + paramsProperty={'title'} + inputTargetValue={alert.title ?? undefined} + wrapField={true} + formRowProps={{ + label: translations.TITLE_LABEL, + fullWidth: true, + helpText: '', + isInvalid: + errors['createAlertParam.title'] !== undefined && + errors['createAlertParam.title'].length > 0 && + alert.title !== undefined, + error: errors['createAlertParam.title'] as string, + }} + errors={errors['createAlertParam.title'] as string[]} + /> + - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'description'} - inputTargetValue={alert.description ?? undefined} - errors={errors['createAlertParam.description'] as string[]} - /> - - 0 && - alert.type !== undefined - } - label={translations.TYPE_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'type'} - inputTargetValue={alert.type ?? undefined} - errors={(errors['createAlertParam.type'] ?? []) as string[]} - /> - - 0 && - alert.source !== undefined - } - label={translations.SOURCE_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - paramsProperty={'source'} - inputTargetValue={alert.source ?? undefined} - errors={(errors['createAlertParam.source'] ?? []) as string[]} - /> - - 0 && - alert.sourceRef !== undefined - } - label={translations.SOURCE_REF_LABEL} - labelAppend={ - - Required - - } - > - { - editAction('subActionParams', { ...alert, [key]: value }, index); - }} - messageVariables={messageVariables} - paramsProperty={'sourceRef'} - inputTargetValue={alert.sourceRef ?? undefined} - errors={(errors['createAlertParam.sourceRef'] ?? []) as string[]} - /> - + editAction={(key, value) => { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + messageVariables={messageVariables} + paramsProperty={'description'} + inputTargetValue={alert.description ?? undefined} + errors={errors['createAlertParam.description'] as string[]} + /> + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'type'} + inputTargetValue={alert.type ?? undefined} + wrapField={true} + formRowProps={{ + label: translations.TYPE_LABEL, + fullWidth: true, + helpText: '', + isInvalid: + errors['createAlertParam.type'] !== undefined && + errors['createAlertParam.type'].length > 0 && + alert.type !== undefined, + error: errors['createAlertParam.type'] as string, + }} + errors={errors['createAlertParam.type'] as string[]} + /> + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + paramsProperty={'source'} + inputTargetValue={alert.source ?? undefined} + wrapField={true} + formRowProps={{ + label: translations.SOURCE_LABEL, + fullWidth: true, + helpText: '', + isInvalid: + errors['createAlertParam.source'] !== undefined && + errors['createAlertParam.source'].length > 0 && + alert.source !== undefined, + error: errors['createAlertParam.source'] as string, + }} + errors={errors['createAlertParam.source'] as string[]} + /> + { + editAction('subActionParams', { ...alert, [key]: value }, index); + }} + messageVariables={messageVariables} + paramsProperty={'sourceRef'} + inputTargetValue={alert.sourceRef ?? undefined} + wrapField={true} + formRowProps={{ + label: translations.SOURCE_REF_LABEL, + fullWidth: true, + helpText: '', + isInvalid: + errors['createAlertParam.sourceRef'] !== undefined && + errors['createAlertParam.sourceRef'].length > 0 && + alert.sourceRef !== undefined, + error: errors['createAlertParam.sourceRef'] as string, + }} + errors={errors['createAlertParam.sourceRef'] as string[]} + /> { - editAction('subActionParams', { ...alert, severity: parseInt(e.target.value) }, index); - setSeverity(parseInt(e.target.value)); + editAction( + 'subActionParams', + { ...alert, severity: parseInt(e.target.value, 10) }, + index + ); + setSeverity(parseInt(e.target.value, 10)); }} /> { - editAction('subActionParams', { ...alert, tlp: parseInt(e.target.value) }, index); - setTlp(parseInt(e.target.value)); + editAction('subActionParams', { ...alert, tlp: parseInt(e.target.value, 10) }, index); + setTlp(parseInt(e.target.value, 10)); }} /> { it('all Params fields is rendered', () => { const { getByTestId } = render(); - expect(getByTestId('title-row')).toBeInTheDocument(); - expect(getByTestId('description-row')).toBeInTheDocument(); - expect(getByTestId('eventTags')).toBeInTheDocument(); - expect(getByTestId('eventSeveritySelect')).toBeInTheDocument(); - expect(getByTestId('eventTlpSelect')).toBeInTheDocument(); + expect(getByTestId('titleInput')).toBeInTheDocument(); + expect(getByTestId('descriptionTextArea')).toBeInTheDocument(); + expect(getByTestId('tagsInput')).toBeInTheDocument(); + expect(getByTestId('severitySelectInput')).toBeInTheDocument(); + expect(getByTestId('tlpSelectInput')).toBeInTheDocument(); expect(getByTestId('commentsTextArea')).toBeInTheDocument(); - expect(getByTestId('eventSeveritySelect')).toHaveValue('2'); - expect(getByTestId('eventTlpSelect')).toHaveValue('2'); + expect(getByTestId('severitySelectInput')).toHaveValue('2'); + expect(getByTestId('tlpSelectInput')).toHaveValue('2'); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx index 1f818ea7c4966c..aaa063dcd89b83 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx @@ -11,7 +11,7 @@ import { TextAreaWithMessageVariables, ActionParamsProps, } from '@kbn/triggers-actions-ui-plugin/public'; -import { EuiFormRow, EuiSelect, EuiText, EuiComboBox } from '@elastic/eui'; +import { EuiFormRow, EuiSelect, EuiComboBox } from '@elastic/eui'; import { ExecutorParams, ExecutorSubActionPushParams } from '../../../common/thehive/types'; import { severityOptions, tlpOptions } from './constants'; import * as translations from './translations'; @@ -67,73 +67,53 @@ export const TheHiveParamsCaseFields: React.FC editSubActionProperty('tags', [...(incident.tags ?? []), searchValue]); }; - const onChange = (selectedOptions: Array<{ label: string }>) => { - setSelected(selectedOptions); + const onChange = (selectedOptionList: Array<{ label: string }>) => { + setSelected(selectedOptionList); editSubActionProperty( 'tags', - selectedOptions.map((option) => option.label) + selectedOptionList.map((option) => option.label) ); }; return ( <> - 0 && - incident.title !== undefined - } - label={translations.TITLE_LABEL} - labelAppend={ - - Required - - } - > - - - 0 && - incident.description !== undefined - } + 0 && + incident.title !== undefined, + error: errors['pushToServiceParam.incident.title'] as string, + }} + errors={errors['pushToServiceParam.incident.title'] as string[]} + /> + - Required - - } - > - - + editAction={editSubActionProperty} + messageVariables={messageVariables} + paramsProperty={'description'} + inputTargetValue={incident.description ?? undefined} + errors={errors['pushToServiceParam.incident.description'] as string[]} + /> { - editSubActionProperty('severity', parseInt(e.target.value)); - setSeverity(parseInt(e.target.value)); + editSubActionProperty('severity', parseInt(e.target.value, 10)); + setSeverity(parseInt(e.target.value, 10)); }} /> @@ -141,19 +121,18 @@ export const TheHiveParamsCaseFields: React.FC { - editSubActionProperty('tlp', parseInt(e.target.value)); - setTlp(parseInt(e.target.value)); + editSubActionProperty('tlp', parseInt(e.target.value, 10)); + setTlp(parseInt(e.target.value, 10)); }} /> /> new TheHiveConnector(params), + getService: (params) => new TheHiveConnector(params, PushToServiceIncidentSchema), supportedFeatureIds: [ AlertingConnectorFeatureId, SecurityConnectorFeatureId, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts index c2b35ebd3cc1fc..6218d48ae33fa4 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.test.ts @@ -15,23 +15,24 @@ import { TheHiveUpdateIncidentResponseSchema, TheHiveAddCommentResponseSchema, TheHiveCreateAlertResponseSchema, + PushToServiceIncidentSchema, } from '../../../common/thehive/schema'; -import type { - ExecutorSubActionPushParams, - ExecutorSubActionCreateAlertParams, -} from '../../../common/thehive/types'; +import type { ExecutorSubActionCreateAlertParams, Incident } from '../../../common/thehive/types'; const mockTime = new Date('2024-04-03T09:10:30.000'); describe('TheHiveConnector', () => { - const connector = new TheHiveConnector({ - configurationUtilities: actionsConfigMock.create(), - connector: { id: '1', type: THEHIVE_CONNECTOR_ID }, - config: { url: 'https://example.com', organisation: null }, - secrets: { apiKey: 'test123' }, - logger: loggingSystemMock.createLogger(), - services: actionsMock.createServices(), - }); + const connector = new TheHiveConnector( + { + configurationUtilities: actionsConfigMock.create(), + connector: { id: '1', type: THEHIVE_CONNECTOR_ID }, + config: { url: 'https://example.com', organisation: null }, + secrets: { apiKey: 'test123' }, + logger: loggingSystemMock.createLogger(), + services: actionsMock.createServices(), + }, + PushToServiceIncidentSchema + ); let mockRequest: jest.Mock; let mockError: jest.Mock; @@ -114,13 +115,12 @@ describe('TheHiveConnector', () => { jest.clearAllMocks(); }); - const incident: ExecutorSubActionPushParams['incident'] = { + const incident: Incident = { title: 'title', description: 'description', severity: 1, tlp: 2, tags: ['tag1', 'tag2'], - externalId: null, }; it('TheHive API call is successful with correct parameters', async () => { @@ -164,13 +164,12 @@ describe('TheHiveConnector', () => { jest.clearAllMocks(); }); - const incident: ExecutorSubActionPushParams['incident'] = { + const incident: Incident = { title: 'new title', description: 'new description', severity: 3, tlp: 1, tags: ['tag3'], - externalId: null, }; it('TheHive API call is successful with correct parameters', async () => { @@ -225,7 +224,7 @@ describe('TheHiveConnector', () => { }); it('TheHive API call is successful with correct parameters', async () => { - const response = await connector.addComment({ + await connector.addComment({ incidentId: '~172064', comment: 'test comment', }); @@ -240,11 +239,6 @@ describe('TheHiveConnector', () => { 'X-Organisation': null, }, }); - expect(response).toEqual({ - commentId: '~41156688', - externalCommentId: '~41156688', - pushedDate: '2024-04-03T15:31:20.100Z', - }); }); it('errors during API calls are properly handled', async () => { @@ -330,12 +324,7 @@ describe('TheHiveConnector', () => { 'X-Organisation': null, }, }); - expect(response).toEqual({ - id: '~172064', - url: 'https://example.com/cases/~172064/details', - title: 'title', - pushedDate: mockTime.toISOString(), - }); + expect(response).toEqual(mockResponse.data); }); it('errors during API calls are properly handled', async () => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts index efe139c4b5e2b5..341ab651c5798a 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts @@ -7,6 +7,7 @@ import { ServiceParams, CaseConnector } from '@kbn/actions-plugin/server'; import type { AxiosError } from 'axios'; +import { Type } from '@kbn/config-schema'; import { SUB_ACTION } from '../../../common/thehive/constants'; import { TheHiveIncidentResponseSchema, @@ -18,23 +19,31 @@ import { import type { TheHiveConfig, TheHiveSecrets, - ExecutorSubActionPushParams, ExecutorSubActionCreateAlertParams, TheHiveFailureResponse, ExternalServiceIncidentResponse, - ExternalServiceCommentResponse, + Incident, + GetIncidentResponse, } from '../../../common/thehive/types'; export const API_VERSION = 'v1'; -export class TheHiveConnector extends CaseConnector { +export class TheHiveConnector extends CaseConnector< + TheHiveConfig, + TheHiveSecrets, + Incident, + GetIncidentResponse +> { private url: string; private apiKey: string; private organisation: string | null; private urlWithoutTrailingSlash: string; - constructor(params: ServiceParams) { - super(params); + constructor( + params: ServiceParams, + pushToServiceParamsExtendedSchema: Record> + ) { + super(params, pushToServiceParamsExtendedSchema); this.registerSubAction({ name: SUB_ACTION.CREATE_ALERT, @@ -59,9 +68,7 @@ export class TheHiveConnector extends CaseConnector { + public async createIncident(incident: Incident): Promise { const res = await this.request({ method: 'post', url: `${this.url}/api/${API_VERSION}/case`, @@ -78,26 +85,14 @@ export class TheHiveConnector extends CaseConnector { - const res = await this.request({ + public async addComment({ incidentId, comment }: { incidentId: string; comment: string }) { + await this.request({ method: 'post', url: `${this.url}/api/${API_VERSION}/case/${incidentId}/comment`, data: { message: comment }, headers: this.getAuthHeaders(), responseSchema: TheHiveAddCommentResponseSchema, }); - - return { - commentId: res.data._id, - externalCommentId: res.data._id, - pushedDate: new Date(res.data.createdAt).toISOString(), - }; } public async updateIncident({ @@ -105,7 +100,7 @@ export class TheHiveConnector extends CaseConnector { await this.request({ method: 'patch', @@ -123,19 +118,14 @@ export class TheHiveConnector extends CaseConnector { + public async getIncident({ id }: { id: string }): Promise { const res = await this.request({ url: `${this.url}/api/${API_VERSION}/case/${id}`, headers: this.getAuthHeaders(), responseSchema: TheHiveIncidentResponseSchema, }); - return { - id: res.data._id, - title: res.data.title, - url: `${this.urlWithoutTrailingSlash}/cases/${res.data._id}/details`, - pushedDate: new Date().toISOString(), - }; + return res.data; } public async createAlert(alert: ExecutorSubActionCreateAlertParams) { From fa742458044e5f766a5ce02dbf613434d844a145 Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Thu, 30 May 2024 16:05:35 +0530 Subject: [PATCH 09/15] Disable Recovered ActionGroup --- x-pack/plugins/alerting/common/disabled_action_groups.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/common/disabled_action_groups.ts b/x-pack/plugins/alerting/common/disabled_action_groups.ts index b6b603c10c0f1c..08b45d41a4a1e3 100644 --- a/x-pack/plugins/alerting/common/disabled_action_groups.ts +++ b/x-pack/plugins/alerting/common/disabled_action_groups.ts @@ -8,7 +8,7 @@ import { RecoveredActionGroup } from './builtin_action_groups'; const DisabledActionGroupsByActionType: Record = { - [RecoveredActionGroup.id]: ['.jira', '.resilient'], + [RecoveredActionGroup.id]: ['.jira', '.resilient', '.thehive'], }; export const DisabledActionTypeIdsForActionGroup: Map = new Map( From 51d4beac3625867bb3445917974e5a739a41e221 Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Tue, 16 Jul 2024 11:40:39 +0530 Subject: [PATCH 10/15] Apply Translations and fix test cases --- .../integration_tests/mocks/connector_types.ts | 1 + .../public/connector_types/thehive/params_alert.tsx | 2 +- .../public/connector_types/thehive/params_case.tsx | 2 +- .../public/connector_types/thehive/translations.ts | 7 +++++++ .../plugins/stack_connectors/server/plugin.test.ts | 12 ++++++------ 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts b/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts index 79d6b4c8d79640..8161ea97db77ed 100644 --- a/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts +++ b/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts @@ -24,6 +24,7 @@ export const connectorTypes: string[] = [ '.torq', '.opsgenie', '.tines', + '.thehive', '.gen-ai', '.bedrock', '.gemini', diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx index 69fa1097d291ae..9d6a9cd2188031 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx @@ -179,7 +179,7 @@ export const TheHiveParamsAlertFields: React.FC { expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith( 8, expect.objectContaining({ - id: '.sentinelone', - name: 'Sentinel One', + id: '.thehive', + name: 'TheHive', }) ); expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith( 9, expect.objectContaining({ - id: '.crowdstrike', - name: 'CrowdStrike', + id: '.sentinelone', + name: 'Sentinel One', }) ); expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith( 10, expect.objectContaining({ - id: '.thehive', - name: 'TheHive', + id: '.crowdstrike', + name: 'CrowdStrike', }) ); }); From c9636fe592c6cf692fe1be4af4445d5baa90493c Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Tue, 16 Jul 2024 19:53:29 +0530 Subject: [PATCH 11/15] Resolve list and test issue --- .../actions/server/integration_tests/mocks/connector_types.ts | 2 +- .../public/connector_types/thehive/params.tsx | 4 ++-- .../server/connector_types/thehive/thehive.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts b/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts index 8161ea97db77ed..a26c775a74a5b1 100644 --- a/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts +++ b/x-pack/plugins/actions/server/integration_tests/mocks/connector_types.ts @@ -24,12 +24,12 @@ export const connectorTypes: string[] = [ '.torq', '.opsgenie', '.tines', - '.thehive', '.gen-ai', '.bedrock', '.gemini', '.d3security', '.resilient', + '.thehive', '.sentinelone', '.crowdstrike', '.cases', diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx index cade42f66a4c83..19593225f827e8 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx @@ -67,11 +67,11 @@ const TheHiveParamsFields: React.FunctionComponent { editAction('subAction', eventAction, index); - }, [eventAction]); + }, [eventAction, editAction, index]); const setEventActionType = (eventActionType: SUB_ACTION) => { const subActionParams = diff --git a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts index 341ab651c5798a..fe0caf8788f285 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/thehive/thehive.ts @@ -54,7 +54,7 @@ export class TheHiveConnector extends CaseConnector< this.url = this.config.url; this.organisation = this.config.organisation; this.apiKey = this.secrets.apiKey; - this.urlWithoutTrailingSlash = this.url.endsWith('/') ? this.url.slice(0, -1) : this.url; + this.urlWithoutTrailingSlash = this.url?.endsWith('/') ? this.url.slice(0, -1) : this.url; } private getAuthHeaders() { From beb6a40feb43782d5f1a28b87934b536944ab1d1 Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Wed, 17 Jul 2024 10:14:19 +0530 Subject: [PATCH 12/15] Resolve build failure --- .../connector_types.test.ts.snap | 610 ++++++++++++++++++ 1 file changed, 610 insertions(+) diff --git a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index 05d74f781c4346..9d18baa53d6bb6 100644 --- a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -31652,6 +31652,616 @@ Object { } `; +exports[`Connector type config checks detect connector type changes for: .thehive 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "comments": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "comment": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "commentId": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "type": "object", + }, + ], + "type": "array", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "incident": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "description": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "externalId": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "severity": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "tags": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "title": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "tlp": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "type": "object", + }, + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .thehive 2`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "description": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "severity": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": 2, + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "source": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "sourceRef": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "tags": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "title": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "tlp": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": 2, + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "type": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .thehive 3`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "organisation": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "url": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .thehive 4`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "apiKey": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .thehive 5`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "subAction": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "subActionParams": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + "unknown": true, + }, + "keys": Object {}, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "type": "object", +} +`; + exports[`Connector type config checks detect connector type changes for: .tines 1`] = ` Object { "flags": Object { From f5a5a601a2d45e013f0d923c23896934de04f7ed Mon Sep 17 00:00:00 2001 From: Janki Salvi Date: Thu, 25 Jul 2024 16:38:59 +0100 Subject: [PATCH 13/15] hide the connector in UI for intermediate release --- .../stack_connectors/public/connector_types/thehive/thehive.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx index d52d2783e707b6..5523a24e05d505 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/thehive.tsx @@ -24,6 +24,7 @@ export function getConnectorType(): TheHiveConnector { defaultMessage: 'Create cases and alerts in TheHive', }), actionTypeTitle: THEHIVE_TITLE, + hideInUi: true, validateParams: async ( actionParams: ExecutorParams ): Promise> => { From d5c5ecab7f1178f1b49a4b100099ad66946d802a Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Mon, 29 Jul 2024 11:45:15 +0530 Subject: [PATCH 14/15] Resolve bugfix --- .../public/connector_types/thehive/params.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx index 19593225f827e8..f0221ce7a460bd 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params.tsx @@ -67,11 +67,13 @@ const TheHiveParamsFields: React.FunctionComponent { editAction('subAction', eventAction, index); - }, [eventAction, editAction, index]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [eventAction]); const setEventActionType = (eventActionType: SUB_ACTION) => { const subActionParams = From a6c054e2b481e85c215bdd13eb3d953814b6edf3 Mon Sep 17 00:00:00 2001 From: brijesh-elastic Date: Tue, 30 Jul 2024 11:42:55 +0530 Subject: [PATCH 15/15] Preserve severity, tlp and tag values in UI --- .../public/connector_types/thehive/params_alert.tsx | 12 +++++++----- .../public/connector_types/thehive/params_case.tsx | 12 +++++++----- .../public/connector_types/thehive/translations.ts | 7 ------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx index 9d6a9cd2188031..c54860dd064a98 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_alert.tsx @@ -23,10 +23,6 @@ export const TheHiveParamsAlertFields: React.FC { - const [severity, setSeverity] = useState(severityOptions[1].value); - const [tlp, setTlp] = useState(tlpOptions[2].value); - const [selectedOptions, setSelected] = useState>([]); - const alert = useMemo( () => (actionParams.subActionParams as ExecutorSubActionCreateAlertParams) ?? @@ -38,6 +34,12 @@ export const TheHiveParamsAlertFields: React.FC>( + alert.tags?.map((tag) => ({ label: tag })) ?? [] + ); + const onCreateOption = (searchValue: string) => { setSelected([...selectedOptions, { label: searchValue }]); editAction('subActionParams', { ...alert, tags: [...(alert.tags ?? []), searchValue] }, index); @@ -179,10 +181,10 @@ export const TheHiveParamsAlertFields: React.FC diff --git a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx index 47b55f31cea93d..417b52f9207919 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/thehive/params_case.tsx @@ -23,10 +23,6 @@ export const TheHiveParamsCaseFields: React.FC errors, messageVariables, }) => { - const [severity, setSeverity] = useState(severityOptions[1].value); - const [tlp, setTlp] = useState(tlpOptions[2].value); - const [selectedOptions, setSelected] = useState>([]); - const { incident, comments } = useMemo( () => (actionParams.subActionParams as ExecutorSubActionPushParams) ?? @@ -41,6 +37,12 @@ export const TheHiveParamsCaseFields: React.FC [actionParams.subActionParams] ); + const [severity, setSeverity] = useState(incident.severity ?? severityOptions[1].value); + const [tlp, setTlp] = useState(incident.tlp ?? tlpOptions[2].value); + const [selectedOptions, setSelected] = useState>( + incident.tags?.map((tag) => ({ label: tag })) ?? [] + ); + const editSubActionProperty = useCallback( (key: string, value: any) => { const newProps = @@ -133,10 +135,10 @@ export const TheHiveParamsCaseFields: React.FC