From 45fcbe7a701777f6fa53a28dde5f5cce4eb44457 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 13:06:37 +0200 Subject: [PATCH 01/15] TEMP COMMIT. REBASE ME --- .../rules/bulk_actions/request_schema.mock.ts | 6 +- .../rules/bulk_actions/request_schema.test.ts | 44 ++++++------- .../api/rules/bulk_actions/request_schema.ts | 64 +++++++++---------- .../api/rules/bulk_actions/route.ts | 10 ++- 4 files changed, 58 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts index 125a329872727..548d8b571660e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts @@ -6,15 +6,15 @@ */ import { BulkAction, BulkActionEditType } from './request_schema'; -import type { PerformBulkActionSchema } from './request_schema'; +import type { PerformBulkActionRequestBody } from './request_schema'; -export const getPerformBulkActionSchemaMock = (): PerformBulkActionSchema => ({ +export const getPerformBulkActionSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, action: BulkAction.disable, }); -export const getPerformBulkActionEditSchemaMock = (): PerformBulkActionSchema => ({ +export const getPerformBulkActionEditSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, action: BulkAction.edit, diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts index a641b38a25dc7..fcdc958478446 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts @@ -8,11 +8,11 @@ import { left } from 'fp-ts/lib/Either'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import type { PerformBulkActionSchema } from './request_schema'; -import { performBulkActionSchema, BulkAction, BulkActionEditType } from './request_schema'; +import type { PerformBulkActionRequestBody } from './request_schema'; +import { PerformBulkActionRequestBody, BulkAction, BulkActionEditType } from './request_schema'; const retrieveValidationMessage = (payload: unknown) => { - const decoded = performBulkActionSchema.decode(payload); + const decoded = PerformBulkActionRequestBody.decode(payload); const checked = exactCheck(payload, decoded); return foldLeftRight(checked); }; @@ -21,7 +21,7 @@ describe('Perform bulk action request schema', () => { describe('cases common to every bulk action', () => { // missing query means it will request for all rules test('valid request: missing query', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: undefined, action: BulkAction.enable, }; @@ -32,7 +32,7 @@ describe('Perform bulk action request schema', () => { }); test('invalid request: missing action', () => { - const payload: Omit = { + const payload: Omit = { query: 'name: test', }; const message = retrieveValidationMessage(payload); @@ -45,7 +45,7 @@ describe('Perform bulk action request schema', () => { }); test('invalid request: unknown action', () => { - const payload: Omit & { action: 'unknown' } = { + const payload: Omit & { action: 'unknown' } = { query: 'name: test', action: 'unknown', }; @@ -84,7 +84,7 @@ describe('Perform bulk action request schema', () => { describe('bulk enable', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.enable, }; @@ -96,7 +96,7 @@ describe('Perform bulk action request schema', () => { describe('bulk disable', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.disable, }; @@ -108,7 +108,7 @@ describe('Perform bulk action request schema', () => { describe('bulk export', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.export, }; @@ -120,7 +120,7 @@ describe('Perform bulk action request schema', () => { describe('bulk delete', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.delete, }; @@ -132,7 +132,7 @@ describe('Perform bulk action request schema', () => { describe('bulk duplicate', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.duplicate, }; @@ -268,7 +268,7 @@ describe('Perform bulk action request schema', () => { }); test('valid request: set_index_patterns edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [{ type: BulkActionEditType.set_index_patterns, value: ['logs-*'] }], @@ -281,7 +281,7 @@ describe('Perform bulk action request schema', () => { }); test('valid request: add_index_patterns edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [{ type: BulkActionEditType.add_index_patterns, value: ['logs-*'] }], @@ -294,7 +294,7 @@ describe('Perform bulk action request schema', () => { }); test('valid request: delete_index_patterns edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -353,7 +353,7 @@ describe('Perform bulk action request schema', () => { }); test('valid request: set_timeline edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -405,7 +405,7 @@ describe('Perform bulk action request schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -431,7 +431,7 @@ describe('Perform bulk action request schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -457,7 +457,7 @@ describe('Perform bulk action request schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -472,7 +472,7 @@ describe('Perform bulk action request schema', () => { }); test('valid request: set_schedule edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -484,7 +484,7 @@ describe('Perform bulk action request schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -587,7 +587,7 @@ describe('Perform bulk action request schema', () => { }); test('valid request: add_rule_actions edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -618,7 +618,7 @@ describe('Perform bulk action request schema', () => { }); test('valid request: set_rule_actions edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts index 2b4787c2b8aa7..c962b4f1dfabb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts @@ -46,7 +46,8 @@ export enum BulkActionEditType { 'set_schedule' = 'set_schedule', } -export const throttleForBulkActions = t.union([ +export type ThrottleForBulkActions = t.TypeOf; +export const ThrottleForBulkActions = t.union([ t.literal('rule'), TimeDuration({ allowedDurations: [ @@ -56,9 +57,9 @@ export const throttleForBulkActions = t.union([ ], }), ]); -export type ThrottleForBulkActions = t.TypeOf; -const bulkActionEditPayloadTags = t.type({ +type BulkActionEditPayloadTags = t.TypeOf; +const BulkActionEditPayloadTags = t.type({ type: t.union([ t.literal(BulkActionEditType.add_tags), t.literal(BulkActionEditType.delete_tags), @@ -67,9 +68,8 @@ const bulkActionEditPayloadTags = t.type({ value: RuleTagArray, }); -export type BulkActionEditPayloadTags = t.TypeOf; - -const bulkActionEditPayloadIndexPatterns = t.intersection([ +type BulkActionEditPayloadIndexPatterns = t.TypeOf; +const BulkActionEditPayloadIndexPatterns = t.intersection([ t.type({ type: t.union([ t.literal(BulkActionEditType.add_index_patterns), @@ -81,11 +81,8 @@ const bulkActionEditPayloadIndexPatterns = t.intersection([ t.exact(t.partial({ overwrite_data_views: t.boolean })), ]); -export type BulkActionEditPayloadIndexPatterns = t.TypeOf< - typeof bulkActionEditPayloadIndexPatterns ->; - -const bulkActionEditPayloadTimeline = t.type({ +type BulkActionEditPayloadTimeline = t.TypeOf; +const BulkActionEditPayloadTimeline = t.type({ type: t.literal(BulkActionEditType.set_timeline), value: t.type({ timeline_id: TimelineTemplateId, @@ -93,13 +90,12 @@ const bulkActionEditPayloadTimeline = t.type({ }), }); -export type BulkActionEditPayloadTimeline = t.TypeOf; - /** * per rulesClient.bulkEdit rules actions operation contract (x-pack/plugins/alerting/server/rules_client/rules_client.ts) * normalized rule action object is expected (NormalizedAlertAction) as value for the edit operation */ -const normalizedRuleAction = t.exact( +type NormalizedRuleAction = t.TypeOf; +const NormalizedRuleAction = t.exact( t.type({ group: RuleActionGroup, id: RuleActionId, @@ -107,37 +103,35 @@ const normalizedRuleAction = t.exact( }) ); -const bulkActionEditPayloadRuleActions = t.type({ +export type BulkActionEditPayloadRuleActions = t.TypeOf; +export const BulkActionEditPayloadRuleActions = t.type({ type: t.union([ t.literal(BulkActionEditType.add_rule_actions), t.literal(BulkActionEditType.set_rule_actions), ]), value: t.type({ - throttle: throttleForBulkActions, - actions: t.array(normalizedRuleAction), + throttle: ThrottleForBulkActions, + actions: t.array(NormalizedRuleAction), }), }); -export type BulkActionEditPayloadRuleActions = t.TypeOf; - -const bulkActionEditPayloadSchedule = t.type({ +export type BulkActionEditPayloadSchedule = t.TypeOf; +export const BulkActionEditPayloadSchedule = t.type({ type: t.literal(BulkActionEditType.set_schedule), value: t.type({ interval: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), lookback: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), }), }); -export type BulkActionEditPayloadSchedule = t.TypeOf; - -export const bulkActionEditPayload = t.union([ - bulkActionEditPayloadTags, - bulkActionEditPayloadIndexPatterns, - bulkActionEditPayloadTimeline, - bulkActionEditPayloadRuleActions, - bulkActionEditPayloadSchedule, -]); -export type BulkActionEditPayload = t.TypeOf; +export type BulkActionEditPayload = t.TypeOf; +export const BulkActionEditPayload = t.union([ + BulkActionEditPayloadTags, + BulkActionEditPayloadIndexPatterns, + BulkActionEditPayloadTimeline, + BulkActionEditPayloadRuleActions, + BulkActionEditPayloadSchedule, +]); /** * actions that modify rules attributes @@ -155,7 +149,8 @@ export type BulkActionEditForRuleParams = | BulkActionEditPayloadTimeline | BulkActionEditPayloadSchedule; -export const performBulkActionSchema = t.intersection([ +export type PerformBulkActionRequestBody = t.TypeOf; +export const PerformBulkActionRequestBody = t.intersection([ t.exact( t.type({ query: t.union([RuleQuery, t.undefined]), @@ -177,16 +172,15 @@ export const performBulkActionSchema = t.intersection([ t.exact( t.type({ action: t.literal(BulkAction.edit), - [BulkAction.edit]: NonEmptyArray(bulkActionEditPayload), + [BulkAction.edit]: NonEmptyArray(BulkActionEditPayload), }) ), ]), ]); -export const performBulkActionQuerySchema = t.exact( +export type PerformBulkActionRequestQuery = t.TypeOf; +export const PerformBulkActionRequestQuery = t.exact( t.partial({ dry_run: t.union([t.literal('true'), t.literal('false')]), }) ); - -export type PerformBulkActionSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index 656060ccdb3b1..1dac0b68441fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -22,9 +22,9 @@ import { RULES_TABLE_MAX_PAGE_SIZE, } from '../../../../../../../common/constants'; import { - performBulkActionSchema, - performBulkActionQuerySchema, BulkAction, + PerformBulkActionRequestBody, + PerformBulkActionRequestQuery, } from '../../../../../../../common/detection_engine/rule_management'; import type { SetupPlugins } from '../../../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; @@ -267,10 +267,8 @@ export const performBulkActionRoute = ( { path: DETECTION_ENGINE_RULES_BULK_ACTION, validate: { - body: buildRouteValidation(performBulkActionSchema), - query: buildRouteValidation( - performBulkActionQuerySchema - ), + body: buildRouteValidation(PerformBulkActionRequestBody), + query: buildRouteValidation(PerformBulkActionRequestQuery), }, options: { tags: ['access:securitySolution', routeLimitedConcurrencyTag(MAX_ROUTE_CONCURRENCY)], From 53769b8d23ad9252794f68621804e8bd5307d53a Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 13:33:27 +0200 Subject: [PATCH 02/15] TEMP COMMIT. REBASE ME --- .../rules/bulk_actions/request_schema.test.ts | 2 - .../api/rules/bulk_actions/request_schema.ts | 6 ++ .../bulk_create_rules/request_schema.test.ts | 62 ++++++++++--------- .../rules/bulk_create_rules/request_schema.ts | 8 ++- .../rule_management/api/register_routes.ts | 36 +++++------ .../bulk_create_rules/get_duplicates.test.ts | 43 +++++++++++++ .../rules/bulk_create_rules/get_duplicates.ts | 24 +++++++ .../api/rules/bulk_create_rules/route.test.ts | 6 +- .../api/rules/bulk_create_rules/route.ts | 8 +-- .../api/rules/bulk_delete_rules/route.test.ts | 6 +- .../api/rules/bulk_delete_rules/route.ts | 2 +- .../api/rules/bulk_patch_rules/route.test.ts | 6 +- .../api/rules/bulk_patch_rules/route.ts | 2 +- .../api/rules/bulk_update_rules/route.test.ts | 6 +- .../api/rules/bulk_update_rules/route.ts | 2 +- .../api/rules/create_rule/route.test.ts | 6 +- .../api/rules/create_rule/route.ts | 2 +- .../api/rules/delete_rule/route.test.ts | 6 +- .../api/rules/delete_rule/route.ts | 2 +- .../api/rules/find_rules/route.test.ts | 2 +- .../api/rules/import_rules/route.test.ts | 2 +- .../api/rules/patch_rule/route.test.ts | 6 +- .../api/rules/patch_rule/route.ts | 2 +- .../api/rules/read_rule/route.test.ts | 6 +- .../api/rules/read_rule/route.ts | 2 +- .../api/rules/update_rule/route.test.ts | 6 +- .../api/rules/update_rule/route.ts | 2 +- .../rule_management/utils/utils.test.ts | 35 ----------- .../rule_management/utils/utils.ts | 22 ++----- 29 files changed, 174 insertions(+), 146 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts index fcdc958478446..63de1d45ec3cb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts @@ -7,8 +7,6 @@ import { left } from 'fp-ts/lib/Either'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - -import type { PerformBulkActionRequestBody } from './request_schema'; import { PerformBulkActionRequestBody, BulkAction, BulkActionEditType } from './request_schema'; const retrieveValidationMessage = (payload: unknown) => { diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts index c962b4f1dfabb..a60ba33934a62 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts @@ -149,6 +149,9 @@ export type BulkActionEditForRuleParams = | BulkActionEditPayloadTimeline | BulkActionEditPayloadSchedule; +/** + * Request body parameters of the API route. + */ export type PerformBulkActionRequestBody = t.TypeOf; export const PerformBulkActionRequestBody = t.intersection([ t.exact( @@ -178,6 +181,9 @@ export const PerformBulkActionRequestBody = t.intersection([ ]), ]); +/** + * Query string parameters of the API route. + */ export type PerformBulkActionRequestQuery = t.TypeOf; export const PerformBulkActionRequestQuery = t.exact( t.partial({ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.test.ts index 5b6efd946e87a..1a343229ea3a2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.test.ts @@ -5,19 +5,18 @@ * 2.0. */ -import type { CreateRulesBulkSchema } from './request_schema'; -import { createRulesBulkSchema } from './request_schema'; +import { BulkCreateRulesRequestBody } from './request_schema'; import { exactCheck, foldLeftRight, formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { getCreateRulesSchemaMock } from '../../../../schemas/request/rule_schemas.mock'; // only the basics of testing are here. // see: rule_schemas.test.ts for the bulk of the validation tests // this just wraps createRulesSchema in an array -describe('create_rules_bulk_schema', () => { +describe('Bulk create rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: CreateRulesBulkSchema = []; + const payload: BulkCreateRulesRequestBody = []; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(output.errors).toEqual([]); @@ -27,7 +26,7 @@ describe('create_rules_bulk_schema', () => { test('made up values do not validate for a single element', () => { const payload: Array<{ madeUp: string }> = [{ madeUp: 'hi' }]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toContain( @@ -44,9 +43,9 @@ describe('create_rules_bulk_schema', () => { }); test('single array element does validate', () => { - const payload: CreateRulesBulkSchema = [getCreateRulesSchemaMock()]; + const payload: BulkCreateRulesRequestBody = [getCreateRulesSchemaMock()]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -54,9 +53,12 @@ describe('create_rules_bulk_schema', () => { }); test('two array elements do validate', () => { - const payload: CreateRulesBulkSchema = [getCreateRulesSchemaMock(), getCreateRulesSchemaMock()]; + const payload: BulkCreateRulesRequestBody = [ + getCreateRulesSchemaMock(), + getCreateRulesSchemaMock(), + ]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -67,9 +69,9 @@ describe('create_rules_bulk_schema', () => { const singleItem = getCreateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem]; + const payload: BulkCreateRulesRequestBody = [singleItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -83,9 +85,9 @@ describe('create_rules_bulk_schema', () => { const secondItem = getCreateRulesSchemaMock(); // @ts-expect-error delete secondItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -99,9 +101,9 @@ describe('create_rules_bulk_schema', () => { const secondItem = getCreateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -117,9 +119,9 @@ describe('create_rules_bulk_schema', () => { delete singleItem.risk_score; // @ts-expect-error delete secondItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -134,9 +136,9 @@ describe('create_rules_bulk_schema', () => { madeUpValue: 'something', }; const secondItem = getCreateRulesSchemaMock(); - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -149,9 +151,9 @@ describe('create_rules_bulk_schema', () => { ...getCreateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -167,9 +169,9 @@ describe('create_rules_bulk_schema', () => { ...getCreateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue,madeUpValue"']); @@ -180,7 +182,7 @@ describe('create_rules_bulk_schema', () => { const badSeverity = { ...getCreateRulesSchemaMock(), severity: 'madeup' }; const payload = [badSeverity]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['Invalid value "madeup" supplied to "severity"']); @@ -188,11 +190,11 @@ describe('create_rules_bulk_schema', () => { }); test('You can set "note" to a string', () => { - const payload: CreateRulesBulkSchema = [ + const payload: BulkCreateRulesRequestBody = [ { ...getCreateRulesSchemaMock(), note: '# test markdown' }, ]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -200,9 +202,9 @@ describe('create_rules_bulk_schema', () => { }); test('You can set "note" to an empty string', () => { - const payload: CreateRulesBulkSchema = [{ ...getCreateRulesSchemaMock(), note: '' }]; + const payload: BulkCreateRulesRequestBody = [{ ...getCreateRulesSchemaMock(), note: '' }]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -219,7 +221,7 @@ describe('create_rules_bulk_schema', () => { }, ]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.ts index 3fa77ba760a79..f2129fb083dc2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_create_rules/request_schema.ts @@ -6,8 +6,10 @@ */ import * as t from 'io-ts'; - import { createRulesSchema } from '../../../../schemas/request/rule_schemas'; -export const createRulesBulkSchema = t.array(createRulesSchema); -export type CreateRulesBulkSchema = t.TypeOf; +/** + * Request body parameters of the API route. + */ +export type BulkCreateRulesRequestBody = t.TypeOf; +export const BulkCreateRulesRequestBody = t.array(createRulesSchema); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts index 1e4319da3d5c1..90a37a7dbe7da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts @@ -11,18 +11,18 @@ import type { SetupPlugins } from '../../../../plugin_contract'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { performBulkActionRoute } from './rules/bulk_actions/route'; -import { createRulesBulkRoute } from './rules/bulk_create_rules/route'; -import { deleteRulesBulkRoute } from './rules/bulk_delete_rules/route'; -import { patchRulesBulkRoute } from './rules/bulk_patch_rules/route'; -import { updateRulesBulkRoute } from './rules/bulk_update_rules/route'; -import { createRulesRoute } from './rules/create_rule/route'; -import { deleteRulesRoute } from './rules/delete_rule/route'; +import { bulkCreateRulesRoute } from './rules/bulk_create_rules/route'; +import { bulkDeleteRulesRoute } from './rules/bulk_delete_rules/route'; +import { bulkPatchRulesRoute } from './rules/bulk_patch_rules/route'; +import { bulkUpdateRulesRoute } from './rules/bulk_update_rules/route'; +import { createRuleRoute } from './rules/create_rule/route'; +import { deleteRuleRoute } from './rules/delete_rule/route'; import { exportRulesRoute } from './rules/export_rules/route'; import { findRulesRoute } from './rules/find_rules/route'; import { importRulesRoute } from './rules/import_rules/route'; -import { patchRulesRoute } from './rules/patch_rule/route'; -import { readRulesRoute } from './rules/read_rule/route'; -import { updateRulesRoute } from './rules/update_rule/route'; +import { patchRuleRoute } from './rules/patch_rule/route'; +import { readRuleRoute } from './rules/read_rule/route'; +import { updateRuleRoute } from './rules/update_rule/route'; import { readTagsRoute } from './tags/read_tags/route'; export const registerRuleManagementRoutes = ( @@ -32,17 +32,17 @@ export const registerRuleManagementRoutes = ( logger: Logger ) => { // Rules CRUD - createRulesRoute(router, ml); - readRulesRoute(router, logger); - updateRulesRoute(router, ml); - patchRulesRoute(router, ml); - deleteRulesRoute(router); + createRuleRoute(router, ml); + readRuleRoute(router, logger); + updateRuleRoute(router, ml); + patchRuleRoute(router, ml); + deleteRuleRoute(router); // Rules bulk CRUD - createRulesBulkRoute(router, ml, logger); - updateRulesBulkRoute(router, ml, logger); - patchRulesBulkRoute(router, ml, logger); - deleteRulesBulkRoute(router, logger); + bulkCreateRulesRoute(router, ml, logger); + bulkUpdateRulesRoute(router, ml, logger); + bulkPatchRulesRoute(router, ml, logger); + bulkDeleteRulesRoute(router, logger); // Rules bulk actions performBulkActionRoute(router, ml, logger); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.test.ts new file mode 100644 index 0000000000000..bc15b93639cdb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.test.ts @@ -0,0 +1,43 @@ +/* + * 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 type { BulkCreateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; +import { getDuplicates } from './get_duplicates'; + +describe('getDuplicates', () => { + test("returns array of ruleIds showing the duplicate keys of 'value2' and 'value3'", () => { + const output = getDuplicates( + [ + { rule_id: 'value1' }, + { rule_id: 'value2' }, + { rule_id: 'value2' }, + { rule_id: 'value3' }, + { rule_id: 'value3' }, + {}, + {}, + ] as BulkCreateRulesRequestBody, + 'rule_id' + ); + const expected = ['value2', 'value3']; + expect(output).toEqual(expected); + }); + + test('returns null when given a map of no duplicates', () => { + const output = getDuplicates( + [ + { rule_id: 'value1' }, + { rule_id: 'value2' }, + { rule_id: 'value3' }, + {}, + {}, + ] as BulkCreateRulesRequestBody, + 'rule_id' + ); + const expected: string[] = []; + expect(output).toEqual(expected); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.ts new file mode 100644 index 0000000000000..3d537bbd25c46 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.ts @@ -0,0 +1,24 @@ +/* + * 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 { countBy } from 'lodash/fp'; +import type { BulkCreateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; + +export const getDuplicates = ( + ruleDefinitions: BulkCreateRulesRequestBody, + by: 'rule_id' +): string[] => { + const mappedDuplicates = countBy( + by, + ruleDefinitions.filter((r) => r[by] != null) + ); + const hasDuplicates = Object.values(mappedDuplicates).some((i) => i > 1); + if (hasDuplicates) { + return Object.keys(mappedDuplicates).filter((key) => mappedDuplicates[key] > 1); + } + return []; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts index 8f944228cbad1..4aba0ba8eb88d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts @@ -21,7 +21,7 @@ import { getBasicNoShardsSearchResponse, } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; -import { createRulesBulkRoute } from './route'; +import { bulkCreateRulesRoute } from './route'; import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; @@ -29,7 +29,7 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; jest.mock('../../../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('create_rules_bulk', () => { +describe('Bulk create rules route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -46,7 +46,7 @@ describe('create_rules_bulk', () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - createRulesBulkRoute(server.router, ml, logger); + bulkCreateRulesRoute(server.router, ml, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts index aff069d57bb00..4c22c8f255256 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts @@ -8,7 +8,7 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { Logger } from '@kbn/core/server'; import { createRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents'; -import { createRulesBulkSchema } from '../../../../../../../common/detection_engine/rule_management'; +import { BulkCreateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; import { rulesBulkSchema } from '../../../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../../../common/constants'; @@ -16,7 +16,7 @@ import type { SetupPlugins } from '../../../../../../plugin'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../../machine_learning/validation'; import { readRules } from '../../../logic/crud/read_rules'; -import { getDuplicates } from '../../../utils/utils'; +import { getDuplicates } from './get_duplicates'; import { transformValidateBulkError } from '../../../utils/validate'; import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; @@ -31,7 +31,7 @@ import { createRules } from '../../../logic/crud/create_rules'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const createRulesBulkRoute = ( +export const bulkCreateRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], logger: Logger @@ -40,7 +40,7 @@ export const createRulesBulkRoute = ( { path: DETECTION_ENGINE_RULES_BULK_CREATE, validate: { - body: buildRouteValidation(createRulesBulkSchema), + body: buildRouteValidation(BulkCreateRulesRequestBody), }, options: { tags: ['access:securitySolution'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts index 5d9ac55b48ab7..750419c464b2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts @@ -16,10 +16,10 @@ import { getEmptySavedObjectsResponse, } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; -import { deleteRulesBulkRoute } from './route'; +import { bulkDeleteRulesRoute } from './route'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -describe('delete_rules', () => { +describe('Bulk delete rules route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -32,7 +32,7 @@ describe('delete_rules', () => { clients.rulesClient.delete.mockResolvedValue({}); // successful deletion clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // rule status request - deleteRulesBulkRoute(server.router, logger); + bulkDeleteRulesRoute(server.router, logger); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts index 5a3bbfe488bea..c3ba2f149fda0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts @@ -43,7 +43,7 @@ type Handler = RequestHandler< /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { const config: Config = { validate: { body: buildRouteValidation( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts index b92780897fd16..19a3fd722faf1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts @@ -22,7 +22,7 @@ import { typicalMlRulePayload, } from '../../../../routes/__mocks__/request_responses'; import { serverMock, requestContextMock, requestMock } from '../../../../routes/__mocks__'; -import { patchRulesBulkRoute } from './route'; +import { bulkPatchRulesRoute } from './route'; import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; @@ -39,7 +39,7 @@ jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { }; }); -describe('patch_rules_bulk', () => { +describe('Bulk patch rules route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -55,7 +55,7 @@ describe('patch_rules_bulk', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - patchRulesBulkRoute(server.router, ml, logger); + bulkPatchRulesRoute(server.router, ml, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts index b64cbbc5ecd36..04d54d7252a2f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts @@ -27,7 +27,7 @@ import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../. /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const patchRulesBulkRoute = ( +export const bulkPatchRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], logger: Logger diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts index ed4242fcf93d2..1b0f8975ebca1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts @@ -19,7 +19,7 @@ import { typicalMlRulePayload, } from '../../../../routes/__mocks__/request_responses'; import { serverMock, requestContextMock, requestMock } from '../../../../routes/__mocks__'; -import { updateRulesBulkRoute } from './route'; +import { bulkUpdateRulesRoute } from './route'; import type { BulkError } from '../../../../routes/utils'; import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; @@ -37,7 +37,7 @@ jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { }; }); -describe('update_rules_bulk', () => { +describe('Bulk update rules route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -55,7 +55,7 @@ describe('update_rules_bulk', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - updateRulesBulkRoute(server.router, ml, logger); + bulkUpdateRulesRoute(server.router, ml, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index e7957240979dd..b833e2ee7c116 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -32,7 +32,7 @@ import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../. /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const updateRulesBulkRoute = ( +export const bulkUpdateRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], logger: Logger diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts index 8b647e975f537..f0eaa31705180 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts @@ -20,14 +20,14 @@ import { } from '../../../../../machine_learning/mocks'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; -import { createRulesRoute } from './route'; +import { createRuleRoute } from './route'; import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; jest.mock('../../../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('create_rules', () => { +describe('Create rule route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -43,7 +43,7 @@ describe('create_rules', () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - createRulesRoute(server.router, ml); + createRuleRoute(server.router, ml); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index 4986d41473d31..6e85bd54ad99e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -21,7 +21,7 @@ import { createRuleValidateTypeDependents } from '../../../../../../../common/de import { createRules } from '../../../logic/crud/create_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; -export const createRulesRoute = ( +export const createRuleRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'] ): void => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts index 1e2b66e291bb8..5f5e81a6c3960 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts @@ -16,7 +16,7 @@ import { getRuleMock, } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; -import { deleteRulesRoute } from './route'; +import { deleteRuleRoute } from './route'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; // eslint-disable-next-line no-restricted-imports import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; @@ -29,7 +29,7 @@ jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { }; }); -describe('delete_rules', () => { +describe('Delete rule route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); @@ -42,7 +42,7 @@ describe('delete_rules', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - deleteRulesRoute(server.router); + deleteRuleRoute(server.router); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts index 423d63c86b990..d6daa037b4839 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts @@ -20,7 +20,7 @@ import { readRules } from '../../../logic/crud/read_rules'; // eslint-disable-next-line no-restricted-imports import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; -export const deleteRulesRoute = (router: SecuritySolutionPluginRouter) => { +export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { router.delete( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts index 84743f5b90105..e34dff9733794 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts @@ -17,7 +17,7 @@ import { } from '../../../../routes/__mocks__/request_responses'; import { findRulesRoute } from './route'; -describe('find_rules', () => { +describe('Find rules route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let logger: ReturnType; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts index 9b3ace870861b..bb2b7fb564700 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts @@ -33,7 +33,7 @@ import { getQueryRuleParams } from '../../../../rule_schema/mocks'; jest.mock('../../../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -describe('import_rules_route', () => { +describe('Import rules route', () => { let config: ReturnType; let server: ReturnType; let request: ReturnType; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts index 77bf538347b66..8012d8f475e94 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts @@ -20,7 +20,7 @@ import { typicalMlRulePayload, } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; -import { patchRulesRoute } from './route'; +import { patchRuleRoute } from './route'; import { getPatchRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.mock'; import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; // eslint-disable-next-line no-restricted-imports @@ -36,7 +36,7 @@ jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { }; }); -describe('patch_rules', () => { +describe('Patch rule route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -52,7 +52,7 @@ describe('patch_rules', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - patchRulesRoute(server.router, ml); + patchRuleRoute(server.router, ml); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 19556ea3bb746..924f221b9ffd0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -23,7 +23,7 @@ import { readRules } from '../../../logic/crud/read_rules'; // eslint-disable-next-line no-restricted-imports import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; -export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { +export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.patch( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.test.ts index 190b03a59e210..573a85cc97b16 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.test.ts @@ -8,7 +8,7 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; -import { readRulesRoute } from './route'; +import { readRuleRoute } from './route'; import { getEmptyFindResult, getReadRequest, @@ -21,7 +21,7 @@ import { import { requestMock, requestContextMock, serverMock } from '../../../../routes/__mocks__'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -describe('read_rules', () => { +describe('Read rule route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let logger: ReturnType; @@ -41,7 +41,7 @@ describe('read_rules', () => { }), id: myFakeId, }); - readRulesRoute(server.router, logger); + readRuleRoute(server.router, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts index 3d8f768ce39c4..03805bd57096c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts @@ -20,7 +20,7 @@ import { readRules } from '../../../logic/crud/read_rules'; // eslint-disable-next-line no-restricted-imports import { legacyGetRuleActionsSavedObject } from '../../../../rule_actions_legacy'; -export const readRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.get( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts index c95e2aa22ea89..35eff7c1e6206 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts @@ -20,7 +20,7 @@ import { } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; -import { updateRulesRoute } from './route'; +import { updateRuleRoute } from './route'; import { getUpdateRulesSchemaMock } from '../../../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; // eslint-disable-next-line no-restricted-imports @@ -36,7 +36,7 @@ jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { }; }); -describe('update_rules', () => { +describe('Update rule route', () => { let server: ReturnType; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType; @@ -53,7 +53,7 @@ describe('update_rules', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - updateRulesRoute(server.router, ml); + updateRuleRoute(server.router, ml); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index 4b89e810a41db..d27c76afa059d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -24,7 +24,7 @@ import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migrati import { readRules } from '../../../logic/crud/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; -export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { +export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.put( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts index f73de2ceb960a..9963692738515 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts @@ -15,7 +15,6 @@ import { transform, getIdBulkError, transformAlertsToRules, - getDuplicates, getTupleDuplicateErrorsAndUniqueRules, getInvalidConnectors, swapActionIds, @@ -30,7 +29,6 @@ import type { PartialRule } from '@kbn/alerting-plugin/server'; // TODO: https://github.com/elastic/kibana/pull/142950 import { createRulesAndExceptionsStreamFromNdJson } from '../logic/import/create_rules_stream_from_ndjson'; import type { RuleAlertType } from '../../rule_schema'; -import type { CreateRulesBulkSchema } from '../../../../../common/detection_engine/rule_management'; import type { ImportRulesSchema } from '../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getMlRuleParams, getQueryRuleParams, getThreatRuleParams } from '../../rule_schema/mocks'; @@ -492,39 +490,6 @@ describe('utils', () => { }); }); - describe('getDuplicates', () => { - test("returns array of ruleIds showing the duplicate keys of 'value2' and 'value3'", () => { - const output = getDuplicates( - [ - { rule_id: 'value1' }, - { rule_id: 'value2' }, - { rule_id: 'value2' }, - { rule_id: 'value3' }, - { rule_id: 'value3' }, - {}, - {}, - ] as CreateRulesBulkSchema, - 'rule_id' - ); - const expected = ['value2', 'value3']; - expect(output).toEqual(expected); - }); - test('returns null when given a map of no duplicates', () => { - const output = getDuplicates( - [ - { rule_id: 'value1' }, - { rule_id: 'value2' }, - { rule_id: 'value3' }, - {}, - {}, - ] as CreateRulesBulkSchema, - 'rule_id' - ); - const expected: string[] = []; - expect(output).toEqual(expected); - }); - }); - describe('getTupleDuplicateErrorsAndUniqueRules', () => { test('returns tuple of empty duplicate errors array and rule array with instance of Syntax Error when imported rule contains parse error', async () => { // This is a string because we have a double "::" below to make an error happen on purpose. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts index 0c862dc3cd095..3b73a800987c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { countBy, partition } from 'lodash/fp'; -import uuid from 'uuid'; -import type { RuleAction } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { SavedObjectsClientContract } from '@kbn/core/server'; +import { partition } from 'lodash/fp'; import pMap from 'p-map'; +import uuid from 'uuid'; +import type { SavedObjectsClientContract } from '@kbn/core/server'; +import type { RuleAction } from '@kbn/securitysolution-io-ts-alerting-types'; import type { PartialRule, FindResult } from '@kbn/alerting-plugin/server'; import type { ActionsClient, FindActionResult } from '@kbn/actions-plugin/server'; -import type { CreateRulesBulkSchema } from '../../../../../common/detection_engine/rule_management'; + import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; import type { ImportRulesSchema } from '../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; @@ -132,18 +132,6 @@ export const transform = ( return null; }; -export const getDuplicates = (ruleDefinitions: CreateRulesBulkSchema, by: 'rule_id'): string[] => { - const mappedDuplicates = countBy( - by, - ruleDefinitions.filter((r) => r[by] != null) - ); - const hasDuplicates = Object.values(mappedDuplicates).some((i) => i > 1); - if (hasDuplicates) { - return Object.keys(mappedDuplicates).filter((key) => mappedDuplicates[key] > 1); - } - return []; -}; - export const getTupleDuplicateErrorsAndUniqueRules = ( rules: PromiseFromStreams[], isOverwrite: boolean From 61849bdc85b45c0c3b8810808ce86fbe065ecac5 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 14:03:48 +0200 Subject: [PATCH 03/15] TEMP COMMIT. REBASE ME --- ..._schema.test.ts => request_schema.test.ts} | 35 ++++++++++--------- ...rules_bulk_schema.ts => request_schema.ts} | 11 +++--- .../read_rule/query_rules_type_dependents.ts | 8 ++--- .../detection_engine/rule_management/index.ts | 1 + .../api/rules/bulk_delete_rules/route.ts | 19 +++++----- 5 files changed, 37 insertions(+), 37 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/{query_rules_bulk_schema.test.ts => request_schema.test.ts} (73%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/{query_rules_bulk_schema.ts => request_schema.ts} (54%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.test.ts similarity index 73% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.test.ts index dfbb168f24520..6a709db317109 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.test.ts @@ -5,18 +5,17 @@ * 2.0. */ -import type { QueryRulesBulkSchema } from './query_rules_bulk_schema'; -import { queryRulesBulkSchema } from './query_rules_bulk_schema'; import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; +import { BulkDeleteRulesRequestBody } from './request_schema'; // only the basics of testing are here. // see: query_rules_schema.test.ts for the bulk of the validation tests // this just wraps queryRulesSchema in an array -describe('query_rules_bulk_schema', () => { +describe('Bulk delete rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: QueryRulesBulkSchema = []; + const payload: BulkDeleteRulesRequestBody = []; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -24,13 +23,13 @@ describe('query_rules_bulk_schema', () => { }); test('non uuid being supplied to id does not validate', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { id: '1', }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['Invalid value "1" supplied to "id"']); @@ -38,14 +37,14 @@ describe('query_rules_bulk_schema', () => { }); test('both rule_id and id being supplied do validate', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { rule_id: '1', id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f', }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -53,12 +52,12 @@ describe('query_rules_bulk_schema', () => { }); test('only id validates with two elements', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, { id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -66,9 +65,11 @@ describe('query_rules_bulk_schema', () => { }); test('only rule_id validates', () => { - const payload: QueryRulesBulkSchema = [{ rule_id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }]; + const payload: BulkDeleteRulesRequestBody = [ + { rule_id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, + ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -76,12 +77,12 @@ describe('query_rules_bulk_schema', () => { }); test('only rule_id validates with two elements', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { rule_id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, { rule_id: '2' }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -89,12 +90,12 @@ describe('query_rules_bulk_schema', () => { }); test('both id and rule_id validates with two separate elements', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, { rule_id: '2' }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.ts similarity index 54% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.ts index 5227e3bb55204..2e5817cc70eb9 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.ts @@ -6,11 +6,10 @@ */ import * as t from 'io-ts'; - -import type { QueryRulesSchemaDecoded } from '../read_rule/query_rules_schema'; import { queryRulesSchema } from '../read_rule/query_rules_schema'; -export const queryRulesBulkSchema = t.array(queryRulesSchema); -export type QueryRulesBulkSchema = t.TypeOf; - -export type QueryRulesBulkSchemaDecoded = QueryRulesSchemaDecoded[]; +/** + * Request body parameters of the API route. + */ +export type BulkDeleteRulesRequestBody = t.TypeOf; +export const BulkDeleteRulesRequestBody = t.array(queryRulesSchema); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.ts index a646d0e97f96a..51652f38f5dd3 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.ts @@ -7,6 +7,10 @@ import type { QueryRulesSchema } from './query_rules_schema'; +export const queryRuleValidateTypeDependents = (schema: QueryRulesSchema): string[] => { + return [...validateId(schema)]; +}; + export const validateId = (rule: QueryRulesSchema): string[] => { if (rule.id != null && rule.rule_id != null) { return ['both "id" and "rule_id" cannot exist, choose one or the other']; @@ -16,7 +20,3 @@ export const validateId = (rule: QueryRulesSchema): string[] => { return []; } }; - -export const queryRuleValidateTypeDependents = (schema: QueryRulesSchema): string[] => { - return [...validateId(schema)]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 9d5c70652e4ca..bd2579dcdfe6a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -7,4 +7,5 @@ export * from './api/rules/bulk_actions/request_schema'; export * from './api/rules/bulk_create_rules/request_schema'; +export * from './api/rules/bulk_delete_rules/request_schema'; // export * from './api/urls'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts index c3ba2f149fda0..5b248d31d1a5e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts @@ -5,19 +5,20 @@ * 2.0. */ +import type { RouteConfig, RequestHandler, Logger } from '@kbn/core/server'; import { validate } from '@kbn/securitysolution-io-ts-utils'; -import type { RouteConfig, RequestHandler, Logger } from '@kbn/core/server'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../../../common/constants'; +import { BulkDeleteRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; import { queryRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; -import type { QueryRulesBulkSchemaDecoded } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema'; -import { queryRulesBulkSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_delete_rules/query_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; + +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter, SecuritySolutionRequestHandlerContext, } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../../../common/constants'; + import { getIdBulkError } from '../../../utils/utils'; import { transformValidateBulkError } from '../../../utils/validate'; import { @@ -31,11 +32,11 @@ import { readRules } from '../../../logic/crud/read_rules'; import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; -type Config = RouteConfig; +type Config = RouteConfig; type Handler = RequestHandler< unknown, unknown, - QueryRulesBulkSchemaDecoded, + BulkDeleteRulesRequestBody, SecuritySolutionRequestHandlerContext, 'delete' | 'post' >; @@ -46,9 +47,7 @@ type Handler = RequestHandler< export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { const config: Config = { validate: { - body: buildRouteValidation( - queryRulesBulkSchema - ), + body: buildRouteValidation(BulkDeleteRulesRequestBody), }, path: DETECTION_ENGINE_RULES_BULK_DELETE, options: { From a5e371a93c66a41331c4b665650d1edd9aec98e7 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 14:27:10 +0200 Subject: [PATCH 04/15] TEMP COMMIT. REBASE ME --- ..._schema.test.ts => request_schema.test.ts} | 27 +++++++++---------- ...rules_bulk_schema.ts => request_schema.ts} | 8 +++--- .../detection_engine/rule_management/index.ts | 1 + .../detection_engine/schemas/request/index.ts | 7 +++-- .../api/rules/bulk_patch_rules/route.ts | 10 ++++--- 5 files changed, 28 insertions(+), 25 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/{patch_rules_bulk_schema.test.ts => request_schema.test.ts} (76%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/{patch_rules_bulk_schema.ts => request_schema.ts} (63%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.test.ts similarity index 76% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.test.ts index f32c4b4ef7536..c1f46fe6d0c83 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.test.ts @@ -5,19 +5,18 @@ * 2.0. */ -import type { PatchRulesBulkSchema } from './patch_rules_bulk_schema'; -import { patchRulesBulkSchema } from './patch_rules_bulk_schema'; import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; import type { PatchRulesSchema } from '../patch_rule/patch_rules_schema'; +import { BulkPatchRulesRequestBody } from './request_schema'; // only the basics of testing are here. // see: patch_rules_schema.test.ts for the bulk of the validation tests // this just wraps patchRulesSchema in an array -describe('patch_rules_bulk_schema', () => { +describe('Bulk patch rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: PatchRulesBulkSchema = []; + const payload: BulkPatchRulesRequestBody = []; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(output.errors).toEqual([]); @@ -25,9 +24,9 @@ describe('patch_rules_bulk_schema', () => { }); test('single array of [id] does validate', () => { - const payload: PatchRulesBulkSchema = [{ id: '4125761e-51da-4de9-a0c8-42824f532ddb' }]; + const payload: BulkPatchRulesRequestBody = [{ id: '4125761e-51da-4de9-a0c8-42824f532ddb' }]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -35,12 +34,12 @@ describe('patch_rules_bulk_schema', () => { }); test('two arrays of [id] validate', () => { - const payload: PatchRulesBulkSchema = [ + const payload: BulkPatchRulesRequestBody = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { id: '192f403d-b285-4251-9e8b-785fcfcf22e8' }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -48,12 +47,12 @@ describe('patch_rules_bulk_schema', () => { }); test('can set "note" to be a string', () => { - const payload: PatchRulesBulkSchema = [ + const payload: BulkPatchRulesRequestBody = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { note: 'hi' }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -61,12 +60,12 @@ describe('patch_rules_bulk_schema', () => { }); test('can set "note" to be an empty string', () => { - const payload: PatchRulesBulkSchema = [ + const payload: BulkPatchRulesRequestBody = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { note: '' }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -79,7 +78,7 @@ describe('patch_rules_bulk_schema', () => { { note: { someprop: 'some value here' } }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.ts similarity index 63% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.ts index d43460b18f6fb..9246f730ab5b7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.ts @@ -6,8 +6,10 @@ */ import * as t from 'io-ts'; - import { patchRulesSchema } from '../patch_rule/patch_rules_schema'; -export const patchRulesBulkSchema = t.array(patchRulesSchema); -export type PatchRulesBulkSchema = t.TypeOf; +/** + * Request body parameters of the API route. + */ +export type BulkPatchRulesRequestBody = t.TypeOf; +export const BulkPatchRulesRequestBody = t.array(patchRulesSchema); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index bd2579dcdfe6a..659d43f078831 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -8,4 +8,5 @@ export * from './api/rules/bulk_actions/request_schema'; export * from './api/rules/bulk_create_rules/request_schema'; export * from './api/rules/bulk_delete_rules/request_schema'; +export * from './api/rules/bulk_patch_rules/request_schema'; // export * from './api/urls'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index e4533431aa0fa..85ef86938fcab 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -7,12 +7,11 @@ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports export * from '../../rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema'; -export * from '../../rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema'; -export * from '../../rule_management/api/rules/read_rule/query_rules_schema'; -export * from '../../rule_management/api/rules/patch_rule/patch_rules_schema'; -export * from '../../rule_management/api/rules/find_rules/find_rules_schema'; export * from '../../rule_management/api/rules/export_rules/export_rules_schema'; +export * from '../../rule_management/api/rules/find_rules/find_rules_schema'; export * from '../../rule_management/api/rules/import_rules/import_rules_schema'; +export * from '../../rule_management/api/rules/patch_rule/patch_rules_schema'; +export * from '../../rule_management/api/rules/read_rule/query_rules_schema'; export * from './query_signals_index_schema'; export * from './rule_schemas'; export * from './set_signal_status_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts index 04d54d7252a2f..fb230cd50f6ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts @@ -7,11 +7,13 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { Logger } from '@kbn/core/server'; -import { patchRulesBulkSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_patch_rules/patch_rules_bulk_schema'; -import { buildRouteValidationNonExact } from '../../../../../../utils/build_validation/route_validation'; + +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; +import { BulkPatchRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; import { rulesBulkSchema } from '../../../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; + +import { buildRouteValidationNonExact } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; import type { SetupPlugins } from '../../../../../../plugin'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../../machine_learning/validation'; @@ -36,7 +38,7 @@ export const bulkPatchRulesRoute = ( { path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { - body: buildRouteValidationNonExact(patchRulesBulkSchema), + body: buildRouteValidationNonExact(BulkPatchRulesRequestBody), }, options: { tags: ['access:securitySolution'], From eaa9ddf6974041f4e4c10dbe764759005b3370cc Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 14:34:15 +0200 Subject: [PATCH 05/15] TEMP COMMIT. REBASE ME --- ..._schema.test.ts => request_schema.test.ts} | 66 ++++++++++--------- ...rules_bulk_schema.ts => request_schema.ts} | 7 +- .../detection_engine/rule_management/index.ts | 1 + .../detection_engine/schemas/request/index.ts | 1 - .../api/rules/bulk_update_rules/route.ts | 10 +-- 5 files changed, 46 insertions(+), 39 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/{update_rules_bulk_schema.test.ts => request_schema.test.ts} (79%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/{update_rules_bulk_schema.ts => request_schema.ts} (63%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/request_schema.test.ts similarity index 79% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/request_schema.test.ts index fc70522f07b86..01c220f0b333f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/request_schema.test.ts @@ -5,20 +5,19 @@ * 2.0. */ -import type { UpdateRulesBulkSchema } from './update_rules_bulk_schema'; -import { updateRulesBulkSchema } from './update_rules_bulk_schema'; import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; -import { getUpdateRulesSchemaMock } from '../../../../schemas/request/rule_schemas.mock'; import type { UpdateRulesSchema } from '../../../../schemas/request/rule_schemas'; +import { getUpdateRulesSchemaMock } from '../../../../schemas/request/rule_schemas.mock'; +import { BulkUpdateRulesRequestBody } from './request_schema'; // only the basics of testing are here. // see: update_rules_schema.test.ts for the bulk of the validation tests // this just wraps updateRulesSchema in an array -describe('update_rules_bulk_schema', () => { +describe('Bulk update rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: UpdateRulesBulkSchema = []; + const payload: BulkUpdateRulesRequestBody = []; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(output.errors).toEqual([]); @@ -28,7 +27,7 @@ describe('update_rules_bulk_schema', () => { test('made up values do not validate for a single element', () => { const payload: Array<{ madeUp: string }> = [{ madeUp: 'hi' }]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toContain( @@ -45,9 +44,9 @@ describe('update_rules_bulk_schema', () => { }); test('single array element does validate', () => { - const payload: UpdateRulesBulkSchema = [getUpdateRulesSchemaMock()]; + const payload: BulkUpdateRulesRequestBody = [getUpdateRulesSchemaMock()]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -55,9 +54,12 @@ describe('update_rules_bulk_schema', () => { }); test('two array elements do validate', () => { - const payload: UpdateRulesBulkSchema = [getUpdateRulesSchemaMock(), getUpdateRulesSchemaMock()]; + const payload: BulkUpdateRulesRequestBody = [ + getUpdateRulesSchemaMock(), + getUpdateRulesSchemaMock(), + ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -68,9 +70,9 @@ describe('update_rules_bulk_schema', () => { const singleItem = getUpdateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -84,9 +86,9 @@ describe('update_rules_bulk_schema', () => { const secondItem = getUpdateRulesSchemaMock(); // @ts-expect-error delete secondItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -100,9 +102,9 @@ describe('update_rules_bulk_schema', () => { const secondItem = getUpdateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -118,9 +120,9 @@ describe('update_rules_bulk_schema', () => { delete singleItem.risk_score; // @ts-expect-error delete secondItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -137,7 +139,7 @@ describe('update_rules_bulk_schema', () => { const secondItem = getUpdateRulesSchemaMock(); const payload = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -150,9 +152,9 @@ describe('update_rules_bulk_schema', () => { ...getUpdateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -168,9 +170,9 @@ describe('update_rules_bulk_schema', () => { ...getUpdateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue,madeUpValue"']); @@ -181,7 +183,7 @@ describe('update_rules_bulk_schema', () => { const badSeverity = { ...getUpdateRulesSchemaMock(), severity: 'madeup' }; const payload = [badSeverity]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['Invalid value "madeup" supplied to "severity"']); @@ -189,11 +191,11 @@ describe('update_rules_bulk_schema', () => { }); test('You can set "namespace" to a string', () => { - const payload: UpdateRulesBulkSchema = [ + const payload: BulkUpdateRulesRequestBody = [ { ...getUpdateRulesSchemaMock(), namespace: 'a namespace' }, ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -201,11 +203,11 @@ describe('update_rules_bulk_schema', () => { }); test('You can set "note" to a string', () => { - const payload: UpdateRulesBulkSchema = [ + const payload: BulkUpdateRulesRequestBody = [ { ...getUpdateRulesSchemaMock(), note: '# test markdown' }, ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -213,9 +215,9 @@ describe('update_rules_bulk_schema', () => { }); test('You can set "note" to an empty string', () => { - const payload: UpdateRulesBulkSchema = [{ ...getUpdateRulesSchemaMock(), note: '' }]; + const payload: BulkUpdateRulesRequestBody = [{ ...getUpdateRulesSchemaMock(), note: '' }]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -232,7 +234,7 @@ describe('update_rules_bulk_schema', () => { }, ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/request_schema.ts similarity index 63% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/request_schema.ts index 207f2aa91102f..9d0692039e8ac 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_update_rules/request_schema.ts @@ -8,5 +8,8 @@ import * as t from 'io-ts'; import { updateRulesSchema } from '../../../../schemas/request/rule_schemas'; -export const updateRulesBulkSchema = t.array(updateRulesSchema); -export type UpdateRulesBulkSchema = t.TypeOf; +/** + * Request body parameters of the API route. + */ +export type BulkUpdateRulesRequestBody = t.TypeOf; +export const BulkUpdateRulesRequestBody = t.array(updateRulesSchema); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 659d43f078831..54c26d09c6989 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -9,4 +9,5 @@ export * from './api/rules/bulk_actions/request_schema'; export * from './api/rules/bulk_create_rules/request_schema'; export * from './api/rules/bulk_delete_rules/request_schema'; export * from './api/rules/bulk_patch_rules/request_schema'; +export * from './api/rules/bulk_update_rules/request_schema'; // export * from './api/urls'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index 85ef86938fcab..4808785adf509 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -6,7 +6,6 @@ */ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports -export * from '../../rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema'; export * from '../../rule_management/api/rules/export_rules/export_rules_schema'; export * from '../../rule_management/api/rules/find_rules/find_rules_schema'; export * from '../../rule_management/api/rules/import_rules/import_rules_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index b833e2ee7c116..39f25c2430c74 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -5,12 +5,14 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { Logger } from '@kbn/core/server'; +import { validate } from '@kbn/securitysolution-io-ts-utils'; + +import { BulkUpdateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; import { updateRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; -import { updateRulesBulkSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_update_rules/update_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; + +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; import type { SetupPlugins } from '../../../../../../plugin'; @@ -41,7 +43,7 @@ export const bulkUpdateRulesRoute = ( { path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { - body: buildRouteValidation(updateRulesBulkSchema), + body: buildRouteValidation(BulkUpdateRulesRequestBody), }, options: { tags: ['access:securitySolution'], From c6e1cdace23f1d92751ee035ac3cf81116f7d7f2 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 14:55:40 +0200 Subject: [PATCH 06/15] TEMP COMMIT. REBASE ME --- .../create_rules_type_dependents.ts | 26 ++++---- ..._schema.test.ts => request_schema.test.ts} | 63 +++++++++---------- ...port_rules_schema.ts => request_schema.ts} | 30 +++++---- .../detection_engine/rule_management/index.ts | 1 + .../detection_engine/schemas/request/index.ts | 1 - .../api/rules/export_rules/route.ts | 23 +++---- 6 files changed, 73 insertions(+), 71 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/{export_rules_schema.test.ts => request_schema.test.ts} (72%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/{export_rules_schema.ts => request_schema.ts} (58%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.ts index 79565b55b5805..ee99071c3af7a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.ts @@ -7,7 +7,16 @@ import type { CreateRulesSchema } from '../../../../schemas/request/rule_schemas'; -export const validateTimelineId = (rule: CreateRulesSchema): string[] => { +export const createRuleValidateTypeDependents = (rule: CreateRulesSchema): string[] => { + return [ + ...validateTimelineId(rule), + ...validateTimelineTitle(rule), + ...validateThreatMapping(rule), + ...validateThreshold(rule), + ]; +}; + +const validateTimelineId = (rule: CreateRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +29,7 @@ export const validateTimelineId = (rule: CreateRulesSchema): string[] => { return []; }; -export const validateTimelineTitle = (rule: CreateRulesSchema): string[] => { +const validateTimelineTitle = (rule: CreateRulesSchema): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,7 +42,7 @@ export const validateTimelineTitle = (rule: CreateRulesSchema): string[] => { return []; }; -export const validateThreatMapping = (rule: CreateRulesSchema): string[] => { +const validateThreatMapping = (rule: CreateRulesSchema): string[] => { const errors: string[] = []; if (rule.type === 'threat_match') { if (rule.concurrent_searches != null && rule.items_per_search == null) { @@ -46,7 +55,7 @@ export const validateThreatMapping = (rule: CreateRulesSchema): string[] => { return errors; }; -export const validateThreshold = (rule: CreateRulesSchema): string[] => { +const validateThreshold = (rule: CreateRulesSchema): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if (!rule.threshold) { @@ -65,12 +74,3 @@ export const validateThreshold = (rule: CreateRulesSchema): string[] => { } return errors; }; - -export const createRuleValidateTypeDependents = (rule: CreateRulesSchema): string[] => { - return [ - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreatMapping(rule), - ...validateThreshold(rule), - ]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.test.ts similarity index 72% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.test.ts index d2b29a5152e96..0e0ec3cbaca3b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.test.ts @@ -5,22 +5,19 @@ * 2.0. */ -import type { - ExportRulesSchema, - ExportRulesQuerySchema, - ExportRulesQuerySchemaDecoded, -} from './export_rules_schema'; -import { exportRulesQuerySchema, exportRulesSchema } from './export_rules_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { ExportRulesRequestBody, ExportRulesRequestQuery } from './request_schema'; +import type { ExportRulesRequestQueryDecoded } from './request_schema'; -describe('create rules schema', () => { - describe('exportRulesSchema', () => { +describe('Export rules request schema', () => { + describe('ExportRulesRequestBody', () => { test('null value or absent values validate', () => { - const payload: Partial = null; + const payload: Partial = null; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -30,7 +27,7 @@ describe('create rules schema', () => { test('empty object does not validate', () => { const payload = {}; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -41,9 +38,9 @@ describe('create rules schema', () => { }); test('empty object array does validate', () => { - const payload: ExportRulesSchema = { objects: [] }; + const payload: ExportRulesRequestBody = { objects: [] }; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -51,9 +48,9 @@ describe('create rules schema', () => { }); test('array with rule_id validates', () => { - const payload: ExportRulesSchema = { objects: [{ rule_id: 'test-1' }] }; + const payload: ExportRulesRequestBody = { objects: [{ rule_id: 'test-1' }] }; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -61,11 +58,11 @@ describe('create rules schema', () => { }); test('array with id does not validate as we do not allow that on purpose since we export rule_id', () => { - const payload: Omit & { objects: [{ id: string }] } = { + const payload: Omit & { objects: [{ id: string }] } = { objects: [{ id: '4a7ff83d-3055-4bb2-ba68-587b9c6c15a4' }], }; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -76,15 +73,15 @@ describe('create rules schema', () => { }); }); - describe('exportRulesQuerySchema', () => { + describe('ExportRulesRequestQuery', () => { test('default value for file_name is export.ndjson and default for exclude_export_details is false', () => { - const payload: Partial = {}; + const payload: Partial = {}; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ExportRulesQuerySchemaDecoded = { + const expected: ExportRulesRequestQueryDecoded = { file_name: 'export.ndjson', exclude_export_details: false, }; @@ -93,15 +90,15 @@ describe('create rules schema', () => { }); test('file_name validates', () => { - const payload: ExportRulesQuerySchema = { + const payload: ExportRulesRequestQuery = { file_name: 'test.ndjson', }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ExportRulesQuerySchemaDecoded = { + const expected: ExportRulesRequestQueryDecoded = { file_name: 'test.ndjson', exclude_export_details: false, }; @@ -110,11 +107,11 @@ describe('create rules schema', () => { }); test('file_name does not validate with a number', () => { - const payload: Omit & { file_name: number } = { + const payload: Omit & { file_name: number } = { file_name: 10, }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -124,15 +121,15 @@ describe('create rules schema', () => { }); test('exclude_export_details validates with a boolean true', () => { - const payload: ExportRulesQuerySchema = { + const payload: ExportRulesRequestQuery = { exclude_export_details: true, }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ExportRulesQuerySchemaDecoded = { + const expected: ExportRulesRequestQueryDecoded = { exclude_export_details: true, file_name: 'export.ndjson', }; @@ -140,13 +137,13 @@ describe('create rules schema', () => { }); test('exclude_export_details does not validate with a string', () => { - const payload: Omit & { + const payload: Omit & { exclude_export_details: string; } = { exclude_export_details: 'invalid string', }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.ts similarity index 58% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.ts index 6a9964373140f..682e69e55d5a4 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.ts @@ -6,25 +6,33 @@ */ import * as t from 'io-ts'; - import { DefaultExportFileName } from '@kbn/securitysolution-io-ts-alerting-types'; import { DefaultStringBooleanFalse } from '@kbn/securitysolution-io-ts-types'; -import { RuleSignatureId } from '../../../../rule_schema'; + import type { FileName, ExcludeExportDetails } from '../../../../schemas/common/schemas'; +import { RuleSignatureId } from '../../../../rule_schema'; -const objects = t.array(t.exact(t.type({ rule_id: RuleSignatureId }))); -export const exportRulesSchema = t.union([t.exact(t.type({ objects })), t.null]); -export type ExportRulesSchema = t.TypeOf; -export type ExportRulesSchemaDecoded = ExportRulesSchema; +const ObjectsWithRuleId = t.array(t.exact(t.type({ rule_id: RuleSignatureId }))); -export const exportRulesQuerySchema = t.exact( +/** + * Request body parameters of the API route. + */ +export type ExportRulesRequestBody = t.TypeOf; +export const ExportRulesRequestBody = t.union([ + t.exact(t.type({ objects: ObjectsWithRuleId })), + t.null, +]); + +/** + * Query string parameters of the API route. + */ +export type ExportRulesRequestQuery = t.TypeOf; +export const ExportRulesRequestQuery = t.exact( t.partial({ file_name: DefaultExportFileName, exclude_export_details: DefaultStringBooleanFalse }) ); -export type ExportRulesQuerySchema = t.TypeOf; - -export type ExportRulesQuerySchemaDecoded = Omit< - ExportRulesQuerySchema, +export type ExportRulesRequestQueryDecoded = Omit< + ExportRulesRequestQuery, 'file_name' | 'exclude_export_details' > & { file_name: FileName; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 54c26d09c6989..94127dd1610ae 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -10,4 +10,5 @@ export * from './api/rules/bulk_create_rules/request_schema'; export * from './api/rules/bulk_delete_rules/request_schema'; export * from './api/rules/bulk_patch_rules/request_schema'; export * from './api/rules/bulk_update_rules/request_schema'; +export * from './api/rules/export_rules/request_schema'; // export * from './api/urls'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index 4808785adf509..0693bc90d786d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -6,7 +6,6 @@ */ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports -export * from '../../rule_management/api/rules/export_rules/export_rules_schema'; export * from '../../rule_management/api/rules/find_rules/find_rules_schema'; export * from '../../rule_management/api/rules/import_rules/import_rules_schema'; export * from '../../rule_management/api/rules/patch_rule/patch_rules_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts index 38105d8748914..ec96f38f5f86e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts @@ -7,17 +7,16 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { Logger } from '@kbn/core/server'; -import type { - ExportRulesQuerySchemaDecoded, - ExportRulesSchemaDecoded, -} from '../../../../../../../common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import type { ExportRulesRequestQueryDecoded } from '../../../../../../../common/detection_engine/rule_management'; import { - exportRulesQuerySchema, - exportRulesSchema, -} from '../../../../../../../common/detection_engine/rule_management/api/rules/export_rules/export_rules_schema'; + ExportRulesRequestBody, + ExportRulesRequestQuery, +} from '../../../../../../../common/detection_engine/rule_management'; + import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { ConfigType } from '../../../../../../config'; import { getNonPackagedRulesCount } from '../../../logic/search/get_existing_prepackaged_rules'; import { getExportByObjectIds } from '../../../logic/export/get_export_by_object_ids'; @@ -33,12 +32,10 @@ export const exportRulesRoute = ( { path: `${DETECTION_ENGINE_RULES_URL}/_export`, validate: { - query: buildRouteValidation( - exportRulesQuerySchema - ), - body: buildRouteValidation( - exportRulesSchema + query: buildRouteValidation( + ExportRulesRequestQuery ), + body: buildRouteValidation(ExportRulesRequestBody), }, options: { tags: ['access:securitySolution'], From 889fb1afeb71aef01b45e224fdc3d1371e871038 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 15:14:02 +0200 Subject: [PATCH 07/15] TEMP COMMIT. REBASE ME --- x-pack/plugins/security_solution/common/constants.ts | 1 + .../common/detection_engine/rule_management/index.ts | 2 ++ .../common/detection_engine/rule_management/mocks.ts | 1 + .../model/export}/export_rules_details_schema.mock.ts | 4 ++-- .../model/export}/export_rules_details_schema.test.ts | 0 .../model/export}/export_rules_details_schema.ts | 2 +- .../rules_table/bulk_actions/utils/dry_run_result.ts | 2 +- .../rule_management_ui/components/rules_table/helpers.ts | 2 +- .../rule_management/logic/export/get_export_all.test.ts | 2 +- .../logic/export/get_export_by_object_ids.test.ts | 2 +- .../rule_management/logic/export/get_export_details_ndjson.ts | 3 +-- .../logic/import/create_rules_stream_from_ndjson.test.ts | 2 +- 12 files changed, 13 insertions(+), 10 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/{schemas/response => rule_management/model/export}/export_rules_details_schema.mock.ts (100%) rename x-pack/plugins/security_solution/common/detection_engine/{schemas/response => rule_management/model/export}/export_rules_details_schema.test.ts (100%) rename x-pack/plugins/security_solution/common/detection_engine/{schemas/response => rule_management/model/export}/export_rules_details_schema.ts (96%) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 6f4f8318bcb49..65c5757a1b1bb 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -448,6 +448,7 @@ export const NEW_FEATURES_TOUR_STORAGE_KEYS = { export const RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY = 'securitySolution.ruleDetails.ruleExecutionLog.showMetrics.v8.2'; +// TODO: https://github.com/elastic/kibana/pull/142950 /** * Error codes that can be thrown during _bulk_action API dry_run call and be processed and displayed to end user */ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 94127dd1610ae..2e26f39060d98 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -12,3 +12,5 @@ export * from './api/rules/bulk_patch_rules/request_schema'; export * from './api/rules/bulk_update_rules/request_schema'; export * from './api/rules/export_rules/request_schema'; // export * from './api/urls'; + +export * from './model/export/export_rules_details_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts index 7b5a510eee12d..c46948bb10ff7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts @@ -6,3 +6,4 @@ */ export * from './api/rules/bulk_actions/request_schema.mock'; +export * from './model/export/export_rules_details_schema.mock'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.mock.ts index 9b79238bef6c7..64b82abdb3755 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { ExportRulesDetails } from './export_rules_details_schema'; -import type { ExportExceptionDetailsMock } from '@kbn/lists-plugin/common/schemas/response/exception_export_details_schema.mock'; import { getExceptionExportDetailsMock } from '@kbn/lists-plugin/common/schemas/response/exception_export_details_schema.mock'; +import type { ExportExceptionDetailsMock } from '@kbn/lists-plugin/common/schemas/response/exception_export_details_schema.mock'; +import type { ExportRulesDetails } from './export_rules_details_schema'; interface RuleDetailsMock { totalCount?: number; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.test.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.test.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.ts similarity index 96% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.ts index 05df728aa3f5c..85b423135566b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.ts @@ -16,7 +16,7 @@ const createSchema = ( return t.intersection([t.exact(t.type(requiredFields)), t.exact(t.partial(optionalFields))]); }; -export const exportRulesDetails = { +const exportRulesDetails = { exported_count: t.number, exported_rules_count: t.number, missing_rules: t.array( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/dry_run_result.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/dry_run_result.ts index ef7043f9c145f..627b7bf54cfd3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/dry_run_result.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/dry_run_result.ts @@ -6,7 +6,7 @@ */ import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; -import type { ExportRulesDetails } from '../../../../../../../common/detection_engine/schemas/response/export_rules_details_schema'; +import type { ExportRulesDetails } from '../../../../../../../common/detection_engine/rule_management'; import type { BulkActionResponse } from '../../../../../rule_management/logic'; import type { DryRunResult } from '../types'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.ts index af63a7ed89946..13666f514b0be 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.ts @@ -6,7 +6,7 @@ */ import type { Query } from '@elastic/eui'; -import type { ExportRulesDetails } from '../../../../../common/detection_engine/schemas/response/export_rules_details_schema'; +import type { ExportRulesDetails } from '../../../../../common/detection_engine/rule_management'; import type { BulkActionSummary } from '../../../rule_management/logic'; export const caseInsensitiveSort = (tags: string[]): string[] => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts index bde7438babe9d..b7395236a2152 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts @@ -18,7 +18,7 @@ import { getThreatMock } from '../../../../../../common/detection_engine/schemas import { getOutputDetailsSampleWithExceptions, getSampleDetailsAsNdjson, -} from '../../../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; +} from '../../../../../../common/detection_engine/rule_management/mocks'; import { getQueryRuleParams } from '../../../rule_schema/mocks'; import { getExceptionListClientMock } from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client.mock'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts index 0a4b38f50699d..0242f17509a99 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts @@ -19,7 +19,7 @@ import { getThreatMock } from '../../../../../../common/detection_engine/schemas import { getSampleDetailsAsNdjson, getOutputDetailsSampleWithExceptions, -} from '../../../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; +} from '../../../../../../common/detection_engine/rule_management/mocks'; import { getQueryRuleParams } from '../../../rule_schema/mocks'; import { getExceptionListClientMock } from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client.mock'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.ts index cbe6062b02671..a5f4e990df821 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.ts @@ -6,10 +6,9 @@ */ import type { ExportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types'; +import type { ExportRulesDetails } from '../../../../../../common/detection_engine/rule_management'; import type { FullResponseSchema } from '../../../../../../common/detection_engine/schemas/request'; -import type { ExportRulesDetails } from '../../../../../../common/detection_engine/schemas/response/export_rules_details_schema'; - export const getExportDetailsNdjson = ( rules: FullResponseSchema[], missingRules: Array<{ rule_id: string }> = [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts index 4e98e5c4aaa94..4ec49b846ae6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts @@ -13,7 +13,7 @@ import type { ImportRulesSchema } from '../../../../../../common/detection_engin import { getOutputDetailsSample, getSampleDetailsAsNdjson, -} from '../../../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; +} from '../../../../../../common/detection_engine/rule_management/mocks'; import type { RuleExceptionsPromiseFromStreams } from './import_rules_utils'; export const getOutputSample = (): Partial => ({ From 93056d228c41d2a517eae5da45c7570927ef5a18 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 15:45:44 +0200 Subject: [PATCH 08/15] TEMP COMMIT. REBASE ME --- .../find_rule_type_dependents.test.ts | 46 ---------- .../find_rules/find_rules_type_dependents.ts | 24 ----- ..._schema.test.ts => request_schema.test.ts} | 92 ++++++++++--------- ...find_rules_schema.ts => request_schema.ts} | 10 +- .../request_schema_validation.test.ts | 48 ++++++++++ .../find_rules/request_schema_validation.ts | 27 ++++++ .../detection_engine/rule_management/index.ts | 2 + .../detection_engine/schemas/request/index.ts | 1 - .../api/rules/find_rules/route.ts | 20 ++-- 9 files changed, 142 insertions(+), 128 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rule_type_dependents.test.ts delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_type_dependents.ts rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/{find_rules_schema.test.ts => request_schema.test.ts} (63%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/{find_rules_schema.ts => request_schema.ts} (74%) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts create mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rule_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rule_type_dependents.test.ts deleted file mode 100644 index 50afe7f970acb..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rule_type_dependents.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 type { FindRulesSchema } from './find_rules_schema'; -import { findRuleValidateTypeDependents } from './find_rules_type_dependents'; - -describe('find_rules_type_dependents', () => { - test('You can have an empty sort_field and empty sort_order', () => { - const schema: FindRulesSchema = {}; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - - test('You can have both a sort_field and and a sort_order', () => { - const schema: FindRulesSchema = { - sort_field: 'some field', - sort_order: 'asc', - }; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - - test('You cannot have sort_field without sort_order', () => { - const schema: FindRulesSchema = { - sort_field: 'some field', - }; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([ - 'when "sort_order" and "sort_field" must exist together or not at all', - ]); - }); - - test('You cannot have sort_order without sort_field', () => { - const schema: FindRulesSchema = { - sort_order: 'asc', - }; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([ - 'when "sort_order" and "sort_field" must exist together or not at all', - ]); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_type_dependents.ts deleted file mode 100644 index f9bd6dc56f104..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_type_dependents.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 type { FindRulesSchema } from './find_rules_schema'; - -export const validateSortOrder = (find: FindRulesSchema): string[] => { - if (find.sort_order != null || find.sort_field != null) { - if (find.sort_order == null || find.sort_field == null) { - return ['when "sort_order" and "sort_field" must exist together or not at all']; - } else { - return []; - } - } else { - return []; - } -}; - -export const findRuleValidateTypeDependents = (schema: FindRulesSchema): string[] => { - return [...validateSortOrder(schema)]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts similarity index 63% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts index 41e765c8c268b..67a3d045d746d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts @@ -5,17 +5,17 @@ * 2.0. */ -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import type { FindRulesSchema } from './find_rules_schema'; -import { findRulesSchema } from './find_rules_schema'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { FindRulesRequestQuery } from './request_schema'; -describe('find_rules_schema', () => { +describe('Find rules request schema', () => { test('empty objects do validate', () => { - const payload: FindRulesSchema = {}; + const payload: FindRulesRequestQuery = {}; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -26,7 +26,7 @@ describe('find_rules_schema', () => { }); test('all values validate', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { per_page: 5, page: 1, sort_field: 'some field', @@ -35,7 +35,7 @@ describe('find_rules_schema', () => { sort_order: 'asc', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -43,9 +43,11 @@ describe('find_rules_schema', () => { }); test('made up parameters do not validate', () => { - const payload: Partial & { madeUp: string } = { madeUp: 'invalid value' }; + const payload: Partial & { madeUp: string } = { + madeUp: 'invalid value', + }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); @@ -53,71 +55,71 @@ describe('find_rules_schema', () => { }); test('per_page validates', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { per_page: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).per_page).toEqual(payload.per_page); + expect((message.schema as FindRulesRequestQuery).per_page).toEqual(payload.per_page); }); test('page validates', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { page: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).page).toEqual(payload.page); + expect((message.schema as FindRulesRequestQuery).page).toEqual(payload.page); }); test('sort_field validates', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { sort_field: 'value', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).sort_field).toEqual('value'); + expect((message.schema as FindRulesRequestQuery).sort_field).toEqual('value'); }); test('fields validates with a string', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { fields: ['some value'], }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).fields).toEqual(payload.fields); + expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); }); test('fields validates with multiple strings', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { fields: ['some value 1', 'some value 2'], }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).fields).toEqual(payload.fields); + expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); }); test('fields does not validate with a number', () => { - const payload: Omit & { fields: number } = { + const payload: Omit & { fields: number } = { fields: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "fields"']); @@ -125,43 +127,43 @@ describe('find_rules_schema', () => { }); test('per_page has a default of 20', () => { - const payload: FindRulesSchema = {}; + const payload: FindRulesRequestQuery = {}; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).per_page).toEqual(20); + expect((message.schema as FindRulesRequestQuery).per_page).toEqual(20); }); test('page has a default of 1', () => { - const payload: FindRulesSchema = {}; + const payload: FindRulesRequestQuery = {}; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).page).toEqual(1); + expect((message.schema as FindRulesRequestQuery).page).toEqual(1); }); test('filter works with a string', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { filter: 'some value 1', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).filter).toEqual(payload.filter); + expect((message.schema as FindRulesRequestQuery).filter).toEqual(payload.filter); }); test('filter does not work with a number', () => { - const payload: Omit & { filter: number } = { + const payload: Omit & { filter: number } = { filter: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "filter"']); @@ -169,26 +171,26 @@ describe('find_rules_schema', () => { }); test('sort_order validates with desc and sort_field', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { sort_order: 'desc', sort_field: 'some field', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).sort_order).toEqual(payload.sort_order); - expect((message.schema as FindRulesSchema).sort_field).toEqual(payload.sort_field); + expect((message.schema as FindRulesRequestQuery).sort_order).toEqual(payload.sort_order); + expect((message.schema as FindRulesRequestQuery).sort_field).toEqual(payload.sort_field); }); test('sort_order does not validate with a string other than asc and desc', () => { - const payload: Omit & { sort_order: string } = { + const payload: Omit & { sort_order: string } = { sort_order: 'some other string', sort_field: 'some field', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts similarity index 74% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts index 98a8d095a2465..9b321d443b2de 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts @@ -6,12 +6,15 @@ */ import * as t from 'io-ts'; - import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; import type { PerPage, Page } from '../../../../schemas/common'; import { queryFilter, fields, SortField, SortOrder } from '../../../../schemas/common'; -export const findRulesSchema = t.exact( +/** + * Query string parameters of the API route. + */ +export type FindRulesRequestQuery = t.TypeOf; +export const FindRulesRequestQuery = t.exact( t.partial({ fields, filter: queryFilter, @@ -22,8 +25,7 @@ export const findRulesSchema = t.exact( }) ); -export type FindRulesSchema = t.TypeOf; -export type FindRulesSchemaDecoded = Omit & { +export type FindRulesRequestQueryDecoded = Omit & { page: Page; per_page: PerPage; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts new file mode 100644 index 0000000000000..862bf7cc1a350 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts @@ -0,0 +1,48 @@ +/* + * 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 type { FindRulesRequestQuery } from './request_schema'; +import { validateFindRulesRequestQuery } from './request_schema_validation'; + +describe('Find rules request schema, additional validation', () => { + describe('validateFindRulesRequestQuery', () => { + test('You can have an empty sort_field and empty sort_order', () => { + const schema: FindRulesRequestQuery = {}; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([]); + }); + + test('You can have both a sort_field and and a sort_order', () => { + const schema: FindRulesRequestQuery = { + sort_field: 'some field', + sort_order: 'asc', + }; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([]); + }); + + test('You cannot have sort_field without sort_order', () => { + const schema: FindRulesRequestQuery = { + sort_field: 'some field', + }; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([ + 'when "sort_order" and "sort_field" must exist together or not at all', + ]); + }); + + test('You cannot have sort_order without sort_field', () => { + const schema: FindRulesRequestQuery = { + sort_order: 'asc', + }; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([ + 'when "sort_order" and "sort_field" must exist together or not at all', + ]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.ts new file mode 100644 index 0000000000000..a8ceb09dea5b4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.ts @@ -0,0 +1,27 @@ +/* + * 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 type { FindRulesRequestQuery } from './request_schema'; + +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateFindRulesRequestQuery = (query: FindRulesRequestQuery): string[] => { + return [...validateSortOrder(query)]; +}; + +const validateSortOrder = (query: FindRulesRequestQuery): string[] => { + if (query.sort_order != null || query.sort_field != null) { + if (query.sort_order == null || query.sort_field == null) { + return ['when "sort_order" and "sort_field" must exist together or not at all']; + } else { + return []; + } + } else { + return []; + } +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 2e26f39060d98..676e769579736 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -11,6 +11,8 @@ export * from './api/rules/bulk_delete_rules/request_schema'; export * from './api/rules/bulk_patch_rules/request_schema'; export * from './api/rules/bulk_update_rules/request_schema'; export * from './api/rules/export_rules/request_schema'; +export * from './api/rules/find_rules/request_schema_validation'; +export * from './api/rules/find_rules/request_schema'; // export * from './api/urls'; export * from './model/export/export_rules_details_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index 0693bc90d786d..f91b26f10fcc7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -6,7 +6,6 @@ */ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports -export * from '../../rule_management/api/rules/find_rules/find_rules_schema'; export * from '../../rule_management/api/rules/import_rules/import_rules_schema'; export * from '../../rule_management/api/rules/patch_rule/patch_rules_schema'; export * from '../../rule_management/api/rules/read_rule/query_rules_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts index a4cb3b1f8d415..34117ca07f817 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts @@ -5,13 +5,17 @@ * 2.0. */ -import { transformError } from '@kbn/securitysolution-es-utils'; import type { Logger } from '@kbn/core/server'; -import { findRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/find_rules/find_rules_type_dependents'; -import type { FindRulesSchemaDecoded } from '../../../../../../../common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema'; -import { findRulesSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/find_rules/find_rules_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { transformError } from '@kbn/securitysolution-es-utils'; + import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../../../common/constants'; +import type { FindRulesRequestQueryDecoded } from '../../../../../../../common/detection_engine/rule_management'; +import { + FindRulesRequestQuery, + validateFindRulesRequestQuery, +} from '../../../../../../../common/detection_engine/rule_management'; + +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { findRules } from '../../../logic/search/find_rules'; import { buildSiemResponse } from '../../../../routes/utils'; import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; @@ -25,8 +29,8 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log { path: DETECTION_ENGINE_RULES_URL_FIND, validate: { - query: buildRouteValidation( - findRulesSchema + query: buildRouteValidation( + FindRulesRequestQuery ), }, options: { @@ -36,7 +40,7 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = findRuleValidateTypeDependents(request.query); + const validationErrors = validateFindRulesRequestQuery(request.query); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } From 38161062a88bbfbbcd446e2354f833e39c68d6a5 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 16:32:01 +0200 Subject: [PATCH 09/15] TEMP COMMIT. REBASE ME --- ..._schema.mock.ts => request_schema.mock.ts} | 10 +- ..._schema.test.ts => request_schema.test.ts} | 319 +++++++++--------- ...port_rules_schema.ts => request_schema.ts} | 10 +- ...t.ts => rule_to_import_validation.test.ts} | 14 +- ...ndents.ts => rule_to_import_validation.ts} | 10 +- .../detection_engine/rule_management/index.ts | 2 + .../detection_engine/rule_management/mocks.ts | 2 + .../detection_engine/schemas/request/index.ts | 2 +- .../api/rules/import_rules/route.test.ts | 2 +- .../api/rules/import_rules/route.ts | 4 +- .../check_rule_exception_references.test.ts | 4 +- .../import/check_rule_exception_references.ts | 4 +- .../create_rules_stream_from_ndjson.test.ts | 6 +- .../import/create_rules_stream_from_ndjson.ts | 33 +- .../gather_referenced_exceptions.test.ts | 3 +- .../import/gather_referenced_exceptions.ts | 4 +- .../logic/import/import_rules_utils.test.ts | 2 +- .../logic/import/import_rules_utils.ts | 5 +- .../rule_management/utils/utils.test.ts | 13 +- .../rule_management/utils/utils.ts | 6 +- .../read_stream/create_stream_from_ndjson.ts | 4 +- 21 files changed, 234 insertions(+), 225 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/{import_rules_schema.mock.ts => request_schema.mock.ts} (88%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/{import_rules_schema.test.ts => request_schema.test.ts} (81%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/{import_rules_schema.ts => request_schema.ts} (87%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/{import_rules_type_dependents.test.ts => rule_to_import_validation.test.ts} (81%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/{import_rules_type_dependents.ts => rule_to_import_validation.ts} (79%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.mock.ts similarity index 88% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.mock.ts index c469926104131..1deac1e845b6d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { ImportRulesSchema } from './import_rules_schema'; +import type { RuleToImport } from './request_schema'; -export const getImportRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ +export const getImportRulesSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -18,7 +18,7 @@ export const getImportRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema = rule_id: ruleId, }); -export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ +export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ id: '6afb8ce1-ea94-4790-8653-fd0b021d2113', description: 'some description', name: 'Query with a rule id', @@ -35,7 +35,7 @@ export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSc * as we might import/export * @param rules Array of rule objects with which to generate rule JSON */ -export const rulesToNdJsonString = (rules: ImportRulesSchema[]) => { +export const rulesToNdJsonString = (rules: RuleToImport[]) => { return rules.map((rule) => JSON.stringify(rule)).join('\r\n'); }; @@ -49,7 +49,7 @@ export const ruleIdsToNdJsonString = (ruleIds: string[]) => { return rulesToNdJsonString(rules); }; -export const getImportThreatMatchRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ +export const getImportThreatMatchRulesSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts similarity index 81% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts index 05e15b068ac99..68523d52bcf9f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts @@ -5,22 +5,23 @@ * 2.0. */ -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import type { ImportRulesPayloadSchema, ImportRulesSchema } from './import_rules_schema'; -import { importRulesPayloadSchema, importRulesSchema } from './import_rules_schema'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import type { ImportRulesPayloadSchema, RuleToImport } from './request_schema'; +import { ImportRulesPayloadSchema, RuleToImport } from './request_schema'; import { getImportRulesSchemaMock, getImportThreatMatchRulesSchemaMock, -} from './import_rules_schema.mock'; +} from './request_schema.mock'; import { getListArrayMock } from '../../../../schemas/types/lists.mock'; describe('import rules schema', () => { test('empty objects do not validate', () => { - const payload: Partial = {}; + const payload: Partial = {}; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -42,12 +43,12 @@ describe('import rules schema', () => { }); test('made up values do not validate', () => { - const payload: ImportRulesSchema & { madeUp: string } = { + const payload: RuleToImport & { madeUp: string } = { ...getImportRulesSchemaMock(), madeUp: 'hi', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); @@ -55,11 +56,11 @@ describe('import rules schema', () => { }); test('[rule_id] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -78,12 +79,12 @@ describe('import rules schema', () => { }); test('[rule_id, description] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -99,13 +100,13 @@ describe('import rules schema', () => { }); test('[rule_id, description, from] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -121,14 +122,14 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -144,7 +145,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -152,7 +153,7 @@ describe('import rules schema', () => { name: 'some-name', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -165,7 +166,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -174,7 +175,7 @@ describe('import rules schema', () => { severity: 'low', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -184,7 +185,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -194,7 +195,7 @@ describe('import rules schema', () => { type: 'query', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -204,7 +205,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -215,7 +216,7 @@ describe('import rules schema', () => { type: 'query', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -225,7 +226,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -237,7 +238,7 @@ describe('import rules schema', () => { index: ['index-1'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -247,7 +248,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -261,14 +262,14 @@ describe('import rules schema', () => { interval: '5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - const payload: Partial = { + const payload: Partial = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -282,7 +283,7 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -292,7 +293,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -307,14 +308,14 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -330,14 +331,14 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -350,14 +351,14 @@ describe('import rules schema', () => { risk_score: 50, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -371,26 +372,26 @@ describe('import rules schema', () => { type: 'query', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can send in an empty array to threat', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), threat: [], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -421,19 +422,19 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('allows references to be sent as valid', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), references: ['index-1'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -441,19 +442,19 @@ describe('import rules schema', () => { test('defaults references to an array if it is not sent in', () => { const { references, ...noReferences } = getImportRulesSchemaMock(); - const decoded = importRulesSchema.decode(noReferences); + const decoded = RuleToImport.decode(noReferences); const checked = exactCheck(noReferences, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('references cannot be numbers', () => { - const payload: Omit & { references: number[] } = { + const payload: Omit & { references: number[] } = { ...getImportRulesSchemaMock(), references: [5], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); @@ -461,12 +462,12 @@ describe('import rules schema', () => { }); test('indexes cannot be numbers', () => { - const payload: Omit & { index: number[] } = { + const payload: Omit & { index: number[] } = { ...getImportRulesSchemaMock(), index: [5], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); @@ -475,11 +476,11 @@ describe('import rules schema', () => { test('defaults interval to 5 min', () => { const { interval, ...noInterval } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noInterval, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -488,11 +489,11 @@ describe('import rules schema', () => { test('defaults max signals to 100', () => { // eslint-disable-next-line @typescript-eslint/naming-convention const { max_signals, ...noMaxSignals } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noMaxSignals, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -504,19 +505,19 @@ describe('import rules schema', () => { filters: [], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('filters cannot be a string', () => { - const payload: Omit & { filters: string } = { + const payload: Omit & { filters: string } = { ...getImportRulesSchemaMock(), filters: 'some string', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -531,7 +532,7 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -543,19 +544,19 @@ describe('import rules schema', () => { language: 'lucene', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('language does not validate with something made up', () => { - const payload: Omit & { language: string } = { + const payload: Omit & { language: string } = { ...getImportRulesSchemaMock(), language: 'something-made-up', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -565,12 +566,12 @@ describe('import rules schema', () => { }); test('max_signals cannot be negative', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), max_signals: -1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -580,12 +581,12 @@ describe('import rules schema', () => { }); test('max_signals cannot be zero', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), max_signals: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); @@ -593,36 +594,36 @@ describe('import rules schema', () => { }); test('max_signals can be 1', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), max_signals: 1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can optionally send in an array of tags', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), tags: ['tag_1', 'tag_2'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot send in an array of tags that are numbers', () => { - const payload: Omit & { tags: number[] } = { + const payload: Omit & { tags: number[] } = { ...getImportRulesSchemaMock(), tags: [0, 1, 2], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -634,8 +635,8 @@ describe('import rules schema', () => { }); test('You cannot send in an array of threat that are missing "framework"', () => { - const payload: Omit & { - threat: Array>>; + const payload: Omit & { + threat: Array>>; } = { ...getImportRulesSchemaMock(), threat: [ @@ -656,7 +657,7 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -666,8 +667,8 @@ describe('import rules schema', () => { }); test('You cannot send in an array of threat that are missing "tactic"', () => { - const payload: Omit & { - threat: Array>>; + const payload: Omit & { + threat: Array>>; } = { ...getImportRulesSchemaMock(), threat: [ @@ -684,7 +685,7 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -694,8 +695,8 @@ describe('import rules schema', () => { }); test('You can send in an array of threat that are missing "technique"', () => { - const payload: Omit & { - threat: Array>>; + const payload: Omit & { + threat: Array>>; } = { ...getImportRulesSchemaMock(), threat: [ @@ -710,31 +711,31 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can optionally send in an array of false positives', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), false_positives: ['false_1', 'false_2'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot send in an array of false positives that are numbers', () => { - const payload: Omit & { false_positives: number[] } = { + const payload: Omit & { false_positives: number[] } = { ...getImportRulesSchemaMock(), false_positives: [5, 4], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -745,12 +746,12 @@ describe('import rules schema', () => { }); test('You cannot set the immutable to a number when trying to create a rule', () => { - const payload: Omit & { immutable: number } = { + const payload: Omit & { immutable: number } = { ...getImportRulesSchemaMock(), immutable: 5, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); @@ -758,24 +759,24 @@ describe('import rules schema', () => { }); test('You can optionally set the immutable to be false', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), immutable: false, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot set the immutable to be true', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), immutable: true, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -785,12 +786,12 @@ describe('import rules schema', () => { }); test('You cannot set the immutable to be a number', () => { - const payload: Omit & { immutable: number } = { + const payload: Omit & { immutable: number } = { ...getImportRulesSchemaMock(), immutable: 5, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); @@ -798,12 +799,12 @@ describe('import rules schema', () => { }); test('You cannot set the risk_score to 101', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: 101, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -813,12 +814,12 @@ describe('import rules schema', () => { }); test('You cannot set the risk_score to -1', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: -1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "risk_score"']); @@ -826,50 +827,50 @@ describe('import rules schema', () => { }); test('You can set the risk_score to 0', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set the risk_score to 100', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: 100, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set meta to any object you want', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), meta: { somethingMadeUp: { somethingElse: true }, }, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot create meta as a string', () => { - const payload: Omit & { meta: string } = { + const payload: Omit & { meta: string } = { ...getImportRulesSchemaMock(), meta: 'should not work', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -879,27 +880,27 @@ describe('import rules schema', () => { }); test('validates with timeline_id and timeline_title', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), timeline_id: 'timeline-id', timeline_title: 'timeline-title', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('rule_id is required and you cannot get by with just id', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', }; // @ts-expect-error delete payload.rule_id; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -909,7 +910,7 @@ describe('import rules schema', () => { }); test('it validates with created_at, updated_at, created_by, updated_by values', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), created_at: '2020-01-09T06:15:24.749Z', updated_at: '2020-01-09T06:15:24.749Z', @@ -917,19 +918,19 @@ describe('import rules schema', () => { updated_by: 'Evan Hassanabad', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('it does not validate with epoch strings for created_at', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), created_at: '1578550728650', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -939,12 +940,12 @@ describe('import rules schema', () => { }); test('it does not validate with epoch strings for updated_at', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), updated_at: '1578550728650', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -957,7 +958,7 @@ describe('import rules schema', () => { test('does not validate with an empty object', () => { const payload = {}; - const decoded = importRulesPayloadSchema.decode(payload); + const decoded = ImportRulesPayloadSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -971,7 +972,7 @@ describe('import rules schema', () => { madeUpKey: 'madeupstring', }; - const decoded = importRulesPayloadSchema.decode(payload); + const decoded = ImportRulesPayloadSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -983,7 +984,7 @@ describe('import rules schema', () => { test('does validate with a file object', () => { const payload: ImportRulesPayloadSchema = { file: {} }; - const decoded = importRulesPayloadSchema.decode(payload); + const decoded = ImportRulesPayloadSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -993,11 +994,11 @@ describe('import rules schema', () => { test('The default for "from" will be "now-6m"', () => { const { from, ...noFrom } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noFrom, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1005,23 +1006,23 @@ describe('import rules schema', () => { test('The default for "to" will be "now"', () => { const { to, ...noTo } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noTo, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { - const payload: Omit & { severity: string } = { + const payload: Omit & { severity: string } = { ...getImportRulesSchemaMock(), severity: 'junk', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); @@ -1030,22 +1031,22 @@ describe('import rules schema', () => { test('The default for "actions" will be an empty array', () => { const { actions, ...noActions } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noActions, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot send in an array of actions that are missing "group"', () => { - const payload: Omit = { + const payload: Omit = { ...getImportRulesSchemaMock(), actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1055,12 +1056,12 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are missing "id"', () => { - const payload: Omit = { + const payload: Omit = { ...getImportRulesSchemaMock(), actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1070,12 +1071,12 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are missing "action_type_id"', () => { - const payload: Omit = { + const payload: Omit = { ...getImportRulesSchemaMock(), actions: [{ group: 'group', id: 'id', params: {} }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1085,12 +1086,12 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are missing "params"', () => { - const payload: Omit = { + const payload: Omit = { ...getImportRulesSchemaMock(), actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1100,7 +1101,7 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { - const payload: Omit = { + const payload: Omit = { ...getImportRulesSchemaMock(), actions: [ { @@ -1112,7 +1113,7 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1123,11 +1124,11 @@ describe('import rules schema', () => { test('The default for "throttle" will be null', () => { const { throttle, ...noThrottle } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noThrottle, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1135,38 +1136,38 @@ describe('import rules schema', () => { describe('note', () => { test('You can set note to a string', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), note: '# documentation markdown here', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set note to an empty string', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), note: '', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot create note as an object', () => { - const payload: Omit & { note: {} } = { + const payload: Omit & { note: {} } = { ...getImportRulesSchemaMock(), note: { somethingHere: 'something else', }, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1176,7 +1177,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1190,7 +1191,7 @@ describe('import rules schema', () => { note: '# some markdown', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1199,7 +1200,7 @@ describe('import rules schema', () => { describe('exception_list', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and exceptions_list] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1215,14 +1216,14 @@ describe('import rules schema', () => { exceptions_list: getListArrayMock(), }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1238,7 +1239,7 @@ describe('import rules schema', () => { exceptions_list: [], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1261,7 +1262,7 @@ describe('import rules schema', () => { exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1273,7 +1274,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1288,7 +1289,7 @@ describe('import rules schema', () => { note: '# some markdown', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1298,7 +1299,7 @@ describe('import rules schema', () => { describe('threat_mapping', () => { test('You can set a threat query, index, mapping, filters on an imported rule', () => { const payload = getImportThreatMatchRulesSchemaMock(); - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1307,7 +1308,7 @@ describe('import rules schema', () => { describe('data_view_id', () => { test('Defined data_view_id and empty index does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1322,7 +1323,7 @@ describe('import rules schema', () => { interval: '5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1330,7 +1331,7 @@ describe('import rules schema', () => { // Both can be defined, but if a data_view_id is defined, rule will use that one test('Defined data_view_id and index does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1345,19 +1346,19 @@ describe('import rules schema', () => { interval: '5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('data_view_id cannot be a number', () => { - const payload: Omit & { data_view_id: number } = { + const payload: Omit & { data_view_id: number } = { ...getImportRulesSchemaMock(), data_view_id: 5, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts similarity index 87% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts index 9f368a82034e6..bcf173bd47965 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts @@ -28,7 +28,8 @@ import { baseCreateParams, createTypeSpecific } from '../../../../schemas/reques * - created_by is optional (but ignored in the import code) * - updated_by is optional (but ignored in the import code) */ -export const importRulesSchema = t.intersection([ +export type RuleToImport = t.TypeOf; +export const RuleToImport = t.intersection([ baseCreateParams, createTypeSpecific, t.exact(t.type({ rule_id: RuleSignatureId })), @@ -47,12 +48,9 @@ export const importRulesSchema = t.intersection([ ), ]); -export type ImportRulesSchema = t.TypeOf; - -export const importRulesPayloadSchema = t.exact( +export type ImportRulesPayloadSchema = t.TypeOf; +export const ImportRulesPayloadSchema = t.exact( t.type({ file: t.object, }) ); - -export type ImportRulesPayloadSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.test.ts similarity index 81% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.test.ts index cd7ec37a85edb..6bc807d82b13a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { getImportRulesSchemaMock } from './import_rules_schema.mock'; -import type { ImportRulesSchema } from './import_rules_schema'; -import { importRuleValidateTypeDependents } from './import_rules_type_dependents'; +import { getImportRulesSchemaMock } from './request_schema.mock'; +import type { RuleToImport } from './request_schema'; +import { importRuleValidateTypeDependents } from './rule_to_import_validation'; describe('import_rules_type_dependents', () => { test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: ImportRulesSchema = { + const schema: RuleToImport = { ...getImportRulesSchemaMock(), timeline_id: '123', }; @@ -21,7 +21,7 @@ describe('import_rules_type_dependents', () => { }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: ImportRulesSchema = { + const schema: RuleToImport = { ...getImportRulesSchemaMock(), timeline_id: '123', timeline_title: '', @@ -31,7 +31,7 @@ describe('import_rules_type_dependents', () => { }); test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: ImportRulesSchema = { + const schema: RuleToImport = { ...getImportRulesSchemaMock(), timeline_id: '', timeline_title: 'some-title', @@ -41,7 +41,7 @@ describe('import_rules_type_dependents', () => { }); test('You cannot have timeline_title without timeline_id', () => { - const schema: ImportRulesSchema = { + const schema: RuleToImport = { ...getImportRulesSchemaMock(), timeline_title: 'some-title', }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.ts similarity index 79% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.ts index bdb025583b404..4b7f80bdfa21e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/import_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { ImportRulesSchema } from './import_rules_schema'; +import type { RuleToImport } from './request_schema'; -export const validateTimelineId = (rule: ImportRulesSchema): string[] => { +export const validateTimelineId = (rule: RuleToImport): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +20,7 @@ export const validateTimelineId = (rule: ImportRulesSchema): string[] => { return []; }; -export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => { +export const validateTimelineTitle = (rule: RuleToImport): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,7 +33,7 @@ export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => { return []; }; -export const validateThreshold = (rule: ImportRulesSchema): string[] => { +export const validateThreshold = (rule: RuleToImport): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if ( @@ -49,6 +49,6 @@ export const validateThreshold = (rule: ImportRulesSchema): string[] => { return errors; }; -export const importRuleValidateTypeDependents = (rule: ImportRulesSchema): string[] => { +export const importRuleValidateTypeDependents = (rule: RuleToImport): string[] => { return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 676e769579736..b147b28bb897b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -13,6 +13,8 @@ export * from './api/rules/bulk_update_rules/request_schema'; export * from './api/rules/export_rules/request_schema'; export * from './api/rules/find_rules/request_schema_validation'; export * from './api/rules/find_rules/request_schema'; +export * from './api/rules/import_rules/request_schema'; +export * from './api/rules/import_rules/rule_to_import_validation'; // export * from './api/urls'; export * from './model/export/export_rules_details_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts index c46948bb10ff7..9ca26dc54fcb0 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts @@ -6,4 +6,6 @@ */ export * from './api/rules/bulk_actions/request_schema.mock'; +export * from './api/rules/import_rules/request_schema.mock'; + export * from './model/export/export_rules_details_schema.mock'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index f91b26f10fcc7..8e8ca610a75f5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -6,7 +6,7 @@ */ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports -export * from '../../rule_management/api/rules/import_rules/import_rules_schema'; +export * from '../../rule_management/api/rules/import_rules/request_schema'; export * from '../../rule_management/api/rules/patch_rule/patch_rules_schema'; export * from '../../rule_management/api/rules/read_rule/query_rules_schema'; export * from './query_signals_index_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts index bb2b7fb564700..6ea1ba7e4111f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts @@ -27,7 +27,7 @@ import { getImportRulesWithIdSchemaMock, ruleIdsToNdJsonString, rulesToNdJsonString, -} from '../../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.mock'; +} from '../../../../../../../common/detection_engine/rule_management/mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index fbdfb23e7576e..03d1206080ed2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -15,6 +15,7 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { ImportQuerySchemaDecoded } from '@kbn/securitysolution-io-ts-types'; import { importQuerySchema } from '@kbn/securitysolution-io-ts-types'; +import type { RuleToImport } from '../../../../../../../common/detection_engine/rule_management'; import type { ImportRulesSchema as ImportRulesResponseSchema } from '../../../../../../../common/detection_engine/schemas/response/import_rules_schema'; import { importRulesSchema as importRulesResponseSchema } from '../../../../../../../common/detection_engine/schemas/response/import_rules_schema'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; @@ -36,7 +37,6 @@ import type { RuleExceptionsPromiseFromStreams } from '../../../logic/import/imp import { importRules as importRulesHelper } from '../../../logic/import/import_rules_utils'; import { getReferencedExceptionLists } from '../../../logic/import/gather_referenced_exceptions'; import { importRuleExceptions } from '../../../logic/import/import_rule_exceptions'; -import type { ImportRulesSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; import type { HapiReadableStream } from '../../../logic/import/hapi_readable_stream'; const CHUNK_PARSED_OBJECT_SIZE = 50; @@ -132,7 +132,7 @@ export const importRulesRoute = ( let parsedRules; let actionErrors: BulkError[] = []; const actualRules = rules.filter( - (rule): rule is ImportRulesSchema => !(rule instanceof Error) + (rule): rule is RuleToImport => !(rule instanceof Error) ); if (actualRules.some((rule) => rule.actions && rule.actions.length > 0)) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts index ad963d7ca0b88..fdee52eac1a24 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.mock'; -import { checkRuleExceptionReferences } from './check_rule_exception_references'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; +import { checkRuleExceptionReferences } from './check_rule_exception_references'; describe('checkRuleExceptionReferences', () => { it('returns empty array if rule has no exception list references', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.ts index 16e774baf5fef..23494c3fc23dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.ts @@ -6,7 +6,7 @@ */ import type { ListArray, ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; import type { BulkError } from '../../../routes/utils'; import { createBulkErrorObject } from '../../../routes/utils'; @@ -25,7 +25,7 @@ export const checkRuleExceptionReferences = ({ rule, existingLists, }: { - rule: ImportRulesSchema; + rule: RuleToImport; existingLists: Record; }): [BulkError[], ListArray] => { let ruleExceptions: ListArray = []; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts index 4ec49b846ae6e..5adf0656f67ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts @@ -9,14 +9,14 @@ import { Readable } from 'stream'; import { createPromiseFromStreams } from '@kbn/utils'; import { createRulesAndExceptionsStreamFromNdJson } from './create_rules_stream_from_ndjson'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; import { getOutputDetailsSample, getSampleDetailsAsNdjson, } from '../../../../../../common/detection_engine/rule_management/mocks'; import type { RuleExceptionsPromiseFromStreams } from './import_rules_utils'; -export const getOutputSample = (): Partial => ({ +export const getOutputSample = (): Partial => ({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -30,7 +30,7 @@ export const getOutputSample = (): Partial => ({ type: 'query', }); -export const getSampleAsNdjson = (sample: Partial): string => { +export const getSampleAsNdjson = (sample: Partial): string => { return `${JSON.stringify(sample)}\n`; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts index e7d5e03ae5bdc..f8184a687457f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts @@ -5,10 +5,11 @@ * 2.0. */ -import type { Transform } from 'stream'; -import type * as t from 'io-ts'; +import { has } from 'lodash/fp'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; +import type * as t from 'io-ts'; +import type { Transform } from 'stream'; import { createSplitStream, createMapStream, @@ -16,16 +17,18 @@ import { createReduceStream, } from '@kbn/utils'; -import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; +import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; import type { ImportExceptionListItemSchema, ImportExceptionsListSchema, } from '@kbn/securitysolution-io-ts-list-types'; -import { has } from 'lodash/fp'; -import { importRuleValidateTypeDependents } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_type_dependents'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; -import { importRulesSchema } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; + +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; +import { + RuleToImport, + importRuleValidateTypeDependents, +} from '../../../../../../common/detection_engine/rule_management'; import { parseNdjsonStrings, createRulesLimitStream, @@ -38,7 +41,7 @@ import { export const validateRulesStream = (): Transform => { return createMapStream<{ exceptions: Array; - rules: Array; + rules: Array; }>((items) => ({ exceptions: items.exceptions, rules: validateRules(items.rules), @@ -46,16 +49,16 @@ export const validateRulesStream = (): Transform => { }; export const validateRules = ( - rules: Array -): Array => { - return rules.map((obj: ImportRulesSchema | Error) => { + rules: Array +): Array => { + return rules.map((obj: RuleToImport | Error) => { if (!(obj instanceof Error)) { - const decoded = importRulesSchema.decode(obj); + const decoded = RuleToImport.decode(obj); const checked = exactCheck(obj, decoded); - const onLeft = (errors: t.Errors): BadRequestError | ImportRulesSchema => { + const onLeft = (errors: t.Errors): BadRequestError | RuleToImport => { return new BadRequestError(formatErrors(errors).join()); }; - const onRight = (schema: ImportRulesSchema): BadRequestError | ImportRulesSchema => { + const onRight = (schema: RuleToImport): BadRequestError | RuleToImport => { const validationErrors = importRuleValidateTypeDependents(schema); if (validationErrors.length) { return new BadRequestError(validationErrors.join()); @@ -79,7 +82,7 @@ export const validateRules = ( export const sortImports = (): Transform => { return createReduceStream<{ exceptions: Array; - rules: Array; + rules: Array; }>( (acc, importItem) => { if (has('list_id', importItem) || has('item_id', importItem) || has('entries', importItem)) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts index f6b5d73741e49..797c45ea45fa2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts @@ -6,12 +6,11 @@ */ import type { SavedObjectsClientContract } from '@kbn/core/server'; - import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { findExceptionList } from '@kbn/lists-plugin/server/services/exception_lists/find_exception_list'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; import { getReferencedExceptionLists } from './gather_referenced_exceptions'; -import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.mock'; jest.mock('@kbn/lists-plugin/server/services/exception_lists/find_exception_list'); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts index 48c4adac0dfd1..cbf6050b10b1c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts @@ -8,7 +8,7 @@ import type { ExceptionListSchema, ListArray } from '@kbn/securitysolution-io-ts import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { ExceptionListQueryInfo } from '@kbn/lists-plugin/server/services/exception_lists/utils/import/find_all_exception_list_types'; import { getAllListTypes } from '@kbn/lists-plugin/server/services/exception_lists/utils/import/find_all_exception_list_types'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; /** * Helper that takes rules, goes through their referenced exception lists and @@ -21,7 +21,7 @@ export const getReferencedExceptionLists = async ({ rules, savedObjectsClient, }: { - rules: Array; + rules: Array; savedObjectsClient: SavedObjectsClientContract; }): Promise> => { const [lists] = rules.reduce((acc, rule) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts index b50db98e5b4d8..3bf848651e1f9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts @@ -13,7 +13,7 @@ import { getFindResultWithSingleHit, } from '../../../routes/__mocks__/request_responses'; import { getQueryRuleParams } from '../../../rule_schema/mocks'; -import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; import { createRules } from '../crud/create_rules'; import { patchRules } from '../crud/patch_rules'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts index f8fb54be43f98..694bc916fefe3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts @@ -14,6 +14,8 @@ import type { import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; + +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; // eslint-disable-next-line no-restricted-imports import { legacyMigrate } from '../rule_actions/legacy_action_migration'; import type { ImportRuleResponse } from '../../../routes/utils'; @@ -21,12 +23,11 @@ import { createBulkErrorObject } from '../../../routes/utils'; import { createRules } from '../crud/create_rules'; import { readRules } from '../crud/read_rules'; import { patchRules } from '../crud/patch_rules'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; import type { MlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; import { checkRuleExceptionReferences } from './check_rule_exception_references'; -export type PromiseFromStreams = ImportRulesSchema | Error; +export type PromiseFromStreams = RuleToImport | Error; export interface RuleExceptionsPromiseFromStreams { rules: PromiseFromStreams[]; exceptions: Array; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts index 9963692738515..ff8a4a04fe04e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts @@ -5,9 +5,14 @@ * 2.0. */ +import { partition } from 'lodash/fp'; import { Readable } from 'stream'; import { createPromiseFromStreams } from '@kbn/utils'; import type { RuleAction, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { PartialRule } from '@kbn/alerting-plugin/server'; + +import type { RuleToImport } from '../../../../../common/detection_engine/rule_management'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getIdError, @@ -25,12 +30,11 @@ import type { PartialFilter } from '../../types'; import type { BulkError } from '../../routes/utils'; import { createBulkErrorObject } from '../../routes/utils'; import { getOutputRuleAlertForRest } from '../../routes/__mocks__/utils'; -import type { PartialRule } from '@kbn/alerting-plugin/server'; + // TODO: https://github.com/elastic/kibana/pull/142950 import { createRulesAndExceptionsStreamFromNdJson } from '../logic/import/create_rules_stream_from_ndjson'; import type { RuleAlertType } from '../../rule_schema'; -import type { ImportRulesSchema } from '../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; + import { getMlRuleParams, getQueryRuleParams, getThreatRuleParams } from '../../rule_schema/mocks'; // TODO: https://github.com/elastic/kibana/pull/142950 import { internalRuleToAPIResponse } from '../normalization/rule_converters'; @@ -43,9 +47,8 @@ import type { } from '../../rule_actions_legacy'; // TODO: https://github.com/elastic/kibana/pull/142950 import type { RuleExceptionsPromiseFromStreams } from '../logic/import/import_rules_utils'; -import { partition } from 'lodash/fp'; -type PromiseFromStreams = ImportRulesSchema | Error; +type PromiseFromStreams = RuleToImport | Error; const createMockImportRule = async (rule: ReturnType) => { const ndJsonStream = new Readable({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts index 3b73a800987c9..a1bbc659bd733 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts @@ -14,9 +14,9 @@ import type { RuleAction } from '@kbn/securitysolution-io-ts-alerting-types'; import type { PartialRule, FindResult } from '@kbn/alerting-plugin/server'; import type { ActionsClient, FindActionResult } from '@kbn/actions-plugin/server'; +import type { RuleToImport } from '../../../../../common/detection_engine/rule_management'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; -import type { ImportRulesSchema } from '../../../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; import type { RuleAlertType, RuleParams } from '../../rule_schema'; import { isAlertType } from '../../rule_schema'; import type { BulkError, OutputError } from '../../routes/utils'; @@ -27,7 +27,7 @@ import { internalRuleToAPIResponse } from '../normalization/rule_converters'; import type { LegacyRulesActionsSavedObject } from '../../rule_actions_legacy'; import type { RuleExecutionSummariesByRuleId } from '../../rule_monitoring'; -type PromiseFromStreams = ImportRulesSchema | Error; +type PromiseFromStreams = RuleToImport | Error; const MAX_CONCURRENT_SEARCHES = 10; export const getIdError = ({ @@ -238,7 +238,7 @@ export const migrateLegacyActionsIds = async ( rules: PromiseFromStreams[], savedObjectsClient: SavedObjectsClientContract ): Promise => { - const isImportRule = (r: unknown): r is ImportRulesSchema => !(r instanceof Error); + const isImportRule = (r: unknown): r is RuleToImport => !(r instanceof Error); const toReturn = await pMap( rules, diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts index e7011acdbc029..e04ecd40ef04a 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts @@ -9,7 +9,7 @@ import { Transform } from 'stream'; import { has, isString } from 'lodash/fp'; import { createMapStream, createFilterStream } from '@kbn/utils'; -import type { ImportRulesSchema } from '../../../common/detection_engine/rule_management/api/rules/import_rules/import_rules_schema'; +import type { RuleToImport } from '../../../common/detection_engine/rule_management'; export interface RulesObjectsExportResultDetails { /** number of successfully exported objects */ @@ -29,7 +29,7 @@ export const parseNdjsonStrings = (): Transform => { }; export const filterExportedCounts = (): Transform => { - return createFilterStream( + return createFilterStream( (obj) => obj != null && !has('exported_count', obj) ); }; From 98fb3b76ab008a70675ca5b9e17d9407380e2d7d Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 19:04:02 +0200 Subject: [PATCH 10/15] TEMP COMMIT. REBASE ME --- .../rules/import_rules/request_schema.test.ts | 1322 +--------------- .../api/rules/import_rules/request_schema.ts | 41 - .../rule_to_import_validation.test.ts | 52 - .../detection_engine/rule_management/index.ts | 3 +- .../detection_engine/rule_management/mocks.ts | 2 +- .../import/rule_to_import.mock.ts} | 2 +- .../model/import/rule_to_import.test.ts | 1332 +++++++++++++++++ .../model/import/rule_to_import.ts | 49 + .../import/rule_to_import_validation.test.ts | 54 + .../import}/rule_to_import_validation.ts | 19 +- .../detection_engine/schemas/request/index.ts | 1 - .../api/rules/import_rules/route.test.ts | 32 +- .../create_rules_stream_from_ndjson.test.ts | 1 + .../import/create_rules_stream_from_ndjson.ts | 9 +- .../logic/import/import_rules_utils.test.ts | 8 +- 15 files changed, 1479 insertions(+), 1448 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.test.ts rename x-pack/plugins/security_solution/common/detection_engine/rule_management/{api/rules/import_rules/request_schema.mock.ts => model/import/rule_to_import.mock.ts} (97%) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.test.ts create mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.ts create mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.test.ts rename x-pack/plugins/security_solution/common/detection_engine/rule_management/{api/rules/import_rules => model/import}/rule_to_import_validation.ts (78%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts index 68523d52bcf9f..19bc424175427 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts @@ -9,951 +9,9 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import type { ImportRulesPayloadSchema, RuleToImport } from './request_schema'; -import { ImportRulesPayloadSchema, RuleToImport } from './request_schema'; -import { - getImportRulesSchemaMock, - getImportThreatMatchRulesSchemaMock, -} from './request_schema.mock'; -import { getListArrayMock } from '../../../../schemas/types/lists.mock'; - -describe('import rules schema', () => { - test('empty objects do not validate', () => { - const payload: Partial = {}; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "description"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "risk_score"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "name"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "severity"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "rule_id"' - ); - expect(message.schema).toEqual({}); - }); - - test('made up values do not validate', () => { - const payload: RuleToImport & { madeUp: string } = { - ...getImportRulesSchemaMock(), - madeUp: 'hi', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); - expect(message.schema).toEqual({}); - }); - - test('[rule_id] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "description"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "risk_score"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "name"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "severity"' - ); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "risk_score"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "name"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "severity"' - ); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "risk_score"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "name"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "severity"' - ); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "risk_score"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "name"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "severity"' - ); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, name] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "risk_score"' - ); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "severity"' - ); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, name, severity] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toContain( - 'Invalid value "undefined" supplied to "risk_score"' - ); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - ]); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - ]); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - interval: '5m', - index: ['index-1'], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - ]); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - const payload: Partial = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - ]); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You can send in an empty array to threat', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - threat: [], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - threat: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - technique: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('allows references to be sent as valid', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - references: ['index-1'], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('defaults references to an array if it is not sent in', () => { - const { references, ...noReferences } = getImportRulesSchemaMock(); - const decoded = RuleToImport.decode(noReferences); - const checked = exactCheck(noReferences, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('references cannot be numbers', () => { - const payload: Omit & { references: number[] } = { - ...getImportRulesSchemaMock(), - references: [5], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); - expect(message.schema).toEqual({}); - }); - - test('indexes cannot be numbers', () => { - const payload: Omit & { index: number[] } = { - ...getImportRulesSchemaMock(), - index: [5], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); - expect(message.schema).toEqual({}); - }); - - test('defaults interval to 5 min', () => { - const { interval, ...noInterval } = getImportRulesSchemaMock(); - const payload: RuleToImport = { - ...noInterval, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('defaults max signals to 100', () => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { max_signals, ...noMaxSignals } = getImportRulesSchemaMock(); - const payload: RuleToImport = { - ...noMaxSignals, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('saved_query type can have filters with it', () => { - const payload = { - ...getImportRulesSchemaMock(), - filters: [], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('filters cannot be a string', () => { - const payload: Omit & { filters: string } = { - ...getImportRulesSchemaMock(), - filters: 'some string', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "filters"', - ]); - expect(message.schema).toEqual({}); - }); - - test('language validates with kuery', () => { - const payload = { - ...getImportRulesSchemaMock(), - language: 'kuery', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('language validates with lucene', () => { - const payload = { - ...getImportRulesSchemaMock(), - language: 'lucene', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('language does not validate with something made up', () => { - const payload: Omit & { language: string } = { - ...getImportRulesSchemaMock(), - language: 'something-made-up', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "something-made-up" supplied to "language"', - ]); - expect(message.schema).toEqual({}); - }); - - test('max_signals cannot be negative', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - max_signals: -1, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "-1" supplied to "max_signals"', - ]); - expect(message.schema).toEqual({}); - }); - - test('max_signals cannot be zero', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - max_signals: 0, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); - expect(message.schema).toEqual({}); - }); - - test('max_signals can be 1', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - max_signals: 1, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You can optionally send in an array of tags', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - tags: ['tag_1', 'tag_2'], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You cannot send in an array of tags that are numbers', () => { - const payload: Omit & { tags: number[] } = { - ...getImportRulesSchemaMock(), - tags: [0, 1, 2], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "0" supplied to "tags"', - 'Invalid value "1" supplied to "tags"', - 'Invalid value "2" supplied to "tags"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot send in an array of threat that are missing "framework"', () => { - const payload: Omit & { - threat: Array>>; - } = { - ...getImportRulesSchemaMock(), - threat: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - technique: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "threat,framework"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot send in an array of threat that are missing "tactic"', () => { - const payload: Omit & { - threat: Array>>; - } = { - ...getImportRulesSchemaMock(), - threat: [ - { - framework: 'fake', - technique: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "threat,tactic"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You can send in an array of threat that are missing "technique"', () => { - const payload: Omit & { - threat: Array>>; - } = { - ...getImportRulesSchemaMock(), - threat: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You can optionally send in an array of false positives', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - false_positives: ['false_1', 'false_2'], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You cannot send in an array of false positives that are numbers', () => { - const payload: Omit & { false_positives: number[] } = { - ...getImportRulesSchemaMock(), - false_positives: [5, 4], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "5" supplied to "false_positives"', - 'Invalid value "4" supplied to "false_positives"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot set the immutable to a number when trying to create a rule', () => { - const payload: Omit & { immutable: number } = { - ...getImportRulesSchemaMock(), - immutable: 5, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); - expect(message.schema).toEqual({}); - }); - - test('You can optionally set the immutable to be false', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - immutable: false, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You cannot set the immutable to be true', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - immutable: true, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "true" supplied to "immutable"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot set the immutable to be a number', () => { - const payload: Omit & { immutable: number } = { - ...getImportRulesSchemaMock(), - immutable: 5, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); - expect(message.schema).toEqual({}); - }); - - test('You cannot set the risk_score to 101', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - risk_score: 101, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "101" supplied to "risk_score"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot set the risk_score to -1', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - risk_score: -1, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "risk_score"']); - expect(message.schema).toEqual({}); - }); - - test('You can set the risk_score to 0', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - risk_score: 0, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You can set the risk_score to 100', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - risk_score: 100, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You can set meta to any object you want', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You cannot create meta as a string', () => { - const payload: Omit & { meta: string } = { - ...getImportRulesSchemaMock(), - meta: 'should not work', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "should not work" supplied to "meta"', - ]); - expect(message.schema).toEqual({}); - }); - - test('validates with timeline_id and timeline_title', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - timeline_id: 'timeline-id', - timeline_title: 'timeline-title', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('rule_id is required and you cannot get by with just id', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', - }; - // @ts-expect-error - delete payload.rule_id; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "rule_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it validates with created_at, updated_at, created_by, updated_by values', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - created_at: '2020-01-09T06:15:24.749Z', - updated_at: '2020-01-09T06:15:24.749Z', - created_by: 'Braden Hassanabad', - updated_by: 'Evan Hassanabad', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('it does not validate with epoch strings for created_at', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - created_at: '1578550728650', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1578550728650" supplied to "created_at"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it does not validate with epoch strings for updated_at', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - updated_at: '1578550728650', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1578550728650" supplied to "updated_at"', - ]); - expect(message.schema).toEqual({}); - }); +import { ImportRulesPayloadSchema } from './request_schema'; +describe('Import rules schema', () => { describe('importRulesPayloadSchema', () => { test('does not validate with an empty object', () => { const payload = {}; @@ -991,380 +49,4 @@ describe('import rules schema', () => { expect(message.schema).toEqual(payload); }); }); - - test('The default for "from" will be "now-6m"', () => { - const { from, ...noFrom } = getImportRulesSchemaMock(); - const payload: RuleToImport = { - ...noFrom, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('The default for "to" will be "now"', () => { - const { to, ...noTo } = getImportRulesSchemaMock(); - const payload: RuleToImport = { - ...noTo, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You cannot set the severity to a value other than low, medium, high, or critical', () => { - const payload: Omit & { severity: string } = { - ...getImportRulesSchemaMock(), - severity: 'junk', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); - expect(message.schema).toEqual({}); - }); - - test('The default for "actions" will be an empty array', () => { - const { actions, ...noActions } = getImportRulesSchemaMock(); - const payload: RuleToImport = { - ...noActions, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - test('You cannot send in an array of actions that are missing "group"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "actions,group"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot send in an array of actions that are missing "id"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "actions,id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot send in an array of actions that are missing "action_type_id"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ group: 'group', id: 'id', params: {} }], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "actions,action_type_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot send in an array of actions that are missing "params"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "actions,params"', - ]); - expect(message.schema).toEqual({}); - }); - - test('You cannot send in an array of actions that are including "actionTypeId"', () => { - const payload: Omit = { - ...getImportRulesSchemaMock(), - actions: [ - { - group: 'group', - id: 'id', - actionTypeId: 'actionTypeId', - params: {}, - }, - ], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "actions,action_type_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('The default for "throttle" will be null', () => { - const { throttle, ...noThrottle } = getImportRulesSchemaMock(); - const payload: RuleToImport = { - ...noThrottle, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - describe('note', () => { - test('You can set note to a string', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - note: '# documentation markdown here', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You can set note to an empty string', () => { - const payload: RuleToImport = { - ...getImportRulesSchemaMock(), - note: '', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('You cannot create note as an object', () => { - const payload: Omit & { note: {} } = { - ...getImportRulesSchemaMock(), - note: { - somethingHere: 'something else', - }, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "{"somethingHere":"something else"}" supplied to "note"', - ]); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - }); - - describe('exception_list', () => { - test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and exceptions_list] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - filters: [], - risk_score: 50, - note: '# some markdown', - exceptions_list: getListArrayMock(), - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - filters: [], - risk_score: 50, - note: '# some markdown', - exceptions_list: [], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and invalid exceptions_list] does NOT validate', () => { - const payload = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - filters: [], - risk_score: 50, - note: '# some markdown', - exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "exceptions_list,list_id"', - 'Invalid value "undefined" supplied to "exceptions_list,type"', - 'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - filters: [], - risk_score: 50, - note: '# some markdown', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - }); - - describe('threat_mapping', () => { - test('You can set a threat query, index, mapping, filters on an imported rule', () => { - const payload = getImportThreatMatchRulesSchemaMock(); - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - }); - - describe('data_view_id', () => { - test('Defined data_view_id and empty index does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - data_view_id: 'logs-*', - index: [], - interval: '5m', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - // Both can be defined, but if a data_view_id is defined, rule will use that one - test('Defined data_view_id and index does validate', () => { - const payload: RuleToImport = { - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - data_view_id: 'logs-*', - index: ['auditbeat-*'], - interval: '5m', - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - }); - - test('data_view_id cannot be a number', () => { - const payload: Omit & { data_view_id: number } = { - ...getImportRulesSchemaMock(), - data_view_id: 5, - }; - - const decoded = RuleToImport.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "5" supplied to "data_view_id"', - ]); - expect(message.schema).toEqual({}); - }); - }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts index bcf173bd47965..1ecfc7cc61b89 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts @@ -7,47 +7,6 @@ import * as t from 'io-ts'; -import { OnlyFalseAllowed } from '@kbn/securitysolution-io-ts-types'; -import { - RelatedIntegrationArray, - RequiredFieldArray, - RuleObjectId, - RuleSignatureId, - SetupGuide, -} from '../../../../rule_schema'; -import { created_at, updated_at, created_by, updated_by } from '../../../../schemas/common'; -import { baseCreateParams, createTypeSpecific } from '../../../../schemas/request/rule_schemas'; - -/** - * Differences from this and the createRulesSchema are - * - rule_id is required - * - id is optional (but ignored in the import code - rule_id is exclusively used for imports) - * - immutable is optional but if it is any value other than false it will be rejected - * - created_at is optional (but ignored in the import code) - * - updated_at is optional (but ignored in the import code) - * - created_by is optional (but ignored in the import code) - * - updated_by is optional (but ignored in the import code) - */ -export type RuleToImport = t.TypeOf; -export const RuleToImport = t.intersection([ - baseCreateParams, - createTypeSpecific, - t.exact(t.type({ rule_id: RuleSignatureId })), - t.exact( - t.partial({ - id: RuleObjectId, - immutable: OnlyFalseAllowed, - updated_at, - updated_by, - created_at, - created_by, - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, - }) - ), -]); - export type ImportRulesPayloadSchema = t.TypeOf; export const ImportRulesPayloadSchema = t.exact( t.type({ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.test.ts deleted file mode 100644 index 6bc807d82b13a..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 { getImportRulesSchemaMock } from './request_schema.mock'; -import type { RuleToImport } from './request_schema'; -import { importRuleValidateTypeDependents } from './rule_to_import_validation'; - -describe('import_rules_type_dependents', () => { - test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), - timeline_id: '123', - }; - delete schema.timeline_title; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); - }); - - test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), - timeline_id: '123', - timeline_title: '', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_title" cannot be an empty string']); - }); - - test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), - timeline_id: '', - timeline_title: 'some-title', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_id" cannot be an empty string']); - }); - - test('You cannot have timeline_title without timeline_id', () => { - const schema: RuleToImport = { - ...getImportRulesSchemaMock(), - timeline_title: 'some-title', - }; - delete schema.timeline_id; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index b147b28bb897b..a3b5b3fc7f9f2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -14,7 +14,8 @@ export * from './api/rules/export_rules/request_schema'; export * from './api/rules/find_rules/request_schema_validation'; export * from './api/rules/find_rules/request_schema'; export * from './api/rules/import_rules/request_schema'; -export * from './api/rules/import_rules/rule_to_import_validation'; // export * from './api/urls'; export * from './model/export/export_rules_details_schema'; +export * from './model/import/rule_to_import_validation'; +export * from './model/import/rule_to_import'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts index 9ca26dc54fcb0..0332c07134931 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts @@ -6,6 +6,6 @@ */ export * from './api/rules/bulk_actions/request_schema.mock'; -export * from './api/rules/import_rules/request_schema.mock'; export * from './model/export/export_rules_details_schema.mock'; +export * from './model/import/rule_to_import.mock'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.mock.ts similarity index 97% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.mock.ts index 1deac1e845b6d..d1dc9e8ac4663 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RuleToImport } from './request_schema'; +import type { RuleToImport } from './rule_to_import'; export const getImportRulesSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ description: 'some description', diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.test.ts new file mode 100644 index 0000000000000..84478b4af27fe --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.test.ts @@ -0,0 +1,1332 @@ +/* + * 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 { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { getListArrayMock } from '../../../schemas/types/lists.mock'; +import { RuleToImport } from './rule_to_import'; +import { + getImportRulesSchemaMock, + getImportThreatMatchRulesSchemaMock, +} from './rule_to_import.mock'; + +describe('RuleToImport', () => { + test('empty objects do not validate', () => { + const payload: Partial = {}; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "rule_id"' + ); + expect(message.schema).toEqual({}); + }); + + test('made up values do not validate', () => { + const payload: RuleToImport & { madeUp: string } = { + ...getImportRulesSchemaMock(), + madeUp: 'hi', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); + expect(message.schema).toEqual({}); + }); + + test('[rule_id] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, name] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, name, severity] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, name, severity, type] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "risk_score"', + ]); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "risk_score"', + ]); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + interval: '5m', + index: ['index-1'], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "risk_score"', + ]); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { + const payload: Partial = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "risk_score"', + ]); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + risk_score: 50, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You can send in an empty array to threat', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + threat: [], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + threat: [ + { + framework: 'someFramework', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + technique: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('allows references to be sent as valid', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + references: ['index-1'], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('defaults references to an array if it is not sent in', () => { + const { references, ...noReferences } = getImportRulesSchemaMock(); + const decoded = RuleToImport.decode(noReferences); + const checked = exactCheck(noReferences, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('references cannot be numbers', () => { + const payload: Omit & { references: number[] } = { + ...getImportRulesSchemaMock(), + references: [5], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); + expect(message.schema).toEqual({}); + }); + + test('indexes cannot be numbers', () => { + const payload: Omit & { index: number[] } = { + ...getImportRulesSchemaMock(), + index: [5], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); + expect(message.schema).toEqual({}); + }); + + test('defaults interval to 5 min', () => { + const { interval, ...noInterval } = getImportRulesSchemaMock(); + const payload: RuleToImport = { + ...noInterval, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('defaults max signals to 100', () => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { max_signals, ...noMaxSignals } = getImportRulesSchemaMock(); + const payload: RuleToImport = { + ...noMaxSignals, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('saved_query type can have filters with it', () => { + const payload = { + ...getImportRulesSchemaMock(), + filters: [], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('filters cannot be a string', () => { + const payload: Omit & { filters: string } = { + ...getImportRulesSchemaMock(), + filters: 'some string', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "some string" supplied to "filters"', + ]); + expect(message.schema).toEqual({}); + }); + + test('language validates with kuery', () => { + const payload = { + ...getImportRulesSchemaMock(), + language: 'kuery', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('language validates with lucene', () => { + const payload = { + ...getImportRulesSchemaMock(), + language: 'lucene', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('language does not validate with something made up', () => { + const payload: Omit & { language: string } = { + ...getImportRulesSchemaMock(), + language: 'something-made-up', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "something-made-up" supplied to "language"', + ]); + expect(message.schema).toEqual({}); + }); + + test('max_signals cannot be negative', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + max_signals: -1, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "-1" supplied to "max_signals"', + ]); + expect(message.schema).toEqual({}); + }); + + test('max_signals cannot be zero', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + max_signals: 0, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); + expect(message.schema).toEqual({}); + }); + + test('max_signals can be 1', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + max_signals: 1, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You can optionally send in an array of tags', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + tags: ['tag_1', 'tag_2'], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You cannot send in an array of tags that are numbers', () => { + const payload: Omit & { tags: number[] } = { + ...getImportRulesSchemaMock(), + tags: [0, 1, 2], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "0" supplied to "tags"', + 'Invalid value "1" supplied to "tags"', + 'Invalid value "2" supplied to "tags"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot send in an array of threat that are missing "framework"', () => { + const payload: Omit & { + threat: Array>>; + } = { + ...getImportRulesSchemaMock(), + threat: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + technique: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "threat,framework"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot send in an array of threat that are missing "tactic"', () => { + const payload: Omit & { + threat: Array>>; + } = { + ...getImportRulesSchemaMock(), + threat: [ + { + framework: 'fake', + technique: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "threat,tactic"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You can send in an array of threat that are missing "technique"', () => { + const payload: Omit & { + threat: Array>>; + } = { + ...getImportRulesSchemaMock(), + threat: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + }, + ], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You can optionally send in an array of false positives', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + false_positives: ['false_1', 'false_2'], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You cannot send in an array of false positives that are numbers', () => { + const payload: Omit & { false_positives: number[] } = { + ...getImportRulesSchemaMock(), + false_positives: [5, 4], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "5" supplied to "false_positives"', + 'Invalid value "4" supplied to "false_positives"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot set the immutable to a number when trying to create a rule', () => { + const payload: Omit & { immutable: number } = { + ...getImportRulesSchemaMock(), + immutable: 5, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); + expect(message.schema).toEqual({}); + }); + + test('You can optionally set the immutable to be false', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + immutable: false, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You cannot set the immutable to be true', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + immutable: true, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "true" supplied to "immutable"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot set the immutable to be a number', () => { + const payload: Omit & { immutable: number } = { + ...getImportRulesSchemaMock(), + immutable: 5, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); + expect(message.schema).toEqual({}); + }); + + test('You cannot set the risk_score to 101', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + risk_score: 101, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "101" supplied to "risk_score"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot set the risk_score to -1', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + risk_score: -1, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "risk_score"']); + expect(message.schema).toEqual({}); + }); + + test('You can set the risk_score to 0', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + risk_score: 0, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You can set the risk_score to 100', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + risk_score: 100, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You can set meta to any object you want', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + meta: { + somethingMadeUp: { somethingElse: true }, + }, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You cannot create meta as a string', () => { + const payload: Omit & { meta: string } = { + ...getImportRulesSchemaMock(), + meta: 'should not work', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "should not work" supplied to "meta"', + ]); + expect(message.schema).toEqual({}); + }); + + test('validates with timeline_id and timeline_title', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_id: 'timeline-id', + timeline_title: 'timeline-title', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('rule_id is required and you cannot get by with just id', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', + }; + // @ts-expect-error + delete payload.rule_id; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "rule_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it validates with created_at, updated_at, created_by, updated_by values', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + created_at: '2020-01-09T06:15:24.749Z', + updated_at: '2020-01-09T06:15:24.749Z', + created_by: 'Braden Hassanabad', + updated_by: 'Evan Hassanabad', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('it does not validate with epoch strings for created_at', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + created_at: '1578550728650', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1578550728650" supplied to "created_at"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it does not validate with epoch strings for updated_at', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + updated_at: '1578550728650', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1578550728650" supplied to "updated_at"', + ]); + expect(message.schema).toEqual({}); + }); + + test('The default for "from" will be "now-6m"', () => { + const { from, ...noFrom } = getImportRulesSchemaMock(); + const payload: RuleToImport = { + ...noFrom, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('The default for "to" will be "now"', () => { + const { to, ...noTo } = getImportRulesSchemaMock(); + const payload: RuleToImport = { + ...noTo, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + const payload: Omit & { severity: string } = { + ...getImportRulesSchemaMock(), + severity: 'junk', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); + expect(message.schema).toEqual({}); + }); + + test('The default for "actions" will be an empty array', () => { + const { actions, ...noActions } = getImportRulesSchemaMock(); + const payload: RuleToImport = { + ...noActions, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You cannot send in an array of actions that are missing "group"', () => { + const payload: Omit = { + ...getImportRulesSchemaMock(), + actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "actions,group"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot send in an array of actions that are missing "id"', () => { + const payload: Omit = { + ...getImportRulesSchemaMock(), + actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "actions,id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot send in an array of actions that are missing "action_type_id"', () => { + const payload: Omit = { + ...getImportRulesSchemaMock(), + actions: [{ group: 'group', id: 'id', params: {} }], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "actions,action_type_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot send in an array of actions that are missing "params"', () => { + const payload: Omit = { + ...getImportRulesSchemaMock(), + actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "actions,params"', + ]); + expect(message.schema).toEqual({}); + }); + + test('You cannot send in an array of actions that are including "actionTypeId"', () => { + const payload: Omit = { + ...getImportRulesSchemaMock(), + actions: [ + { + group: 'group', + id: 'id', + actionTypeId: 'actionTypeId', + params: {}, + }, + ], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "actions,action_type_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('The default for "throttle" will be null', () => { + const { throttle, ...noThrottle } = getImportRulesSchemaMock(); + const payload: RuleToImport = { + ...noThrottle, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + describe('note', () => { + test('You can set note to a string', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + note: '# documentation markdown here', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You can set note to an empty string', () => { + const payload: RuleToImport = { + ...getImportRulesSchemaMock(), + note: '', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('You cannot create note as an object', () => { + const payload: Omit & { note: {} } = { + ...getImportRulesSchemaMock(), + note: { + somethingHere: 'something else', + }, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "{"somethingHere":"something else"}" supplied to "note"', + ]); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + risk_score: 50, + note: '# some markdown', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + }); + + describe('exception_list', () => { + test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and exceptions_list] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + filters: [], + risk_score: 50, + note: '# some markdown', + exceptions_list: getListArrayMock(), + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + filters: [], + risk_score: 50, + note: '# some markdown', + exceptions_list: [], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and invalid exceptions_list] does NOT validate', () => { + const payload = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + filters: [], + risk_score: 50, + note: '# some markdown', + exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "exceptions_list,list_id"', + 'Invalid value "undefined" supplied to "exceptions_list,type"', + 'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'low', + interval: '5m', + type: 'query', + filters: [], + risk_score: 50, + note: '# some markdown', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + }); + + describe('threat_mapping', () => { + test('You can set a threat query, index, mapping, filters on an imported rule', () => { + const payload = getImportThreatMatchRulesSchemaMock(); + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + }); + + describe('data_view_id', () => { + test('Defined data_view_id and empty index does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + query: 'some query', + data_view_id: 'logs-*', + index: [], + interval: '5m', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + // Both can be defined, but if a data_view_id is defined, rule will use that one + test('Defined data_view_id and index does validate', () => { + const payload: RuleToImport = { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + query: 'some query', + data_view_id: 'logs-*', + index: ['auditbeat-*'], + interval: '5m', + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + }); + + test('data_view_id cannot be a number', () => { + const payload: Omit & { data_view_id: number } = { + ...getImportRulesSchemaMock(), + data_view_id: 5, + }; + + const decoded = RuleToImport.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "5" supplied to "data_view_id"', + ]); + expect(message.schema).toEqual({}); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.ts new file mode 100644 index 0000000000000..5584c85eea58b --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.ts @@ -0,0 +1,49 @@ +/* + * 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 t from 'io-ts'; + +import { OnlyFalseAllowed } from '@kbn/securitysolution-io-ts-types'; +import { + RelatedIntegrationArray, + RequiredFieldArray, + RuleObjectId, + RuleSignatureId, + SetupGuide, +} from '../../../rule_schema'; +import { created_at, updated_at, created_by, updated_by } from '../../../schemas/common'; +import { baseCreateParams, createTypeSpecific } from '../../../schemas/request/rule_schemas'; + +/** + * Differences from this and the createRulesSchema are + * - rule_id is required + * - id is optional (but ignored in the import code - rule_id is exclusively used for imports) + * - immutable is optional but if it is any value other than false it will be rejected + * - created_at is optional (but ignored in the import code) + * - updated_at is optional (but ignored in the import code) + * - created_by is optional (but ignored in the import code) + * - updated_by is optional (but ignored in the import code) + */ +export type RuleToImport = t.TypeOf; +export const RuleToImport = t.intersection([ + baseCreateParams, + createTypeSpecific, + t.exact(t.type({ rule_id: RuleSignatureId })), + t.exact( + t.partial({ + id: RuleObjectId, + immutable: OnlyFalseAllowed, + updated_at, + updated_by, + created_at, + created_by, + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, + setup: SetupGuide, + }) + ), +]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.test.ts new file mode 100644 index 0000000000000..31ac993eb4053 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.test.ts @@ -0,0 +1,54 @@ +/* + * 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 type { RuleToImport } from './rule_to_import'; +import { getImportRulesSchemaMock } from './rule_to_import.mock'; +import { validateRuleToImport } from './rule_to_import_validation'; + +describe('Rule to import schema, additional validation', () => { + describe('validateRuleToImport', () => { + test('You cannot omit timeline_title when timeline_id is present', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_id: '123', + }; + delete schema.timeline_title; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_id: '123', + timeline_title: '', + }; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['"timeline_title" cannot be an empty string']); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_id: '', + timeline_title: 'some-title', + }; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['"timeline_id" cannot be an empty string']); + }); + + test('You cannot have timeline_title without timeline_id', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_title: 'some-title', + }; + delete schema.timeline_id; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.ts similarity index 78% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.ts index 4b7f80bdfa21e..de21ac3a7964c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/rule_to_import_validation.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.ts @@ -5,9 +5,16 @@ * 2.0. */ -import type { RuleToImport } from './request_schema'; +import type { RuleToImport } from './rule_to_import'; -export const validateTimelineId = (rule: RuleToImport): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateRuleToImport = (rule: RuleToImport): string[] => { + return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; +}; + +const validateTimelineId = (rule: RuleToImport): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +27,7 @@ export const validateTimelineId = (rule: RuleToImport): string[] => { return []; }; -export const validateTimelineTitle = (rule: RuleToImport): string[] => { +const validateTimelineTitle = (rule: RuleToImport): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,7 +40,7 @@ export const validateTimelineTitle = (rule: RuleToImport): string[] => { return []; }; -export const validateThreshold = (rule: RuleToImport): string[] => { +const validateThreshold = (rule: RuleToImport): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if ( @@ -48,7 +55,3 @@ export const validateThreshold = (rule: RuleToImport): string[] => { } return errors; }; - -export const importRuleValidateTypeDependents = (rule: RuleToImport): string[] => { - return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index 8e8ca610a75f5..1bbb06203bf66 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -6,7 +6,6 @@ */ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports -export * from '../../rule_management/api/rules/import_rules/request_schema'; export * from '../../rule_management/api/rules/patch_rule/patch_rules_schema'; export * from '../../rule_management/api/rules/read_rule/query_rules_schema'; export * from './query_signals_index_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts index 6ea1ba7e4111f..1753ca13399f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts @@ -5,6 +5,22 @@ * 2.0. */ +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; + +import { + getImportRulesWithIdSchemaMock, + ruleIdsToNdJsonString, + rulesToNdJsonString, +} from '../../../../../../../common/detection_engine/rule_management/mocks'; + +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { + mlServicesMock, + mlAuthzMock as mockMlAuthzFactory, +} from '../../../../../machine_learning/mocks'; + +import type { requestMock } from '../../../../routes/__mocks__'; +import { createMockConfig, requestContextMock, serverMock } from '../../../../routes/__mocks__'; import { buildHapiStream } from '../../../../routes/__mocks__/utils'; import { getImportRulesRequest, @@ -14,22 +30,10 @@ import { getFindResultWithSingleHit, getBasicEmptySearchResponse, } from '../../../../routes/__mocks__/request_responses'; -import type { requestMock } from '../../../../routes/__mocks__'; -import { createMockConfig, requestContextMock, serverMock } from '../../../../routes/__mocks__'; -import { - mlServicesMock, - mlAuthzMock as mockMlAuthzFactory, -} from '../../../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../../../machine_learning/authz'; -import { importRulesRoute } from './route'; + import * as createRulesAndExceptionsStreamFromNdJson from '../../../logic/import/create_rules_stream_from_ndjson'; -import { - getImportRulesWithIdSchemaMock, - ruleIdsToNdJsonString, - rulesToNdJsonString, -} from '../../../../../../../common/detection_engine/rule_management/mocks'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; +import { importRulesRoute } from './route'; jest.mock('../../../../../machine_learning/authz', () => mockMlAuthzFactory.create()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts index 5adf0656f67ec..47a4a4832b034 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts @@ -9,6 +9,7 @@ import { Readable } from 'stream'; import { createPromiseFromStreams } from '@kbn/utils'; import { createRulesAndExceptionsStreamFromNdJson } from './create_rules_stream_from_ndjson'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; + import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; import { getOutputDetailsSample, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts index f8184a687457f..e325496225d9f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts @@ -24,10 +24,9 @@ import type { ImportExceptionsListSchema, } from '@kbn/securitysolution-io-ts-list-types'; -import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; import { RuleToImport, - importRuleValidateTypeDependents, + validateRuleToImport, } from '../../../../../../common/detection_engine/rule_management'; import { parseNdjsonStrings, @@ -48,9 +47,7 @@ export const validateRulesStream = (): Transform => { })); }; -export const validateRules = ( - rules: Array -): Array => { +export const validateRules = (rules: Array): Array => { return rules.map((obj: RuleToImport | Error) => { if (!(obj instanceof Error)) { const decoded = RuleToImport.decode(obj); @@ -59,7 +56,7 @@ export const validateRules = ( return new BadRequestError(formatErrors(errors).join()); }; const onRight = (schema: RuleToImport): BadRequestError | RuleToImport => { - const validationErrors = importRuleValidateTypeDependents(schema); + const validationErrors = validateRuleToImport(schema); if (validationErrors.length) { return new BadRequestError(validationErrors.join()); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts index 3bf848651e1f9..2de1816e4541f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts @@ -5,17 +5,19 @@ * 2.0. */ +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; + import { requestContextMock } from '../../../routes/__mocks__'; -import { importRules } from './import_rules_utils'; import { getRuleMock, getEmptyFindResult, getFindResultWithSingleHit, } from '../../../routes/__mocks__/request_responses'; -import { getQueryRuleParams } from '../../../rule_schema/mocks'; -import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; + import { createRules } from '../crud/create_rules'; import { patchRules } from '../crud/patch_rules'; +import { importRules } from './import_rules_utils'; jest.mock('../crud/create_rules'); jest.mock('../crud/patch_rules'); From e04f3189dc9971a33a904772d5fff4629b9e3119 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 19:19:51 +0200 Subject: [PATCH 11/15] TEMP COMMIT. REBASE ME --- .../rules/import_rules/request_schema.test.ts | 14 ++-- .../api/rules/import_rules/request_schema.ts | 4 +- .../import_rules/response_schema.test.ts} | 71 ++++++++++--------- .../rules/import_rules/response_schema.ts} | 15 ++-- .../detection_engine/rule_management/index.ts | 1 + .../schemas/response/index.ts | 3 +- .../api/rules/import_rules/route.ts | 14 ++-- 7 files changed, 59 insertions(+), 63 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/{schemas/response/import_rules_schema.test.ts => rule_management/api/rules/import_rules/response_schema.test.ts} (82%) rename x-pack/plugins/security_solution/common/detection_engine/{schemas/response/import_rules_schema.ts => rule_management/api/rules/import_rules/response_schema.ts} (69%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts index 19bc424175427..bd63dd7472aaf 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts @@ -9,14 +9,14 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { ImportRulesPayloadSchema } from './request_schema'; +import { ImportRulesRequestBody } from './request_schema'; describe('Import rules schema', () => { - describe('importRulesPayloadSchema', () => { + describe('ImportRulesRequestBody', () => { test('does not validate with an empty object', () => { const payload = {}; - const decoded = ImportRulesPayloadSchema.decode(payload); + const decoded = ImportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -26,11 +26,11 @@ describe('Import rules schema', () => { }); test('does not validate with a made string', () => { - const payload: Omit & { madeUpKey: string } = { + const payload: Omit & { madeUpKey: string } = { madeUpKey: 'madeupstring', }; - const decoded = ImportRulesPayloadSchema.decode(payload); + const decoded = ImportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -40,9 +40,9 @@ describe('Import rules schema', () => { }); test('does validate with a file object', () => { - const payload: ImportRulesPayloadSchema = { file: {} }; + const payload: ImportRulesRequestBody = { file: {} }; - const decoded = ImportRulesPayloadSchema.decode(payload); + const decoded = ImportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts index 1ecfc7cc61b89..cd99c1397c9bc 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts @@ -7,8 +7,8 @@ import * as t from 'io-ts'; -export type ImportRulesPayloadSchema = t.TypeOf; -export const ImportRulesPayloadSchema = t.exact( +export type ImportRulesRequestBody = t.TypeOf; +export const ImportRulesRequestBody = t.exact( t.type({ file: t.object, }) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.test.ts similarity index 82% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.test.ts index db1fc33e9d44e..2f11e1a9c120f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.test.ts @@ -8,15 +8,15 @@ import { pipe } from 'fp-ts/lib/pipeable'; import type { Either } from 'fp-ts/lib/Either'; import { left } from 'fp-ts/lib/Either'; -import type { ImportRulesSchema } from './import_rules_schema'; -import { importRulesSchema } from './import_rules_schema'; -import type { ErrorSchema } from './error_schema'; import type { Errors } from 'io-ts'; + import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import type { ErrorSchema } from '../../../../schemas/response/error_schema'; +import { ImportRulesResponse } from './response_schema'; -describe('import_rules_schema', () => { +describe('Import rules response schema', () => { test('it should validate an empty import response with no errors', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: true, success_count: 0, rules_count: 0, @@ -25,7 +25,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -34,7 +34,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with a single error', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -43,7 +43,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -52,7 +52,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with a single exceptions error', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -61,7 +61,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -70,7 +70,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with two errors', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -82,7 +82,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -91,7 +91,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with two exception errors', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -103,7 +103,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -112,7 +112,7 @@ describe('import_rules_schema', () => { }); test('it should NOT validate a success_count that is a negative number', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: -1, rules_count: 0, @@ -121,7 +121,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -132,7 +132,7 @@ describe('import_rules_schema', () => { }); test('it should NOT validate a exceptions_success_count that is a negative number', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -141,7 +141,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: -1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -170,7 +170,7 @@ describe('import_rules_schema', () => { >; } >; - const payload: Omit & { success: string } = { + const payload: Omit & { success: string } = { success: 'hello', success_count: 0, rules_count: 0, @@ -179,7 +179,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded as UnsafeCastForTest); const message = pipe(checked, foldLeftRight); @@ -207,17 +207,18 @@ describe('import_rules_schema', () => { >; } >; - const payload: Omit & { exceptions_success: string } = - { - success: true, - success_count: 0, - rules_count: 0, - errors: [], - exceptions_errors: [], - exceptions_success: 'hello', - exceptions_success_count: 0, - }; - const decoded = importRulesSchema.decode(payload); + const payload: Omit & { + exceptions_success: string; + } = { + success: true, + success_count: 0, + rules_count: 0, + errors: [], + exceptions_errors: [], + exceptions_success: 'hello', + exceptions_success_count: 0, + }; + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded as UnsafeCastForTest); const message = pipe(checked, foldLeftRight); @@ -228,7 +229,7 @@ describe('import_rules_schema', () => { }); test('it should NOT validate a success an extra invalid field', () => { - const payload: ImportRulesSchema & { invalid_field: string } = { + const payload: ImportRulesResponse & { invalid_field: string } = { success: true, success_count: 0, rules_count: 0, @@ -238,7 +239,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -248,7 +249,7 @@ describe('import_rules_schema', () => { test('it should NOT validate an extra field in the second position of the errors array', () => { type InvalidError = ErrorSchema & { invalid_data?: string }; - const payload: Omit & { + const payload: Omit & { errors: InvalidError[]; } = { success: true, @@ -262,7 +263,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.ts similarity index 69% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.ts index d577b2135d58d..77ccd0812c2c9 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.ts @@ -5,22 +5,19 @@ * 2.0. */ -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; +import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; +import { errorSchema } from '../../../../schemas/response/error_schema'; -import { success, success_count } from '../common/schemas'; -import { errorSchema } from './error_schema'; - -export const importRulesSchema = t.exact( +export type ImportRulesResponse = t.TypeOf; +export const ImportRulesResponse = t.exact( t.type({ exceptions_success: t.boolean, exceptions_success_count: PositiveInteger, exceptions_errors: t.array(errorSchema), rules_count: PositiveInteger, - success, - success_count, + success: t.boolean, + success_count: PositiveInteger, errors: t.array(errorSchema), }) ); - -export type ImportRulesSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index a3b5b3fc7f9f2..6cf3780e6d348 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -14,6 +14,7 @@ export * from './api/rules/export_rules/request_schema'; export * from './api/rules/find_rules/request_schema_validation'; export * from './api/rules/find_rules/request_schema'; export * from './api/rules/import_rules/request_schema'; +export * from './api/rules/import_rules/response_schema'; // export * from './api/urls'; export * from './model/export/export_rules_details_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts index 55db3111b229e..9b82189782908 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts @@ -5,6 +5,5 @@ * 2.0. */ -export * from './error_schema'; -export * from './import_rules_schema'; export * from './rules_bulk_schema'; +export * from './error_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index 03d1206080ed2..e306caac12194 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -15,11 +15,11 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { ImportQuerySchemaDecoded } from '@kbn/securitysolution-io-ts-types'; import { importQuerySchema } from '@kbn/securitysolution-io-ts-types'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { RuleToImport } from '../../../../../../../common/detection_engine/rule_management'; -import type { ImportRulesSchema as ImportRulesResponseSchema } from '../../../../../../../common/detection_engine/schemas/response/import_rules_schema'; -import { importRulesSchema as importRulesResponseSchema } from '../../../../../../../common/detection_engine/schemas/response/import_rules_schema'; +import { ImportRulesResponse } from '../../../../../../../common/detection_engine/rule_management'; + import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { ConfigType } from '../../../../../../config'; import type { SetupPlugins } from '../../../../../../plugin'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; @@ -131,9 +131,7 @@ export const importRulesRoute = ( let parsedRules; let actionErrors: BulkError[] = []; - const actualRules = rules.filter( - (rule): rule is RuleToImport => !(rule instanceof Error) - ); + const actualRules = rules.filter((rule): rule is RuleToImport => !(rule instanceof Error)); if (actualRules.some((rule) => rule.actions && rule.actions.length > 0)) { const [nonExistentActionErrors, uniqueParsedObjects] = await getInvalidConnectors( @@ -173,7 +171,7 @@ export const importRulesRoute = ( return false; } }); - const importRules: ImportRulesResponseSchema = { + const importRules: ImportRulesResponse = { success: errorsResp.length === 0, success_count: successes.length, rules_count: rules.length, @@ -183,7 +181,7 @@ export const importRulesRoute = ( exceptions_success_count: exceptionsSuccessCount, }; - const [validated, errors] = validate(importRules, importRulesResponseSchema); + const [validated, errors] = validate(importRules, ImportRulesResponse); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { From 7514aaf0d9f0c5d305ad960720c7053d6adb61a1 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 20:28:21 +0200 Subject: [PATCH 12/15] TEMP COMMIT. REBASE ME --- .../bulk_patch_rules/request_schema.test.ts | 4 +- .../rules/bulk_patch_rules/request_schema.ts | 4 +- .../api/rules/import_rules/request_schema.ts | 3 + .../patch_rules_type_dependents.test.ts | 117 ------- ..._schema.mock.ts => request_schema.mock.ts} | 6 +- ..._schema.test.ts => request_schema.test.ts} | 312 +++++++++--------- ...atch_rules_schema.ts => request_schema.ts} | 15 +- .../request_schema_validation.test.ts | 116 +++++++ ...ndents.ts => request_schema_validation.ts} | 49 +-- .../detection_engine/rule_management/index.ts | 2 + .../detection_engine/rule_management/mocks.ts | 1 + .../detection_engine/schemas/request/index.ts | 1 - .../rule_management/api/api.test.ts | 21 +- .../api/rules/patch_rule/route.test.ts | 12 +- .../api/rules/patch_rule/route.ts | 23 +- .../normalization/rule_converters.ts | 76 +++-- 16 files changed, 394 insertions(+), 368 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents.test.ts rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/{patch_rules_schema.mock.ts => request_schema.mock.ts} (81%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/{patch_rules_schema.test.ts => request_schema.test.ts} (80%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/{patch_rules_schema.ts => request_schema.ts} (53%) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema_validation.test.ts rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/{patch_rules_type_dependents.ts => request_schema_validation.ts} (80%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.test.ts index c1f46fe6d0c83..2feede822e1d1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.test.ts @@ -6,7 +6,7 @@ */ import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; -import type { PatchRulesSchema } from '../patch_rule/patch_rules_schema'; +import type { PatchRuleRequestBody } from '../patch_rule/request_schema'; import { BulkPatchRulesRequestBody } from './request_schema'; // only the basics of testing are here. @@ -73,7 +73,7 @@ describe('Bulk patch rules request schema', () => { }); test('cannot set "note" to be anything other than a string', () => { - const payload: Array & { note?: object }> = [ + const payload: Array & { note?: object }> = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { note: { someprop: 'some value here' } }, ]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.ts index 9246f730ab5b7..58fe6e3286b16 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_patch_rules/request_schema.ts @@ -6,10 +6,10 @@ */ import * as t from 'io-ts'; -import { patchRulesSchema } from '../patch_rule/patch_rules_schema'; +import { PatchRuleRequestBody } from '../patch_rule/request_schema'; /** * Request body parameters of the API route. */ export type BulkPatchRulesRequestBody = t.TypeOf; -export const BulkPatchRulesRequestBody = t.array(patchRulesSchema); +export const BulkPatchRulesRequestBody = t.array(PatchRuleRequestBody); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts index cd99c1397c9bc..8f64df3ea71b1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts @@ -7,6 +7,9 @@ import * as t from 'io-ts'; +/** + * Request body parameters of the API route. + */ export type ImportRulesRequestBody = t.TypeOf; export const ImportRulesRequestBody = t.exact( t.type({ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents.test.ts deleted file mode 100644 index abb64ec5522f2..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 { - getPatchRulesSchemaMock, - getPatchThresholdRulesSchemaMock, -} from './patch_rules_schema.mock'; -import type { PatchRulesSchema, ThresholdPatchSchema } from './patch_rules_schema'; -import { patchRuleValidateTypeDependents } from './patch_rules_type_dependents'; - -describe('patch_rules_type_dependents', () => { - test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_id: '123', - }; - delete schema.timeline_title; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); - }); - - test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_id: '123', - timeline_title: '', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_title" cannot be an empty string']); - }); - - test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_id: '', - timeline_title: 'some-title', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_id" cannot be an empty string']); - }); - - test('You cannot have timeline_title without timeline_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_title: 'some-title', - }; - delete schema.timeline_id; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); - }); - - test('You cannot have both an id and a rule_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - id: 'some-id', - rule_id: 'some-rule-id', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); - }); - - test('You must set either an id or a rule_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - }; - delete schema.id; - delete schema.rule_id; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['either "id" or "rule_id" must be set']); - }); - - test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { - const schema: ThresholdPatchSchema = { - ...getPatchThresholdRulesSchemaMock(), - threshold: { - field: '', - value: -1, - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); - }); - - test('threshold.field should contain 3 items or less', () => { - const schema: ThresholdPatchSchema = { - ...getPatchThresholdRulesSchemaMock(), - threshold: { - field: ['field-1', 'field-2', 'field-3', 'field-4'], - value: 1, - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Number of fields must be 3 or less']); - }); - - test('threshold.cardinality[0].field should not be in threshold.field', () => { - const schema: ThresholdPatchSchema = { - ...getPatchThresholdRulesSchemaMock(), - threshold: { - field: ['field-1', 'field-2', 'field-3'], - value: 1, - cardinality: [ - { - field: 'field-1', - value: 2, - }, - ], - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.mock.ts similarity index 81% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.mock.ts index 1b3dab2e00691..285e773456d98 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { PatchRulesSchema, ThresholdPatchSchema } from './patch_rules_schema'; +import type { PatchRuleRequestBody, ThresholdPatchRuleRequestBody } from './request_schema'; -export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ +export const getPatchRulesSchemaMock = (): PatchRuleRequestBody => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -18,7 +18,7 @@ export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ rule_id: 'rule-1', }); -export const getPatchThresholdRulesSchemaMock = (): ThresholdPatchSchema => ({ +export const getPatchThresholdRulesSchemaMock = (): ThresholdPatchRuleRequestBody => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.test.ts similarity index 80% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.test.ts index f7fcbc3e9084b..731a7c173cf0d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.test.ts @@ -5,56 +5,56 @@ * 2.0. */ -import type { PatchRulesSchema } from './patch_rules_schema'; -import { patchRulesSchema } from './patch_rules_schema'; -import { getPatchRulesSchemaMock } from './patch_rules_schema.mock'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + import { getListArrayMock } from '../../../../schemas/types/lists.mock'; +import { PatchRuleRequestBody } from './request_schema'; +import { getPatchRulesSchemaMock } from './request_schema.mock'; -describe('patch_rules_schema', () => { +describe('Patch rule request schema', () => { test('[id] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; expect(message.schema).toEqual(expected); }); test('[rule_id] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', }; expect(message.schema).toEqual(expected); }); test('[rule_id, description] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', }; @@ -62,16 +62,16 @@ describe('patch_rules_schema', () => { }); test('[id, description] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', }; @@ -79,16 +79,16 @@ describe('patch_rules_schema', () => { }); test('[id, risk_score] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', risk_score: 10, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', risk_score: 10, }; @@ -96,17 +96,17 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -115,18 +115,18 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -136,18 +136,18 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -157,7 +157,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -165,11 +165,11 @@ describe('patch_rules_schema', () => { name: 'some-name', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -180,7 +180,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -188,11 +188,11 @@ describe('patch_rules_schema', () => { name: 'some-name', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -203,7 +203,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name, severity] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -212,11 +212,11 @@ describe('patch_rules_schema', () => { severity: 'low', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -228,7 +228,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name, severity] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -237,11 +237,11 @@ describe('patch_rules_schema', () => { severity: 'low', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -253,7 +253,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name, severity, type] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -263,11 +263,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -280,7 +280,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name, severity, type] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -290,11 +290,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -307,7 +307,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -318,11 +318,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -336,7 +336,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name, severity, type, interval] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -347,11 +347,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -365,7 +365,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -378,11 +378,11 @@ describe('patch_rules_schema', () => { query: 'some query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -398,7 +398,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -411,11 +411,11 @@ describe('patch_rules_schema', () => { language: 'kuery', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -431,7 +431,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -439,11 +439,11 @@ describe('patch_rules_schema', () => { name: 'some-name', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -454,7 +454,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, index, name, severity, type, filters] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -466,11 +466,11 @@ describe('patch_rules_schema', () => { filters: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -485,7 +485,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, type, filters] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -497,11 +497,11 @@ describe('patch_rules_schema', () => { filters: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -516,7 +516,7 @@ describe('patch_rules_schema', () => { }); test('allows references to be sent as a valid value to patch with', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -531,11 +531,11 @@ describe('patch_rules_schema', () => { language: 'kuery', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -553,48 +553,48 @@ describe('patch_rules_schema', () => { }); test('does not default references to an array', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).references).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).references).toEqual(undefined); }); test('does not default interval', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).interval).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).interval).toEqual(undefined); }); test('does not default max_signals', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).max_signals).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).max_signals).toEqual(undefined); }); test('references cannot be numbers', () => { - const payload: Omit & { references: number[] } = { + const payload: Omit & { references: number[] } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', references: [5], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); @@ -602,13 +602,13 @@ describe('patch_rules_schema', () => { }); test('indexes cannot be numbers', () => { - const payload: Omit & { index: number[] } = { + const payload: Omit & { index: number[] } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'query', index: [5], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -619,16 +619,16 @@ describe('patch_rules_schema', () => { }); test('saved_id is not required when type is saved_query and will validate without it', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'saved_query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'saved_query', }; @@ -642,7 +642,7 @@ describe('patch_rules_schema', () => { saved_id: 'some id', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -661,7 +661,7 @@ describe('patch_rules_schema', () => { filters: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -680,7 +680,7 @@ describe('patch_rules_schema', () => { language: 'kuery', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -699,7 +699,7 @@ describe('patch_rules_schema', () => { language: 'lucene', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -719,7 +719,7 @@ describe('patch_rules_schema', () => { language: 'something-made-up', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -735,7 +735,7 @@ describe('patch_rules_schema', () => { max_signals: -1, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -751,7 +751,7 @@ describe('patch_rules_schema', () => { max_signals: 0, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); @@ -765,7 +765,7 @@ describe('patch_rules_schema', () => { max_signals: 1, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -778,16 +778,16 @@ describe('patch_rules_schema', () => { }); test('meta can be patched', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), meta: { whateverYouWant: 'anything_at_all' }, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), meta: { whateverYouWant: 'anything_at_all' }, }; @@ -795,12 +795,12 @@ describe('patch_rules_schema', () => { }); test('You cannot patch meta as a string', () => { - const payload: Omit & { meta: string } = { + const payload: Omit & { meta: string } = { ...getPatchRulesSchemaMock(), meta: 'should not work', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -810,12 +810,12 @@ describe('patch_rules_schema', () => { }); test('filters cannot be a string', () => { - const payload: Omit & { filters: string } = { + const payload: Omit & { filters: string } = { ...getPatchRulesSchemaMock(), filters: 'should not work', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -825,12 +825,12 @@ describe('patch_rules_schema', () => { }); test('name cannot be an empty string', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), name: '', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "name"']); @@ -838,12 +838,12 @@ describe('patch_rules_schema', () => { }); test('description cannot be an empty string', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), description: '', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "description"']); @@ -851,32 +851,32 @@ describe('patch_rules_schema', () => { }); test('threat is not defaulted to empty array on patch', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).threat).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).threat).toEqual(undefined); }); test('threat is not defaulted to undefined on patch with empty array', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).threat).toEqual([]); + expect((message.schema as PatchRuleRequestBody).threat).toEqual([]); }); test('threat is valid when updated with all sub-objects', () => { - const threat: PatchRulesSchema['threat'] = [ + const threat: PatchRuleRequestBody['threat'] = [ { framework: 'fake', tactic: { @@ -893,12 +893,12 @@ describe('patch_rules_schema', () => { ], }, ]; - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -906,7 +906,7 @@ describe('patch_rules_schema', () => { }); test('threat is invalid when updated with missing property framework', () => { - const threat: Omit = [ + const threat: Omit = [ { tactic: { id: 'fakeId', @@ -922,12 +922,12 @@ describe('patch_rules_schema', () => { ], }, ]; - const payload: Omit = { + const payload: Omit = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -937,7 +937,7 @@ describe('patch_rules_schema', () => { }); test('threat is invalid when updated with missing tactic sub-object', () => { - const threat: Omit = [ + const threat: Omit = [ { framework: 'fake', technique: [ @@ -950,12 +950,12 @@ describe('patch_rules_schema', () => { }, ]; - const payload: Omit = { + const payload: Omit = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -965,7 +965,7 @@ describe('patch_rules_schema', () => { }); test('threat is valid when updated with missing technique', () => { - const threat: Omit = [ + const threat: Omit = [ { framework: 'fake', tactic: { @@ -976,12 +976,12 @@ describe('patch_rules_schema', () => { }, ]; - const payload: Omit = { + const payload: Omit = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -989,17 +989,17 @@ describe('patch_rules_schema', () => { }); test('validates with timeline_id and timeline_title', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), timeline_id: 'some-id', timeline_title: 'some-title', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), timeline_id: 'some-id', timeline_title: 'some-title', @@ -1008,12 +1008,12 @@ describe('patch_rules_schema', () => { }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { - const payload: Omit & { severity: string } = { + const payload: Omit & { severity: string } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', severity: 'junk', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); @@ -1022,7 +1022,7 @@ describe('patch_rules_schema', () => { describe('note', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, note] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1035,11 +1035,11 @@ describe('patch_rules_schema', () => { note: '# some documentation markdown', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1055,16 +1055,16 @@ describe('patch_rules_schema', () => { }); test('note can be patched', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', note: '# new documentation markdown', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', note: '# new documentation markdown', }; @@ -1072,14 +1072,14 @@ describe('patch_rules_schema', () => { }); test('You cannot patch note as an object', () => { - const payload: Omit & { note: object } = { + const payload: Omit & { note: object } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', note: { someProperty: 'something else here', }, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1090,12 +1090,12 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are missing "group"', () => { - const payload: Omit = { + const payload: Omit = { ...getPatchRulesSchemaMock(), actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1105,12 +1105,12 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are missing "id"', () => { - const payload: Omit = { + const payload: Omit = { ...getPatchRulesSchemaMock(), actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1120,12 +1120,12 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are missing "params"', () => { - const payload: Omit = { + const payload: Omit = { ...getPatchRulesSchemaMock(), actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1135,7 +1135,7 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { - const payload: Omit = { + const payload: Omit = { ...getPatchRulesSchemaMock(), actions: [ { @@ -1147,7 +1147,7 @@ describe('patch_rules_schema', () => { ], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1158,7 +1158,7 @@ describe('patch_rules_schema', () => { describe('exception_list', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filters, note, and exceptions_list] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1173,11 +1173,11 @@ describe('patch_rules_schema', () => { exceptions_list: getListArrayMock(), }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1208,7 +1208,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1224,11 +1224,11 @@ describe('patch_rules_schema', () => { exceptions_list: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1263,7 +1263,7 @@ describe('patch_rules_schema', () => { exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1275,7 +1275,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1290,11 +1290,11 @@ describe('patch_rules_schema', () => { note: '# some markdown', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.ts similarity index 53% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.ts index e86d2e58e13fd..538ea50bb9455 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema.ts @@ -6,7 +6,6 @@ */ import * as t from 'io-ts'; - import { patchTypeSpecific, sharedPatchSchema, @@ -14,10 +13,14 @@ import { } from '../../../../schemas/request/rule_schemas'; /** - * All of the patch elements should default to undefined if not set + * Request body parameters of the API route. + * All of the patch elements should default to undefined if not set. */ -export const patchRulesSchema = t.intersection([patchTypeSpecific, sharedPatchSchema]); -export type PatchRulesSchema = t.TypeOf; +export type PatchRuleRequestBody = t.TypeOf; +export const PatchRuleRequestBody = t.intersection([patchTypeSpecific, sharedPatchSchema]); -const thresholdPatchSchema = t.intersection([thresholdPatchParams, sharedPatchSchema]); -export type ThresholdPatchSchema = t.TypeOf; +export type ThresholdPatchRuleRequestBody = t.TypeOf; +export const ThresholdPatchRuleRequestBody = t.intersection([ + thresholdPatchParams, + sharedPatchSchema, +]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema_validation.test.ts new file mode 100644 index 0000000000000..c00e3c38fb91b --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema_validation.test.ts @@ -0,0 +1,116 @@ +/* + * 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 type { PatchRuleRequestBody, ThresholdPatchRuleRequestBody } from './request_schema'; +import { getPatchRulesSchemaMock, getPatchThresholdRulesSchemaMock } from './request_schema.mock'; +import { validatePatchRuleRequestBody } from './request_schema_validation'; + +describe('Patch rule request schema, additional validation', () => { + describe('validatePatchRuleRequestBody', () => { + test('You cannot omit timeline_title when timeline_id is present', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_id: '123', + }; + delete schema.timeline_title; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_id: '123', + timeline_title: '', + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['"timeline_title" cannot be an empty string']); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_id: '', + timeline_title: 'some-title', + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['"timeline_id" cannot be an empty string']); + }); + + test('You cannot have timeline_title without timeline_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_title: 'some-title', + }; + delete schema.timeline_id; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); + }); + + test('You cannot have both an id and a rule_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + id: 'some-id', + rule_id: 'some-rule-id', + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); + }); + + test('You must set either an id or a rule_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + }; + delete schema.id; + delete schema.rule_id; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['either "id" or "rule_id" must be set']); + }); + + test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { + const schema: ThresholdPatchRuleRequestBody = { + ...getPatchThresholdRulesSchemaMock(), + threshold: { + field: '', + value: -1, + }, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); + }); + + test('threshold.field should contain 3 items or less', () => { + const schema: ThresholdPatchRuleRequestBody = { + ...getPatchThresholdRulesSchemaMock(), + threshold: { + field: ['field-1', 'field-2', 'field-3', 'field-4'], + value: 1, + }, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['Number of fields must be 3 or less']); + }); + + test('threshold.cardinality[0].field should not be in threshold.field', () => { + const schema: ThresholdPatchRuleRequestBody = { + ...getPatchThresholdRulesSchemaMock(), + threshold: { + field: ['field-1', 'field-2', 'field-3'], + value: 1, + cardinality: [ + { + field: 'field-1', + value: 2, + }, + ], + }, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema_validation.ts similarity index 80% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema_validation.ts index 263f28e28ac30..72d10bf5e58de 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/patch_rule/request_schema_validation.ts @@ -5,9 +5,31 @@ * 2.0. */ -import type { PatchRulesSchema } from './patch_rules_schema'; +import type { PatchRuleRequestBody } from './request_schema'; -export const validateTimelineId = (rule: PatchRulesSchema): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validatePatchRuleRequestBody = (rule: PatchRuleRequestBody): string[] => { + return [ + ...validateId(rule), + ...validateTimelineId(rule), + ...validateTimelineTitle(rule), + ...validateThreshold(rule), + ]; +}; + +const validateId = (rule: PatchRuleRequestBody): string[] => { + if (rule.id != null && rule.rule_id != null) { + return ['both "id" and "rule_id" cannot exist, choose one or the other']; + } else if (rule.id == null && rule.rule_id == null) { + return ['either "id" or "rule_id" must be set']; + } else { + return []; + } +}; + +const validateTimelineId = (rule: PatchRuleRequestBody): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +42,7 @@ export const validateTimelineId = (rule: PatchRulesSchema): string[] => { return []; }; -export const validateTimelineTitle = (rule: PatchRulesSchema): string[] => { +const validateTimelineTitle = (rule: PatchRuleRequestBody): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,17 +55,7 @@ export const validateTimelineTitle = (rule: PatchRulesSchema): string[] => { return []; }; -export const validateId = (rule: PatchRulesSchema): string[] => { - if (rule.id != null && rule.rule_id != null) { - return ['both "id" and "rule_id" cannot exist, choose one or the other']; - } else if (rule.id == null && rule.rule_id == null) { - return ['either "id" or "rule_id" must be set']; - } else { - return []; - } -}; - -export const validateThreshold = (rule: PatchRulesSchema): string[] => { +const validateThreshold = (rule: PatchRuleRequestBody): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if (!rule.threshold) { @@ -65,12 +77,3 @@ export const validateThreshold = (rule: PatchRulesSchema): string[] => { } return errors; }; - -export const patchRuleValidateTypeDependents = (rule: PatchRulesSchema): string[] => { - return [ - ...validateId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 6cf3780e6d348..1ec1c723bf727 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -15,6 +15,8 @@ export * from './api/rules/find_rules/request_schema_validation'; export * from './api/rules/find_rules/request_schema'; export * from './api/rules/import_rules/request_schema'; export * from './api/rules/import_rules/response_schema'; +export * from './api/rules/patch_rule/request_schema_validation'; +export * from './api/rules/patch_rule/request_schema'; // export * from './api/urls'; export * from './model/export/export_rules_details_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts index 0332c07134931..51711507f76b1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts @@ -6,6 +6,7 @@ */ export * from './api/rules/bulk_actions/request_schema.mock'; +export * from './api/rules/patch_rule/request_schema.mock'; export * from './model/export/export_rules_details_schema.mock'; export * from './model/import/rule_to_import.mock'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index 1bbb06203bf66..b6433144dc2bf 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -6,7 +6,6 @@ */ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports -export * from '../../rule_management/api/rules/patch_rule/patch_rules_schema'; export * from '../../rule_management/api/rules/read_rule/query_rules_schema'; export * from './query_signals_index_schema'; export * from './rule_schemas'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index 5cd6dd7cc92c4..8d10b3dce5823 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -8,6 +8,18 @@ import { buildEsQuery } from '@kbn/es-query'; import { KibanaServices } from '../../../common/lib/kibana'; +import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../common/detection_engine/rule_exceptions'; +import { getPatchRulesSchemaMock } from '../../../../common/detection_engine/rule_management/mocks'; + +import { + getCreateRulesSchemaMock, + getUpdateRulesSchemaMock, +} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; +import { getRulesSchemaMock } from '../../../../common/detection_engine/schemas/response/rules_schema.mocks'; + +import { rulesMock } from '../logic/mock'; +import type { FindRulesReferencedByExceptionsListProp } from '../logic/types'; + import { createRule, updateRule, @@ -22,15 +34,6 @@ import { previewRule, findRuleExceptionReferences, } from './api'; -import { getRulesSchemaMock } from '../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import { - getCreateRulesSchemaMock, - getUpdateRulesSchemaMock, -} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../common/detection_engine/rule_exceptions'; -import { getPatchRulesSchemaMock } from '../../../../common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.mock'; -import { rulesMock } from '../logic/mock'; -import type { FindRulesReferencedByExceptionsListProp } from '../logic/types'; const abortCtrl = new AbortController(); const mockKibanaServices = KibanaServices.get as jest.Mock; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts index 8012d8f475e94..1a5a747f1490e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts @@ -6,11 +6,15 @@ */ import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { getPatchRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_management/mocks'; + +import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory, } from '../../../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../../../machine_learning/authz'; + +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; import { getEmptyFindResult, getRuleMock, @@ -19,13 +23,13 @@ import { nonRuleFindResult, typicalMlRulePayload, } from '../../../../routes/__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; -import { patchRuleRoute } from './route'; -import { getPatchRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema.mock'; + import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; // eslint-disable-next-line no-restricted-imports import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { patchRuleRoute } from './route'; + jest.mock('../../../../../machine_learning/authz', () => mockMlAuthzFactory.create()); jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 924f221b9ffd0..0e15e73b5e34d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -6,22 +6,27 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { patchRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_type_dependents'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { + PatchRuleRequestBody, + validatePatchRuleRequestBody, +} from '../../../../../../../common/detection_engine/rule_management'; + import { buildRouteValidationNonExact } from '../../../../../../utils/build_validation/route_validation'; -import { patchRulesSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { SetupPlugins } from '../../../../../../plugin'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../../machine_learning/validation'; -import { patchRules } from '../../../logic/crud/patch_rules'; import { buildSiemResponse } from '../../../../routes/utils'; -import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; -import { getIdError } from '../../../utils/utils'; -import { transformValidate } from '../../../utils/validate'; + import { readRules } from '../../../logic/crud/read_rules'; +import { patchRules } from '../../../logic/crud/patch_rules'; +import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; // eslint-disable-next-line no-restricted-imports import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { getIdError } from '../../../utils/utils'; +import { transformValidate } from '../../../utils/validate'; export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.patch( @@ -31,7 +36,7 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPl // Use non-exact validation because everything is optional in patch - since everything is optional, // io-ts can't find the right schema from the type specific union and the exact check breaks. // We do type specific validation after fetching the existing rule so we know the rule type. - body: buildRouteValidationNonExact(patchRulesSchema), + body: buildRouteValidationNonExact(PatchRuleRequestBody), }, options: { tags: ['access:securitySolution'], @@ -39,7 +44,7 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPl }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = patchRuleValidateTypeDependents(request.body); + const validationErrors = validatePatchRuleRequestBody(request.body); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index cb7025a81002d..83aad3f4dcdf9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -8,37 +8,17 @@ import uuid from 'uuid'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; - +import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import type { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/common'; + import { - normalizeMachineLearningJobIds, - normalizeThresholdObject, -} from '../../../../../common/detection_engine/utils'; -import type { - InternalRuleCreate, - RuleParams, - TypeSpecificRuleParams, - BaseRuleParams, - EqlRuleParams, - EqlSpecificRuleParams, - ThreatRuleParams, - ThreatSpecificRuleParams, - QueryRuleParams, - QuerySpecificRuleParams, - SavedQuerySpecificRuleParams, - SavedQueryRuleParams, - ThresholdRuleParams, - ThresholdSpecificRuleParams, - MachineLearningRuleParams, - MachineLearningSpecificRuleParams, - InternalRuleUpdate, - NewTermsRuleParams, - NewTermsSpecificRuleParams, -} from '../../rule_schema'; -import { assertUnreachable } from '../../../../../common/utility_types'; + DEFAULT_INDICATOR_SOURCE_PATH, + DEFAULT_MAX_SIGNALS, + SERVER_APP_ID, +} from '../../../../../common/constants'; +import type { PatchRuleRequestBody } from '../../../../../common/detection_engine/rule_management'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; import type { RelatedIntegrationArray, @@ -67,20 +47,44 @@ import type { ThreatMatchPatchParams, ThresholdPatchParams, } from '../../../../../common/detection_engine/schemas/request'; -import type { PatchRulesSchema } from '../../../../../common/detection_engine/rule_management/api/rules/patch_rule/patch_rules_schema'; -import { - DEFAULT_INDICATOR_SOURCE_PATH, - DEFAULT_MAX_SIGNALS, - SERVER_APP_ID, -} from '../../../../../common/constants'; + import { transformAlertToRuleResponseAction, transformRuleToAlertAction, transformRuleToAlertResponseAction, } from '../../../../../common/detection_engine/transform_actions'; + +import { + normalizeMachineLearningJobIds, + normalizeThresholdObject, +} from '../../../../../common/detection_engine/utils'; + +import { assertUnreachable } from '../../../../../common/utility_types'; + // eslint-disable-next-line no-restricted-imports import type { LegacyRuleActions } from '../../rule_actions_legacy'; import { mergeRuleExecutionSummary } from '../../rule_monitoring'; +import type { + InternalRuleCreate, + RuleParams, + TypeSpecificRuleParams, + BaseRuleParams, + EqlRuleParams, + EqlSpecificRuleParams, + ThreatRuleParams, + ThreatSpecificRuleParams, + QueryRuleParams, + QuerySpecificRuleParams, + SavedQuerySpecificRuleParams, + SavedQueryRuleParams, + ThresholdRuleParams, + ThresholdSpecificRuleParams, + MachineLearningRuleParams, + MachineLearningSpecificRuleParams, + InternalRuleUpdate, + NewTermsRuleParams, + NewTermsSpecificRuleParams, +} from '../../rule_schema'; import { transformActions, transformFromAlertThrottle, @@ -323,7 +327,7 @@ const parseValidationError = (error: string | null): BadRequestError => { }; export const patchTypeSpecificSnakeToCamel = ( - params: PatchRulesSchema, + params: PatchRuleRequestBody, existingRule: RuleParams ): TypeSpecificRuleParams => { // Here we do the validation of patch params by rule type to ensure that the fields that are @@ -388,7 +392,7 @@ export const patchTypeSpecificSnakeToCamel = ( }; const versionExcludedKeys = ['enabled', 'id', 'rule_id']; -const incrementVersion = (nextParams: PatchRulesSchema, existingRule: RuleParams) => { +const incrementVersion = (nextParams: PatchRuleRequestBody, existingRule: RuleParams) => { // The the version from nextParams if it's provided if (nextParams.version) { return nextParams.version; @@ -410,7 +414,7 @@ const incrementVersion = (nextParams: PatchRulesSchema, existingRule: RuleParams // eslint-disable-next-line complexity export const convertPatchAPIToInternalSchema = ( - nextParams: PatchRulesSchema & { + nextParams: PatchRuleRequestBody & { related_integrations?: RelatedIntegrationArray; required_fields?: RequiredFieldArray; setup?: SetupGuide; From 00bc9374844363411fa4bf2b3a461f4238cec74a Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 21:02:19 +0200 Subject: [PATCH 13/15] TEMP COMMIT. REBASE ME --- .../rules/bulk_delete_rules/request_schema.ts | 4 ++-- .../api/rules/delete_rule/TODO | 0 ...hema.test.ts => query_rule_by_ids.test.ts} | 13 +++++------ ...y_rules_schema.ts => query_rule_by_ids.ts} | 6 ++--- ...s => query_rule_by_ids_validation.test.ts} | 14 +++++------ ...nts.ts => query_rule_by_ids_validation.ts} | 9 +++++--- .../detection_engine/rule_management/index.ts | 2 ++ .../detection_engine/schemas/request/index.ts | 3 +-- .../api/rules/bulk_delete_rules/route.ts | 8 ++++--- .../api/rules/delete_rule/route.ts | 23 ++++++++++--------- .../api/rules/read_rule/route.ts | 23 ++++++++++--------- 11 files changed, 55 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/delete_rule/TODO rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/{query_rules_schema.test.ts => query_rule_by_ids.test.ts} (72%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/{query_rules_schema.ts => query_rule_by_ids.ts} (72%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/{query_rules_type_dependents.test.ts => query_rule_by_ids_validation.test.ts} (61%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/{query_rules_type_dependents.ts => query_rule_by_ids_validation.ts} (66%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.ts index 2e5817cc70eb9..dd33a28edaa4d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_delete_rules/request_schema.ts @@ -6,10 +6,10 @@ */ import * as t from 'io-ts'; -import { queryRulesSchema } from '../read_rule/query_rules_schema'; +import { QueryRuleByIds } from '../read_rule/query_rule_by_ids'; /** * Request body parameters of the API route. */ export type BulkDeleteRulesRequestBody = t.TypeOf; -export const BulkDeleteRulesRequestBody = t.array(queryRulesSchema); +export const BulkDeleteRulesRequestBody = t.array(QueryRuleByIds); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/delete_rule/TODO b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/delete_rule/TODO new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids.test.ts similarity index 72% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids.test.ts index faccffb1e6864..d5ba9b37ae65a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids.test.ts @@ -5,17 +5,16 @@ * 2.0. */ -import type { QueryRulesSchema } from './query_rules_schema'; -import { queryRulesSchema } from './query_rules_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { QueryRuleByIds } from './query_rule_by_ids'; -describe('query_rules_schema', () => { +describe('Query rule by IDs schema', () => { test('empty objects do validate', () => { - const payload: Partial = {}; + const payload: Partial = {}; - const decoded = queryRulesSchema.decode(payload); + const decoded = QueryRuleByIds.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids.ts similarity index 72% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids.ts index 320998a254c9e..04dba9d50294a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids.ts @@ -8,12 +8,10 @@ import * as t from 'io-ts'; import { RuleObjectId, RuleSignatureId } from '../../../../rule_schema'; -export const queryRulesSchema = t.exact( +export type QueryRuleByIds = t.TypeOf; +export const QueryRuleByIds = t.exact( t.partial({ rule_id: RuleSignatureId, id: RuleObjectId, }) ); - -export type QueryRulesSchema = t.TypeOf; -export type QueryRulesSchemaDecoded = QueryRulesSchema; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids_validation.test.ts similarity index 61% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids_validation.test.ts index 060fd189a40a9..1d0981df5f411 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids_validation.test.ts @@ -5,22 +5,22 @@ * 2.0. */ -import type { QueryRulesSchema } from './query_rules_schema'; -import { queryRuleValidateTypeDependents } from './query_rules_type_dependents'; +import type { QueryRuleByIds } from './query_rule_by_ids'; +import { validateQueryRuleByIds } from './query_rule_by_ids_validation'; -describe('query_rules_type_dependents', () => { +describe('Query rule by IDs schema, additional validation', () => { test('You cannot have both an id and a rule_id', () => { - const schema: QueryRulesSchema = { + const schema: QueryRuleByIds = { id: 'some-id', rule_id: 'some-rule-id', }; - const errors = queryRuleValidateTypeDependents(schema); + const errors = validateQueryRuleByIds(schema); expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); }); test('You must set either an id or a rule_id', () => { - const schema: QueryRulesSchema = {}; - const errors = queryRuleValidateTypeDependents(schema); + const schema: QueryRuleByIds = {}; + const errors = validateQueryRuleByIds(schema); expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids_validation.ts similarity index 66% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids_validation.ts index 51652f38f5dd3..4f0a7eedc71dd 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/read_rule/query_rule_by_ids_validation.ts @@ -5,13 +5,16 @@ * 2.0. */ -import type { QueryRulesSchema } from './query_rules_schema'; +import type { QueryRuleByIds } from './query_rule_by_ids'; -export const queryRuleValidateTypeDependents = (schema: QueryRulesSchema): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateQueryRuleByIds = (schema: QueryRuleByIds): string[] => { return [...validateId(schema)]; }; -export const validateId = (rule: QueryRulesSchema): string[] => { +const validateId = (rule: QueryRuleByIds): string[] => { if (rule.id != null && rule.rule_id != null) { return ['both "id" and "rule_id" cannot exist, choose one or the other']; } else if (rule.id == null && rule.rule_id == null) { diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 1ec1c723bf727..ed8074c6e3f9a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -17,6 +17,8 @@ export * from './api/rules/import_rules/request_schema'; export * from './api/rules/import_rules/response_schema'; export * from './api/rules/patch_rule/request_schema_validation'; export * from './api/rules/patch_rule/request_schema'; +export * from './api/rules/read_rule/query_rule_by_ids_validation'; +export * from './api/rules/read_rule/query_rule_by_ids'; // export * from './api/urls'; export * from './model/export/export_rules_details_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index b6433144dc2bf..4e38f31304aa7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -6,7 +6,6 @@ */ // TODO: https://github.com/elastic/kibana/pull/142950 Delete re-exports -export * from '../../rule_management/api/rules/read_rule/query_rules_schema'; -export * from './query_signals_index_schema'; export * from './rule_schemas'; +export * from './query_signals_index_schema'; export * from './set_signal_status_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts index 5b248d31d1a5e..6f6a69c22d0f1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts @@ -9,8 +9,10 @@ import type { RouteConfig, RequestHandler, Logger } from '@kbn/core/server'; import { validate } from '@kbn/securitysolution-io-ts-utils'; import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../../../common/constants'; -import { BulkDeleteRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; -import { queryRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents'; +import { + BulkDeleteRulesRequestBody, + validateQueryRuleByIds, +} from '../../../../../../../common/detection_engine/rule_management'; import { rulesBulkSchema } from '../../../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; @@ -69,7 +71,7 @@ export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logge request.body.map(async (payloadRule) => { const { id, rule_id: ruleId } = payloadRule; const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)'; - const validationErrors = queryRuleValidateTypeDependents(payloadRule); + const validationErrors = validateQueryRuleByIds(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ ruleId: idOrRuleIdOrUnknown, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts index d6daa037b4839..f5dfb61cd32ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts @@ -6,28 +6,29 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { queryRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents'; -import type { QueryRulesSchemaDecoded } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema'; -import { queryRulesSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; -import type { SecuritySolutionPluginRouter } from '../../../../../../types'; + import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; -import { deleteRules } from '../../../logic/crud/delete_rules'; -import { getIdError, transform } from '../../../utils/utils'; +import { + QueryRuleByIds, + validateQueryRuleByIds, +} from '../../../../../../../common/detection_engine/rule_management'; + +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; +import { deleteRules } from '../../../logic/crud/delete_rules'; import { readRules } from '../../../logic/crud/read_rules'; // eslint-disable-next-line no-restricted-imports import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { getIdError, transform } from '../../../utils/utils'; export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { router.delete( { path: DETECTION_ENGINE_RULES_URL, validate: { - query: buildRouteValidation( - queryRulesSchema - ), + query: buildRouteValidation(QueryRuleByIds), }, options: { tags: ['access:securitySolution'], @@ -35,7 +36,7 @@ export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = queryRuleValidateTypeDependents(request.query); + const validationErrors = validateQueryRuleByIds(request.query); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts index 03805bd57096c..eaa58bf00a1be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts @@ -5,16 +5,19 @@ * 2.0. */ -import { transformError } from '@kbn/securitysolution-es-utils'; import type { Logger } from '@kbn/core/server'; -import { queryRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_type_dependents'; -import type { QueryRulesSchemaDecoded } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema'; -import { queryRulesSchema } from '../../../../../../../common/detection_engine/rule_management/api/rules/read_rule/query_rules_schema'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; -import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { transformError } from '@kbn/securitysolution-es-utils'; + import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; -import { getIdError, transform } from '../../../utils/utils'; +import { + QueryRuleByIds, + validateQueryRuleByIds, +} from '../../../../../../../common/detection_engine/rule_management'; + +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; +import { getIdError, transform } from '../../../utils/utils'; import { readRules } from '../../../logic/crud/read_rules'; // eslint-disable-next-line no-restricted-imports @@ -25,9 +28,7 @@ export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logg { path: DETECTION_ENGINE_RULES_URL, validate: { - query: buildRouteValidation( - queryRulesSchema - ), + query: buildRouteValidation(QueryRuleByIds), }, options: { tags: ['access:securitySolution'], @@ -35,7 +36,7 @@ export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logg }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = queryRuleValidateTypeDependents(request.query); + const validationErrors = validateQueryRuleByIds(request.query); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } From e1843ce6d1138af388da518b8863c1ea71020192 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 21:15:46 +0200 Subject: [PATCH 14/15] TEMP COMMIT. REBASE ME --- ...t.ts => request_schema_validation.test.ts} | 18 +++---- ...ndents.ts => request_schema_validation.ts} | 47 ++++++++++--------- .../detection_engine/rule_management/index.ts | 1 + .../api/rules/bulk_update_rules/route.ts | 8 ++-- .../api/rules/update_rule/route.ts | 7 +-- 5 files changed, 44 insertions(+), 37 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/{update_rules_type_dependents.test.ts => request_schema_validation.test.ts} (81%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/{update_rules_type_dependents.ts => request_schema_validation.ts} (82%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/request_schema_validation.test.ts similarity index 81% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/request_schema_validation.test.ts index ff8e59fb59f88..1603cdbab208d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/request_schema_validation.test.ts @@ -5,18 +5,18 @@ * 2.0. */ -import { getUpdateRulesSchemaMock } from '../../../../schemas/request/rule_schemas.mock'; import type { UpdateRulesSchema } from '../../../../schemas/request/rule_schemas'; -import { updateRuleValidateTypeDependents } from './update_rules_type_dependents'; +import { getUpdateRulesSchemaMock } from '../../../../schemas/request/rule_schemas.mock'; +import { validateUpdateRuleSchema } from './request_schema_validation'; -describe('update_rules_type_dependents', () => { +describe('Update rule request schema, additional validation', () => { test('You cannot omit timeline_title when timeline_id is present', () => { const schema: UpdateRulesSchema = { ...getUpdateRulesSchemaMock(), timeline_id: '123', }; delete schema.timeline_title; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleSchema(schema); expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); }); @@ -26,7 +26,7 @@ describe('update_rules_type_dependents', () => { timeline_id: '123', timeline_title: '', }; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleSchema(schema); expect(errors).toEqual(['"timeline_title" cannot be an empty string']); }); @@ -36,7 +36,7 @@ describe('update_rules_type_dependents', () => { timeline_id: '', timeline_title: 'some-title', }; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleSchema(schema); expect(errors).toEqual(['"timeline_id" cannot be an empty string']); }); @@ -46,7 +46,7 @@ describe('update_rules_type_dependents', () => { timeline_title: 'some-title', }; delete schema.timeline_id; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleSchema(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); @@ -56,7 +56,7 @@ describe('update_rules_type_dependents', () => { id: 'some-id', rule_id: 'some-rule-id', }; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleSchema(schema); expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); }); @@ -66,7 +66,7 @@ describe('update_rules_type_dependents', () => { }; delete schema.id; delete schema.rule_id; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleSchema(schema); expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/request_schema_validation.ts similarity index 82% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/request_schema_validation.ts index 1a2d1e32eed5b..66e36bedc48cd 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/update_rule/request_schema_validation.ts @@ -7,7 +7,29 @@ import type { UpdateRulesSchema } from '../../../../schemas/request/rule_schemas'; -export const validateTimelineId = (rule: UpdateRulesSchema): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateUpdateRuleSchema = (rule: UpdateRulesSchema): string[] => { + return [ + ...validateId(rule), + ...validateTimelineId(rule), + ...validateTimelineTitle(rule), + ...validateThreshold(rule), + ]; +}; + +const validateId = (rule: UpdateRulesSchema): string[] => { + if (rule.id != null && rule.rule_id != null) { + return ['both "id" and "rule_id" cannot exist, choose one or the other']; + } else if (rule.id == null && rule.rule_id == null) { + return ['either "id" or "rule_id" must be set']; + } else { + return []; + } +}; + +const validateTimelineId = (rule: UpdateRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +42,7 @@ export const validateTimelineId = (rule: UpdateRulesSchema): string[] => { return []; }; -export const validateTimelineTitle = (rule: UpdateRulesSchema): string[] => { +const validateTimelineTitle = (rule: UpdateRulesSchema): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,17 +55,7 @@ export const validateTimelineTitle = (rule: UpdateRulesSchema): string[] => { return []; }; -export const validateId = (rule: UpdateRulesSchema): string[] => { - if (rule.id != null && rule.rule_id != null) { - return ['both "id" and "rule_id" cannot exist, choose one or the other']; - } else if (rule.id == null && rule.rule_id == null) { - return ['either "id" or "rule_id" must be set']; - } else { - return []; - } -}; - -export const validateThreshold = (rule: UpdateRulesSchema): string[] => { +const validateThreshold = (rule: UpdateRulesSchema): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if (!rule.threshold) { @@ -62,12 +74,3 @@ export const validateThreshold = (rule: UpdateRulesSchema): string[] => { } return errors; }; - -export const updateRuleValidateTypeDependents = (rule: UpdateRulesSchema): string[] => { - return [ - ...validateId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index ed8074c6e3f9a..2e6f6101c2195 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -19,6 +19,7 @@ export * from './api/rules/patch_rule/request_schema_validation'; export * from './api/rules/patch_rule/request_schema'; export * from './api/rules/read_rule/query_rule_by_ids_validation'; export * from './api/rules/read_rule/query_rule_by_ids'; +export * from './api/rules/update_rule/request_schema_validation'; // export * from './api/urls'; export * from './model/export/export_rules_details_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index 39f25c2430c74..98880e4c0dd72 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -8,8 +8,10 @@ import type { Logger } from '@kbn/core/server'; import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { BulkUpdateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; -import { updateRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents'; +import { + BulkUpdateRulesRequestBody, + validateUpdateRuleSchema, +} from '../../../../../../../common/detection_engine/rule_management'; import { rulesBulkSchema } from '../../../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; @@ -71,7 +73,7 @@ export const bulkUpdateRulesRoute = ( request.body.map(async (payloadRule) => { const idOrRuleIdOrUnknown = payloadRule.id ?? payloadRule.rule_id ?? '(unknown id)'; try { - const validationErrors = updateRuleValidateTypeDependents(payloadRule); + const validationErrors = validateUpdateRuleSchema(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ ruleId: payloadRule.rule_id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index d27c76afa059d..7e6ce647707ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -6,10 +6,11 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { validateUpdateRuleSchema } from '../../../../../../../common/detection_engine/rule_management'; import { updateRulesSchema } from '../../../../../../../common/detection_engine/schemas/request'; -import { updateRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/update_rule/update_rules_type_dependents'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { SetupPlugins } from '../../../../../../plugin'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../../machine_learning/validation'; @@ -37,7 +38,7 @@ export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupP }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = updateRuleValidateTypeDependents(request.body); + const validationErrors = validateUpdateRuleSchema(request.body); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } From e5670fcfd4a81d2961fdc02831f1a6c42583cb33 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sun, 16 Oct 2022 21:28:02 +0200 Subject: [PATCH 15/15] TEMP COMMIT. REBASE ME --- ...t.ts => request_schema_validation.test.ts} | 20 +++++++++--------- ...ndents.ts => request_schema_validation.ts} | 5 ++++- .../detection_engine/rule_management/index.ts | 1 + .../api/rules/bulk_create_rules/route.ts | 16 ++++++++------ .../api/rules/create_rule/route.ts | 12 ++++++----- .../rule_preview/api/preview_rules/route.ts | 21 +++++++++++-------- 6 files changed, 44 insertions(+), 31 deletions(-) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/{create_rules_type_dependents.test.ts => request_schema_validation.test.ts} (82%) rename x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/{create_rules_type_dependents.ts => request_schema_validation.ts} (93%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/request_schema_validation.test.ts similarity index 82% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/request_schema_validation.test.ts index ef76fb0258d97..65d68e580ef5a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/request_schema_validation.test.ts @@ -5,21 +5,21 @@ * 2.0. */ +import type { CreateRulesSchema } from '../../../../schemas/request/rule_schemas'; import { getCreateRulesSchemaMock, getCreateThreatMatchRulesSchemaMock, } from '../../../../schemas/request/rule_schemas.mock'; -import type { CreateRulesSchema } from '../../../../schemas/request/rule_schemas'; -import { createRuleValidateTypeDependents } from './create_rules_type_dependents'; +import { validateCreateRuleSchema } from './request_schema_validation'; -describe('create_rules_type_dependents', () => { +describe('Create rule request schema, additional validation', () => { test('You cannot omit timeline_title when timeline_id is present', () => { const schema: CreateRulesSchema = { ...getCreateRulesSchemaMock(), timeline_id: '123', }; delete schema.timeline_title; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleSchema(schema); expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); }); @@ -29,7 +29,7 @@ describe('create_rules_type_dependents', () => { timeline_id: '123', timeline_title: '', }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleSchema(schema); expect(errors).toEqual(['"timeline_title" cannot be an empty string']); }); @@ -39,7 +39,7 @@ describe('create_rules_type_dependents', () => { timeline_id: '', timeline_title: 'some-title', }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleSchema(schema); expect(errors).toEqual(['"timeline_id" cannot be an empty string']); }); @@ -49,7 +49,7 @@ describe('create_rules_type_dependents', () => { timeline_title: 'some-title', }; delete schema.timeline_id; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleSchema(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); @@ -59,7 +59,7 @@ describe('create_rules_type_dependents', () => { concurrent_searches: 10, items_per_search: 10, }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleSchema(schema); expect(errors).toEqual([]); }); @@ -68,7 +68,7 @@ describe('create_rules_type_dependents', () => { ...getCreateThreatMatchRulesSchemaMock(), items_per_search: 10, }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleSchema(schema); expect(errors).toEqual([ 'when "items_per_search" exists, "concurrent_searches" must also exist', ]); @@ -79,7 +79,7 @@ describe('create_rules_type_dependents', () => { ...getCreateThreatMatchRulesSchemaMock(), concurrent_searches: 10, }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleSchema(schema); expect(errors).toEqual([ 'when "concurrent_searches" exists, "items_per_search" must also exist', ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/request_schema_validation.ts similarity index 93% rename from x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/request_schema_validation.ts index ee99071c3af7a..b2f1bd9440324 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/create_rule/request_schema_validation.ts @@ -7,7 +7,10 @@ import type { CreateRulesSchema } from '../../../../schemas/request/rule_schemas'; -export const createRuleValidateTypeDependents = (rule: CreateRulesSchema): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateCreateRuleSchema = (rule: CreateRulesSchema): string[] => { return [ ...validateTimelineId(rule), ...validateTimelineTitle(rule), diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts index 2e6f6101c2195..f43c6f1603b4e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -10,6 +10,7 @@ export * from './api/rules/bulk_create_rules/request_schema'; export * from './api/rules/bulk_delete_rules/request_schema'; export * from './api/rules/bulk_patch_rules/request_schema'; export * from './api/rules/bulk_update_rules/request_schema'; +export * from './api/rules/create_rule/request_schema_validation'; export * from './api/rules/export_rules/request_schema'; export * from './api/rules/find_rules/request_schema_validation'; export * from './api/rules/find_rules/request_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts index 4c22c8f255256..9b469822ae28f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts @@ -5,16 +5,21 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { Logger } from '@kbn/core/server'; -import { createRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents'; -import { BulkCreateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; +import { validate } from '@kbn/securitysolution-io-ts-utils'; + +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../../../common/constants'; +import { + BulkCreateRulesRequestBody, + validateCreateRuleSchema, +} from '../../../../../../../common/detection_engine/rule_management'; import { rulesBulkSchema } from '../../../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; + import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../../../common/constants'; import type { SetupPlugins } from '../../../../../../plugin'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../../machine_learning/validation'; +import { createRules } from '../../../logic/crud/create_rules'; import { readRules } from '../../../logic/crud/read_rules'; import { getDuplicates } from './get_duplicates'; import { transformValidateBulkError } from '../../../utils/validate'; @@ -26,7 +31,6 @@ import { buildSiemResponse, } from '../../../../routes/utils'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; -import { createRules } from '../../../logic/crud/create_rules'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead @@ -86,7 +90,7 @@ export const bulkCreateRulesRoute = ( } try { - const validationErrors = createRuleValidateTypeDependents(payloadRule); + const validationErrors = validateCreateRuleSchema(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ ruleId: payloadRule.rule_id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index 6e85bd54ad99e..28e22d3ebb43d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -6,8 +6,12 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; + import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { validateCreateRuleSchema } from '../../../../../../../common/detection_engine/rule_management'; +import { createRulesSchema } from '../../../../../../../common/detection_engine/schemas/request'; + +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import type { SetupPlugins } from '../../../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; @@ -15,11 +19,9 @@ import { throwAuthzError } from '../../../../../machine_learning/validation'; import { readRules } from '../../../logic/crud/read_rules'; import { buildSiemResponse } from '../../../../routes/utils'; -import { createRulesSchema } from '../../../../../../../common/detection_engine/schemas/request'; -import { transformValidate } from '../../../utils/validate'; -import { createRuleValidateTypeDependents } from '../../../../../../../common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents'; import { createRules } from '../../../logic/crud/create_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; +import { transformValidate } from '../../../utils/validate'; export const createRuleRoute = ( router: SecuritySolutionPluginRouter, @@ -37,7 +39,7 @@ export const createRuleRoute = ( }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = createRuleValidateTypeDependents(request.body); + const validationErrors = validateCreateRuleSchema(request.body); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts index 48f1f1e7332cb..fc5999d9a4e04 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts @@ -17,6 +17,16 @@ import type { import { parseDuration } from '@kbn/alerting-plugin/common'; import type { ExecutorType } from '@kbn/alerting-plugin/server/types'; import type { Alert } from '@kbn/alerting-plugin/server'; + +import { + DEFAULT_PREVIEW_INDEX, + DETECTION_ENGINE_RULES_PREVIEW, +} from '../../../../../../common/constants'; +import { validateCreateRuleSchema } from '../../../../../../common/detection_engine/rule_management'; +import { RuleExecutionStatus } from '../../../../../../common/detection_engine/rule_monitoring'; +import type { RulePreviewLogs } from '../../../../../../common/detection_engine/schemas/request'; +import { previewRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; + import type { StartPlugins, SetupPlugins } from '../../../../../plugin'; import { buildSiemResponse } from '../../../routes/utils'; import { convertCreateAPIToInternalSchema } from '../../../rule_management'; @@ -27,14 +37,7 @@ import { buildMlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; import { buildRouteValidation } from '../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { createRuleValidateTypeDependents } from '../../../../../../common/detection_engine/rule_management/api/rules/create_rule/create_rules_type_dependents'; -import { - DEFAULT_PREVIEW_INDEX, - DETECTION_ENGINE_RULES_PREVIEW, -} from '../../../../../../common/constants'; -import type { RulePreviewLogs } from '../../../../../../common/detection_engine/schemas/request'; -import { previewRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; -import { RuleExecutionStatus } from '../../../../../../common/detection_engine/rule_monitoring'; + import type { RuleExecutionContext, StatusChangeArgs } from '../../../rule_monitoring'; import type { ConfigType } from '../../../../../config'; @@ -81,7 +84,7 @@ export const previewRulesRoute = async ( }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = createRuleValidateTypeDependents(request.body); + const validationErrors = validateCreateRuleSchema(request.body); const coreContext = await context.core; if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors });