Skip to content

Commit 1095946

Browse files
cnasikasmikecote
andcommitted
[Security Solution][Case] Case action type (#80870)
* Init connector * Add test * Improve comment type * Add integration tests * Fix i18n * Improve tests * Show unknown when username is null * Improve comment type * Pass connector to case client * Improve type after PR #82125 * Add comment migration test * Fix integration tests * Fix reporter on table * Create case connector ui * Add connector to README * Improve casting on executor * Translate name * Improve test * Create comment type enum * Fix type * Fix i18n * Move README to cases * Filter out case connector from alerting Co-authored-by: Mike Côté <mikecote@users.noreply.github.com> Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>
1 parent 2103911 commit 1095946

File tree

60 files changed

+2621
-151
lines changed

Some content is hidden

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

60 files changed

+2621
-151
lines changed

x-pack/.i18nrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"xpack.apm": "plugins/apm",
1010
"xpack.beatsManagement": "plugins/beats_management",
1111
"xpack.canvas": "plugins/canvas",
12+
"xpack.case": "plugins/case",
1213
"xpack.cloud": "plugins/cloud",
1314
"xpack.dashboard": "plugins/dashboard_enhanced",
1415
"xpack.discover": "plugins/discover_enhanced",

x-pack/plugins/actions/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,4 +724,4 @@ Instead of `schema.maybe()`, use `schema.nullable()`, which is the same as `sche
724724

725725
## user interface
726726

727-
In order to make this action usable in the Kibana UI, you will need to provide all the UI editing aspects of the action. The existing action type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui).
727+
In order to make this action usable in the Kibana UI, you will need to provide all the UI editing aspects of the action. The existing action type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui).

x-pack/plugins/case/README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,91 @@ Elastic is developing a Case Management Workflow. Follow our progress:
77
- [Case API Documentation](https://documenter.getpostman.com/view/172706/SW7c2SuF?version=latest)
88
- [Github Meta](https://github.com/elastic/kibana/issues/50103)
99

10+
11+
# Action types
12+
13+
14+
See [Kibana Actions](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions) for more information.
15+
16+
## Case
17+
18+
ID: `.case`
19+
20+
The params properties are modelled after the arguments to the [Cases API](https://www.elastic.co/guide/en/security/master/cases-api-overview.html).
21+
22+
### `config`
23+
24+
This action has no `config` properties.
25+
26+
### `secrets`
27+
28+
This action type has no `secrets` properties.
29+
30+
### `params`
31+
32+
| Property | Description | Type |
33+
| --------------- | ------------------------------------------------------------------------- | ------ |
34+
| subAction | The sub action to perform. It can be `create`, `update`, and `addComment` | string |
35+
| subActionParams | The parameters of the sub action | object |
36+
37+
#### `subActionParams (create)`
38+
39+
| Property | Description | Type |
40+
| ----------- | --------------------------------------------------------------------- | ----------------------- |
41+
| tile | The case’s title. | string |
42+
| description | The case’s description. | string |
43+
| tags | String array containing words and phrases that help categorize cases. | string[] |
44+
| connector | Object containing the connector’s configuration. | [connector](#connector) |
45+
46+
#### `subActionParams (update)`
47+
48+
| Property | Description | Type |
49+
| ----------- | ---------------------------------------------------------- | ----------------------- |
50+
| id | The ID of the case being updated. | string |
51+
| tile | The updated case title. | string |
52+
| description | The updated case description. | string |
53+
| tags | The updated case tags. | string |
54+
| connector | Object containing the connector’s configuration. | [connector](#connector) |
55+
| status | The updated case status, which can be: `open` or `closed`. | string |
56+
| version | The current case version. | string |
57+
58+
#### `subActionParams (addComment)`
59+
60+
| Property | Description | Type |
61+
| -------- | --------------------------------------------------------- | ------ |
62+
| comment | The case’s new comment. | string |
63+
| type | The type of the comment, which can be: `user` or `alert`. | string |
64+
65+
#### `connector`
66+
67+
| Property | Description | Type |
68+
| -------- | ------------------------------------------------------------------------------------------------- | ----------------- |
69+
| id | ID of the connector used for pushing case updates to external systems. | string |
70+
| name | The connector name. | string |
71+
| type | The type of the connector. Must be one of these: `.servicenow`, `jira`, `.resilient`, and `.none` | string |
72+
| fields | Object containing the connector’s fields. | [fields](#fields) |
73+
74+
#### `fields`
75+
76+
For ServiceNow connectors:
77+
78+
| Property | Description | Type |
79+
| -------- | ----------------------------- | ------ |
80+
| urgency | The urgency of the incident. | string |
81+
| severity | The severity of the incident. | string |
82+
| impact | The impact of the incident. | string |
83+
84+
For Jira connectors:
85+
86+
| Property | Description | Type |
87+
| --------- | -------------------------------------------------------------------- | ------ |
88+
| issueType | The issue type of the issue. | string |
89+
| priority | The priority of the issue. | string |
90+
| parent | The key of the parent issue (Valid when the issue type is Sub-task). | string |
91+
92+
For IBM Resilient connectors:
93+
94+
| Property | Description | Type |
95+
| ------------ | ------------------------------- | -------- |
96+
| issueTypes | The issue types of the issue. | string[] |
97+
| severityCode | The severity code of the issue. | string |

x-pack/plugins/case/common/api/cases/comment.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { UserRT } from '../user';
1010

1111
const CommentBasicRt = rt.type({
1212
comment: rt.string,
13+
type: rt.union([rt.literal('alert'), rt.literal('user')]),
1314
});
1415

1516
export const CommentAttributesRt = rt.intersection([
@@ -37,7 +38,7 @@ export const CommentResponseRt = rt.intersection([
3738
export const AllCommentsResponseRT = rt.array(CommentResponseRt);
3839

3940
export const CommentPatchRequestRt = rt.intersection([
40-
rt.partial(CommentRequestRt.props),
41+
rt.partial(CommentBasicRt.props),
4142
rt.type({ id: rt.string, version: rt.string }),
4243
]);
4344

@@ -48,6 +49,11 @@ export const CommentsResponseRt = rt.type({
4849
total: rt.number,
4950
});
5051

52+
export enum CommentType {
53+
user = 'user',
54+
alert = 'alert',
55+
}
56+
5157
export const AllCommentsResponseRt = rt.array(CommentResponseRt);
5258

5359
export type CommentAttributes = rt.TypeOf<typeof CommentAttributesRt>;

x-pack/plugins/case/server/client/cases/create.test.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ describe('create', () => {
180180

181181
describe('unhappy path', () => {
182182
test('it throws when missing title', async () => {
183-
expect.assertions(1);
183+
expect.assertions(3);
184184
const postCase = {
185185
description: 'This is a brand new case of a bad meanie defacing data',
186186
tags: ['defacement'],
@@ -199,11 +199,15 @@ describe('create', () => {
199199
caseClient.client
200200
// @ts-expect-error
201201
.create({ theCase: postCase })
202-
.catch((e) => expect(e).not.toBeNull());
202+
.catch((e) => {
203+
expect(e).not.toBeNull();
204+
expect(e.isBoom).toBe(true);
205+
expect(e.output.statusCode).toBe(400);
206+
});
203207
});
204208

205209
test('it throws when missing description', async () => {
206-
expect.assertions(1);
210+
expect.assertions(3);
207211
const postCase = {
208212
title: 'a title',
209213
tags: ['defacement'],
@@ -222,11 +226,15 @@ describe('create', () => {
222226
caseClient.client
223227
// @ts-expect-error
224228
.create({ theCase: postCase })
225-
.catch((e) => expect(e).not.toBeNull());
229+
.catch((e) => {
230+
expect(e).not.toBeNull();
231+
expect(e.isBoom).toBe(true);
232+
expect(e.output.statusCode).toBe(400);
233+
});
226234
});
227235

228236
test('it throws when missing tags', async () => {
229-
expect.assertions(1);
237+
expect.assertions(3);
230238
const postCase = {
231239
title: 'a title',
232240
description: 'This is a brand new case of a bad meanie defacing data',
@@ -245,11 +253,15 @@ describe('create', () => {
245253
caseClient.client
246254
// @ts-expect-error
247255
.create({ theCase: postCase })
248-
.catch((e) => expect(e).not.toBeNull());
256+
.catch((e) => {
257+
expect(e).not.toBeNull();
258+
expect(e.isBoom).toBe(true);
259+
expect(e.output.statusCode).toBe(400);
260+
});
249261
});
250262

251263
test('it throws when missing connector ', async () => {
252-
expect.assertions(1);
264+
expect.assertions(3);
253265
const postCase = {
254266
title: 'a title',
255267
description: 'This is a brand new case of a bad meanie defacing data',
@@ -263,11 +275,15 @@ describe('create', () => {
263275
caseClient.client
264276
// @ts-expect-error
265277
.create({ theCase: postCase })
266-
.catch((e) => expect(e).not.toBeNull());
278+
.catch((e) => {
279+
expect(e).not.toBeNull();
280+
expect(e.isBoom).toBe(true);
281+
expect(e.output.statusCode).toBe(400);
282+
});
267283
});
268284

269285
test('it throws when connector missing the right fields', async () => {
270-
expect.assertions(1);
286+
expect.assertions(3);
271287
const postCase = {
272288
title: 'a title',
273289
description: 'This is a brand new case of a bad meanie defacing data',
@@ -287,11 +303,15 @@ describe('create', () => {
287303
caseClient.client
288304
// @ts-expect-error
289305
.create({ theCase: postCase })
290-
.catch((e) => expect(e).not.toBeNull());
306+
.catch((e) => {
307+
expect(e).not.toBeNull();
308+
expect(e.isBoom).toBe(true);
309+
expect(e.output.statusCode).toBe(400);
310+
});
291311
});
292312

293313
test('it throws if you passing status for a new case', async () => {
294-
expect.assertions(1);
314+
expect.assertions(3);
295315
const postCase = {
296316
title: 'a title',
297317
description: 'This is a brand new case of a bad meanie defacing data',
@@ -309,7 +329,11 @@ describe('create', () => {
309329
caseSavedObject: mockCases,
310330
});
311331
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
312-
caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull());
332+
caseClient.client.create({ theCase: postCase }).catch((e) => {
333+
expect(e).not.toBeNull();
334+
expect(e.isBoom).toBe(true);
335+
expect(e.output.statusCode).toBe(400);
336+
});
313337
});
314338

315339
it(`Returns an error if postNewCase throws`, async () => {
@@ -329,7 +353,11 @@ describe('create', () => {
329353
});
330354
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
331355

332-
caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull());
356+
caseClient.client.create({ theCase: postCase }).catch((e) => {
357+
expect(e).not.toBeNull();
358+
expect(e.isBoom).toBe(true);
359+
expect(e.output.statusCode).toBe(400);
360+
});
333361
});
334362
});
335363
});

x-pack/plugins/case/server/client/cases/update.test.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ describe('update', () => {
247247

248248
describe('unhappy path', () => {
249249
test('it throws when missing id', async () => {
250-
expect.assertions(1);
250+
expect.assertions(3);
251251
const patchCases = {
252252
cases: [
253253
{
@@ -270,11 +270,15 @@ describe('update', () => {
270270
caseClient.client
271271
// @ts-expect-error
272272
.update({ cases: patchCases })
273-
.catch((e) => expect(e).not.toBeNull());
273+
.catch((e) => {
274+
expect(e).not.toBeNull();
275+
expect(e.isBoom).toBe(true);
276+
expect(e.output.statusCode).toBe(400);
277+
});
274278
});
275279

276280
test('it throws when missing version', async () => {
277-
expect.assertions(1);
281+
expect.assertions(3);
278282
const patchCases = {
279283
cases: [
280284
{
@@ -297,11 +301,15 @@ describe('update', () => {
297301
caseClient.client
298302
// @ts-expect-error
299303
.update({ cases: patchCases })
300-
.catch((e) => expect(e).not.toBeNull());
304+
.catch((e) => {
305+
expect(e).not.toBeNull();
306+
expect(e.isBoom).toBe(true);
307+
expect(e.output.statusCode).toBe(400);
308+
});
301309
});
302310

303311
test('it throws when fields are identical', async () => {
304-
expect.assertions(1);
312+
expect.assertions(4);
305313
const patchCases = {
306314
cases: [
307315
{
@@ -317,14 +325,16 @@ describe('update', () => {
317325
});
318326

319327
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
320-
caseClient.client
321-
.update({ cases: patchCases })
322-
.catch((e) =>
323-
expect(e.message).toBe('All update fields are identical to current version.')
324-
);
328+
caseClient.client.update({ cases: patchCases }).catch((e) => {
329+
expect(e).not.toBeNull();
330+
expect(e.isBoom).toBe(true);
331+
expect(e.output.statusCode).toBe(406);
332+
expect(e.message).toBe('All update fields are identical to current version.');
333+
});
325334
});
326335

327336
test('it throws when case does not exist', async () => {
337+
expect.assertions(4);
328338
const patchCases = {
329339
cases: [
330340
{
@@ -345,17 +355,18 @@ describe('update', () => {
345355
});
346356

347357
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
348-
caseClient.client
349-
.update({ cases: patchCases })
350-
.catch((e) =>
351-
expect(e.message).toBe(
352-
'These cases not-exists do not exist. Please check you have the correct ids.'
353-
)
358+
caseClient.client.update({ cases: patchCases }).catch((e) => {
359+
expect(e).not.toBeNull();
360+
expect(e.isBoom).toBe(true);
361+
expect(e.output.statusCode).toBe(404);
362+
expect(e.message).toBe(
363+
'These cases not-exists do not exist. Please check you have the correct ids.'
354364
);
365+
});
355366
});
356367

357368
test('it throws when cases conflicts', async () => {
358-
expect.assertions(1);
369+
expect.assertions(4);
359370
const patchCases = {
360371
cases: [
361372
{
@@ -371,13 +382,14 @@ describe('update', () => {
371382
});
372383

373384
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
374-
caseClient.client
375-
.update({ cases: patchCases })
376-
.catch((e) =>
377-
expect(e.message).toBe(
378-
'These cases mock-id-1 has been updated. Please refresh before saving additional updates.'
379-
)
385+
caseClient.client.update({ cases: patchCases }).catch((e) => {
386+
expect(e).not.toBeNull();
387+
expect(e.isBoom).toBe(true);
388+
expect(e.output.statusCode).toBe(409);
389+
expect(e.message).toBe(
390+
'These cases mock-id-1 has been updated. Please refresh before saving additional updates.'
380391
);
392+
});
381393
});
382394
});
383395
});

0 commit comments

Comments
 (0)