Skip to content

Commit a3061a8

Browse files
authored
[Security Solution][Detections] adds bulk edit rule actions (#138900)
## Summary - addresses elastic/security-team#2072 - adds new bulk edit actions: `add_rule_actions`, `set_rule_actions` - moved immutability check from rule `validateMutatedParams` to action validator. Because, rule immutability depends on actions performed on it, not only on `immutable` property - adds some test coverage - using workaround for #139084, by muting/unmuting single rule. This would only happen: - if rule was muted before, throttle set to some value - rule was unmuted, throttle set to `no_actions` ### Feature recording Note: callouts on recording are not up to date https://user-images.githubusercontent.com/92328789/185381912-6c4a25f6-fb36-4c31-bf08-8ec28f2358c0.mov ### Screen <img width="1465" alt="Screenshot 2022-08-25 at 17 23 56" src="https://user-images.githubusercontent.com/92328789/186731607-574687b8-8a7a-43de-8f30-6cda3dcecfc5.png"> ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Release note Adding bulk edit of rule actions
1 parent 5ebefbb commit a3061a8

File tree

57 files changed

+1706
-308
lines changed

Some content is hidden

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

57 files changed

+1706
-308
lines changed

x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts

-73
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
/* eslint-disable @typescript-eslint/naming-convention */
99

1010
import {
11-
enumeration,
1211
IsoDateString,
1312
NonEmptyString,
1413
PositiveInteger,
@@ -359,75 +358,3 @@ export const privilege = t.type({
359358
});
360359

361360
export type Privilege = t.TypeOf<typeof privilege>;
362-
363-
export enum BulkAction {
364-
'enable' = 'enable',
365-
'disable' = 'disable',
366-
'export' = 'export',
367-
'delete' = 'delete',
368-
'duplicate' = 'duplicate',
369-
'edit' = 'edit',
370-
}
371-
372-
export const bulkAction = enumeration('BulkAction', BulkAction);
373-
374-
export enum BulkActionEditType {
375-
'add_tags' = 'add_tags',
376-
'delete_tags' = 'delete_tags',
377-
'set_tags' = 'set_tags',
378-
'add_index_patterns' = 'add_index_patterns',
379-
'delete_index_patterns' = 'delete_index_patterns',
380-
'set_index_patterns' = 'set_index_patterns',
381-
'set_timeline' = 'set_timeline',
382-
}
383-
384-
const bulkActionEditPayloadTags = t.type({
385-
type: t.union([
386-
t.literal(BulkActionEditType.add_tags),
387-
t.literal(BulkActionEditType.delete_tags),
388-
t.literal(BulkActionEditType.set_tags),
389-
]),
390-
value: tags,
391-
});
392-
393-
export type BulkActionEditPayloadTags = t.TypeOf<typeof bulkActionEditPayloadTags>;
394-
395-
const bulkActionEditPayloadIndexPatterns = t.intersection([
396-
t.type({
397-
type: t.union([
398-
t.literal(BulkActionEditType.add_index_patterns),
399-
t.literal(BulkActionEditType.delete_index_patterns),
400-
t.literal(BulkActionEditType.set_index_patterns),
401-
]),
402-
value: index,
403-
}),
404-
t.exact(t.partial({ overwrite_data_views: t.boolean })),
405-
]);
406-
407-
export type BulkActionEditPayloadIndexPatterns = t.TypeOf<
408-
typeof bulkActionEditPayloadIndexPatterns
409-
>;
410-
411-
const bulkActionEditPayloadTimeline = t.type({
412-
type: t.literal(BulkActionEditType.set_timeline),
413-
value: t.type({
414-
timeline_id,
415-
timeline_title,
416-
}),
417-
});
418-
419-
export type BulkActionEditPayloadTimeline = t.TypeOf<typeof bulkActionEditPayloadTimeline>;
420-
421-
export const bulkActionEditPayload = t.union([
422-
bulkActionEditPayloadTags,
423-
bulkActionEditPayloadIndexPatterns,
424-
bulkActionEditPayloadTimeline,
425-
]);
426-
427-
export type BulkActionEditPayload = t.TypeOf<typeof bulkActionEditPayload>;
428-
429-
export type BulkActionEditForRuleAttributes = BulkActionEditPayloadTags;
430-
431-
export type BulkActionEditForRuleParams =
432-
| BulkActionEditPayloadIndexPatterns
433-
| BulkActionEditPayloadTimeline;

x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.mock.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import { BulkAction, BulkActionEditType } from '../common/schemas';
8+
import { BulkAction, BulkActionEditType } from './perform_bulk_action_schema';
99
import type { PerformBulkActionSchema } from './perform_bulk_action_schema';
1010

1111
export const getPerformBulkActionSchemaMock = (): PerformBulkActionSchema => ({

x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.test.ts

+169-8
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
*/
77

88
import type { PerformBulkActionSchema } from './perform_bulk_action_schema';
9-
import { performBulkActionSchema } from './perform_bulk_action_schema';
9+
import {
10+
performBulkActionSchema,
11+
BulkAction,
12+
BulkActionEditType,
13+
} from './perform_bulk_action_schema';
1014
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
1115
import { left } from 'fp-ts/lib/Either';
12-
import { BulkAction, BulkActionEditType } from '../common/schemas';
1316

1417
const retrieveValidationMessage = (payload: unknown) => {
1518
const decoded = performBulkActionSchema.decode(payload);
@@ -343,12 +346,12 @@ describe('perform_bulk_action_schema', () => {
343346

344347
const message = retrieveValidationMessage(payload);
345348

346-
expect(getPaths(left(message.errors))).toEqual([
347-
'Invalid value "edit" supplied to "action"',
348-
'Invalid value "set_timeline" supplied to "edit,type"',
349-
'Invalid value "{"timeline_title":"Test timeline title"}" supplied to "edit,value"',
350-
'Invalid value "undefined" supplied to "edit,value,timeline_id"',
351-
]);
349+
expect(getPaths(left(message.errors))).toEqual(
350+
expect.arrayContaining([
351+
'Invalid value "{"timeline_title":"Test timeline title"}" supplied to "edit,value"',
352+
'Invalid value "undefined" supplied to "edit,value,timeline_id"',
353+
])
354+
);
352355
expect(message.schema).toEqual({});
353356
});
354357

@@ -373,5 +376,163 @@ describe('perform_bulk_action_schema', () => {
373376
expect(message.schema).toEqual(payload);
374377
});
375378
});
379+
380+
describe('rule actions', () => {
381+
test('invalid request: invalid rule actions payload', () => {
382+
const payload = {
383+
query: 'name: test',
384+
action: BulkAction.edit,
385+
[BulkAction.edit]: [{ type: BulkActionEditType.add_rule_actions, value: [] }],
386+
};
387+
388+
const message = retrieveValidationMessage(payload);
389+
390+
expect(getPaths(left(message.errors))).toEqual(
391+
expect.arrayContaining(['Invalid value "[]" supplied to "edit,value"'])
392+
);
393+
expect(message.schema).toEqual({});
394+
});
395+
396+
test('invalid request: missing throttle in payload', () => {
397+
const payload = {
398+
query: 'name: test',
399+
action: BulkAction.edit,
400+
[BulkAction.edit]: [
401+
{
402+
type: BulkActionEditType.add_rule_actions,
403+
value: {
404+
actions: [],
405+
},
406+
},
407+
],
408+
};
409+
410+
const message = retrieveValidationMessage(payload);
411+
412+
expect(getPaths(left(message.errors))).toEqual(
413+
expect.arrayContaining(['Invalid value "undefined" supplied to "edit,value,throttle"'])
414+
);
415+
expect(message.schema).toEqual({});
416+
});
417+
418+
test('invalid request: missing actions in payload', () => {
419+
const payload = {
420+
query: 'name: test',
421+
action: BulkAction.edit,
422+
[BulkAction.edit]: [
423+
{
424+
type: BulkActionEditType.add_rule_actions,
425+
value: {
426+
throttle: '1h',
427+
},
428+
},
429+
],
430+
};
431+
432+
const message = retrieveValidationMessage(payload);
433+
434+
expect(getPaths(left(message.errors))).toEqual(
435+
expect.arrayContaining(['Invalid value "undefined" supplied to "edit,value,actions"'])
436+
);
437+
expect(message.schema).toEqual({});
438+
});
439+
440+
test('invalid request: invalid action_type_id property in actions array', () => {
441+
const payload = {
442+
query: 'name: test',
443+
action: BulkAction.edit,
444+
[BulkAction.edit]: [
445+
{
446+
type: BulkActionEditType.add_rule_actions,
447+
value: {
448+
throttle: '1h',
449+
actions: [
450+
{
451+
action_type_id: '.webhook',
452+
group: 'default',
453+
id: '458a50e0-1a28-11ed-9098-47fd8e1f3345',
454+
params: {
455+
body: {
456+
rule_id: '{{rule.id}}',
457+
},
458+
},
459+
},
460+
],
461+
},
462+
},
463+
],
464+
};
465+
466+
const message = retrieveValidationMessage(payload);
467+
expect(getPaths(left(message.errors))).toEqual(
468+
expect.arrayContaining(['invalid keys "action_type_id"'])
469+
);
470+
expect(message.schema).toEqual({});
471+
});
472+
473+
test('valid request: add_rule_actions edit action', () => {
474+
const payload: PerformBulkActionSchema = {
475+
query: 'name: test',
476+
action: BulkAction.edit,
477+
[BulkAction.edit]: [
478+
{
479+
type: BulkActionEditType.add_rule_actions,
480+
value: {
481+
throttle: '1h',
482+
actions: [
483+
{
484+
group: 'default',
485+
id: '458a50e0-1a28-11ed-9098-47fd8e1f3345',
486+
params: {
487+
body: {
488+
rule_id: '{{rule.id}}',
489+
},
490+
},
491+
},
492+
],
493+
},
494+
},
495+
],
496+
};
497+
498+
const message = retrieveValidationMessage(payload);
499+
500+
expect(getPaths(left(message.errors))).toEqual([]);
501+
expect(message.schema).toEqual(payload);
502+
});
503+
504+
test('valid request: set_rule_actions edit action', () => {
505+
const payload: PerformBulkActionSchema = {
506+
query: 'name: test',
507+
action: BulkAction.edit,
508+
[BulkAction.edit]: [
509+
{
510+
type: BulkActionEditType.set_rule_actions,
511+
value: {
512+
throttle: '1h',
513+
actions: [
514+
{
515+
group: 'default',
516+
id: '458a50e0-1a28-11ed-9098-47fd8e1f3345',
517+
params: {
518+
documents: [
519+
{
520+
rule_id: '{{rule.id}}',
521+
},
522+
],
523+
},
524+
},
525+
],
526+
},
527+
},
528+
],
529+
};
530+
531+
const message = retrieveValidationMessage(payload);
532+
533+
expect(getPaths(left(message.errors))).toEqual([]);
534+
expect(message.schema).toEqual(payload);
535+
});
536+
});
376537
});
377538
});

0 commit comments

Comments
 (0)