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 d778849347d18..fad093938de40 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 @@ -2251,6 +2251,78 @@ Object { ], "type": "string", }, + "getIncidentJson": 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", + }, + "getIncidentMethod": Object { + "flags": Object { + "default": "get", + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "get", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "post", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, "getIncidentResponseExternalTitleKey": Object { "flags": Object { "error": [Function], diff --git a/x-pack/plugins/stack_connectors/common/auth/constants.ts b/x-pack/plugins/stack_connectors/common/auth/constants.ts index bdd5b7352f921..ecf7637c956ee 100644 --- a/x-pack/plugins/stack_connectors/common/auth/constants.ts +++ b/x-pack/plugins/stack_connectors/common/auth/constants.ts @@ -19,4 +19,5 @@ export enum WebhookMethods { PATCH = 'patch', POST = 'post', PUT = 'put', + GET = 'get', } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/get.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/get.tsx index e8f233408a4c9..5bf2689506ec4 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/get.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/get.tsx @@ -6,14 +6,25 @@ */ import React, { FunctionComponent } from 'react'; +import { css } from '@emotion/react'; import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { + FIELD_TYPES, + UseField, + useFormData, +} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; -import { MustacheTextFieldWrapper } from '@kbn/triggers-actions-ui-plugin/public'; -import { containsExternalId, containsExternalIdOrTitle } from '../validator'; +import { JsonFieldWrapper, MustacheTextFieldWrapper } from '@kbn/triggers-actions-ui-plugin/public'; +import { WebhookMethods } from '../../../../common/auth/constants'; +import { + containsExternalIdForGet, + containsExternalIdOrTitle, + requiredJsonForPost, +} from '../validator'; import { urlVars, urlVarsExt } from '../action_variables'; import * as i18n from '../translations'; + const { emptyField, urlField } = fieldValidators; interface Props { @@ -21,88 +32,157 @@ interface Props { readOnly: boolean; } -export const GetStep: FunctionComponent = ({ display, readOnly }) => ( - - -

{i18n.STEP_3}

- -

{i18n.STEP_3_DESCRIPTION}

-
-
- - - - - - = ({ display, readOnly }) => { + const [{ config }] = useFormData({ + watch: ['config.getIncidentMethod'], + }); + const { getIncidentMethod = WebhookMethods.GET } = config ?? {}; + + return ( + + +

{i18n.STEP_3}

+ +

{i18n.STEP_3_DESCRIPTION}

+
+
+ + + + ({ + text: verb.toUpperCase(), + value: verb, + })), + readOnly, + }, + }} + /> + + + + + + {getIncidentMethod === WebhookMethods.POST ? ( + + + + ) : null} + + - - - + + + - - -
-); + }} + /> +
+
+
+ ); +}; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts index 0b007e07cfd91..8c44b6197ef9c 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts @@ -178,13 +178,24 @@ export const ADD_CASES_VARIABLE = i18n.translate( defaultMessage: 'Add variable', } ); - +export const GET_INCIDENT_METHOD = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.getIncidentMethodTextFieldLabel', + { + defaultMessage: 'Get case method', + } +); export const GET_INCIDENT_URL = i18n.translate( 'xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel', { defaultMessage: 'Get case URL', } ); +export const GET_METHOD_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredGetMethodText', + { + defaultMessage: 'Get case method is required.', + } +); export const GET_INCIDENT_URL_HELP = i18n.translate( 'xpack.stackConnectors.components.casesWebhook.getIncidentUrlHelp', { @@ -206,6 +217,28 @@ export const GET_INCIDENT_TITLE_KEY_HELP = i18n.translate( } ); +export const GET_INCIDENT_JSON_HELP = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.getIncidentJsonHelp', + { + defaultMessage: + 'JSON object to get a case. Use the variable selector to add cases data to the payload.', + } +); + +export const GET_INCIDENT_JSON = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.getIncidentJsonTextFieldLabel', + { + defaultMessage: 'Get case object', + } +); + +export const GET_INCIDENT_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredGetIncidentText', + { + defaultMessage: 'Get case object is required and must be valid JSON.', + } +); + export const EXTERNAL_INCIDENT_VIEW_URL = i18n.translate( 'xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel', { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts index d3d7f6dc8e612..d972c9bbd1f86 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { ERROR_CODE } from '@kbn/es-ui-shared-plugin/static/forms/helpers/field_validators/types'; import { ValidationError, @@ -12,6 +13,7 @@ import { } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { containsChars, isUrl } from '@kbn/es-ui-shared-plugin/static/validators/string'; import { templateActionVariable } from '@kbn/triggers-actions-ui-plugin/public'; +import { WebhookMethods } from '../../../common/auth/constants'; import * as i18n from './translations'; import { casesVars, commentVars, urlVars, urlVarsExt } from './action_variables'; @@ -42,17 +44,20 @@ export const containsTitleAndDesc = } }; -export const containsExternalId = - () => +export const containsExternalIdForGet = + (method?: string) => (...args: Parameters): ReturnType> => { const [{ value, path }] = args; const id = templateActionVariable( urlVars.find((actionVariable) => actionVariable.name === 'external.system.id')! ); - return containsChars(id)(value as string).doesContain - ? undefined - : missingVariableErrorMessage(path, [id]); + + return method === WebhookMethods.GET && + value !== null && + !containsChars(id)(value as string).doesContain + ? missingVariableErrorMessage(path, [id]) + : undefined; }; export const containsExternalIdOrTitle = @@ -77,6 +82,20 @@ export const containsExternalIdOrTitle = return error; }; +export const requiredJsonForPost = + (method?: string) => + (...args: Parameters): ReturnType> => { + const [{ value, path }] = args; + + const error = { + code: errorCode, + path, + message: i18n.GET_INCIDENT_REQUIRED, + }; + + return method === WebhookMethods.POST && (value === null || isEmpty(value)) ? error : undefined; + }; + export const containsCommentsOrEmpty = (message: string) => (...args: Parameters): ReturnType> => { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx index 8df473fef2ae8..713f2bd9e6f83 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx @@ -42,6 +42,7 @@ const config = { headers: [{ key: 'content-type', value: 'text' }], viewIncidentUrl: 'https://coolsite.net/browse/{{{external.system.title}}}', getIncidentUrl: 'https://coolsite.net/rest/api/2/issue/{{{external.system.id}}}', + getIncidentMethod: 'get', updateIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}', updateIncidentMethod: 'put', @@ -536,5 +537,78 @@ describe('CasesWebhookActionConnectorFields renders', () => { ).toBeInTheDocument(); } ); + + it('validates get incident json required correctly', async () => { + const connector = { + ...actionConnector, + config: { + ...actionConnector.config, + getIncidentUrl: 'https://coolsite.net/rest/api/2/issue', + getIncidentMethod: 'post', + headers: [], + }, + }; + + render( + + {}} + /> + + ); + + await userEvent.click(await screen.findByTestId('form-test-provide-submit')); + await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false })); + expect(await screen.findByText(i18n.GET_INCIDENT_REQUIRED)).toBeInTheDocument(); + }); + + it('validation succeeds get incident url with post correctly', async () => { + const connector = { + ...actionConnector, + config: { + ...actionConnector.config, + getIncidentUrl: 'https://coolsite.net/rest/api/2/issue/{{{external.system.id}}}', + getIncidentMethod: 'post', + getIncidentJson: '{"id": {{{external.system.id}}} }', + headers: [], + }, + }; + + const { isPreconfigured, ...rest } = actionConnector; + const { headers, ...rest2 } = actionConnector.config; + + render( + + {}} + /> + + ); + + await userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => + expect(onSubmit).toHaveBeenCalledWith({ + data: { + __internal__: { + hasCA: false, + hasHeaders: true, + }, + ...rest, + config: { + ...rest2, + getIncidentUrl: 'https://coolsite.net/rest/api/2/issue/{{{external.system.id}}}', + getIncidentMethod: 'post', + getIncidentJson: '{"id": {{{external.system.id}}} }', + }, + }, + isValid: true, + }) + ); + }); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.tsx index 73e424901469a..5aaf56fa8dd90 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.tsx @@ -22,7 +22,7 @@ import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import * as i18n from './translations'; import { AuthStep, CreateStep, GetStep, UpdateStep } from './steps'; -export const HTTP_VERBS = ['post', 'put', 'patch']; +export const HTTP_VERBS = ['post', 'put', 'patch', 'get']; const fields = { step1: [ 'config.hasAuth', @@ -38,7 +38,9 @@ const fields = { 'config.createIncidentResponseKey', ], step3: [ + 'config.getIncidentMethod', 'config.getIncidentUrl', + 'config.getIncidentJson', 'config.getIncidentResponseExternalTitleKey', 'config.viewIncidentUrl', ], diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts index 00b4fdc60a3ab..25b0d66e885b4 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts @@ -21,7 +21,14 @@ export const ExternalIncidentServiceConfiguration = { ), createIncidentJson: schema.string(), // stringified object createIncidentResponseKey: schema.string(), + getIncidentMethod: schema.oneOf( + [schema.literal(WebhookMethods.GET), schema.literal(WebhookMethods.POST)], + { + defaultValue: WebhookMethods.GET, + } + ), getIncidentUrl: schema.string(), + getIncidentJson: schema.nullable(schema.string()), getIncidentResponseExternalTitleKey: schema.string(), viewIncidentUrl: schema.string(), updateIncidentUrl: schema.string(), diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts index a44b34bf88fce..aaeca30be920a 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts @@ -47,6 +47,8 @@ const config: CasesWebhookPublicConfigurationType = { headers: { ['content-type']: 'application/json', foo: 'bar' }, viewIncidentUrl: 'https://coolsite.net/browse/{{{external.system.title}}}', getIncidentUrl: 'https://coolsite.net/issue/{{{external.system.id}}}', + getIncidentMethod: WebhookMethods.GET, + getIncidentJson: null, updateIncidentJson: '{"fields":{"title":{{{case.title}}},"description":{{{case.description}}},"tags":{{{case.tags}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}', updateIncidentMethod: WebhookMethods.PUT, @@ -239,6 +241,7 @@ describe('Cases webhook service', () => { configurationUtilities, sslOverrides: defaultSSLOverrides, connectorUsageCollector: expect.any(ConnectorUsageCollector), + method: WebhookMethods.GET, }); }); @@ -282,6 +285,7 @@ describe('Cases webhook service', () => { "trace": [MockFunction], "warn": [MockFunction], }, + "method": "get", "sslOverrides": Object { "cert": Object { "data": Array [ @@ -440,6 +444,271 @@ describe('Cases webhook service', () => { '[Action][Webhook - Case Management]: Unable to get case with id 1. Error: Response is missing the expected field: key' ); }); + + it('it returns the incident correctly with POST', async () => { + const postService: ExternalService = createExternalService( + actionId, + { + config: { + ...config, + getIncidentMethod: WebhookMethods.POST, + getIncidentJson: '{"id": {{{external.system.id}}} }', + getIncidentUrl: 'https://coolsite.net/issue', + }, + secrets, + }, + logger, + configurationUtilities, + connectorUsageCollector + ); + + requestMock.mockImplementation(() => createAxiosResponse(axiosRes)); + const res = await postService.getIncident('1'); + expect(res).toEqual({ + id: '1', + title: 'CK-1', + }); + }); + + it('it should call request with correct arguments using POST', async () => { + const postService: ExternalService = createExternalService( + actionId, + { + config: { + ...config, + getIncidentMethod: WebhookMethods.POST, + getIncidentJson: '{"id": {{{external.system.id}}} }', + getIncidentUrl: 'https://coolsite.net/issue', + }, + secrets, + }, + logger, + configurationUtilities, + connectorUsageCollector + ); + + requestMock.mockImplementation(() => createAxiosResponse(axiosRes)); + + await postService.getIncident('1'); + expect(requestMock).toHaveBeenCalledWith({ + axios, + url: 'https://coolsite.net/issue', + logger, + configurationUtilities, + sslOverrides: defaultSSLOverrides, + connectorUsageCollector: expect.any(ConnectorUsageCollector), + method: WebhookMethods.POST, + data: '{"id": "1" }', + }); + }); + + it('it should call request with correct arguments when authType=SSL using POST', async () => { + const postSslService = createExternalService( + actionId, + { + config: { + ...sslConfig, + getIncidentMethod: WebhookMethods.POST, + getIncidentJson: '{"id": {{{external.system.id}}} }', + getIncidentUrl: 'https://coolsite.net/issue', + }, + secrets: sslSecrets, + }, + logger, + configurationUtilities, + connectorUsageCollector + ); + + requestMock.mockImplementation(() => createAxiosResponse(axiosRes)); + + await postSslService.getIncident('1'); + + // irrelevant snapshot content + delete requestMock.mock.calls[0][0].configurationUtilities; + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "axios": [Function], + "connectorUsageCollector": ConnectorUsageCollector { + "connectorId": "test-connector-id", + "logger": Object { + "context": Array [], + "debug": [MockFunction], + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "usage": Object { + "requestBodyBytes": 0, + }, + }, + "data": "{\\"id\\": \\"1\\" }", + "logger": Object { + "context": Array [], + "debug": [MockFunction], + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "sslOverrides": Object { + "cert": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "key": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "passphrase": "foobar", + }, + "url": "https://coolsite.net/issue", + } + `); + }); + + it('it should throw if the request payload is not a valid JSON for POST', async () => { + const newService = createExternalService( + actionId, + { + config: { + ...config, + getIncidentMethod: WebhookMethods.POST, + getIncidentJson: '{"id": }', + getIncidentUrl: 'https://coolsite.net/issue', + }, + secrets, + }, + logger, + configurationUtilities, + connectorUsageCollector + ); + + await expect(newService.getIncident('1')).rejects.toThrow( + '[Action][Webhook - Case Management]: Unable to get case with id 1. Error: JSON Error: Get case JSON body must be valid JSON. ' + ); + }); }); describe('createIncident', () => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts index 170c63a1d4e5b..9f14f494c9424 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts @@ -14,6 +14,7 @@ import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/action import { combineHeadersWithBasicAuthHeader } from '@kbn/actions-plugin/server/lib'; import { ConnectorUsageCollector } from '@kbn/actions-plugin/server/types'; import { buildConnectorAuth, validateConnectorAuthConfiguration } from '../../../common/auth/utils'; +import { WebhookMethods } from '../../../common/auth/constants'; import { validateAndNormalizeUrl, validateJson } from './validators'; import { createServiceError, @@ -52,6 +53,8 @@ export const createExternalService = ( createIncidentUrl: createIncidentUrlConfig, getIncidentResponseExternalTitleKey, getIncidentUrl, + getIncidentMethod, + getIncidentJson, hasAuth, authType, headers, @@ -113,10 +116,28 @@ export const createExternalService = ( configurationUtilities, 'Get case URL' ); + + const json = + getIncidentMethod === WebhookMethods.POST && getIncidentJson + ? renderMustacheStringNoEscape(getIncidentJson, { + external: { + system: { + id: JSON.stringify(id), + }, + }, + }) + : null; + + if (json !== null) { + validateJson(json, 'Get case JSON body'); + } + const res = await request({ axios: axiosInstance, url: normalizedUrl, + method: getIncidentMethod, logger, + ...(getIncidentMethod === WebhookMethods.POST ? { data: json } : {}), configurationUtilities, sslOverrides, connectorUsageCollector, @@ -128,6 +149,7 @@ export const createExternalService = ( }); const title = getObjectValueByKeyAsString(res.data, getIncidentResponseExternalTitleKey)!; + return { id, title }; } catch (error) { throw createServiceError(error, `Unable to get case with id ${id}`); @@ -157,6 +179,7 @@ export const createExternalService = ( ); validateJson(json, 'Create case JSON body'); + const res: AxiosResponse = await request({ axios: axiosInstance, url: normalizedUrl, @@ -175,6 +198,7 @@ export const createExternalService = ( requiredAttributesToBeInTheResponse: [createIncidentResponseKey], }); const externalId = getObjectValueByKeyAsString(data, createIncidentResponseKey)!; + const insertedIncident = await getIncident(externalId); logger.debug(`response from webhook action "${actionId}": [HTTP ${status}] ${statusText}`); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts index 72f726d18b0e1..b425db8569f50 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts @@ -38,6 +38,8 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { headers: { ['content-type']: 'application/json', ['kbn-xsrf']: 'abcd' }, viewIncidentUrl: 'https://coolsite.net/browse/{{{external.system.title}}}', getIncidentUrl: 'https://coolsite.net/rest/api/2/issue/{{{external.system.id}}}', + getIncidentMethod: 'get', + getIncidentJson: null, updateIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"labels":{{{case.tags}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}', updateIncidentMethod: 'put', @@ -79,7 +81,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { }; let casesWebhookSimulatorURL: string = ''; - let simulatorConfig: Record>; + let simulatorConfig: Record>; describe('CasesWebhook', () => { before(() => { // use jira because cases webhook works with any third party case management system @@ -135,6 +137,53 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { config: simulatorConfig, }); }); + + it('should return 200 when creating a casesWebhook action with get case info using POST successfully', async () => { + const newConfig = { + ...simulatorConfig, + getIncidentMethod: 'post', + getIncidentJson: '{"id": {{{external.system.id}}} }', + getIncidentUrl: `${casesWebhookSimulatorURL}/rest/api/2/issue`, + }; + + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A casesWebhook action', + connector_type_id: '.cases-webhook', + config: newConfig, + secrets, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + is_preconfigured: false, + is_system_action: false, + is_deprecated: false, + name: 'A casesWebhook action', + connector_type_id: '.cases-webhook', + is_missing_secrets: false, + config: newConfig, + }); + + const { body: fetchedAction } = await supertest + .get(`/api/actions/connector/${createdAction.id}`) + .expect(200); + + expect(fetchedAction).to.eql({ + id: fetchedAction.id, + is_preconfigured: false, + is_system_action: false, + is_deprecated: false, + name: 'A casesWebhook action', + connector_type_id: '.cases-webhook', + is_missing_secrets: false, + config: newConfig, + }); + }); + describe('400s for all required fields when missing', () => { requiredFields.forEach((field) => { it(`should respond with a 400 Bad Request when creating a casesWebhook action with no ${field}`, async () => { @@ -529,6 +578,53 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { }); expect(proxyHaveBeenCalled).to.equal(false); }); + + it('should respond with bad JSON error when get case POST JSON is bad', async () => { + const { body } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A casesWebhook simulator', + connector_type_id: '.cases-webhook', + config: { + ...simulatorConfig, + getIncidentJson: '{"id": "{{{external.system.id}}}" }', + getIncidentUrl: `${casesWebhookSimulatorURL}/rest/api/2/issue`, + getIncidentMethod: 'post', + }, + secrets, + }); + + simulatedActionId = body.id; + + await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + ...mockCasesWebhook.params, + subActionParams: { + incident: { + title: 'success', + description: 'success', + }, + comments: [], + }, + }, + }) + .then((resp: any) => { + expect(resp.body).to.eql({ + connector_id: simulatedActionId, + status: 'error', + retry: true, + message: 'an error occurred while running the action', + errorSource: TaskErrorSource.FRAMEWORK, + service_message: + '[Action][Webhook - Case Management]: Unable to create case. Error: [Action][Webhook - Case Management]: Unable to get case with id 123. Error: JSON Error: Get case JSON body must be valid JSON. . ', + }); + }); + }); + after(() => { if (proxyServer) { proxyServer.close(); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts index d124047831e28..a6e98788e62a3 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/configure/get_connectors.ts @@ -78,6 +78,8 @@ export default ({ getService }: FtrProviderContext): void => { headers: { [`content-type`]: 'application/json' }, viewIncidentUrl: 'http://some.non.existent.com/browse/{{{external.system.title}}}', getIncidentUrl: 'http://some.non.existent.com/{{{external.system.id}}}', + getIncidentMethod: 'get', + getIncidentJson: null, updateIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}', updateIncidentMethod: 'put', diff --git a/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts b/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts index 5ddc3df660142..5029c57d8aec1 100644 --- a/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts +++ b/x-pack/test/cases_api_integration/spaces_only/tests/trial/configure/get_connectors.ts @@ -110,6 +110,8 @@ export default ({ getService }: FtrProviderContext): void => { headers: { [`content-type`]: 'application/json' }, viewIncidentUrl: 'http://some.non.existent.com/browse/{{{external.system.title}}}', getIncidentUrl: 'http://some.non.existent.com/{{{external.system.id}}}', + getIncidentMethod: 'get', + getIncidentJson: null, updateIncidentJson: '{"fields":{"summary":{{{case.title}}},"description":{{{case.description}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}', updateIncidentMethod: 'put',