Skip to content

Commit

Permalink
[Security Solution][Detections] adds bulk edit rule actions (#138900)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
vitaliidm authored Sep 6, 2022
1 parent 5ebefbb commit a3061a8
Show file tree
Hide file tree
Showing 57 changed files with 1,706 additions and 308 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
/* eslint-disable @typescript-eslint/naming-convention */

import {
enumeration,
IsoDateString,
NonEmptyString,
PositiveInteger,
Expand Down Expand Up @@ -359,75 +358,3 @@ export const privilege = t.type({
});

export type Privilege = t.TypeOf<typeof privilege>;

export enum BulkAction {
'enable' = 'enable',
'disable' = 'disable',
'export' = 'export',
'delete' = 'delete',
'duplicate' = 'duplicate',
'edit' = 'edit',
}

export const bulkAction = enumeration('BulkAction', BulkAction);

export enum BulkActionEditType {
'add_tags' = 'add_tags',
'delete_tags' = 'delete_tags',
'set_tags' = 'set_tags',
'add_index_patterns' = 'add_index_patterns',
'delete_index_patterns' = 'delete_index_patterns',
'set_index_patterns' = 'set_index_patterns',
'set_timeline' = 'set_timeline',
}

const bulkActionEditPayloadTags = t.type({
type: t.union([
t.literal(BulkActionEditType.add_tags),
t.literal(BulkActionEditType.delete_tags),
t.literal(BulkActionEditType.set_tags),
]),
value: tags,
});

export type BulkActionEditPayloadTags = t.TypeOf<typeof bulkActionEditPayloadTags>;

const bulkActionEditPayloadIndexPatterns = t.intersection([
t.type({
type: t.union([
t.literal(BulkActionEditType.add_index_patterns),
t.literal(BulkActionEditType.delete_index_patterns),
t.literal(BulkActionEditType.set_index_patterns),
]),
value: index,
}),
t.exact(t.partial({ overwrite_data_views: t.boolean })),
]);

export type BulkActionEditPayloadIndexPatterns = t.TypeOf<
typeof bulkActionEditPayloadIndexPatterns
>;

const bulkActionEditPayloadTimeline = t.type({
type: t.literal(BulkActionEditType.set_timeline),
value: t.type({
timeline_id,
timeline_title,
}),
});

export type BulkActionEditPayloadTimeline = t.TypeOf<typeof bulkActionEditPayloadTimeline>;

export const bulkActionEditPayload = t.union([
bulkActionEditPayloadTags,
bulkActionEditPayloadIndexPatterns,
bulkActionEditPayloadTimeline,
]);

export type BulkActionEditPayload = t.TypeOf<typeof bulkActionEditPayload>;

export type BulkActionEditForRuleAttributes = BulkActionEditPayloadTags;

export type BulkActionEditForRuleParams =
| BulkActionEditPayloadIndexPatterns
| BulkActionEditPayloadTimeline;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { BulkAction, BulkActionEditType } from '../common/schemas';
import { BulkAction, BulkActionEditType } from './perform_bulk_action_schema';
import type { PerformBulkActionSchema } from './perform_bulk_action_schema';

export const getPerformBulkActionSchemaMock = (): PerformBulkActionSchema => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
*/

import type { PerformBulkActionSchema } from './perform_bulk_action_schema';
import { performBulkActionSchema } from './perform_bulk_action_schema';
import {
performBulkActionSchema,
BulkAction,
BulkActionEditType,
} from './perform_bulk_action_schema';
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { left } from 'fp-ts/lib/Either';
import { BulkAction, BulkActionEditType } from '../common/schemas';

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

const message = retrieveValidationMessage(payload);

expect(getPaths(left(message.errors))).toEqual([
'Invalid value "edit" supplied to "action"',
'Invalid value "set_timeline" supplied to "edit,type"',
'Invalid value "{"timeline_title":"Test timeline title"}" supplied to "edit,value"',
'Invalid value "undefined" supplied to "edit,value,timeline_id"',
]);
expect(getPaths(left(message.errors))).toEqual(
expect.arrayContaining([
'Invalid value "{"timeline_title":"Test timeline title"}" supplied to "edit,value"',
'Invalid value "undefined" supplied to "edit,value,timeline_id"',
])
);
expect(message.schema).toEqual({});
});

Expand All @@ -373,5 +376,163 @@ describe('perform_bulk_action_schema', () => {
expect(message.schema).toEqual(payload);
});
});

describe('rule actions', () => {
test('invalid request: invalid rule actions payload', () => {
const payload = {
query: 'name: test',
action: BulkAction.edit,
[BulkAction.edit]: [{ type: BulkActionEditType.add_rule_actions, value: [] }],
};

const message = retrieveValidationMessage(payload);

expect(getPaths(left(message.errors))).toEqual(
expect.arrayContaining(['Invalid value "[]" supplied to "edit,value"'])
);
expect(message.schema).toEqual({});
});

test('invalid request: missing throttle in payload', () => {
const payload = {
query: 'name: test',
action: BulkAction.edit,
[BulkAction.edit]: [
{
type: BulkActionEditType.add_rule_actions,
value: {
actions: [],
},
},
],
};

const message = retrieveValidationMessage(payload);

expect(getPaths(left(message.errors))).toEqual(
expect.arrayContaining(['Invalid value "undefined" supplied to "edit,value,throttle"'])
);
expect(message.schema).toEqual({});
});

test('invalid request: missing actions in payload', () => {
const payload = {
query: 'name: test',
action: BulkAction.edit,
[BulkAction.edit]: [
{
type: BulkActionEditType.add_rule_actions,
value: {
throttle: '1h',
},
},
],
};

const message = retrieveValidationMessage(payload);

expect(getPaths(left(message.errors))).toEqual(
expect.arrayContaining(['Invalid value "undefined" supplied to "edit,value,actions"'])
);
expect(message.schema).toEqual({});
});

test('invalid request: invalid action_type_id property in actions array', () => {
const payload = {
query: 'name: test',
action: BulkAction.edit,
[BulkAction.edit]: [
{
type: BulkActionEditType.add_rule_actions,
value: {
throttle: '1h',
actions: [
{
action_type_id: '.webhook',
group: 'default',
id: '458a50e0-1a28-11ed-9098-47fd8e1f3345',
params: {
body: {
rule_id: '{{rule.id}}',
},
},
},
],
},
},
],
};

const message = retrieveValidationMessage(payload);
expect(getPaths(left(message.errors))).toEqual(
expect.arrayContaining(['invalid keys "action_type_id"'])
);
expect(message.schema).toEqual({});
});

test('valid request: add_rule_actions edit action', () => {
const payload: PerformBulkActionSchema = {
query: 'name: test',
action: BulkAction.edit,
[BulkAction.edit]: [
{
type: BulkActionEditType.add_rule_actions,
value: {
throttle: '1h',
actions: [
{
group: 'default',
id: '458a50e0-1a28-11ed-9098-47fd8e1f3345',
params: {
body: {
rule_id: '{{rule.id}}',
},
},
},
],
},
},
],
};

const message = retrieveValidationMessage(payload);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('valid request: set_rule_actions edit action', () => {
const payload: PerformBulkActionSchema = {
query: 'name: test',
action: BulkAction.edit,
[BulkAction.edit]: [
{
type: BulkActionEditType.set_rule_actions,
value: {
throttle: '1h',
actions: [
{
group: 'default',
id: '458a50e0-1a28-11ed-9098-47fd8e1f3345',
params: {
documents: [
{
rule_id: '{{rule.id}}',
},
],
},
},
],
},
},
],
};

const message = retrieveValidationMessage(payload);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});
});
});
});
Loading

0 comments on commit a3061a8

Please sign in to comment.