Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Case] Case action type #80870

Merged
merged 23 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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