Skip to content

Commit 4dd25fa

Browse files
authored
[Security Solution][Detections] Adds framework for replacing API schemas (#82462)
* Adds framework for replacing API schemas * Update integration tests with new schema * Fix response type on createRule helper * Add unit tests for new rule schema, add defaults for some array fields, clean up API schema definitions * Naming updates and linting fixes * Replace create_rules_bulk_schema and refactor route * Convert update_rules_route to new schema * Fix missing name error * Fix more tests * Fix import * Update patch route with internal schema validation * Reorganize new schema as drop-in replacement for create_rules_schema * Replace updateRulesSchema with new version * Cleanup - remove references to specific files within request folder * Fix imports * Fix tests * Allow a few more fields to be undefined in internal schema * Add static types back to test payloads, add more tests, add NonEmptyArray type builder * Pull defaults into reusable function
1 parent 35faf8c commit 4dd25fa

File tree

61 files changed

+1921
-4261
lines changed

Some content is hidden

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

61 files changed

+1921
-4261
lines changed

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import * as t from 'io-ts';
1010
import { Either } from 'fp-ts/lib/Either';
1111

12+
import {
13+
SavedObjectAttributes,
14+
SavedObjectAttribute,
15+
SavedObjectAttributeSingle,
16+
} from 'src/core/types';
1217
import { RiskScore } from '../types/risk_score';
1318
import { UUID } from '../types/uuid';
1419
import { IsoDateString } from '../types/iso_date_string';
@@ -66,14 +71,30 @@ export type ExcludeExportDetails = t.TypeOf<typeof exclude_export_details>;
6671
export const filters = t.array(t.unknown); // Filters are not easily type-able yet
6772
export type Filters = t.TypeOf<typeof filters>; // Filters are not easily type-able yet
6873

74+
export const filtersOrUndefined = t.union([filters, t.undefined]);
75+
export type FiltersOrUndefined = t.TypeOf<typeof filtersOrUndefined>;
76+
77+
export const saved_object_attribute_single: t.Type<SavedObjectAttributeSingle> = t.recursion(
78+
'saved_object_attribute_single',
79+
() => t.union([t.string, t.number, t.boolean, t.null, t.undefined, saved_object_attributes])
80+
);
81+
export const saved_object_attribute: t.Type<SavedObjectAttribute> = t.recursion(
82+
'saved_object_attribute',
83+
() => t.union([saved_object_attribute_single, t.array(saved_object_attribute_single)])
84+
);
85+
export const saved_object_attributes: t.Type<SavedObjectAttributes> = t.recursion(
86+
'saved_object_attributes',
87+
() => t.record(t.string, saved_object_attribute)
88+
);
89+
6990
/**
7091
* Params is an "object", since it is a type of AlertActionParams which is action templates.
7192
* @see x-pack/plugins/alerts/common/alert.ts
7293
*/
7394
export const action_group = t.string;
7495
export const action_id = t.string;
7596
export const action_action_type_id = t.string;
76-
export const action_params = t.object;
97+
export const action_params = saved_object_attributes;
7798
export const action = t.exact(
7899
t.type({
79100
group: action_group,
@@ -86,6 +107,18 @@ export const action = t.exact(
86107
export const actions = t.array(action);
87108
export type Actions = t.TypeOf<typeof actions>;
88109

110+
export const actionsCamel = t.array(
111+
t.exact(
112+
t.type({
113+
group: action_group,
114+
id: action_id,
115+
actionTypeId: action_action_type_id,
116+
params: action_params,
117+
})
118+
)
119+
);
120+
export type ActionsCamel = t.TypeOf<typeof actions>;
121+
89122
const stringValidator = (input: unknown): input is string => typeof input === 'string';
90123
export const from = new t.Type<string, string, unknown>(
91124
'From',
@@ -416,6 +449,10 @@ export const created_at = IsoDateString;
416449
export const updated_at = IsoDateString;
417450
export const updated_by = t.string;
418451
export const created_by = t.string;
452+
export const updatedByOrNull = t.union([updated_by, t.null]);
453+
export type UpdatedByOrNull = t.TypeOf<typeof updatedByOrNull>;
454+
export const createdByOrNull = t.union([created_by, t.null]);
455+
export type CreatedByOrNull = t.TypeOf<typeof createdByOrNull>;
419456

420457
export const version = PositiveIntegerGreaterThanZero;
421458
export type Version = t.TypeOf<typeof version>;

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

Lines changed: 23 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,14 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import {
8-
createRulesBulkSchema,
9-
CreateRulesBulkSchema,
10-
CreateRulesBulkSchemaDecoded,
11-
} from './create_rules_bulk_schema';
7+
import { createRulesBulkSchema, CreateRulesBulkSchema } from './create_rules_bulk_schema';
128
import { exactCheck } from '../../../exact_check';
139
import { foldLeftRight } from '../../../test_utils';
14-
import {
15-
getCreateRulesSchemaMock,
16-
getCreateRulesSchemaDecodedMock,
17-
} from './create_rules_schema.mock';
1810
import { formatErrors } from '../../../format_errors';
19-
import { CreateRulesSchema } from './create_rules_schema';
11+
import { getCreateRulesSchemaMock } from './rule_schemas.mock';
2012

2113
// only the basics of testing are here.
22-
// see: create_rules_schema.test.ts for the bulk of the validation tests
14+
// see: rule_schemas.test.ts for the bulk of the validation tests
2315
// this just wraps createRulesSchema in an array
2416
describe('create_rules_bulk_schema', () => {
2517
test('can take an empty array and validate it', () => {
@@ -38,13 +30,16 @@ describe('create_rules_bulk_schema', () => {
3830
const decoded = createRulesBulkSchema.decode(payload);
3931
const checked = exactCheck(payload, decoded);
4032
const output = foldLeftRight(checked);
41-
expect(formatErrors(output.errors)).toEqual([
42-
'Invalid value "undefined" supplied to "description"',
43-
'Invalid value "undefined" supplied to "risk_score"',
44-
'Invalid value "undefined" supplied to "name"',
45-
'Invalid value "undefined" supplied to "severity"',
46-
'Invalid value "undefined" supplied to "type"',
47-
]);
33+
expect(formatErrors(output.errors)).toContain(
34+
'Invalid value "undefined" supplied to "description"'
35+
);
36+
expect(formatErrors(output.errors)).toContain(
37+
'Invalid value "undefined" supplied to "risk_score"'
38+
);
39+
expect(formatErrors(output.errors)).toContain('Invalid value "undefined" supplied to "name"');
40+
expect(formatErrors(output.errors)).toContain(
41+
'Invalid value "undefined" supplied to "severity"'
42+
);
4843
expect(output.schema).toEqual({});
4944
});
5045

@@ -55,7 +50,7 @@ describe('create_rules_bulk_schema', () => {
5550
const checked = exactCheck(payload, decoded);
5651
const output = foldLeftRight(checked);
5752
expect(formatErrors(output.errors)).toEqual([]);
58-
expect(output.schema).toEqual([getCreateRulesSchemaDecodedMock()]);
53+
expect(output.schema).toEqual(payload);
5954
});
6055

6156
test('two array elements do validate', () => {
@@ -65,10 +60,7 @@ describe('create_rules_bulk_schema', () => {
6560
const checked = exactCheck(payload, decoded);
6661
const output = foldLeftRight(checked);
6762
expect(formatErrors(output.errors)).toEqual([]);
68-
expect(output.schema).toEqual([
69-
getCreateRulesSchemaDecodedMock(),
70-
getCreateRulesSchemaDecodedMock(),
71-
]);
63+
expect(output.schema).toEqual(payload);
7264
});
7365

7466
test('single array element with a missing value (risk_score) will not validate', () => {
@@ -137,7 +129,7 @@ describe('create_rules_bulk_schema', () => {
137129
});
138130

139131
test('two array elements where the first is invalid (extra key and value) but the second is valid will not validate', () => {
140-
const singleItem: CreateRulesSchema & { madeUpValue: string } = {
132+
const singleItem = {
141133
...getCreateRulesSchemaMock(),
142134
madeUpValue: 'something',
143135
};
@@ -152,8 +144,8 @@ describe('create_rules_bulk_schema', () => {
152144
});
153145

154146
test('two array elements where the second is invalid (extra key and value) but the first is valid will not validate', () => {
155-
const singleItem: CreateRulesSchema = getCreateRulesSchemaMock();
156-
const secondItem: CreateRulesSchema & { madeUpValue: string } = {
147+
const singleItem = getCreateRulesSchemaMock();
148+
const secondItem = {
157149
...getCreateRulesSchemaMock(),
158150
madeUpValue: 'something',
159151
};
@@ -167,11 +159,11 @@ describe('create_rules_bulk_schema', () => {
167159
});
168160

169161
test('two array elements where both are invalid (extra key and value) will not validate', () => {
170-
const singleItem: CreateRulesSchema & { madeUpValue: string } = {
162+
const singleItem = {
171163
...getCreateRulesSchemaMock(),
172164
madeUpValue: 'something',
173165
};
174-
const secondItem: CreateRulesSchema & { madeUpValue: string } = {
166+
const secondItem = {
175167
...getCreateRulesSchemaMock(),
176168
madeUpValue: 'something',
177169
};
@@ -184,28 +176,6 @@ describe('create_rules_bulk_schema', () => {
184176
expect(output.schema).toEqual({});
185177
});
186178

187-
test('The default for "from" will be "now-6m"', () => {
188-
const { from, ...withoutFrom } = getCreateRulesSchemaMock();
189-
const payload: CreateRulesBulkSchema = [withoutFrom];
190-
191-
const decoded = createRulesBulkSchema.decode(payload);
192-
const checked = exactCheck(payload, decoded);
193-
const output = foldLeftRight(checked);
194-
expect(formatErrors(output.errors)).toEqual([]);
195-
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].from).toEqual('now-6m');
196-
});
197-
198-
test('The default for "to" will be "now"', () => {
199-
const { to, ...withoutTo } = getCreateRulesSchemaMock();
200-
const payload: CreateRulesBulkSchema = [withoutTo];
201-
202-
const decoded = createRulesBulkSchema.decode(payload);
203-
const checked = exactCheck(payload, decoded);
204-
const output = foldLeftRight(checked);
205-
expect(formatErrors(output.errors)).toEqual([]);
206-
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].to).toEqual('now');
207-
});
208-
209179
test('You cannot set the severity to a value other than low, medium, high, or critical', () => {
210180
const badSeverity = { ...getCreateRulesSchemaMock(), severity: 'madeup' };
211181
const payload = [badSeverity];
@@ -226,9 +196,7 @@ describe('create_rules_bulk_schema', () => {
226196
const checked = exactCheck(payload, decoded);
227197
const output = foldLeftRight(checked);
228198
expect(formatErrors(output.errors)).toEqual([]);
229-
expect(output.schema).toEqual([
230-
{ ...getCreateRulesSchemaDecodedMock(), note: '# test markdown' },
231-
]);
199+
expect(output.schema).toEqual(payload);
232200
});
233201

234202
test('You can set "note" to an empty string', () => {
@@ -238,10 +206,10 @@ describe('create_rules_bulk_schema', () => {
238206
const checked = exactCheck(payload, decoded);
239207
const output = foldLeftRight(checked);
240208
expect(formatErrors(output.errors)).toEqual([]);
241-
expect(output.schema).toEqual([{ ...getCreateRulesSchemaDecodedMock(), note: '' }]);
209+
expect(output.schema).toEqual(payload);
242210
});
243211

244-
test('You can set "note" to anything other than string', () => {
212+
test('You cant set "note" to anything other than string', () => {
245213
const payload = [
246214
{
247215
...getCreateRulesSchemaMock(),
@@ -259,26 +227,4 @@ describe('create_rules_bulk_schema', () => {
259227
]);
260228
expect(output.schema).toEqual({});
261229
});
262-
263-
test('The default for "actions" will be an empty array', () => {
264-
const { actions, ...withoutActions } = getCreateRulesSchemaMock();
265-
const payload: CreateRulesBulkSchema = [withoutActions];
266-
267-
const decoded = createRulesBulkSchema.decode(payload);
268-
const checked = exactCheck(payload, decoded);
269-
const output = foldLeftRight(checked);
270-
expect(formatErrors(output.errors)).toEqual([]);
271-
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].actions).toEqual([]);
272-
});
273-
274-
test('The default for "throttle" will be null', () => {
275-
const { throttle, ...withoutThrottle } = getCreateRulesSchemaMock();
276-
const payload: CreateRulesBulkSchema = [withoutThrottle];
277-
278-
const decoded = createRulesBulkSchema.decode(payload);
279-
const checked = exactCheck(payload, decoded);
280-
const output = foldLeftRight(checked);
281-
expect(formatErrors(output.errors)).toEqual([]);
282-
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].throttle).toEqual(null);
283-
});
284230
});

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
import * as t from 'io-ts';
88

9-
import { createRulesSchema, CreateRulesSchemaDecoded } from './create_rules_schema';
9+
import { createRulesSchema } from './rule_schemas';
1010

1111
export const createRulesBulkSchema = t.array(createRulesSchema);
1212
export type CreateRulesBulkSchema = t.TypeOf<typeof createRulesBulkSchema>;
13-
14-
export type CreateRulesBulkSchemaDecoded = CreateRulesSchemaDecoded[];

0 commit comments

Comments
 (0)