Skip to content

Commit 4f7f100

Browse files
cnasikasXavierM
andcommitted
[Security Solution][Case] ServiceNow SIR Connector (#88655)
Co-authored-by: Xavier Mouligneau <[email protected]>
1 parent 735d9a9 commit 4f7f100

File tree

174 files changed

+4847
-2824
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

174 files changed

+4847
-2824
lines changed

x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('api', () => {
1616

1717
beforeEach(() => {
1818
externalService = externalServiceMock.create();
19+
jest.clearAllMocks();
1920
});
2021

2122
describe('create incident', () => {
@@ -26,6 +27,7 @@ describe('api', () => {
2627
params,
2728
secrets: {},
2829
logger: mockedLogger,
30+
commentFieldKey: 'comments',
2931
});
3032

3133
expect(res).toEqual({
@@ -57,6 +59,7 @@ describe('api', () => {
5759
params,
5860
secrets: {},
5961
logger: mockedLogger,
62+
commentFieldKey: 'comments',
6063
});
6164

6265
expect(res).toEqual({
@@ -77,6 +80,7 @@ describe('api', () => {
7780
params,
7881
secrets: { username: 'elastic', password: 'elastic' },
7982
logger: mockedLogger,
83+
commentFieldKey: 'comments',
8084
});
8185

8286
expect(externalService.createIncident).toHaveBeenCalledWith({
@@ -99,6 +103,7 @@ describe('api', () => {
99103
params,
100104
secrets: {},
101105
logger: mockedLogger,
106+
commentFieldKey: 'comments',
102107
});
103108
expect(externalService.updateIncident).toHaveBeenCalledTimes(2);
104109
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
@@ -125,6 +130,41 @@ describe('api', () => {
125130
incidentId: 'incident-1',
126131
});
127132
});
133+
134+
test('it post comments to different comment field key', async () => {
135+
const params = { ...apiParams, incident: { ...apiParams.incident, externalId: null } };
136+
await api.pushToService({
137+
externalService,
138+
params,
139+
secrets: {},
140+
logger: mockedLogger,
141+
commentFieldKey: 'work_notes',
142+
});
143+
expect(externalService.updateIncident).toHaveBeenCalledTimes(2);
144+
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
145+
incident: {
146+
severity: '1',
147+
urgency: '2',
148+
impact: '3',
149+
work_notes: 'A comment',
150+
description: 'Incident description',
151+
short_description: 'Incident title',
152+
},
153+
incidentId: 'incident-1',
154+
});
155+
156+
expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
157+
incident: {
158+
severity: '1',
159+
urgency: '2',
160+
impact: '3',
161+
work_notes: 'Another comment',
162+
description: 'Incident description',
163+
short_description: 'Incident title',
164+
},
165+
incidentId: 'incident-1',
166+
});
167+
});
128168
});
129169

130170
describe('update incident', () => {
@@ -134,6 +174,7 @@ describe('api', () => {
134174
params: apiParams,
135175
secrets: {},
136176
logger: mockedLogger,
177+
commentFieldKey: 'comments',
137178
});
138179

139180
expect(res).toEqual({
@@ -161,6 +202,7 @@ describe('api', () => {
161202
params,
162203
secrets: {},
163204
logger: mockedLogger,
205+
commentFieldKey: 'comments',
164206
});
165207

166208
expect(res).toEqual({
@@ -178,6 +220,7 @@ describe('api', () => {
178220
params,
179221
secrets: {},
180222
logger: mockedLogger,
223+
commentFieldKey: 'comments',
181224
});
182225

183226
expect(externalService.updateIncident).toHaveBeenCalledWith({
@@ -200,6 +243,7 @@ describe('api', () => {
200243
params,
201244
secrets: {},
202245
logger: mockedLogger,
246+
commentFieldKey: 'comments',
203247
});
204248
expect(externalService.updateIncident).toHaveBeenCalledTimes(3);
205249
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
@@ -225,6 +269,40 @@ describe('api', () => {
225269
incidentId: 'incident-2',
226270
});
227271
});
272+
273+
test('it post comments to different comment field key', async () => {
274+
const params = { ...apiParams };
275+
await api.pushToService({
276+
externalService,
277+
params,
278+
secrets: {},
279+
logger: mockedLogger,
280+
commentFieldKey: 'work_notes',
281+
});
282+
expect(externalService.updateIncident).toHaveBeenCalledTimes(3);
283+
expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
284+
incident: {
285+
severity: '1',
286+
urgency: '2',
287+
impact: '3',
288+
description: 'Incident description',
289+
short_description: 'Incident title',
290+
},
291+
incidentId: 'incident-3',
292+
});
293+
294+
expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
295+
incident: {
296+
severity: '1',
297+
urgency: '2',
298+
impact: '3',
299+
work_notes: 'A comment',
300+
description: 'Incident description',
301+
short_description: 'Incident title',
302+
},
303+
incidentId: 'incident-2',
304+
});
305+
});
228306
});
229307

230308
describe('getFields', () => {

x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const pushToServiceHandler = async ({
2525
externalService,
2626
params,
2727
secrets,
28+
commentFieldKey,
2829
}: PushToServiceApiHandlerArgs): Promise<PushToServiceResponse> => {
2930
const { comments } = params;
3031
let res: PushToServiceResponse;
@@ -53,7 +54,7 @@ const pushToServiceHandler = async ({
5354
incidentId: res.id,
5455
incident: {
5556
...incident,
56-
comments: currentComment.comment,
57+
[commentFieldKey]: currentComment.comment,
5758
},
5859
});
5960
res.comments = [
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { actionsMock } from '../../mocks';
9+
import { createActionTypeRegistry } from '../index.test';
10+
import {
11+
ServiceNowPublicConfigurationType,
12+
ServiceNowSecretConfigurationType,
13+
ExecutorParams,
14+
PushToServiceResponse,
15+
} from './types';
16+
import {
17+
ServiceNowActionType,
18+
ServiceNowITSMActionTypeId,
19+
ServiceNowSIRActionTypeId,
20+
ServiceNowActionTypeExecutorOptions,
21+
} from '.';
22+
import { api } from './api';
23+
24+
jest.mock('./api', () => ({
25+
api: {
26+
getChoices: jest.fn(),
27+
getFields: jest.fn(),
28+
getIncident: jest.fn(),
29+
handshake: jest.fn(),
30+
pushToService: jest.fn(),
31+
},
32+
}));
33+
34+
const services = actionsMock.createServices();
35+
36+
describe('ServiceNow', () => {
37+
const config = { apiUrl: 'https://instance.com' };
38+
const secrets = { username: 'username', password: 'password' };
39+
const params = {
40+
subAction: 'pushToService',
41+
subActionParams: {
42+
incident: {
43+
short_description: 'An incident',
44+
description: 'This is serious',
45+
},
46+
},
47+
};
48+
49+
beforeEach(() => {
50+
(api.pushToService as jest.Mock).mockResolvedValue({ id: 'some-id' });
51+
});
52+
53+
describe('ServiceNow ITSM', () => {
54+
let actionType: ServiceNowActionType;
55+
56+
beforeAll(() => {
57+
const { actionTypeRegistry } = createActionTypeRegistry();
58+
actionType = actionTypeRegistry.get<
59+
ServiceNowPublicConfigurationType,
60+
ServiceNowSecretConfigurationType,
61+
ExecutorParams,
62+
PushToServiceResponse | {}
63+
>(ServiceNowITSMActionTypeId);
64+
});
65+
66+
describe('execute()', () => {
67+
beforeEach(() => {
68+
jest.clearAllMocks();
69+
});
70+
71+
test('it pass the correct comment field key', async () => {
72+
const actionId = 'some-action-id';
73+
const executorOptions = ({
74+
actionId,
75+
config,
76+
secrets,
77+
params,
78+
services,
79+
} as unknown) as ServiceNowActionTypeExecutorOptions;
80+
await actionType.executor(executorOptions);
81+
expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe('comments');
82+
});
83+
});
84+
});
85+
86+
describe('ServiceNow SIR', () => {
87+
let actionType: ServiceNowActionType;
88+
89+
beforeAll(() => {
90+
const { actionTypeRegistry } = createActionTypeRegistry();
91+
actionType = actionTypeRegistry.get<
92+
ServiceNowPublicConfigurationType,
93+
ServiceNowSecretConfigurationType,
94+
ExecutorParams,
95+
PushToServiceResponse | {}
96+
>(ServiceNowSIRActionTypeId);
97+
});
98+
99+
describe('execute()', () => {
100+
beforeEach(() => {
101+
jest.clearAllMocks();
102+
});
103+
104+
test('it pass the correct comment field key', async () => {
105+
const actionId = 'some-action-id';
106+
const executorOptions = ({
107+
actionId,
108+
config,
109+
secrets,
110+
params,
111+
services,
112+
} as unknown) as ServiceNowActionTypeExecutorOptions;
113+
await actionType.executor(executorOptions);
114+
expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe(
115+
'work_notes'
116+
);
117+
});
118+
});
119+
});
120+
});

x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,21 @@ const serviceNowSIRTable = 'sn_si_incident';
4747
export const ServiceNowITSMActionTypeId = '.servicenow';
4848
export const ServiceNowSIRActionTypeId = '.servicenow-sir';
4949

50-
// action type definition
51-
export function getServiceNowITSMActionType(
52-
params: GetActionTypeParams
53-
): ActionType<
50+
export type ServiceNowActionType = ActionType<
5451
ServiceNowPublicConfigurationType,
5552
ServiceNowSecretConfigurationType,
5653
ExecutorParams,
5754
PushToServiceResponse | {}
58-
> {
55+
>;
56+
57+
export type ServiceNowActionTypeExecutorOptions = ActionTypeExecutorOptions<
58+
ServiceNowPublicConfigurationType,
59+
ServiceNowSecretConfigurationType,
60+
ExecutorParams
61+
>;
62+
63+
// action type definition
64+
export function getServiceNowITSMActionType(params: GetActionTypeParams): ServiceNowActionType {
5965
const { logger, configurationUtilities } = params;
6066
return {
6167
id: ServiceNowITSMActionTypeId,
@@ -74,14 +80,7 @@ export function getServiceNowITSMActionType(
7480
};
7581
}
7682

77-
export function getServiceNowSIRActionType(
78-
params: GetActionTypeParams
79-
): ActionType<
80-
ServiceNowPublicConfigurationType,
81-
ServiceNowSecretConfigurationType,
82-
ExecutorParams,
83-
PushToServiceResponse | {}
84-
> {
83+
export function getServiceNowSIRActionType(params: GetActionTypeParams): ServiceNowActionType {
8584
const { logger, configurationUtilities } = params;
8685
return {
8786
id: ServiceNowSIRActionTypeId,
@@ -96,7 +95,12 @@ export function getServiceNowSIRActionType(
9695
}),
9796
params: ExecutorParamsSchemaSIR,
9897
},
99-
executor: curry(executor)({ logger, configurationUtilities, table: serviceNowSIRTable }),
98+
executor: curry(executor)({
99+
logger,
100+
configurationUtilities,
101+
table: serviceNowSIRTable,
102+
commentFieldKey: 'work_notes',
103+
}),
100104
};
101105
}
102106

@@ -107,12 +111,14 @@ async function executor(
107111
logger,
108112
configurationUtilities,
109113
table,
110-
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; table: string },
111-
execOptions: ActionTypeExecutorOptions<
112-
ServiceNowPublicConfigurationType,
113-
ServiceNowSecretConfigurationType,
114-
ExecutorParams
115-
>
114+
commentFieldKey = 'comments',
115+
}: {
116+
logger: Logger;
117+
configurationUtilities: ActionsConfigurationUtilities;
118+
table: string;
119+
commentFieldKey?: string;
120+
},
121+
execOptions: ServiceNowActionTypeExecutorOptions
116122
): Promise<ActionTypeExecutorResult<ServiceNowExecutorResultData | {}>> {
117123
const { actionId, config, params, secrets } = execOptions;
118124
const { subAction, subActionParams } = params;
@@ -147,6 +153,7 @@ async function executor(
147153
params: pushToServiceParams,
148154
secrets,
149155
logger,
156+
commentFieldKey,
150157
});
151158

152159
logger.debug(`response push to service for incident id: ${data.id}`);

x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const SERVICENOW_ITSM = i18n.translate('xpack.actions.builtin.serviceNowI
1616
});
1717

1818
export const SERVICENOW_SIR = i18n.translate('xpack.actions.builtin.serviceNowSIRTitle', {
19-
defaultMessage: 'ServiceNow SIR',
19+
defaultMessage: 'ServiceNow SecOps',
2020
});
2121

2222
export const ALLOWED_HOSTS_ERROR = (message: string) =>

x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export interface PushToServiceApiHandlerArgs extends ExternalServiceApiHandlerAr
121121
params: PushToServiceApiParams;
122122
secrets: Record<string, unknown>;
123123
logger: Logger;
124+
commentFieldKey: string;
124125
}
125126

126127
export interface GetIncidentApiHandlerArgs extends ExternalServiceApiHandlerArgs {

0 commit comments

Comments
 (0)