Skip to content

Commit

Permalink
[Security Solution][Case] Case action type (elastic#80870)
Browse files Browse the repository at this point in the history
* 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 elastic#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é <[email protected]>

Co-authored-by: Mike Côté <[email protected]>
  • Loading branch information
cnasikas and mikecote committed Nov 4, 2020
1 parent 2103911 commit 1095946
Show file tree
Hide file tree
Showing 60 changed files with 2,621 additions and 151 deletions.
1 change: 1 addition & 0 deletions x-pack/.i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"xpack.apm": "plugins/apm",
"xpack.beatsManagement": "plugins/beats_management",
"xpack.canvas": "plugins/canvas",
"xpack.case": "plugins/case",
"xpack.cloud": "plugins/cloud",
"xpack.dashboard": "plugins/dashboard_enhanced",
"xpack.discover": "plugins/discover_enhanced",
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,4 +724,4 @@ Instead of `schema.maybe()`, use `schema.nullable()`, which is the same as `sche

## user interface

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).
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).
88 changes: 88 additions & 0 deletions x-pack/plugins/case/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,91 @@ Elastic is developing a Case Management Workflow. Follow our progress:
- [Case API Documentation](https://documenter.getpostman.com/view/172706/SW7c2SuF?version=latest)
- [Github Meta](https://github.com/elastic/kibana/issues/50103)


# Action types


See [Kibana Actions](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions) for more information.

## Case

ID: `.case`

The params properties are modelled after the arguments to the [Cases API](https://www.elastic.co/guide/en/security/master/cases-api-overview.html).

### `config`

This action has no `config` properties.

### `secrets`

This action type has no `secrets` properties.

### `params`

| Property | Description | Type |
| --------------- | ------------------------------------------------------------------------- | ------ |
| subAction | The sub action to perform. It can be `create`, `update`, and `addComment` | string |
| subActionParams | The parameters of the sub action | object |

#### `subActionParams (create)`

| Property | Description | Type |
| ----------- | --------------------------------------------------------------------- | ----------------------- |
| tile | The case’s title. | string |
| description | The case’s description. | string |
| tags | String array containing words and phrases that help categorize cases. | string[] |
| connector | Object containing the connector’s configuration. | [connector](#connector) |

#### `subActionParams (update)`

| Property | Description | Type |
| ----------- | ---------------------------------------------------------- | ----------------------- |
| id | The ID of the case being updated. | string |
| tile | The updated case title. | string |
| description | The updated case description. | string |
| tags | The updated case tags. | string |
| connector | Object containing the connector’s configuration. | [connector](#connector) |
| status | The updated case status, which can be: `open` or `closed`. | string |
| version | The current case version. | string |

#### `subActionParams (addComment)`

| Property | Description | Type |
| -------- | --------------------------------------------------------- | ------ |
| comment | The case’s new comment. | string |
| type | The type of the comment, which can be: `user` or `alert`. | string |

#### `connector`

| Property | Description | Type |
| -------- | ------------------------------------------------------------------------------------------------- | ----------------- |
| id | ID of the connector used for pushing case updates to external systems. | string |
| name | The connector name. | string |
| type | The type of the connector. Must be one of these: `.servicenow`, `jira`, `.resilient`, and `.none` | string |
| fields | Object containing the connector’s fields. | [fields](#fields) |

#### `fields`

For ServiceNow connectors:

| Property | Description | Type |
| -------- | ----------------------------- | ------ |
| urgency | The urgency of the incident. | string |
| severity | The severity of the incident. | string |
| impact | The impact of the incident. | string |

For Jira connectors:

| Property | Description | Type |
| --------- | -------------------------------------------------------------------- | ------ |
| issueType | The issue type of the issue. | string |
| priority | The priority of the issue. | string |
| parent | The key of the parent issue (Valid when the issue type is Sub-task). | string |

For IBM Resilient connectors:

| Property | Description | Type |
| ------------ | ------------------------------- | -------- |
| issueTypes | The issue types of the issue. | string[] |
| severityCode | The severity code of the issue. | string |
8 changes: 7 additions & 1 deletion x-pack/plugins/case/common/api/cases/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { UserRT } from '../user';

const CommentBasicRt = rt.type({
comment: rt.string,
type: rt.union([rt.literal('alert'), rt.literal('user')]),
});

export const CommentAttributesRt = rt.intersection([
Expand Down Expand Up @@ -37,7 +38,7 @@ export const CommentResponseRt = rt.intersection([
export const AllCommentsResponseRT = rt.array(CommentResponseRt);

export const CommentPatchRequestRt = rt.intersection([
rt.partial(CommentRequestRt.props),
rt.partial(CommentBasicRt.props),
rt.type({ id: rt.string, version: rt.string }),
]);

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

export enum CommentType {
user = 'user',
alert = 'alert',
}

export const AllCommentsResponseRt = rt.array(CommentResponseRt);

export type CommentAttributes = rt.TypeOf<typeof CommentAttributesRt>;
Expand Down
54 changes: 41 additions & 13 deletions x-pack/plugins/case/server/client/cases/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ describe('create', () => {

describe('unhappy path', () => {
test('it throws when missing title', async () => {
expect.assertions(1);
expect.assertions(3);
const postCase = {
description: 'This is a brand new case of a bad meanie defacing data',
tags: ['defacement'],
Expand All @@ -199,11 +199,15 @@ describe('create', () => {
caseClient.client
// @ts-expect-error
.create({ theCase: postCase })
.catch((e) => expect(e).not.toBeNull());
.catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

test('it throws when missing description', async () => {
expect.assertions(1);
expect.assertions(3);
const postCase = {
title: 'a title',
tags: ['defacement'],
Expand All @@ -222,11 +226,15 @@ describe('create', () => {
caseClient.client
// @ts-expect-error
.create({ theCase: postCase })
.catch((e) => expect(e).not.toBeNull());
.catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

test('it throws when missing tags', async () => {
expect.assertions(1);
expect.assertions(3);
const postCase = {
title: 'a title',
description: 'This is a brand new case of a bad meanie defacing data',
Expand All @@ -245,11 +253,15 @@ describe('create', () => {
caseClient.client
// @ts-expect-error
.create({ theCase: postCase })
.catch((e) => expect(e).not.toBeNull());
.catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

test('it throws when missing connector ', async () => {
expect.assertions(1);
expect.assertions(3);
const postCase = {
title: 'a title',
description: 'This is a brand new case of a bad meanie defacing data',
Expand All @@ -263,11 +275,15 @@ describe('create', () => {
caseClient.client
// @ts-expect-error
.create({ theCase: postCase })
.catch((e) => expect(e).not.toBeNull());
.catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

test('it throws when connector missing the right fields', async () => {
expect.assertions(1);
expect.assertions(3);
const postCase = {
title: 'a title',
description: 'This is a brand new case of a bad meanie defacing data',
Expand All @@ -287,11 +303,15 @@ describe('create', () => {
caseClient.client
// @ts-expect-error
.create({ theCase: postCase })
.catch((e) => expect(e).not.toBeNull());
.catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

test('it throws if you passing status for a new case', async () => {
expect.assertions(1);
expect.assertions(3);
const postCase = {
title: 'a title',
description: 'This is a brand new case of a bad meanie defacing data',
Expand All @@ -309,7 +329,11 @@ describe('create', () => {
caseSavedObject: mockCases,
});
const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull());
caseClient.client.create({ theCase: postCase }).catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

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

caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull());
caseClient.client.create({ theCase: postCase }).catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});
});
});
58 changes: 35 additions & 23 deletions x-pack/plugins/case/server/client/cases/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ describe('update', () => {

describe('unhappy path', () => {
test('it throws when missing id', async () => {
expect.assertions(1);
expect.assertions(3);
const patchCases = {
cases: [
{
Expand All @@ -270,11 +270,15 @@ describe('update', () => {
caseClient.client
// @ts-expect-error
.update({ cases: patchCases })
.catch((e) => expect(e).not.toBeNull());
.catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

test('it throws when missing version', async () => {
expect.assertions(1);
expect.assertions(3);
const patchCases = {
cases: [
{
Expand All @@ -297,11 +301,15 @@ describe('update', () => {
caseClient.client
// @ts-expect-error
.update({ cases: patchCases })
.catch((e) => expect(e).not.toBeNull());
.catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(400);
});
});

test('it throws when fields are identical', async () => {
expect.assertions(1);
expect.assertions(4);
const patchCases = {
cases: [
{
Expand All @@ -317,14 +325,16 @@ describe('update', () => {
});

const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
caseClient.client
.update({ cases: patchCases })
.catch((e) =>
expect(e.message).toBe('All update fields are identical to current version.')
);
caseClient.client.update({ cases: patchCases }).catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(406);
expect(e.message).toBe('All update fields are identical to current version.');
});
});

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

const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
caseClient.client
.update({ cases: patchCases })
.catch((e) =>
expect(e.message).toBe(
'These cases not-exists do not exist. Please check you have the correct ids.'
)
caseClient.client.update({ cases: patchCases }).catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(404);
expect(e.message).toBe(
'These cases not-exists do not exist. Please check you have the correct ids.'
);
});
});

test('it throws when cases conflicts', async () => {
expect.assertions(1);
expect.assertions(4);
const patchCases = {
cases: [
{
Expand All @@ -371,13 +382,14 @@ describe('update', () => {
});

const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient);
caseClient.client
.update({ cases: patchCases })
.catch((e) =>
expect(e.message).toBe(
'These cases mock-id-1 has been updated. Please refresh before saving additional updates.'
)
caseClient.client.update({ cases: patchCases }).catch((e) => {
expect(e).not.toBeNull();
expect(e.isBoom).toBe(true);
expect(e.output.statusCode).toBe(409);
expect(e.message).toBe(
'These cases mock-id-1 has been updated. Please refresh before saving additional updates.'
);
});
});
});
});
Loading

0 comments on commit 1095946

Please sign in to comment.