Skip to content

Commit

Permalink
[Cases] Add the severity field to the cases API (#131394)
Browse files Browse the repository at this point in the history
* Add severity field to create API and migration

* Adds integration test for severity field migration

* remove exclusive test

* Change severity levels

* Update integration tests for post case

* Add more integration tests

* Fix all cases list test

* Fix some server test

* Fix util server test

* Fix client util test

* Convert event log's duration from number to string in Kibana (keep as "long" in Elasticsearch) (#130819)

* Convert event.duration to string in TypeScript, keep as long in Elasticsearch

* Fix jest test

* Fix functional tests

* Add ecsStringOrNumber to event log schema

* Fix jest test

* Add utility functions to event log plugin

* Use new event log utility functions

* PR fixes

Co-authored-by: Kibana Machine <[email protected]>

* filter o11y rule aggregations (#131301)

* [Cloud Posture] Display and save rules per benchmark (#131412)

* Adding aria-label for discover data grid select document checkbox (#131277)

* Update API docs (#130999)

Co-authored-by: Kibana Machine <[email protected]>

* [CI] Use GCS buckets for bazel remote caching (#131345)

* [Actionable Observability] Add license modal to rules table (#131232)

* Add fix license link

* fix localization

* fix CI error

* fix more translation issues

Co-authored-by: Kibana Machine <[email protected]>

* [RAM] Add shareable rule status filter (#130705)

* rule state filter

* turn off experiment

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Status filter API call

* Fix tests

* rename state to status, added tests

* Address comments and fix tests

* Revert experiment flag

* Remove unused translations

* Addressed comments

Co-authored-by: kibanamachine <[email protected]>

* [storybook] Watch for changes in packages (#131467)

* [storybook] Watch for changes in packages

* Update default_config.ts

* Improve saved objects migrations failure errors and logs (#131359)

* [Unified observability] Add tour step to guided setup (#131149)

* [Lens] Improved interval input (#131372)

* [Vega] Adjust vega doc for usage of ems files (#130948)

* adjust vega doc

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <[email protected]>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <[email protected]>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <[email protected]>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <[email protected]>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <[email protected]>

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Nick Peihl <[email protected]>

* Excess intersections

* Create severity user action

* Add severity to create_case user action

* Fix and add integration tests

* Minor improvements

Co-authored-by: Mike Côté <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: mgiota <[email protected]>
Co-authored-by: Jordan <[email protected]>
Co-authored-by: Bhavya RM <[email protected]>
Co-authored-by: Thomas Neirynck <[email protected]>
Co-authored-by: Brian Seeders <[email protected]>
Co-authored-by: Jiawei Wu <[email protected]>
Co-authored-by: Clint Andrew Hall <[email protected]>
Co-authored-by: Christiane (Tina) Heiligers <[email protected]>
Co-authored-by: Alejandro Fernández Gómez <[email protected]>
Co-authored-by: Joe Reuter <[email protected]>
Co-authored-by: Nick Peihl <[email protected]>
Co-authored-by: Christos Nasikas <[email protected]>
  • Loading branch information
15 people authored May 5, 2022
1 parent 1d1e1f6 commit a0294e5
Show file tree
Hide file tree
Showing 33 changed files with 629 additions and 167 deletions.
81 changes: 54 additions & 27 deletions x-pack/plugins/cases/common/api/cases/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ export const SettingsRt = rt.type({
syncAlerts: rt.boolean,
});

export enum CaseSeverity {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
CRITICAL = 'critical',
}

export const CaseSeverityRt = rt.union([
rt.literal(CaseSeverity.LOW),
rt.literal(CaseSeverity.MEDIUM),
rt.literal(CaseSeverity.HIGH),
rt.literal(CaseSeverity.CRITICAL),
]);

const CaseBasicRt = rt.type({
/**
* The description of the case
Expand Down Expand Up @@ -68,6 +82,10 @@ const CaseBasicRt = rt.type({
* The plugin owner of the case
*/
owner: rt.string,
/**
* The severity of the case
*/
severity: CaseSeverityRt,
});

/**
Expand Down Expand Up @@ -106,33 +124,42 @@ export const CaseAttributesRt = rt.intersection([
}),
]);

export const CasePostRequestRt = rt.type({
/**
* Description of the case
*/
description: rt.string,
/**
* Identifiers for the case.
*/
tags: rt.array(rt.string),
/**
* Title of the case
*/
title: rt.string,
/**
* The external configuration for the case
*/
connector: CaseConnectorRt,
/**
* Sync settings for alerts
*/
settings: SettingsRt,
/**
* The owner here must match the string used when a plugin registers a feature with access to the cases plugin. The user
* creating this case must also be granted access to that plugin's feature.
*/
owner: rt.string,
});
export const CasePostRequestRt = rt.intersection([
rt.type({
/**
* Description of the case
*/
description: rt.string,
/**
* Identifiers for the case.
*/
tags: rt.array(rt.string),
/**
* Title of the case
*/
title: rt.string,
/**
* The external configuration for the case
*/
connector: CaseConnectorRt,
/**
* Sync settings for alerts
*/
settings: SettingsRt,
/**
* The owner here must match the string used when a plugin registers a feature with access to the cases plugin. The user
* creating this case must also be granted access to that plugin's feature.
*/
owner: rt.string,
}),
rt.partial({
/**
* The severity of the case. The severity is
* default it to "low" if not provided.
*/
severity: CaseSeverityRt,
}),
]);

export const CasesFindRequestRt = rt.partial({
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const ActionTypes = {
title: 'title',
status: 'status',
settings: 'settings',
severity: 'severity',
create_case: 'create_case',
delete_case: 'delete_case',
} as const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const CommonFieldsRt = rt.type({
const CommonPayloadAttributesRt = rt.type({
description: DescriptionUserActionPayloadRt.props.description,
status: rt.string,
severity: rt.string,
tags: TagsUserActionPayloadRt.props.tags,
title: TitleUserActionPayloadRt.props.title,
settings: SettingsUserActionPayloadRt.props.settings,
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/common/api/cases/user_actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { TitleUserActionRt } from './title';
import { SettingsUserActionRt } from './settings';
import { StatusUserActionRt } from './status';
import { DeleteCaseUserActionRt } from './delete_case';
import { SeverityUserActionRt } from './severity';

export * from './common';
export * from './comment';
Expand All @@ -43,6 +44,7 @@ const CommonUserActionsRt = rt.union([
TitleUserActionRt,
SettingsUserActionRt,
StatusUserActionRt,
SeverityUserActionRt,
]);

export const UserActionsRt = rt.union([
Expand Down
19 changes: 19 additions & 0 deletions x-pack/plugins/cases/common/api/cases/user_actions/severity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 * as rt from 'io-ts';
import { CaseSeverityRt } from '../case';
import { ActionTypes, UserActionWithAttributes } from './common';

export const SeverityUserActionPayloadRt = rt.type({ severity: CaseSeverityRt });

export const SeverityUserActionRt = rt.type({
type: rt.literal(ActionTypes.severity),
payload: SeverityUserActionPayloadRt,
});

export type SeverityUserAction = UserActionWithAttributes<rt.TypeOf<typeof SeverityUserActionRt>>;
94 changes: 55 additions & 39 deletions x-pack/plugins/cases/common/api/runtime_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,18 @@ import { either, fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
import { isObject } from 'lodash/fp';
import { formatErrors } from '@kbn/securitysolution-io-ts-utils';

type ErrorFactory = (message: string) => Error;

/**
* @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts
* Bug fix for the TODO is in the format_errors package
*/
export const formatErrors = (errors: rt.Errors): string[] => {
const err = errors.map((error) => {
if (error.message != null) {
return error.message;
} else {
const keyContext = error.context
.filter(
(entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== ''
)
.map((entry) => entry.key)
.join(',');

const nameContext = error.context.find((entry) => {
// TODO: Put in fix for optional chaining https://github.com/cypress-io/cypress/issues/9298
if (entry.type && entry.type.name) {
return entry.type.name.length > 0;
}
return false;
});
const suppliedValue =
keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : '';
const value = isObject(error.value) ? JSON.stringify(error.value) : error.value;
return `Invalid value "${value}" supplied to "${suppliedValue}"`;
}
});

return [...new Set(err)];
};
export type GenericIntersectionC =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any]>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any, any]>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any, any, any]>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.IntersectionC<[any, any, any, any, any]>;

export const createPlainError = (message: string) => new Error(message);

Expand All @@ -57,6 +33,40 @@ export const decodeOrThrow =
(inputValue: I) =>
pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity));

const getProps = (
codec:
| rt.HasProps
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| rt.RecordC<rt.StringC, any>
| GenericIntersectionC
): rt.Props | null => {
if (codec == null) {
return null;
}
switch (codec._tag) {
case 'DictionaryType':
if (codec.codomain.props != null) {
return codec.codomain.props;
}
const dTypes: rt.HasProps[] = codec.codomain.types;
return dTypes.reduce<rt.Props>((props, type) => Object.assign(props, getProps(type)), {});
case 'RefinementType':
case 'ReadonlyType':
return getProps(codec.type);
case 'InterfaceType':
case 'StrictType':
case 'PartialType':
return codec.props;
case 'IntersectionType':
const iTypes = codec.types as rt.HasProps[];
return iTypes.reduce<rt.Props>((props, type) => {
return Object.assign(props, getProps(type) as rt.Props);
}, {} as rt.Props) as rt.Props;
default:
return null;
}
};

const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] => {
const ex: string[] = [];
for (const k of Object.keys(r)) {
Expand All @@ -67,15 +77,21 @@ const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] =
return ex;
};

export function excess<C extends rt.InterfaceType<rt.Props> | rt.PartialType<rt.Props>>(
codec: C
): C {
export function excess<
C extends rt.InterfaceType<rt.Props> | GenericIntersectionC | rt.PartialType<rt.Props>
>(codec: C): C {
const codecProps = getProps(codec);

const r = new rt.InterfaceType(
codec.name,
codec.is,
(i, c) =>
either.chain(rt.UnknownRecord.validate(i, c), (s: Record<string, unknown>) => {
const ex = getExcessProps(codec.props, s);
if (codecProps == null) {
return rt.failure(i, c, 'unknown codec');
}

const ex = getExcessProps(codecProps, s);
return ex.length > 0
? rt.failure(
i,
Expand All @@ -87,7 +103,7 @@ export function excess<C extends rt.InterfaceType<rt.Props> | rt.PartialType<rt.
: codec.validate(i, c);
}),
codec.encode,
codec.props
codecProps
);
return r as C;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { TestProviders } from '../../common/mock';
import { casesStatus, useGetCasesMockState, mockCase, connectorsMock } from '../../containers/mock';

import { StatusAll } from '../../../common/ui/types';
import { CaseStatuses } from '../../../common/api';
import { CaseSeverity, CaseStatuses } from '../../../common/api';
import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { getEmptyTagValue } from '../empty_value';
import { useDeleteCases } from '../../containers/use_delete_cases';
Expand Down Expand Up @@ -560,6 +560,7 @@ describe('AllCasesListGeneric', () => {
username: 'lknope',
},
description: 'Security banana Issue',
severity: CaseSeverity.LOW,
duration: null,
externalService: {
connectorId: '123',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export const builderMap: UserActionBuilderMap = {
tags: createTagsUserActionBuilder,
title: createTitleUserActionBuilder,
status: createStatusUserActionBuilder,
// TODO: Build severity user action
severity: () => ({
build: () => [],
}),
pushed: createPushedUserActionBuilder,
comment: createCommentUserActionBuilder,
description: createDescriptionUserActionBuilder,
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/cases/public/containers/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
UserActionTypes,
UserActionWithResponse,
CommentUserAction,
CaseSeverity,
} from '../../common/api';
import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases';
Expand Down Expand Up @@ -154,6 +155,7 @@ export const basicCase: Case = {
fields: null,
},
description: 'Security banana Issue',
severity: CaseSeverity.LOW,
duration: null,
externalService: null,
status: CaseStatuses.open,
Expand Down Expand Up @@ -247,6 +249,7 @@ export const mockCase: Case = {
fields: null,
},
duration: null,
severity: CaseSeverity.LOW,
description: 'Security banana Issue',
externalService: null,
status: CaseStatuses.open,
Expand Down Expand Up @@ -512,6 +515,7 @@ export const getUserAction = (
description: 'a desc',
connector: { ...getJiraConnector() },
status: CaseStatuses.open,
severity: CaseSeverity.LOW,
title: 'a title',
tags: ['a tag'],
settings: { syncAlerts: true },
Expand Down
5 changes: 3 additions & 2 deletions x-pack/plugins/cases/server/client/cases/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import { SavedObjectsUtils } from '@kbn/core/server';

import {
throwErrors,
excess,
CaseResponseRt,
CaseResponse,
CasePostRequest,
ActionTypes,
CasePostRequestRt,
excess,
CaseSeverity,
} from '../../../common/api';
import { MAX_TITLE_LENGTH } from '../../../common/constants';
import { isInvalidTag } from '../../../common/utils/validators';
Expand Down Expand Up @@ -85,7 +86,7 @@ export const create = async (
unsecuredSavedObjectsClient,
caseId: newCase.id,
user,
payload: query,
payload: { ...query, severity: query.severity ?? CaseSeverity.LOW },
owner: newCase.attributes.owner,
});

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/server/client/cases/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export const userActions: CaseUserActionsResponse = [
},
settings: { syncAlerts: true },
status: 'open',
severity: 'low',
owner: SECURITY_SOLUTION_OWNER,
},
action_id: 'fd830c60-6646-11eb-a291-51bf6b175a53',
Expand Down
Loading

0 comments on commit a0294e5

Please sign in to comment.