Skip to content

Commit

Permalink
[7.x] [Security Solution][Case] ServiceNow SIR Connector (#88655) (#9…
Browse files Browse the repository at this point in the history
…0743)

Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>

Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
  • Loading branch information
cnasikas and XavierM committed Feb 9, 2021
1 parent 4639de1 commit 9fd5d9d
Show file tree
Hide file tree
Showing 174 changed files with 4,847 additions and 2,824 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('api', () => {

beforeEach(() => {
externalService = externalServiceMock.create();
jest.clearAllMocks();
});

describe('create incident', () => {
Expand All @@ -26,6 +27,7 @@ describe('api', () => {
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
});

expect(res).toEqual({
Expand Down Expand Up @@ -57,6 +59,7 @@ describe('api', () => {
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
});

expect(res).toEqual({
Expand All @@ -77,6 +80,7 @@ describe('api', () => {
params,
secrets: { username: 'elastic', password: 'elastic' },
logger: mockedLogger,
commentFieldKey: 'comments',
});

expect(externalService.createIncident).toHaveBeenCalledWith({
Expand All @@ -99,6 +103,7 @@ describe('api', () => {
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
});
expect(externalService.updateIncident).toHaveBeenCalledTimes(2);
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
Expand All @@ -125,6 +130,41 @@ describe('api', () => {
incidentId: 'incident-1',
});
});

test('it post comments to different comment field key', async () => {
const params = { ...apiParams, incident: { ...apiParams.incident, externalId: null } };
await api.pushToService({
externalService,
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'work_notes',
});
expect(externalService.updateIncident).toHaveBeenCalledTimes(2);
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
incident: {
severity: '1',
urgency: '2',
impact: '3',
work_notes: 'A comment',
description: 'Incident description',
short_description: 'Incident title',
},
incidentId: 'incident-1',
});

expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
incident: {
severity: '1',
urgency: '2',
impact: '3',
work_notes: 'Another comment',
description: 'Incident description',
short_description: 'Incident title',
},
incidentId: 'incident-1',
});
});
});

describe('update incident', () => {
Expand All @@ -134,6 +174,7 @@ describe('api', () => {
params: apiParams,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
});

expect(res).toEqual({
Expand Down Expand Up @@ -161,6 +202,7 @@ describe('api', () => {
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
});

expect(res).toEqual({
Expand All @@ -178,6 +220,7 @@ describe('api', () => {
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
});

expect(externalService.updateIncident).toHaveBeenCalledWith({
Expand All @@ -200,6 +243,7 @@ describe('api', () => {
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
});
expect(externalService.updateIncident).toHaveBeenCalledTimes(3);
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
Expand All @@ -225,6 +269,40 @@ describe('api', () => {
incidentId: 'incident-2',
});
});

test('it post comments to different comment field key', async () => {
const params = { ...apiParams };
await api.pushToService({
externalService,
params,
secrets: {},
logger: mockedLogger,
commentFieldKey: 'work_notes',
});
expect(externalService.updateIncident).toHaveBeenCalledTimes(3);
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
incident: {
severity: '1',
urgency: '2',
impact: '3',
description: 'Incident description',
short_description: 'Incident title',
},
incidentId: 'incident-3',
});

expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
incident: {
severity: '1',
urgency: '2',
impact: '3',
work_notes: 'A comment',
description: 'Incident description',
short_description: 'Incident title',
},
incidentId: 'incident-2',
});
});
});

describe('getFields', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const pushToServiceHandler = async ({
externalService,
params,
secrets,
commentFieldKey,
}: PushToServiceApiHandlerArgs): Promise<PushToServiceResponse> => {
const { comments } = params;
let res: PushToServiceResponse;
Expand Down Expand Up @@ -53,7 +54,7 @@ const pushToServiceHandler = async ({
incidentId: res.id,
incident: {
...incident,
comments: currentComment.comment,
[commentFieldKey]: currentComment.comment,
},
});
res.comments = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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 { actionsMock } from '../../mocks';
import { createActionTypeRegistry } from '../index.test';
import {
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse,
} from './types';
import {
ServiceNowActionType,
ServiceNowITSMActionTypeId,
ServiceNowSIRActionTypeId,
ServiceNowActionTypeExecutorOptions,
} from '.';
import { api } from './api';

jest.mock('./api', () => ({
api: {
getChoices: jest.fn(),
getFields: jest.fn(),
getIncident: jest.fn(),
handshake: jest.fn(),
pushToService: jest.fn(),
},
}));

const services = actionsMock.createServices();

describe('ServiceNow', () => {
const config = { apiUrl: 'https://instance.com' };
const secrets = { username: 'username', password: 'password' };
const params = {
subAction: 'pushToService',
subActionParams: {
incident: {
short_description: 'An incident',
description: 'This is serious',
},
},
};

beforeEach(() => {
(api.pushToService as jest.Mock).mockResolvedValue({ id: 'some-id' });
});

describe('ServiceNow ITSM', () => {
let actionType: ServiceNowActionType;

beforeAll(() => {
const { actionTypeRegistry } = createActionTypeRegistry();
actionType = actionTypeRegistry.get<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse | {}
>(ServiceNowITSMActionTypeId);
});

describe('execute()', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('it pass the correct comment field key', async () => {
const actionId = 'some-action-id';
const executorOptions = ({
actionId,
config,
secrets,
params,
services,
} as unknown) as ServiceNowActionTypeExecutorOptions;
await actionType.executor(executorOptions);
expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe('comments');
});
});
});

describe('ServiceNow SIR', () => {
let actionType: ServiceNowActionType;

beforeAll(() => {
const { actionTypeRegistry } = createActionTypeRegistry();
actionType = actionTypeRegistry.get<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse | {}
>(ServiceNowSIRActionTypeId);
});

describe('execute()', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('it pass the correct comment field key', async () => {
const actionId = 'some-action-id';
const executorOptions = ({
actionId,
config,
secrets,
params,
services,
} as unknown) as ServiceNowActionTypeExecutorOptions;
await actionType.executor(executorOptions);
expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe(
'work_notes'
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,21 @@ const serviceNowSIRTable = 'sn_si_incident';
export const ServiceNowITSMActionTypeId = '.servicenow';
export const ServiceNowSIRActionTypeId = '.servicenow-sir';

// action type definition
export function getServiceNowITSMActionType(
params: GetActionTypeParams
): ActionType<
export type ServiceNowActionType = ActionType<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse | {}
> {
>;

export type ServiceNowActionTypeExecutorOptions = ActionTypeExecutorOptions<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams
>;

// action type definition
export function getServiceNowITSMActionType(params: GetActionTypeParams): ServiceNowActionType {
const { logger, configurationUtilities } = params;
return {
id: ServiceNowITSMActionTypeId,
Expand All @@ -74,14 +80,7 @@ export function getServiceNowITSMActionType(
};
}

export function getServiceNowSIRActionType(
params: GetActionTypeParams
): ActionType<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse | {}
> {
export function getServiceNowSIRActionType(params: GetActionTypeParams): ServiceNowActionType {
const { logger, configurationUtilities } = params;
return {
id: ServiceNowSIRActionTypeId,
Expand All @@ -96,7 +95,12 @@ export function getServiceNowSIRActionType(
}),
params: ExecutorParamsSchemaSIR,
},
executor: curry(executor)({ logger, configurationUtilities, table: serviceNowSIRTable }),
executor: curry(executor)({
logger,
configurationUtilities,
table: serviceNowSIRTable,
commentFieldKey: 'work_notes',
}),
};
}

Expand All @@ -107,12 +111,14 @@ async function executor(
logger,
configurationUtilities,
table,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; table: string },
execOptions: ActionTypeExecutorOptions<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams
>
commentFieldKey = 'comments',
}: {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
table: string;
commentFieldKey?: string;
},
execOptions: ServiceNowActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<ServiceNowExecutorResultData | {}>> {
const { actionId, config, params, secrets } = execOptions;
const { subAction, subActionParams } = params;
Expand Down Expand Up @@ -147,6 +153,7 @@ async function executor(
params: pushToServiceParams,
secrets,
logger,
commentFieldKey,
});

logger.debug(`response push to service for incident id: ${data.id}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const SERVICENOW_ITSM = i18n.translate('xpack.actions.builtin.serviceNowI
});

export const SERVICENOW_SIR = i18n.translate('xpack.actions.builtin.serviceNowSIRTitle', {
defaultMessage: 'ServiceNow SIR',
defaultMessage: 'ServiceNow SecOps',
});

export const ALLOWED_HOSTS_ERROR = (message: string) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export interface PushToServiceApiHandlerArgs extends ExternalServiceApiHandlerAr
params: PushToServiceApiParams;
secrets: Record<string, unknown>;
logger: Logger;
commentFieldKey: string;
}

export interface GetIncidentApiHandlerArgs extends ExternalServiceApiHandlerArgs {
Expand Down
Loading

0 comments on commit 9fd5d9d

Please sign in to comment.