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

[Security Solution][Detections] Extended rule execution logging to Event Log #126063

Merged
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
@@ -0,0 +1,214 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { defaultCsvArray } from '.';

describe('defaultCsvArray', () => {
describe('Creates a schema of an array that works in the following way:', () => {
type TestType = t.TypeOf<typeof TestType>;
const TestType = t.union(
[t.literal('foo'), t.literal('bar'), t.literal('42'), t.null, t.undefined],
'TestType'
);

const TestCsvArray = defaultCsvArray(TestType);

describe('Name of the schema', () => {
it('has a default value', () => {
const CsvArray = defaultCsvArray(TestType);
expect(CsvArray.name).toEqual('DefaultCsvArray<TestType>');
});

it('can be overriden', () => {
const CsvArray = defaultCsvArray(TestType, 'CustomName');
expect(CsvArray.name).toEqual('CustomName');
});
});

describe('Validation succeeds', () => {
describe('when input is a single valid string value', () => {
const cases = [{ input: 'foo' }, { input: 'bar' }, { input: '42' }];

cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = TestCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput = [input]; // note that it's an array after decode

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

describe('when input is an array of valid string values', () => {
const cases = [
{ input: ['foo'] },
{ input: ['foo', 'bar'] },
{ input: ['foo', 'bar', '42'] },
];

cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = TestCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput = input;

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

describe('when input is a string which is a comma-separated array of valid values', () => {
const cases = [
{
input: 'foo,bar',
expectedOutput: ['foo', 'bar'],
},
{
input: 'foo,bar,42',
expectedOutput: ['foo', 'bar', '42'],
},
];

cases.forEach(({ input, expectedOutput }) => {
it(`${input}`, () => {
const decoded = TestCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);

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

describe('Validation fails', () => {
describe('when input is a single invalid value', () => {
const cases = [
{
input: 'val',
expectedErrors: ['Invalid value "val" supplied to "DefaultCsvArray<TestType>"'],
},
{
input: '5',
expectedErrors: ['Invalid value "5" supplied to "DefaultCsvArray<TestType>"'],
},
{
input: 5,
expectedErrors: ['Invalid value "5" supplied to "DefaultCsvArray<TestType>"'],
},
{
input: {},
expectedErrors: ['Invalid value "{}" supplied to "DefaultCsvArray<TestType>"'],
},
];

cases.forEach(({ input, expectedErrors }) => {
it(`${input}`, () => {
const decoded = TestCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
});
});
});

describe('when input is an array of invalid values', () => {
const cases = [
{
input: ['value 1', 5],
expectedErrors: [
'Invalid value "value 1" supplied to "DefaultCsvArray<TestType>"',
'Invalid value "5" supplied to "DefaultCsvArray<TestType>"',
],
},
{
input: ['value 1', 'foo'],
expectedErrors: ['Invalid value "value 1" supplied to "DefaultCsvArray<TestType>"'],
},
{
input: ['', 5, {}],
expectedErrors: [
'Invalid value "" supplied to "DefaultCsvArray<TestType>"',
'Invalid value "5" supplied to "DefaultCsvArray<TestType>"',
'Invalid value "{}" supplied to "DefaultCsvArray<TestType>"',
],
},
];

cases.forEach(({ input, expectedErrors }) => {
it(`${input}`, () => {
const decoded = TestCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
});
});
});

describe('when input is a string which is a comma-separated array of invalid values', () => {
const cases = [
{
input: 'value 1,5',
expectedErrors: [
'Invalid value "value 1" supplied to "DefaultCsvArray<TestType>"',
'Invalid value "5" supplied to "DefaultCsvArray<TestType>"',
],
},
{
input: 'value 1,foo',
expectedErrors: ['Invalid value "value 1" supplied to "DefaultCsvArray<TestType>"'],
},
{
input: ',5,{}',
expectedErrors: [
'Invalid value "" supplied to "DefaultCsvArray<TestType>"',
'Invalid value "5" supplied to "DefaultCsvArray<TestType>"',
'Invalid value "{}" supplied to "DefaultCsvArray<TestType>"',
],
},
];

cases.forEach(({ input, expectedErrors }) => {
it(`${input}`, () => {
const decoded = TestCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
});
});
});
});

describe('Validation returns default value (an empty array)', () => {
describe('when input is', () => {
const cases = [{ input: null }, { input: undefined }, { input: '' }, { input: [] }];

cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = TestCsvArray.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput: string[] = [];

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(expectedOutput);
});
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
import { Either } from 'fp-ts/lib/Either';

/**
* Creates a schema of an array that works in the following way:
* - If input is a CSV string, it will be parsed to an array which will be validated.
* - If input is an array, each item is validated to match `itemSchema`.
* - If input is a single string, it is validated to match `itemSchema`.
* - If input is not specified, the result will be set to [] (empty array):
* - null, undefined, empty string, empty array
*
* In all cases when an input is valid, the resulting decoded value will be an array,
* either an empty one or containing valid items.
*
* @param itemSchema Schema of the array's items.
* @param name (Optional) Name of the resulting schema.
*/
export const defaultCsvArray = <TItem>(
itemSchema: t.Type<TItem>,
name?: string
): t.Type<TItem[]> => {
return new t.Type<TItem[]>(
name ?? `DefaultCsvArray<${itemSchema.name}>`,
t.array(itemSchema).is,
(input, context): Either<t.Errors, TItem[]> => {
if (input == null) {
return t.success([]);
} else if (typeof input === 'string') {
if (input === '') {
return t.success([]);
} else {
return t.array(itemSchema).validate(input.split(','), context);
}
} else {
return t.array(itemSchema).validate(input, context);
}
},
t.identity
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { left } from 'fp-ts/lib/Either';
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { defaultValue } from '.';

describe('defaultValue', () => {
describe('Creates a schema that sets a default value if the input value is not specified', () => {
type TestType = t.TypeOf<typeof TestType>;
const TestType = t.union([t.string, t.number, t.null, t.undefined], 'TestType');

const DefaultValue = defaultValue(TestType, 42);

describe('Name of the schema', () => {
it('has a default value', () => {
expect(defaultValue(TestType, 42).name).toEqual('DefaultValue<TestType>');
});

it('can be overriden', () => {
expect(defaultValue(TestType, 42, 'CustomName').name).toEqual('CustomName');
});
});

describe('Validation succeeds', () => {
describe('when input is a valid value', () => {
const cases = [
{ input: 'foo' },
{ input: '42' },
{ input: 42 },
// including all "falsey" values which are not null or undefined
{ input: '' },
{ input: 0 },
];

cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultValue.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput = input;

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

describe('Validation fails', () => {
describe('when input is an invalid value', () => {
const cases = [
{
input: {},
expectedErrors: ['Invalid value "{}" supplied to "DefaultValue<TestType>"'],
},
{
input: { foo: 42 },
expectedErrors: ['Invalid value "{"foo":42}" supplied to "DefaultValue<TestType>"'],
},
{
input: [],
expectedErrors: ['Invalid value "[]" supplied to "DefaultValue<TestType>"'],
},
{
input: ['foo', 42],
expectedErrors: ['Invalid value "["foo",42]" supplied to "DefaultValue<TestType>"'],
},
];

cases.forEach(({ input, expectedErrors }) => {
it(`${input}`, () => {
const decoded = DefaultValue.decode(input);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual(expectedErrors);
expect(message.schema).toEqual({});
});
});
});
});

describe('Validation returns specified default value', () => {
describe('when input is', () => {
const cases = [{ input: null }, { input: undefined }];

cases.forEach(({ input }) => {
it(`${input}`, () => {
const decoded = DefaultValue.decode(input);
const message = pipe(decoded, foldLeftRight);
const expectedOutput = 42;

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