Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Security Solution][Detections] Adds framework for replacing API schemas (#82462) #83367

Merged
merged 1 commit into from
Nov 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
import * as t from 'io-ts';
import { Either } from 'fp-ts/lib/Either';

import {
SavedObjectAttributes,
SavedObjectAttribute,
SavedObjectAttributeSingle,
} from 'src/core/types';
import { RiskScore } from '../types/risk_score';
import { UUID } from '../types/uuid';
import { IsoDateString } from '../types/iso_date_string';
Expand Down Expand Up @@ -66,14 +71,30 @@ export type ExcludeExportDetails = t.TypeOf<typeof exclude_export_details>;
export const filters = t.array(t.unknown); // Filters are not easily type-able yet
export type Filters = t.TypeOf<typeof filters>; // Filters are not easily type-able yet

export const filtersOrUndefined = t.union([filters, t.undefined]);
export type FiltersOrUndefined = t.TypeOf<typeof filtersOrUndefined>;

export const saved_object_attribute_single: t.Type<SavedObjectAttributeSingle> = t.recursion(
'saved_object_attribute_single',
() => t.union([t.string, t.number, t.boolean, t.null, t.undefined, saved_object_attributes])
);
export const saved_object_attribute: t.Type<SavedObjectAttribute> = t.recursion(
'saved_object_attribute',
() => t.union([saved_object_attribute_single, t.array(saved_object_attribute_single)])
);
export const saved_object_attributes: t.Type<SavedObjectAttributes> = t.recursion(
'saved_object_attributes',
() => t.record(t.string, saved_object_attribute)
);

/**
* Params is an "object", since it is a type of AlertActionParams which is action templates.
* @see x-pack/plugins/alerts/common/alert.ts
*/
export const action_group = t.string;
export const action_id = t.string;
export const action_action_type_id = t.string;
export const action_params = t.object;
export const action_params = saved_object_attributes;
export const action = t.exact(
t.type({
group: action_group,
Expand All @@ -86,6 +107,18 @@ export const action = t.exact(
export const actions = t.array(action);
export type Actions = t.TypeOf<typeof actions>;

export const actionsCamel = t.array(
t.exact(
t.type({
group: action_group,
id: action_id,
actionTypeId: action_action_type_id,
params: action_params,
})
)
);
export type ActionsCamel = t.TypeOf<typeof actions>;

const stringValidator = (input: unknown): input is string => typeof input === 'string';
export const from = new t.Type<string, string, unknown>(
'From',
Expand Down Expand Up @@ -416,6 +449,10 @@ export const created_at = IsoDateString;
export const updated_at = IsoDateString;
export const updated_by = t.string;
export const created_by = t.string;
export const updatedByOrNull = t.union([updated_by, t.null]);
export type UpdatedByOrNull = t.TypeOf<typeof updatedByOrNull>;
export const createdByOrNull = t.union([created_by, t.null]);
export type CreatedByOrNull = t.TypeOf<typeof createdByOrNull>;

export const version = PositiveIntegerGreaterThanZero;
export type Version = t.TypeOf<typeof version>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/

import {
createRulesBulkSchema,
CreateRulesBulkSchema,
CreateRulesBulkSchemaDecoded,
} from './create_rules_bulk_schema';
import { createRulesBulkSchema, CreateRulesBulkSchema } from './create_rules_bulk_schema';
import { exactCheck } from '../../../exact_check';
import { foldLeftRight } from '../../../test_utils';
import {
getCreateRulesSchemaMock,
getCreateRulesSchemaDecodedMock,
} from './create_rules_schema.mock';
import { formatErrors } from '../../../format_errors';
import { CreateRulesSchema } from './create_rules_schema';
import { getCreateRulesSchemaMock } from './rule_schemas.mock';

// only the basics of testing are here.
// see: create_rules_schema.test.ts for the bulk of the validation tests
// see: rule_schemas.test.ts for the bulk of the validation tests
// this just wraps createRulesSchema in an array
describe('create_rules_bulk_schema', () => {
test('can take an empty array and validate it', () => {
Expand All @@ -38,13 +30,16 @@ describe('create_rules_bulk_schema', () => {
const decoded = createRulesBulkSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([
'Invalid value "undefined" supplied to "description"',
'Invalid value "undefined" supplied to "risk_score"',
'Invalid value "undefined" supplied to "name"',
'Invalid value "undefined" supplied to "severity"',
'Invalid value "undefined" supplied to "type"',
]);
expect(formatErrors(output.errors)).toContain(
'Invalid value "undefined" supplied to "description"'
);
expect(formatErrors(output.errors)).toContain(
'Invalid value "undefined" supplied to "risk_score"'
);
expect(formatErrors(output.errors)).toContain('Invalid value "undefined" supplied to "name"');
expect(formatErrors(output.errors)).toContain(
'Invalid value "undefined" supplied to "severity"'
);
expect(output.schema).toEqual({});
});

Expand All @@ -55,7 +50,7 @@ describe('create_rules_bulk_schema', () => {
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect(output.schema).toEqual([getCreateRulesSchemaDecodedMock()]);
expect(output.schema).toEqual(payload);
});

test('two array elements do validate', () => {
Expand All @@ -65,10 +60,7 @@ describe('create_rules_bulk_schema', () => {
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect(output.schema).toEqual([
getCreateRulesSchemaDecodedMock(),
getCreateRulesSchemaDecodedMock(),
]);
expect(output.schema).toEqual(payload);
});

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

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

test('two array elements where the second is invalid (extra key and value) but the first is valid will not validate', () => {
const singleItem: CreateRulesSchema = getCreateRulesSchemaMock();
const secondItem: CreateRulesSchema & { madeUpValue: string } = {
const singleItem = getCreateRulesSchemaMock();
const secondItem = {
...getCreateRulesSchemaMock(),
madeUpValue: 'something',
};
Expand All @@ -167,11 +159,11 @@ describe('create_rules_bulk_schema', () => {
});

test('two array elements where both are invalid (extra key and value) will not validate', () => {
const singleItem: CreateRulesSchema & { madeUpValue: string } = {
const singleItem = {
...getCreateRulesSchemaMock(),
madeUpValue: 'something',
};
const secondItem: CreateRulesSchema & { madeUpValue: string } = {
const secondItem = {
...getCreateRulesSchemaMock(),
madeUpValue: 'something',
};
Expand All @@ -184,28 +176,6 @@ describe('create_rules_bulk_schema', () => {
expect(output.schema).toEqual({});
});

test('The default for "from" will be "now-6m"', () => {
const { from, ...withoutFrom } = getCreateRulesSchemaMock();
const payload: CreateRulesBulkSchema = [withoutFrom];

const decoded = createRulesBulkSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].from).toEqual('now-6m');
});

test('The default for "to" will be "now"', () => {
const { to, ...withoutTo } = getCreateRulesSchemaMock();
const payload: CreateRulesBulkSchema = [withoutTo];

const decoded = createRulesBulkSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].to).toEqual('now');
});

test('You cannot set the severity to a value other than low, medium, high, or critical', () => {
const badSeverity = { ...getCreateRulesSchemaMock(), severity: 'madeup' };
const payload = [badSeverity];
Expand All @@ -226,9 +196,7 @@ describe('create_rules_bulk_schema', () => {
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect(output.schema).toEqual([
{ ...getCreateRulesSchemaDecodedMock(), note: '# test markdown' },
]);
expect(output.schema).toEqual(payload);
});

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

test('You can set "note" to anything other than string', () => {
test('You cant set "note" to anything other than string', () => {
const payload = [
{
...getCreateRulesSchemaMock(),
Expand All @@ -259,26 +227,4 @@ describe('create_rules_bulk_schema', () => {
]);
expect(output.schema).toEqual({});
});

test('The default for "actions" will be an empty array', () => {
const { actions, ...withoutActions } = getCreateRulesSchemaMock();
const payload: CreateRulesBulkSchema = [withoutActions];

const decoded = createRulesBulkSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].actions).toEqual([]);
});

test('The default for "throttle" will be null', () => {
const { throttle, ...withoutThrottle } = getCreateRulesSchemaMock();
const payload: CreateRulesBulkSchema = [withoutThrottle];

const decoded = createRulesBulkSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect((output.schema as CreateRulesBulkSchemaDecoded)[0].throttle).toEqual(null);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

import * as t from 'io-ts';

import { createRulesSchema, CreateRulesSchemaDecoded } from './create_rules_schema';
import { createRulesSchema } from './rule_schemas';

export const createRulesBulkSchema = t.array(createRulesSchema);
export type CreateRulesBulkSchema = t.TypeOf<typeof createRulesBulkSchema>;

export type CreateRulesBulkSchemaDecoded = CreateRulesSchemaDecoded[];
Loading