[Security Solution][Exceptions] - Require non empty entries and non empty string values in exception list items (#72748)
## Summary This PR updates the exception list entries schemas. - **Prior:** `entries` could be `undefined` or empty array on `ExceptionListItemSchema` - **Now:** `entries` is a required field that cannot be empty - there's really no use for an item without `entries` - **Prior:** `field` and `value` could be empty string in `EntryMatch` - **Now:** `field` and `value` can no longer be empty strings - **Prior:** `field` could be empty string and `value` could be empty array in `EntryMatchAny` - **Now:** `field` and `value` can no longer be empty string and array respectively - **Prior:** `field` and `list.id` could be empty string in `EntryList` - **Now:** `field` and `list.id` can no longer be empty strings - **Prior:** `field` could be empty string in `EntryExists` - **Now:** `field` can no longer be empty string - **Prior:** `field` could be empty string in `EntryNested` - **Now:** `field` can no longer be empty string - **Prior:** `entries` could be empty array in `EntryNested` - **Now:** `entries` can no longer be empty array
This commit is contained in:
parent
073bd66a86
commit
9c7d65cfc2
|
@ -142,7 +142,7 @@ describe('create_endpoint_list_item_schema', () => {
|
|||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an undefined for "entries" but return an array', () => {
|
||||
test('it should NOT validate an undefined for "entries"', () => {
|
||||
const inputPayload = getCreateEndpointListItemSchemaMock();
|
||||
const outputPayload = getCreateEndpointListItemSchemaMock();
|
||||
delete inputPayload.entries;
|
||||
|
@ -151,8 +151,10 @@ describe('create_endpoint_list_item_schema', () => {
|
|||
const checked = exactCheck(inputPayload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
delete (message.schema as CreateEndpointListItemSchema).item_id;
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(outputPayload);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => {
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
tags,
|
||||
} from '../common/schemas';
|
||||
import { RequiredKeepUndefined } from '../../types';
|
||||
import { CreateCommentsArray, DefaultCreateCommentsArray, DefaultEntryArray } from '../types';
|
||||
import { CreateCommentsArray, DefaultCreateCommentsArray, nonEmptyEntriesArray } from '../types';
|
||||
import { EntriesArray } from '../types/entries';
|
||||
import { DefaultUuid } from '../../siem_common_deps';
|
||||
|
||||
|
@ -28,6 +28,7 @@ export const createEndpointListItemSchema = t.intersection([
|
|||
t.exact(
|
||||
t.type({
|
||||
description,
|
||||
entries: nonEmptyEntriesArray,
|
||||
name,
|
||||
type: exceptionListItemType,
|
||||
})
|
||||
|
@ -36,7 +37,6 @@ export const createEndpointListItemSchema = t.intersection([
|
|||
t.partial({
|
||||
_tags, // defaults to empty array if not set during decode
|
||||
comments: DefaultCreateCommentsArray, // defaults to empty array if not set during decode
|
||||
entries: DefaultEntryArray, // defaults to empty array if not set during decode
|
||||
item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode
|
||||
meta, // defaults to undefined if not set during decode
|
||||
tags, // defaults to empty array if not set during decode
|
||||
|
|
|
@ -130,7 +130,7 @@ describe('create_exception_list_item_schema', () => {
|
|||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an undefined for "entries" but return an array', () => {
|
||||
test('it should NOT validate an undefined for "entries"', () => {
|
||||
const inputPayload = getCreateExceptionListItemSchemaMock();
|
||||
const outputPayload = getCreateExceptionListItemSchemaMock();
|
||||
delete inputPayload.entries;
|
||||
|
@ -139,8 +139,10 @@ describe('create_exception_list_item_schema', () => {
|
|||
const checked = exactCheck(inputPayload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
delete (message.schema as CreateExceptionListItemSchema).item_id;
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(outputPayload);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an undefined for "namespace_type" but return enum "single" and generate a correct body not counting the auto generated uuid', () => {
|
||||
|
|
|
@ -25,8 +25,8 @@ import { RequiredKeepUndefined } from '../../types';
|
|||
import {
|
||||
CreateCommentsArray,
|
||||
DefaultCreateCommentsArray,
|
||||
DefaultEntryArray,
|
||||
NamespaceType,
|
||||
nonEmptyEntriesArray,
|
||||
} from '../types';
|
||||
import { EntriesArray } from '../types/entries';
|
||||
import { DefaultUuid } from '../../siem_common_deps';
|
||||
|
@ -35,6 +35,7 @@ export const createExceptionListItemSchema = t.intersection([
|
|||
t.exact(
|
||||
t.type({
|
||||
description,
|
||||
entries: nonEmptyEntriesArray,
|
||||
list_id,
|
||||
name,
|
||||
type: exceptionListItemType,
|
||||
|
@ -44,7 +45,6 @@ export const createExceptionListItemSchema = t.intersection([
|
|||
t.partial({
|
||||
_tags, // defaults to empty array if not set during decode
|
||||
comments: DefaultCreateCommentsArray, // defaults to empty array if not set during decode
|
||||
entries: DefaultEntryArray, // defaults to empty array if not set during decode
|
||||
item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode
|
||||
meta, // defaults to undefined if not set during decode
|
||||
namespace_type, // defaults to 'single' if not set during decode
|
||||
|
|
|
@ -97,7 +97,7 @@ describe('update_endpoint_list_item_schema', () => {
|
|||
expect(message.schema).toEqual(outputPayload);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "entries" but return an array', () => {
|
||||
test('it should NOT accept an undefined for "entries"', () => {
|
||||
const inputPayload = getUpdateEndpointListItemSchemaMock();
|
||||
const outputPayload = getUpdateEndpointListItemSchemaMock();
|
||||
delete inputPayload.entries;
|
||||
|
@ -105,8 +105,10 @@ describe('update_endpoint_list_item_schema', () => {
|
|||
const decoded = updateEndpointListItemSchema.decode(inputPayload);
|
||||
const checked = exactCheck(inputPayload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(outputPayload);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "tags" but return an array', () => {
|
||||
|
|
|
@ -22,16 +22,17 @@ import {
|
|||
} from '../common/schemas';
|
||||
import { RequiredKeepUndefined } from '../../types';
|
||||
import {
|
||||
DefaultEntryArray,
|
||||
DefaultUpdateCommentsArray,
|
||||
EntriesArray,
|
||||
UpdateCommentsArray,
|
||||
nonEmptyEntriesArray,
|
||||
} from '../types';
|
||||
|
||||
export const updateEndpointListItemSchema = t.intersection([
|
||||
t.exact(
|
||||
t.type({
|
||||
description,
|
||||
entries: nonEmptyEntriesArray,
|
||||
name,
|
||||
type: exceptionListItemType,
|
||||
})
|
||||
|
@ -41,7 +42,6 @@ export const updateEndpointListItemSchema = t.intersection([
|
|||
_tags, // defaults to empty array if not set during decode
|
||||
_version, // defaults to undefined if not set during decode
|
||||
comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode
|
||||
entries: DefaultEntryArray, // defaults to empty array if not set during decode
|
||||
id, // defaults to undefined if not set during decode
|
||||
item_id: t.union([t.string, t.undefined]),
|
||||
meta, // defaults to undefined if not set during decode
|
||||
|
|
|
@ -97,7 +97,7 @@ describe('update_exception_list_item_schema', () => {
|
|||
expect(message.schema).toEqual(outputPayload);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "entries" but return an array', () => {
|
||||
test('it should NOT accept an undefined for "entries"', () => {
|
||||
const inputPayload = getUpdateExceptionListItemSchemaMock();
|
||||
const outputPayload = getUpdateExceptionListItemSchemaMock();
|
||||
delete inputPayload.entries;
|
||||
|
@ -105,8 +105,10 @@ describe('update_exception_list_item_schema', () => {
|
|||
const decoded = updateExceptionListItemSchema.decode(inputPayload);
|
||||
const checked = exactCheck(inputPayload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(outputPayload);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "namespace_type" but return enum "single"', () => {
|
||||
|
|
|
@ -23,17 +23,18 @@ import {
|
|||
} from '../common/schemas';
|
||||
import { RequiredKeepUndefined } from '../../types';
|
||||
import {
|
||||
DefaultEntryArray,
|
||||
DefaultUpdateCommentsArray,
|
||||
EntriesArray,
|
||||
NamespaceType,
|
||||
UpdateCommentsArray,
|
||||
nonEmptyEntriesArray,
|
||||
} from '../types';
|
||||
|
||||
export const updateExceptionListItemSchema = t.intersection([
|
||||
t.exact(
|
||||
t.type({
|
||||
description,
|
||||
entries: nonEmptyEntriesArray,
|
||||
name,
|
||||
type: exceptionListItemType,
|
||||
})
|
||||
|
@ -43,7 +44,6 @@ export const updateExceptionListItemSchema = t.intersection([
|
|||
_tags, // defaults to empty array if not set during decode
|
||||
_version, // defaults to undefined if not set during decode
|
||||
comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode
|
||||
entries: DefaultEntryArray, // defaults to empty array if not set during decode
|
||||
id, // defaults to undefined if not set during decode
|
||||
item_id: t.union([t.string, t.undefined]),
|
||||
meta, // defaults to undefined if not set during decode
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { DefaultEntryArray } from './default_entries_array';
|
||||
import { EntriesArray } from './entries';
|
||||
import { getEntriesArrayMock, getEntryMatchMock, getEntryNestedMock } from './entries.mock';
|
||||
|
||||
// NOTE: This may seem weird, but when validating schemas that use a union
|
||||
// it checks against every item in that union. Since entries consist of 5
|
||||
// different entry types, it returns 5 of these. To make more readable,
|
||||
// extracted here.
|
||||
const returnedSchemaError =
|
||||
'"Array<({| field: string, operator: "excluded" | "included", type: "match", value: string |} | {| field: string, operator: "excluded" | "included", type: "match_any", value: DefaultStringArray |} | {| field: string, list: {| id: string, type: "binary" | "boolean" | "byte" | "date" | "date_nanos" | "date_range" | "double" | "double_range" | "float" | "float_range" | "geo_point" | "geo_shape" | "half_float" | "integer" | "integer_range" | "ip" | "ip_range" | "keyword" | "long" | "long_range" | "shape" | "short" | "text" |}, operator: "excluded" | "included", type: "list" |} | {| field: string, operator: "excluded" | "included", type: "exists" |} | {| entries: Array<{| field: string, operator: "excluded" | "included", type: "match", value: string |}>, field: string, type: "nested" |})>"';
|
||||
|
||||
describe('default_entries_array', () => {
|
||||
test('it should validate an empty array', () => {
|
||||
const payload: EntriesArray = [];
|
||||
const decoded = DefaultEntryArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of regular and nested entries', () => {
|
||||
const payload: EntriesArray = getEntriesArrayMock();
|
||||
const decoded = DefaultEntryArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of nested entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryNestedMock() }];
|
||||
const decoded = DefaultEntryArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of non nested entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryMatchMock() }];
|
||||
const decoded = DefaultEntryArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate an array of numbers', () => {
|
||||
const payload = [1];
|
||||
const decoded = DefaultEntryArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
// TODO: Known weird error formatting that is on our list to address
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
`Invalid value "1" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "1" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "1" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "1" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "1" supplied to ${returnedSchemaError}`,
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an array of strings', () => {
|
||||
const payload = ['some string'];
|
||||
const decoded = DefaultEntryArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
`Invalid value "some string" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "some string" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "some string" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "some string" supplied to ${returnedSchemaError}`,
|
||||
`Invalid value "some string" supplied to ${returnedSchemaError}`,
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default array entry', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultEntryArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
import { EntriesArray, entriesArray } from './entries';
|
||||
|
||||
/**
|
||||
* Types the DefaultEntriesArray as:
|
||||
* - If null or undefined, then a default array of type entry will be set
|
||||
*/
|
||||
export const DefaultEntryArray = new t.Type<EntriesArray, EntriesArray, unknown>(
|
||||
'DefaultEntryArray',
|
||||
entriesArray.is,
|
||||
(input): Either<t.Errors, EntriesArray> =>
|
||||
input == null ? t.success([]) : entriesArray.decode(input),
|
||||
t.identity
|
||||
);
|
|
@ -4,65 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ENTRY_VALUE,
|
||||
EXISTS,
|
||||
FIELD,
|
||||
LIST,
|
||||
LIST_ID,
|
||||
MATCH,
|
||||
MATCH_ANY,
|
||||
NESTED,
|
||||
OPERATOR,
|
||||
TYPE,
|
||||
} from '../../constants.mock';
|
||||
|
||||
import {
|
||||
EntriesArray,
|
||||
EntryExists,
|
||||
EntryList,
|
||||
EntryMatch,
|
||||
EntryMatchAny,
|
||||
EntryNested,
|
||||
} from './entries';
|
||||
|
||||
export const getEntryMatchMock = (): EntryMatch => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: MATCH,
|
||||
value: ENTRY_VALUE,
|
||||
});
|
||||
|
||||
export const getEntryMatchAnyMock = (): EntryMatchAny => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: MATCH_ANY,
|
||||
value: [ENTRY_VALUE],
|
||||
});
|
||||
|
||||
export const getEntryListMock = (): EntryList => ({
|
||||
field: FIELD,
|
||||
list: { id: LIST_ID, type: TYPE },
|
||||
operator: OPERATOR,
|
||||
type: LIST,
|
||||
});
|
||||
|
||||
export const getEntryExistsMock = (): EntryExists => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: EXISTS,
|
||||
});
|
||||
|
||||
export const getEntryNestedMock = (): EntryNested => ({
|
||||
entries: [getEntryMatchMock(), getEntryMatchMock()],
|
||||
field: FIELD,
|
||||
type: NESTED,
|
||||
});
|
||||
import { EntriesArray } from './entries';
|
||||
import { getEntryMatchMock } from './entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from './entry_match_any.mock';
|
||||
import { getEntryListMock } from './entry_list.mock';
|
||||
import { getEntryExistsMock } from './entry_exists.mock';
|
||||
import { getEntryNestedMock } from './entry_nested.mock';
|
||||
|
||||
export const getEntriesArrayMock = (): EntriesArray => [
|
||||
getEntryMatchMock(),
|
||||
getEntryMatchAnyMock(),
|
||||
getEntryListMock(),
|
||||
getEntryExistsMock(),
|
||||
getEntryNestedMock(),
|
||||
{ ...getEntryMatchMock() },
|
||||
{ ...getEntryMatchAnyMock() },
|
||||
{ ...getEntryListMock() },
|
||||
{ ...getEntryExistsMock() },
|
||||
{ ...getEntryNestedMock() },
|
||||
];
|
||||
|
|
|
@ -9,359 +9,147 @@ import { left } from 'fp-ts/lib/Either';
|
|||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import {
|
||||
getEntryExistsMock,
|
||||
getEntryListMock,
|
||||
getEntryMatchAnyMock,
|
||||
getEntryMatchMock,
|
||||
getEntryNestedMock,
|
||||
} from './entries.mock';
|
||||
import {
|
||||
EntryExists,
|
||||
EntryList,
|
||||
EntryMatch,
|
||||
EntryMatchAny,
|
||||
EntryNested,
|
||||
entriesExists,
|
||||
entriesList,
|
||||
entriesMatch,
|
||||
entriesMatchAny,
|
||||
entriesNested,
|
||||
} from './entries';
|
||||
import { getEntryMatchMock } from './entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from './entry_match_any.mock';
|
||||
import { getEntryListMock } from './entry_list.mock';
|
||||
import { getEntryExistsMock } from './entry_exists.mock';
|
||||
import { getEntryNestedMock } from './entry_nested.mock';
|
||||
import { getEntriesArrayMock } from './entries.mock';
|
||||
import { entriesArray, entriesArrayOrUndefined, entry } from './entries';
|
||||
|
||||
describe('Entries', () => {
|
||||
describe('entriesMatch', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = getEntryMatchMock();
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
describe('entry', () => {
|
||||
test('it should validate a match entry', () => {
|
||||
const payload = { ...getEntryMatchMock() };
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "included"', () => {
|
||||
const payload = getEntryMatchMock();
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
test('it should validate a match_any entry', () => {
|
||||
const payload = { ...getEntryMatchAnyMock() };
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = getEntryMatchMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
test('it should validate a exists entry', () => {
|
||||
const payload = { ...getEntryExistsMock() };
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate when "value" is not string', () => {
|
||||
const payload: Omit<EntryMatch, 'value'> & { value: string[] } = {
|
||||
...getEntryMatchMock(),
|
||||
value: ['some value'],
|
||||
};
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
test('it should validate a list entry', () => {
|
||||
const payload = { ...getEntryListMock() };
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate a nested entry', () => {
|
||||
const payload = { ...getEntryNestedMock() };
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "["some value"]" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "list"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "match"', () => {
|
||||
const payload: Omit<EntryMatch, 'type'> & { type: string } = {
|
||||
...getEntryMatchMock(),
|
||||
type: 'match_any',
|
||||
};
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "match_any" supplied to "type"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryMatch & {
|
||||
extraKey?: string;
|
||||
} = getEntryMatchMock();
|
||||
payload.extraKey = 'some value';
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryMatchMock());
|
||||
});
|
||||
});
|
||||
|
||||
describe('entriesMatchAny', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = getEntryMatchAnyMock();
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "included"', () => {
|
||||
const payload = getEntryMatchAnyMock();
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "excluded"', () => {
|
||||
const payload = getEntryMatchAnyMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate when value is not string array', () => {
|
||||
const payload: Omit<EntryMatchAny, 'value'> & { value: string } = {
|
||||
...getEntryMatchAnyMock(),
|
||||
value: 'some string',
|
||||
};
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "some string" supplied to "value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "match_any"', () => {
|
||||
const payload: Omit<EntryMatchAny, 'type'> & { type: string } = {
|
||||
...getEntryMatchAnyMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryMatchAny & {
|
||||
extraKey?: string;
|
||||
} = getEntryMatchAnyMock();
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryMatchAnyMock());
|
||||
});
|
||||
});
|
||||
|
||||
describe('entriesExists', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = getEntryExistsMock();
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "included"', () => {
|
||||
const payload = getEntryExistsMock();
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = getEntryExistsMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryExists & {
|
||||
extraKey?: string;
|
||||
} = getEntryExistsMock();
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryExistsMock());
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "exists"', () => {
|
||||
const payload: Omit<EntryExists, 'type'> & { type: string } = {
|
||||
...getEntryExistsMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('entriesList', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = getEntryListMock();
|
||||
const decoded = entriesList.decode(payload);
|
||||
describe('entriesArray', () => {
|
||||
test('it should validate an array with match entry', () => {
|
||||
const payload = [{ ...getEntryMatchMock() }];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "included"', () => {
|
||||
const payload = getEntryListMock();
|
||||
const decoded = entriesList.decode(payload);
|
||||
test('it should validate an array with match_any entry', () => {
|
||||
const payload = [{ ...getEntryMatchAnyMock() }];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = getEntryListMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesList.decode(payload);
|
||||
test('it should validate an array with exists entry', () => {
|
||||
const payload = [{ ...getEntryExistsMock() }];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate when "list" is not expected value', () => {
|
||||
const payload: Omit<EntryList, 'list'> & { list: string } = {
|
||||
...getEntryListMock(),
|
||||
list: 'someListId',
|
||||
};
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "someListId" supplied to "list"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "lists"', () => {
|
||||
const payload: Omit<EntryList, 'type'> & { type: 'match_any' } = {
|
||||
...getEntryListMock(),
|
||||
type: 'match_any',
|
||||
};
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "match_any" supplied to "type"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryList & {
|
||||
extraKey?: string;
|
||||
} = getEntryListMock();
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesList.decode(payload);
|
||||
test('it should validate an array with list entry', () => {
|
||||
const payload = [{ ...getEntryListMock() }];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryListMock());
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with nested entry', () => {
|
||||
const payload = [{ ...getEntryNestedMock() }];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with all types of entries', () => {
|
||||
const payload = [...getEntriesArrayMock()];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('entriesNested', () => {
|
||||
test('it should validate a nested entry', () => {
|
||||
const payload = getEntryNestedMock();
|
||||
const decoded = entriesNested.decode(payload);
|
||||
describe('entriesArrayOrUndefined', () => {
|
||||
test('it should validate undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = entriesArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate when "type" is not "nested"', () => {
|
||||
const payload: Omit<EntryNested, 'type'> & { type: 'match' } = {
|
||||
...getEntryNestedMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate when "field" is not a string', () => {
|
||||
const payload: Omit<EntryNested, 'field'> & {
|
||||
field: number;
|
||||
} = { ...getEntryNestedMock(), field: 1 };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "1" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate when "entries" is not a an array', () => {
|
||||
const payload: Omit<EntryNested, 'entries'> & {
|
||||
entries: string;
|
||||
} = { ...getEntryNestedMock(), entries: 'im a string' };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "im a string" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate when "entries" contains an entry item that is not type "match"', () => {
|
||||
const payload: Omit<EntryNested, 'entries'> & {
|
||||
entries: EntryMatchAny[];
|
||||
} = { ...getEntryNestedMock(), entries: [getEntryMatchAnyMock()] };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "match_any" supplied to "entries,type"',
|
||||
'Invalid value "["some host name"]" supplied to "entries,value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryNested & {
|
||||
extraKey?: string;
|
||||
} = getEntryNestedMock();
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesNested.decode(payload);
|
||||
test('it should validate an array with nested entry', () => {
|
||||
const payload = [{ ...getEntryNestedMock() }];
|
||||
const decoded = entriesArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryNestedMock());
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,62 +8,19 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { operator, type } from '../common/schemas';
|
||||
import { DefaultStringArray } from '../../siem_common_deps';
|
||||
|
||||
export const entriesMatch = t.exact(
|
||||
t.type({
|
||||
field: t.string,
|
||||
operator,
|
||||
type: t.keyof({ match: null }),
|
||||
value: t.string,
|
||||
})
|
||||
);
|
||||
export type EntryMatch = t.TypeOf<typeof entriesMatch>;
|
||||
|
||||
export const entriesMatchAny = t.exact(
|
||||
t.type({
|
||||
field: t.string,
|
||||
operator,
|
||||
type: t.keyof({ match_any: null }),
|
||||
value: DefaultStringArray,
|
||||
})
|
||||
);
|
||||
export type EntryMatchAny = t.TypeOf<typeof entriesMatchAny>;
|
||||
|
||||
export const entriesList = t.exact(
|
||||
t.type({
|
||||
field: t.string,
|
||||
list: t.exact(t.type({ id: t.string, type })),
|
||||
operator,
|
||||
type: t.keyof({ list: null }),
|
||||
})
|
||||
);
|
||||
export type EntryList = t.TypeOf<typeof entriesList>;
|
||||
|
||||
export const entriesExists = t.exact(
|
||||
t.type({
|
||||
field: t.string,
|
||||
operator,
|
||||
type: t.keyof({ exists: null }),
|
||||
})
|
||||
);
|
||||
export type EntryExists = t.TypeOf<typeof entriesExists>;
|
||||
|
||||
export const entriesNested = t.exact(
|
||||
t.type({
|
||||
entries: t.array(entriesMatch),
|
||||
field: t.string,
|
||||
type: t.keyof({ nested: null }),
|
||||
})
|
||||
);
|
||||
export type EntryNested = t.TypeOf<typeof entriesNested>;
|
||||
import { entriesMatchAny } from './entry_match_any';
|
||||
import { entriesMatch } from './entry_match';
|
||||
import { entriesExists } from './entry_exists';
|
||||
import { entriesList } from './entry_list';
|
||||
import { entriesNested } from './entry_nested';
|
||||
|
||||
export const entry = t.union([entriesMatch, entriesMatchAny, entriesList, entriesExists]);
|
||||
export type Entry = t.TypeOf<typeof entry>;
|
||||
|
||||
export const entriesArray = t.array(
|
||||
t.union([entriesMatch, entriesMatchAny, entriesList, entriesExists, entriesNested])
|
||||
);
|
||||
export type EntriesArray = t.TypeOf<typeof entriesArray>;
|
||||
|
||||
export const entriesArrayOrUndefined = t.union([entriesArray, t.undefined]);
|
||||
export type EntriesArrayOrUndefined = t.TypeOf<typeof entriesArrayOrUndefined>;
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EXISTS, FIELD, OPERATOR } from '../../constants.mock';
|
||||
|
||||
import { EntryExists } from './entry_exists';
|
||||
|
||||
export const getEntryExistsMock = (): EntryExists => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: EXISTS,
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { getEntryExistsMock } from './entry_exists.mock';
|
||||
import { EntryExists, entriesExists } from './entry_exists';
|
||||
|
||||
describe('entriesExists', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = { ...getEntryExistsMock() };
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "included"', () => {
|
||||
const payload = { ...getEntryExistsMock() };
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = { ...getEntryExistsMock() };
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate when "field" is empty string', () => {
|
||||
const payload: Omit<EntryExists, 'field'> & { field: string } = {
|
||||
...getEntryExistsMock(),
|
||||
field: '',
|
||||
};
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryExists & {
|
||||
extraKey?: string;
|
||||
} = { ...getEntryExistsMock() };
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({ ...getEntryExistsMock() });
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "exists"', () => {
|
||||
const payload: Omit<EntryExists, 'type'> & { type: string } = {
|
||||
...getEntryExistsMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = entriesExists.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
21
x-pack/plugins/lists/common/schemas/types/entry_exists.ts
Normal file
21
x-pack/plugins/lists/common/schemas/types/entry_exists.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../siem_common_deps';
|
||||
import { operator } from '../common/schemas';
|
||||
|
||||
export const entriesExists = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator,
|
||||
type: t.keyof({ exists: null }),
|
||||
})
|
||||
);
|
||||
export type EntryExists = t.TypeOf<typeof entriesExists>;
|
16
x-pack/plugins/lists/common/schemas/types/entry_list.mock.ts
Normal file
16
x-pack/plugins/lists/common/schemas/types/entry_list.mock.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FIELD, LIST, LIST_ID, OPERATOR, TYPE } from '../../constants.mock';
|
||||
|
||||
import { EntryList } from './entry_list';
|
||||
|
||||
export const getEntryListMock = (): EntryList => ({
|
||||
field: FIELD,
|
||||
list: { id: LIST_ID, type: TYPE },
|
||||
operator: OPERATOR,
|
||||
type: LIST,
|
||||
});
|
95
x-pack/plugins/lists/common/schemas/types/entry_list.test.ts
Normal file
95
x-pack/plugins/lists/common/schemas/types/entry_list.test.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { getEntryListMock } from './entry_list.mock';
|
||||
import { EntryList, entriesList } from './entry_list';
|
||||
|
||||
describe('entriesList', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = { ...getEntryListMock() };
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "included"', () => {
|
||||
const payload = { ...getEntryListMock() };
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = { ...getEntryListMock() };
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate when "list" is not expected value', () => {
|
||||
const payload: Omit<EntryList, 'list'> & { list: string } = {
|
||||
...getEntryListMock(),
|
||||
list: 'someListId',
|
||||
};
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "someListId" supplied to "list"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "list.id" is empty string', () => {
|
||||
const payload: Omit<EntryList, 'list'> & { list: { id: string; type: 'ip' } } = {
|
||||
...getEntryListMock(),
|
||||
list: { id: '', type: 'ip' },
|
||||
};
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "list,id"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "lists"', () => {
|
||||
const payload: Omit<EntryList, 'type'> & { type: 'match_any' } = {
|
||||
...getEntryListMock(),
|
||||
type: 'match_any',
|
||||
};
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "match_any" supplied to "type"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryList & {
|
||||
extraKey?: string;
|
||||
} = { ...getEntryListMock() };
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesList.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({ ...getEntryListMock() });
|
||||
});
|
||||
});
|
22
x-pack/plugins/lists/common/schemas/types/entry_list.ts
Normal file
22
x-pack/plugins/lists/common/schemas/types/entry_list.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../siem_common_deps';
|
||||
import { operator, type } from '../common/schemas';
|
||||
|
||||
export const entriesList = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
list: t.exact(t.type({ id: NonEmptyString, type })),
|
||||
operator,
|
||||
type: t.keyof({ list: null }),
|
||||
})
|
||||
);
|
||||
export type EntryList = t.TypeOf<typeof entriesList>;
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ENTRY_VALUE, FIELD, MATCH, OPERATOR } from '../../constants.mock';
|
||||
|
||||
import { EntryMatch } from './entry_match';
|
||||
|
||||
export const getEntryMatchMock = (): EntryMatch => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: MATCH,
|
||||
value: ENTRY_VALUE,
|
||||
});
|
107
x-pack/plugins/lists/common/schemas/types/entry_match.test.ts
Normal file
107
x-pack/plugins/lists/common/schemas/types/entry_match.test.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { getEntryMatchMock } from './entry_match.mock';
|
||||
import { EntryMatch, entriesMatch } from './entry_match';
|
||||
|
||||
describe('entriesMatch', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = { ...getEntryMatchMock() };
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "included"', () => {
|
||||
const payload = { ...getEntryMatchMock() };
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = { ...getEntryMatchMock() };
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate when "field" is empty string', () => {
|
||||
const payload: Omit<EntryMatch, 'field'> & { field: string } = {
|
||||
...getEntryMatchMock(),
|
||||
field: '',
|
||||
};
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "value" is not string', () => {
|
||||
const payload: Omit<EntryMatch, 'value'> & { value: string[] } = {
|
||||
...getEntryMatchMock(),
|
||||
value: ['some value'],
|
||||
};
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "["some value"]" supplied to "value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "value" is empty string', () => {
|
||||
const payload: Omit<EntryMatch, 'value'> & { value: string } = {
|
||||
...getEntryMatchMock(),
|
||||
value: '',
|
||||
};
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "match"', () => {
|
||||
const payload: Omit<EntryMatch, 'type'> & { type: string } = {
|
||||
...getEntryMatchMock(),
|
||||
type: 'match_any',
|
||||
};
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "match_any" supplied to "type"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryMatch & {
|
||||
extraKey?: string;
|
||||
} = { ...getEntryMatchMock() };
|
||||
payload.extraKey = 'some value';
|
||||
const decoded = entriesMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({ ...getEntryMatchMock() });
|
||||
});
|
||||
});
|
22
x-pack/plugins/lists/common/schemas/types/entry_match.ts
Normal file
22
x-pack/plugins/lists/common/schemas/types/entry_match.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../siem_common_deps';
|
||||
import { operator } from '../common/schemas';
|
||||
|
||||
export const entriesMatch = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator,
|
||||
type: t.keyof({ match: null }),
|
||||
value: NonEmptyString,
|
||||
})
|
||||
);
|
||||
export type EntryMatch = t.TypeOf<typeof entriesMatch>;
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ENTRY_VALUE, FIELD, MATCH_ANY, OPERATOR } from '../../constants.mock';
|
||||
|
||||
import { EntryMatchAny } from './entry_match_any';
|
||||
|
||||
export const getEntryMatchAnyMock = (): EntryMatchAny => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: MATCH_ANY,
|
||||
value: [ENTRY_VALUE],
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { getEntryMatchAnyMock } from './entry_match_any.mock';
|
||||
import { EntryMatchAny, entriesMatchAny } from './entry_match_any';
|
||||
|
||||
describe('entriesMatchAny', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = { ...getEntryMatchAnyMock() };
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "included"', () => {
|
||||
const payload = { ...getEntryMatchAnyMock() };
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "excluded"', () => {
|
||||
const payload = { ...getEntryMatchAnyMock() };
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate when field is empty string', () => {
|
||||
const payload: Omit<EntryMatchAny, 'field'> & { field: string } = {
|
||||
...getEntryMatchAnyMock(),
|
||||
field: '',
|
||||
};
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when value is empty array', () => {
|
||||
const payload: Omit<EntryMatchAny, 'value'> & { value: string[] } = {
|
||||
...getEntryMatchAnyMock(),
|
||||
value: [],
|
||||
};
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "[]" supplied to "value"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when value is not string array', () => {
|
||||
const payload: Omit<EntryMatchAny, 'value'> & { value: string } = {
|
||||
...getEntryMatchAnyMock(),
|
||||
value: 'some string',
|
||||
};
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "some string" supplied to "value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate when "type" is not "match_any"', () => {
|
||||
const payload: Omit<EntryMatchAny, 'type'> & { type: string } = {
|
||||
...getEntryMatchAnyMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryMatchAny & {
|
||||
extraKey?: string;
|
||||
} = { ...getEntryMatchAnyMock() };
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({ ...getEntryMatchAnyMock() });
|
||||
});
|
||||
});
|
24
x-pack/plugins/lists/common/schemas/types/entry_match_any.ts
Normal file
24
x-pack/plugins/lists/common/schemas/types/entry_match_any.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../siem_common_deps';
|
||||
import { operator } from '../common/schemas';
|
||||
|
||||
import { nonEmptyOrNullableStringArray } from './non_empty_or_nullable_string_array';
|
||||
|
||||
export const entriesMatchAny = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator,
|
||||
type: t.keyof({ match_any: null }),
|
||||
value: nonEmptyOrNullableStringArray,
|
||||
})
|
||||
);
|
||||
export type EntryMatchAny = t.TypeOf<typeof entriesMatchAny>;
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FIELD, NESTED } from '../../constants.mock';
|
||||
|
||||
import { EntryNested } from './entry_nested';
|
||||
import { getEntryMatchMock } from './entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from './entry_match_any.mock';
|
||||
|
||||
export const getEntryNestedMock = (): EntryNested => ({
|
||||
entries: [{ ...getEntryMatchMock() }, { ...getEntryMatchAnyMock() }],
|
||||
field: FIELD,
|
||||
type: NESTED,
|
||||
});
|
124
x-pack/plugins/lists/common/schemas/types/entry_nested.test.ts
Normal file
124
x-pack/plugins/lists/common/schemas/types/entry_nested.test.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { getEntryNestedMock } from './entry_nested.mock';
|
||||
import { EntryNested, entriesNested } from './entry_nested';
|
||||
import { getEntryMatchAnyMock } from './entry_match_any.mock';
|
||||
import { getEntryExistsMock } from './entry_exists.mock';
|
||||
|
||||
describe('entriesNested', () => {
|
||||
test('it should validate a nested entry', () => {
|
||||
const payload = { ...getEntryNestedMock() };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate when "type" is not "nested"', () => {
|
||||
const payload: Omit<EntryNested, 'type'> & { type: 'match' } = {
|
||||
...getEntryNestedMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate when "field" is empty string', () => {
|
||||
const payload: Omit<EntryNested, 'field'> & {
|
||||
field: string;
|
||||
} = { ...getEntryNestedMock(), field: '' };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate when "field" is not a string', () => {
|
||||
const payload: Omit<EntryNested, 'field'> & {
|
||||
field: number;
|
||||
} = { ...getEntryNestedMock(), field: 1 };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "1" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate when "entries" is not a an array', () => {
|
||||
const payload: Omit<EntryNested, 'entries'> & {
|
||||
entries: string;
|
||||
} = { ...getEntryNestedMock(), entries: 'im a string' };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "im a string" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate when "entries" contains an entry item that is type "match"', () => {
|
||||
const payload = { ...getEntryNestedMock(), entries: [{ ...getEntryMatchAnyMock() }] };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['some host name'],
|
||||
},
|
||||
],
|
||||
field: 'host.name',
|
||||
type: 'nested',
|
||||
});
|
||||
});
|
||||
|
||||
test('it should validate when "entries" contains an entry item that is type "exists"', () => {
|
||||
const payload = { ...getEntryNestedMock(), entries: [{ ...getEntryExistsMock() }] };
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
operator: 'included',
|
||||
type: 'exists',
|
||||
},
|
||||
],
|
||||
field: 'host.name',
|
||||
type: 'nested',
|
||||
});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryNested & {
|
||||
extraKey?: string;
|
||||
} = { ...getEntryNestedMock() };
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = entriesNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({ ...getEntryNestedMock() });
|
||||
});
|
||||
});
|
22
x-pack/plugins/lists/common/schemas/types/entry_nested.ts
Normal file
22
x-pack/plugins/lists/common/schemas/types/entry_nested.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../siem_common_deps';
|
||||
|
||||
import { nonEmptyNestedEntriesArray } from './non_empty_nested_entries_array';
|
||||
|
||||
export const entriesNested = t.exact(
|
||||
t.type({
|
||||
entries: nonEmptyNestedEntriesArray,
|
||||
field: NonEmptyString,
|
||||
type: t.keyof({ nested: null }),
|
||||
})
|
||||
);
|
||||
export type EntryNested = t.TypeOf<typeof entriesNested>;
|
|
@ -10,5 +10,12 @@ export * from './default_comments_array';
|
|||
export * from './default_create_comments_array';
|
||||
export * from './default_update_comments_array';
|
||||
export * from './default_namespace';
|
||||
export * from './default_entries_array';
|
||||
export * from './entries';
|
||||
export * from './entry_match';
|
||||
export * from './entry_match_any';
|
||||
export * from './entry_list';
|
||||
export * from './entry_exists';
|
||||
export * from './entry_nested';
|
||||
export * from './non_empty_entries_array';
|
||||
export * from './non_empty_or_nullable_string_array';
|
||||
export * from './non_empty_nested_entries_array';
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { getEntryMatchMock } from './entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from './entry_match_any.mock';
|
||||
import { getEntryListMock } from './entry_list.mock';
|
||||
import { getEntryExistsMock } from './entry_exists.mock';
|
||||
import { getEntryNestedMock } from './entry_nested.mock';
|
||||
import { getEntriesArrayMock } from './entries.mock';
|
||||
import { nonEmptyEntriesArray } from './non_empty_entries_array';
|
||||
import { EntriesArray } from './entries';
|
||||
|
||||
describe('non_empty_entries_array', () => {
|
||||
test('it should NOT validate an empty array', () => {
|
||||
const payload: EntriesArray = [];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[]" supplied to "NonEmptyEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate "undefined"', () => {
|
||||
const payload = undefined;
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "NonEmptyEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate "null"', () => {
|
||||
const payload = null;
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "null" supplied to "NonEmptyEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an array of "match" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryMatchMock() }, { ...getEntryMatchMock() }];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of "match_any" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryMatchAnyMock() }, { ...getEntryMatchAnyMock() }];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of "exists" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryExistsMock() }, { ...getEntryExistsMock() }];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of "list" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryListMock() }, { ...getEntryListMock() }];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of "nested" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryNestedMock() }, { ...getEntryNestedMock() }];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of entries', () => {
|
||||
const payload: EntriesArray = [...getEntriesArrayMock()];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate an array of non entries', () => {
|
||||
const payload = [1];
|
||||
const decoded = nonEmptyEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "NonEmptyEntriesArray"',
|
||||
'Invalid value "1" supplied to "NonEmptyEntriesArray"',
|
||||
'Invalid value "1" supplied to "NonEmptyEntriesArray"',
|
||||
'Invalid value "1" supplied to "NonEmptyEntriesArray"',
|
||||
'Invalid value "1" supplied to "NonEmptyEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
import { EntriesArray, entriesArray } from './entries';
|
||||
|
||||
/**
|
||||
* Types the nonEmptyEntriesArray as:
|
||||
* - An array of entries of length 1 or greater
|
||||
*
|
||||
*/
|
||||
export const nonEmptyEntriesArray = new t.Type<EntriesArray, EntriesArray, unknown>(
|
||||
'NonEmptyEntriesArray',
|
||||
entriesArray.is,
|
||||
(input, context): Either<t.Errors, EntriesArray> => {
|
||||
if (Array.isArray(input) && input.length === 0) {
|
||||
return t.failure(input, context);
|
||||
} else {
|
||||
return entriesArray.validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type NonEmptyEntriesArray = t.OutputOf<typeof nonEmptyEntriesArray>;
|
||||
export type NonEmptyEntriesArrayDecoded = t.TypeOf<typeof nonEmptyEntriesArray>;
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { getEntryMatchMock } from './entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from './entry_match_any.mock';
|
||||
import { getEntryExistsMock } from './entry_exists.mock';
|
||||
import { getEntryNestedMock } from './entry_nested.mock';
|
||||
import { nonEmptyNestedEntriesArray } from './non_empty_nested_entries_array';
|
||||
import { EntriesArray } from './entries';
|
||||
|
||||
describe('non_empty_nested_entries_array', () => {
|
||||
test('it should NOT validate an empty array', () => {
|
||||
const payload: EntriesArray = [];
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[]" supplied to "NonEmptyNestedEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate "undefined"', () => {
|
||||
const payload = undefined;
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "NonEmptyNestedEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate "null"', () => {
|
||||
const payload = null;
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "null" supplied to "NonEmptyNestedEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an array of "match" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryMatchMock() }, { ...getEntryMatchMock() }];
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of "match_any" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryMatchAnyMock() }, { ...getEntryMatchAnyMock() }];
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of "exists" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryExistsMock() }, { ...getEntryExistsMock() }];
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate an array of "nested" entries', () => {
|
||||
const payload: EntriesArray = [{ ...getEntryNestedMock() }, { ...getEntryNestedMock() }];
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an array of entries', () => {
|
||||
const payload: EntriesArray = [
|
||||
{ ...getEntryExistsMock() },
|
||||
{ ...getEntryMatchAnyMock() },
|
||||
{ ...getEntryMatchMock() },
|
||||
];
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate an array of non entries', () => {
|
||||
const payload = [1];
|
||||
const decoded = nonEmptyNestedEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "NonEmptyNestedEntriesArray"',
|
||||
'Invalid value "1" supplied to "NonEmptyNestedEntriesArray"',
|
||||
'Invalid value "1" supplied to "NonEmptyNestedEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
import { entriesMatchAny } from './entry_match_any';
|
||||
import { entriesMatch } from './entry_match';
|
||||
import { entriesExists } from './entry_exists';
|
||||
|
||||
export const nestedEntriesArray = t.array(t.union([entriesMatch, entriesMatchAny, entriesExists]));
|
||||
export type NestedEntriesArray = t.TypeOf<typeof nestedEntriesArray>;
|
||||
|
||||
/**
|
||||
* Types the nonEmptyNestedEntriesArray as:
|
||||
* - An array of entries of length 1 or greater
|
||||
*
|
||||
*/
|
||||
export const nonEmptyNestedEntriesArray = new t.Type<
|
||||
NestedEntriesArray,
|
||||
NestedEntriesArray,
|
||||
unknown
|
||||
>(
|
||||
'NonEmptyNestedEntriesArray',
|
||||
nestedEntriesArray.is,
|
||||
(input, context): Either<t.Errors, NestedEntriesArray> => {
|
||||
if (Array.isArray(input) && input.length === 0) {
|
||||
return t.failure(input, context);
|
||||
} else {
|
||||
return nestedEntriesArray.validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type NonEmptyNestedEntriesArray = t.OutputOf<typeof nonEmptyNestedEntriesArray>;
|
||||
export type NonEmptyNestedEntriesArrayDecoded = t.TypeOf<typeof nonEmptyNestedEntriesArray>;
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../siem_common_deps';
|
||||
|
||||
import { nonEmptyOrNullableStringArray } from './non_empty_or_nullable_string_array';
|
||||
|
||||
describe('nonEmptyOrNullableStringArray', () => {
|
||||
test('it should NOT validate an empty array', () => {
|
||||
const payload: string[] = [];
|
||||
const decoded = nonEmptyOrNullableStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[]" supplied to "NonEmptyOrNullableStringArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate "undefined"', () => {
|
||||
const payload = undefined;
|
||||
const decoded = nonEmptyOrNullableStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "NonEmptyOrNullableStringArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate "null"', () => {
|
||||
const payload = null;
|
||||
const decoded = nonEmptyOrNullableStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "null" supplied to "NonEmptyOrNullableStringArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an array of with an empty string', () => {
|
||||
const payload: string[] = ['im good', ''];
|
||||
const decoded = nonEmptyOrNullableStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "["im good",""]" supplied to "NonEmptyOrNullableStringArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an array of non strings', () => {
|
||||
const payload = [1];
|
||||
const decoded = nonEmptyOrNullableStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[1]" supplied to "NonEmptyOrNullableStringArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the nonEmptyOrNullableStringArray as:
|
||||
* - An array of non empty strings of length 1 or greater
|
||||
* - This differs from NonEmptyStringArray in that both input and output are type array
|
||||
*
|
||||
*/
|
||||
export const nonEmptyOrNullableStringArray = new t.Type<string[], string[], unknown>(
|
||||
'NonEmptyOrNullableStringArray',
|
||||
t.array(t.string).is,
|
||||
(input, context): Either<t.Errors, string[]> => {
|
||||
const emptyValueFound = Array.isArray(input) && input.some((value) => value === '');
|
||||
const nonStringValueFound =
|
||||
Array.isArray(input) && input.some((value) => typeof value !== 'string');
|
||||
|
||||
if (Array.isArray(input) && (emptyValueFound || nonStringValueFound || input.length === 0)) {
|
||||
return t.failure(input, context);
|
||||
} else {
|
||||
return t.array(t.string).validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type NonEmptyOrNullableStringArray = t.OutputOf<typeof nonEmptyOrNullableStringArray>;
|
||||
export type NonEmptyOrNullableStringArrayDecoded = t.TypeOf<typeof nonEmptyOrNullableStringArray>;
|
|
@ -8,7 +8,7 @@
|
|||
"name": "Sample Endpoint Exception List",
|
||||
"entries": [
|
||||
{
|
||||
"field": "actingProcess.file.signer",
|
||||
"field": "host.ip",
|
||||
"operator": "excluded",
|
||||
"type": "exists"
|
||||
},
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
Description,
|
||||
DescriptionOrUndefined,
|
||||
EntriesArray,
|
||||
EntriesArrayOrUndefined,
|
||||
ExceptionListItemType,
|
||||
ExceptionListItemTypeOrUndefined,
|
||||
ExceptionListType,
|
||||
|
@ -140,7 +139,7 @@ export interface UpdateExceptionListItemOptions {
|
|||
_tags: _TagsOrUndefined;
|
||||
_version: _VersionOrUndefined;
|
||||
comments: UpdateCommentsArray;
|
||||
entries: EntriesArrayOrUndefined;
|
||||
entries: EntriesArray;
|
||||
id: IdOrUndefined;
|
||||
itemId: ItemIdOrUndefined;
|
||||
namespaceType: NamespaceType;
|
||||
|
@ -155,7 +154,7 @@ export interface UpdateEndpointListItemOptions {
|
|||
_tags: _TagsOrUndefined;
|
||||
_version: _VersionOrUndefined;
|
||||
comments: UpdateCommentsArray;
|
||||
entries: EntriesArrayOrUndefined;
|
||||
entries: EntriesArray;
|
||||
id: IdOrUndefined;
|
||||
itemId: ItemIdOrUndefined;
|
||||
name: NameOrUndefined;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { SavedObjectsClientContract } from 'kibana/server';
|
|||
|
||||
import {
|
||||
DescriptionOrUndefined,
|
||||
EntriesArrayOrUndefined,
|
||||
EntriesArray,
|
||||
ExceptionListItemSchema,
|
||||
ExceptionListItemTypeOrUndefined,
|
||||
ExceptionListSoSchema,
|
||||
|
@ -37,7 +37,7 @@ interface UpdateExceptionListItemOptions {
|
|||
_version: _VersionOrUndefined;
|
||||
name: NameOrUndefined;
|
||||
description: DescriptionOrUndefined;
|
||||
entries: EntriesArrayOrUndefined;
|
||||
entries: EntriesArray;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
namespaceType: NamespaceType;
|
||||
itemId: ItemIdOrUndefined;
|
||||
|
|
|
@ -25,6 +25,9 @@ import {
|
|||
Operator,
|
||||
} from '../../../lists/common/schemas';
|
||||
import { getExceptionListItemSchemaMock } from '../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
import { getEntryMatchMock } from '../../../lists/common/schemas/types/entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from '../../../lists/common/schemas/types/entry_match_any.mock';
|
||||
import { getEntryExistsMock } from '../../../lists/common/schemas/types/entry_exists.mock';
|
||||
|
||||
describe('build_exceptions_query', () => {
|
||||
let exclude: boolean;
|
||||
|
@ -295,20 +298,95 @@ describe('build_exceptions_query', () => {
|
|||
const item: EntryNested = {
|
||||
field: 'parent',
|
||||
type: 'nested',
|
||||
entries: [makeMatchEntry({ field: 'nestedField', operator: 'included' })],
|
||||
entries: [
|
||||
{
|
||||
...getEntryMatchMock(),
|
||||
field: 'nestedField',
|
||||
operator: 'included',
|
||||
value: 'value-1',
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = buildNested({ item, language: 'kuery' });
|
||||
|
||||
expect(result).toEqual('parent:{ nestedField:"value-1" }');
|
||||
});
|
||||
|
||||
test('it returns formatted query when entry item is "exists"', () => {
|
||||
const item: EntryNested = {
|
||||
field: 'parent',
|
||||
type: 'nested',
|
||||
entries: [{ ...getEntryExistsMock(), field: 'nestedField', operator: 'included' }],
|
||||
};
|
||||
const result = buildNested({ item, language: 'kuery' });
|
||||
|
||||
expect(result).toEqual('parent:{ nestedField:* }');
|
||||
});
|
||||
|
||||
test('it returns formatted query when entry item is "exists" and operator is "excluded"', () => {
|
||||
const item: EntryNested = {
|
||||
field: 'parent',
|
||||
type: 'nested',
|
||||
entries: [{ ...getEntryExistsMock(), field: 'nestedField', operator: 'excluded' }],
|
||||
};
|
||||
const result = buildNested({ item, language: 'kuery' });
|
||||
|
||||
expect(result).toEqual('parent:{ not nestedField:* }');
|
||||
});
|
||||
|
||||
test('it returns formatted query when entry item is "match_any"', () => {
|
||||
const item: EntryNested = {
|
||||
field: 'parent',
|
||||
type: 'nested',
|
||||
entries: [
|
||||
{
|
||||
...getEntryMatchAnyMock(),
|
||||
field: 'nestedField',
|
||||
operator: 'included',
|
||||
value: ['value1', 'value2'],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = buildNested({ item, language: 'kuery' });
|
||||
|
||||
expect(result).toEqual('parent:{ nestedField:("value1" or "value2") }');
|
||||
});
|
||||
|
||||
test('it returns formatted query when entry item is "match_any" and operator is "excluded"', () => {
|
||||
const item: EntryNested = {
|
||||
field: 'parent',
|
||||
type: 'nested',
|
||||
entries: [
|
||||
{
|
||||
...getEntryMatchAnyMock(),
|
||||
field: 'nestedField',
|
||||
operator: 'excluded',
|
||||
value: ['value1', 'value2'],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = buildNested({ item, language: 'kuery' });
|
||||
|
||||
expect(result).toEqual('parent:{ not nestedField:("value1" or "value2") }');
|
||||
});
|
||||
|
||||
test('it returns formatted query when multiple items in nested entry', () => {
|
||||
const item: EntryNested = {
|
||||
field: 'parent',
|
||||
type: 'nested',
|
||||
entries: [
|
||||
makeMatchEntry({ field: 'nestedField', operator: 'included' }),
|
||||
makeMatchEntry({ field: 'nestedFieldB', operator: 'included', value: 'value-2' }),
|
||||
{
|
||||
...getEntryMatchMock(),
|
||||
field: 'nestedField',
|
||||
operator: 'included',
|
||||
value: 'value-1',
|
||||
},
|
||||
{
|
||||
...getEntryMatchMock(),
|
||||
field: 'nestedFieldB',
|
||||
operator: 'included',
|
||||
value: 'value-2',
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = buildNested({ item, language: 'kuery' });
|
||||
|
@ -514,7 +592,7 @@ describe('build_exceptions_query', () => {
|
|||
entries,
|
||||
});
|
||||
const expectedQuery =
|
||||
'b:("value-1" OR "value-2") AND parent:{ nestedField:"value-3" } AND NOT _exists_e';
|
||||
'b:("value-1" OR "value-2") AND parent:{ NOT nestedField:"value-3" } AND NOT _exists_e';
|
||||
expect(query).toEqual(expectedQuery);
|
||||
});
|
||||
|
||||
|
@ -576,7 +654,7 @@ describe('build_exceptions_query', () => {
|
|||
language: 'kuery',
|
||||
entries,
|
||||
});
|
||||
const expectedQuery = 'b:* and parent:{ c:"value-1" and d:"value-2" } and e:*';
|
||||
const expectedQuery = 'b:* and parent:{ not c:"value-1" and d:"value-2" } and e:*';
|
||||
|
||||
expect(query).toEqual(expectedQuery);
|
||||
});
|
||||
|
@ -642,7 +720,8 @@ describe('build_exceptions_query', () => {
|
|||
language: 'kuery',
|
||||
entries,
|
||||
});
|
||||
const expectedQuery = 'b:"value" and parent:{ c:"valueC" and d:"valueD" } and e:"valueE"';
|
||||
const expectedQuery =
|
||||
'b:"value" and parent:{ not c:"valueC" and not d:"valueD" } and e:"valueE"';
|
||||
|
||||
expect(query).toEqual(expectedQuery);
|
||||
});
|
||||
|
@ -684,7 +763,7 @@ describe('build_exceptions_query', () => {
|
|||
language: 'kuery',
|
||||
entries,
|
||||
});
|
||||
const expectedQuery = 'not b:("value-1" or "value-2") and parent:{ c:"valueC" }';
|
||||
const expectedQuery = 'not b:("value-1" or "value-2") and parent:{ not c:"valueC" }';
|
||||
|
||||
expect(query).toEqual(expectedQuery);
|
||||
});
|
||||
|
@ -800,7 +879,7 @@ describe('build_exceptions_query', () => {
|
|||
exclude,
|
||||
});
|
||||
const expectedQuery =
|
||||
'(some.parentField:{ nested.field:"some value" } and some.not.nested.field:"some value") or (b:("value-1" or "value-2") and parent:{ c:"valueC" and d:"valueD" } and e:("value-1" or "value-2"))';
|
||||
'(some.parentField:{ nested.field:"some value" } and some.not.nested.field:"some value") or (b:("value-1" or "value-2") and parent:{ not c:"valueC" and not d:"valueD" } and e:("value-1" or "value-2"))';
|
||||
|
||||
expect(query).toEqual([{ query: expectedQuery, language: 'kuery' }]);
|
||||
});
|
||||
|
|
|
@ -126,7 +126,7 @@ export const buildNested = ({
|
|||
}): string => {
|
||||
const { field, entries } = item;
|
||||
const and = getLanguageBooleanOperator({ language, value: 'and' });
|
||||
const values = entries.map((entry) => `${entry.field}:"${entry.value}"`);
|
||||
const values = entries.map((entry) => evaluateValues({ item: entry, language }));
|
||||
|
||||
return `${field}:{ ${values.join(` ${and} `)} }`;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';
|
||||
|
||||
import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
|
@ -19,6 +19,7 @@ interface OperatorProps {
|
|||
isClearable: boolean;
|
||||
fieldTypeFilter?: string[];
|
||||
fieldInputWidth?: number;
|
||||
isRequired?: boolean;
|
||||
onChange: (a: IFieldType[]) => void;
|
||||
}
|
||||
|
||||
|
@ -29,10 +30,12 @@ export const FieldComponent: React.FC<OperatorProps> = ({
|
|||
isLoading = false,
|
||||
isDisabled = false,
|
||||
isClearable = false,
|
||||
isRequired = false,
|
||||
fieldTypeFilter = [],
|
||||
fieldInputWidth = 190,
|
||||
onChange,
|
||||
}): JSX.Element => {
|
||||
const [touched, setIsTouched] = useState(false);
|
||||
const getLabel = useCallback((field): string => field.name, []);
|
||||
const optionsMemo = useMemo((): IFieldType[] => {
|
||||
if (indexPattern != null) {
|
||||
|
@ -74,6 +77,8 @@ export const FieldComponent: React.FC<OperatorProps> = ({
|
|||
isLoading={isLoading}
|
||||
isDisabled={isDisabled}
|
||||
isClearable={isClearable}
|
||||
isInvalid={isRequired ? touched && selectedField == null : false}
|
||||
onFocus={() => setIsTouched(true)}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
data-test-subj="fieldAutocompleteComboBox"
|
||||
style={{ width: `${fieldInputWidth}px` }}
|
||||
|
|
|
@ -18,6 +18,7 @@ interface AutocompleteFieldListsProps {
|
|||
isLoading: boolean;
|
||||
isDisabled: boolean;
|
||||
isClearable: boolean;
|
||||
isRequired?: boolean;
|
||||
onChange: (arg: ListSchema) => void;
|
||||
}
|
||||
|
||||
|
@ -28,8 +29,10 @@ export const AutocompleteFieldListsComponent: React.FC<AutocompleteFieldListsPro
|
|||
isLoading = false,
|
||||
isDisabled = false,
|
||||
isClearable = false,
|
||||
isRequired = false,
|
||||
onChange,
|
||||
}): JSX.Element => {
|
||||
const [touched, setIsTouched] = useState(false);
|
||||
const { http } = useKibana().services;
|
||||
const [lists, setLists] = useState<ListSchema[]>([]);
|
||||
const { loading, result, start } = useFindLists();
|
||||
|
@ -97,6 +100,8 @@ export const AutocompleteFieldListsComponent: React.FC<AutocompleteFieldListsPro
|
|||
options={comboOptions}
|
||||
selectedOptions={selectedComboOptions}
|
||||
onChange={handleValuesChange}
|
||||
isInvalid={isRequired ? touched && (selectedValue == null || selectedValue === '') : false}
|
||||
onFocus={() => setIsTouched(true)}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
sortMatchesBy="startsWith"
|
||||
data-test-subj="valuesAutocompleteComboBox listsComboxBox"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
|
@ -22,6 +22,7 @@ interface AutocompleteFieldMatchProps {
|
|||
isLoading: boolean;
|
||||
isDisabled: boolean;
|
||||
isClearable: boolean;
|
||||
isRequired?: boolean;
|
||||
fieldInputWidth?: number;
|
||||
onChange: (arg: string) => void;
|
||||
}
|
||||
|
@ -34,9 +35,11 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
isLoading,
|
||||
isDisabled = false,
|
||||
isClearable = false,
|
||||
isRequired = false,
|
||||
fieldInputWidth,
|
||||
onChange,
|
||||
}): JSX.Element => {
|
||||
const [touched, setIsTouched] = useState(false);
|
||||
const [isLoadingSuggestions, suggestions, updateSuggestions] = useFieldValueAutocomplete({
|
||||
selectedField,
|
||||
operatorType: OperatorTypeEnum.MATCH,
|
||||
|
@ -96,7 +99,8 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
singleSelection={{ asPlainText: true }}
|
||||
onSearchChange={onSearchChange}
|
||||
onCreateOption={onChange}
|
||||
isInvalid={!isValid}
|
||||
isInvalid={isRequired ? touched && !isValid : false}
|
||||
onFocus={() => setIsTouched(true)}
|
||||
sortMatchesBy="startsWith"
|
||||
data-test-subj="valuesAutocompleteComboBox matchComboxBox"
|
||||
style={fieldInputWidth ? { width: `${fieldInputWidth}px` } : {}}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
|
@ -22,6 +22,7 @@ interface AutocompleteFieldMatchAnyProps {
|
|||
isLoading: boolean;
|
||||
isDisabled: boolean;
|
||||
isClearable: boolean;
|
||||
isRequired?: boolean;
|
||||
onChange: (arg: string[]) => void;
|
||||
}
|
||||
|
||||
|
@ -33,8 +34,10 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
isLoading,
|
||||
isDisabled = false,
|
||||
isClearable = false,
|
||||
isRequired = false,
|
||||
onChange,
|
||||
}): JSX.Element => {
|
||||
const [touched, setIsTouched] = useState(false);
|
||||
const [isLoadingSuggestions, suggestions, updateSuggestions] = useFieldValueAutocomplete({
|
||||
selectedField,
|
||||
operatorType: OperatorTypeEnum.MATCH_ANY,
|
||||
|
@ -92,7 +95,8 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
onChange={handleValuesChange}
|
||||
onSearchChange={onSearchChange}
|
||||
onCreateOption={onCreateOption}
|
||||
isInvalid={!isValid}
|
||||
isInvalid={isRequired ? touched && (selectedValue.length === 0 || !isValid) : !isValid}
|
||||
onFocus={() => setIsTouched(true)}
|
||||
delimiter=", "
|
||||
data-test-subj="valuesAutocompleteComboBox matchAnyComboxBox"
|
||||
fullWidth
|
||||
|
|
|
@ -54,16 +54,16 @@ describe('helpers', () => {
|
|||
});
|
||||
|
||||
describe('#validateParams', () => {
|
||||
test('returns true if value is undefined', () => {
|
||||
test('returns false if value is undefined', () => {
|
||||
const isValid = validateParams(undefined, getField('@timestamp'));
|
||||
|
||||
expect(isValid).toBeTruthy();
|
||||
expect(isValid).toBeFalsy();
|
||||
});
|
||||
|
||||
test('returns true if value is empty string', () => {
|
||||
test('returns false if value is empty string', () => {
|
||||
const isValid = validateParams('', getField('@timestamp'));
|
||||
|
||||
expect(isValid).toBeTruthy();
|
||||
expect(isValid).toBeFalsy();
|
||||
});
|
||||
|
||||
test('returns true if type is "date" and value is valid', () => {
|
||||
|
|
|
@ -36,7 +36,7 @@ export const validateParams = (
|
|||
): boolean => {
|
||||
// Box would show error state if empty otherwise
|
||||
if (params == null || params === '') {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const types = field != null && field.esTypes != null ? field.esTypes : [];
|
||||
|
|
|
@ -12,10 +12,8 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
|||
import { ExceptionListItemComponent } from './builder_exception_item';
|
||||
import { fields } from '../../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
import {
|
||||
getEntryMatchMock,
|
||||
getEntryMatchAnyMock,
|
||||
} from '../../../../../../lists/common/schemas/types/entries.mock';
|
||||
import { getEntryMatchMock } from '../../../../../../lists/common/schemas/types/entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from '../../../../../../lists/common/schemas/types/entry_match_any.mock';
|
||||
|
||||
describe('ExceptionListItemComponent', () => {
|
||||
describe('and badge logic', () => {
|
||||
|
|
|
@ -117,6 +117,7 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
|||
isDisabled={isLoading}
|
||||
onChange={handleFieldChange}
|
||||
data-test-subj="exceptionBuilderEntryField"
|
||||
isRequired
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -170,6 +171,7 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
|||
isClearable={false}
|
||||
indexPattern={indexPattern}
|
||||
onChange={handleFieldMatchValueChange}
|
||||
isRequired
|
||||
data-test-subj="exceptionBuilderEntryFieldMatch"
|
||||
/>
|
||||
);
|
||||
|
@ -185,6 +187,7 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
|||
isClearable={false}
|
||||
indexPattern={indexPattern}
|
||||
onChange={handleFieldMatchAnyValueChange}
|
||||
isRequired
|
||||
data-test-subj="exceptionBuilderEntryFieldMatchAny"
|
||||
/>
|
||||
);
|
||||
|
@ -199,6 +202,7 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
|||
isDisabled={isLoading}
|
||||
isClearable={false}
|
||||
onChange={handleFieldListValueChange}
|
||||
isRequired
|
||||
data-test-subj="exceptionBuilderEntryFieldList"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -38,18 +38,20 @@ import {
|
|||
existsOperator,
|
||||
doesNotExistOperator,
|
||||
} from '../autocomplete/operators';
|
||||
import { OperatorTypeEnum, OperatorEnum } from '../../../lists_plugin_deps';
|
||||
import { OperatorTypeEnum, OperatorEnum, EntryNested } from '../../../lists_plugin_deps';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
import {
|
||||
getEntryExistsMock,
|
||||
getEntryListMock,
|
||||
getEntryMatchMock,
|
||||
getEntryMatchAnyMock,
|
||||
getEntriesArrayMock,
|
||||
} from '../../../../../lists/common/schemas/types/entries.mock';
|
||||
import { getEntryMatchMock } from '../../../../../lists/common/schemas/types/entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from '../../../../../lists/common/schemas/types/entry_match_any.mock';
|
||||
import { getEntryExistsMock } from '../../../../../lists/common/schemas/types/entry_exists.mock';
|
||||
import { getEntryListMock } from '../../../../../lists/common/schemas/types/entry_list.mock';
|
||||
import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/comments.mock';
|
||||
import { getEntriesArrayMock } from '../../../../../lists/common/schemas/types/entries.mock';
|
||||
import { ENTRIES } from '../../../../../lists/common/constants.mock';
|
||||
import { ExceptionListItemSchema, EntriesArray } from '../../../../../lists/common/schemas';
|
||||
import {
|
||||
CreateExceptionListItemSchema,
|
||||
ExceptionListItemSchema,
|
||||
EntriesArray,
|
||||
} from '../../../../../lists/common/schemas';
|
||||
import { IIndexPattern } from 'src/plugins/data/common';
|
||||
|
||||
describe('Exception helpers', () => {
|
||||
|
@ -251,8 +253,8 @@ describe('Exception helpers', () => {
|
|||
{
|
||||
fieldName: 'host.name.host.name',
|
||||
isNested: true,
|
||||
operator: 'is',
|
||||
value: 'some host name',
|
||||
operator: 'is one of',
|
||||
value: ['some host name'],
|
||||
},
|
||||
];
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -482,7 +484,7 @@ describe('Exception helpers', () => {
|
|||
});
|
||||
|
||||
describe('#filterExceptionItems', () => {
|
||||
test('it removes empty entry items', () => {
|
||||
test('it removes entry items with "value" of "undefined"', () => {
|
||||
const { entries, ...rest } = getExceptionListItemSchemaMock();
|
||||
const mockEmptyException: EmptyEntry = {
|
||||
field: 'host.name',
|
||||
|
@ -500,6 +502,85 @@ describe('Exception helpers', () => {
|
|||
expect(exceptions).toEqual([getExceptionListItemSchemaMock()]);
|
||||
});
|
||||
|
||||
test('it removes "match" entry items with "value" of empty string', () => {
|
||||
const { entries, ...rest } = { ...getExceptionListItemSchemaMock() };
|
||||
const mockEmptyException: EmptyEntry = {
|
||||
field: 'host.name',
|
||||
type: OperatorTypeEnum.MATCH,
|
||||
operator: OperatorEnum.INCLUDED,
|
||||
value: '',
|
||||
};
|
||||
const output: Array<
|
||||
ExceptionListItemSchema | CreateExceptionListItemSchema
|
||||
> = filterExceptionItems([
|
||||
{
|
||||
...rest,
|
||||
entries: [...entries, mockEmptyException],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]);
|
||||
});
|
||||
|
||||
test('it removes "match" entry items with "field" of empty string', () => {
|
||||
const { entries, ...rest } = { ...getExceptionListItemSchemaMock() };
|
||||
const mockEmptyException: EmptyEntry = {
|
||||
field: '',
|
||||
type: OperatorTypeEnum.MATCH,
|
||||
operator: OperatorEnum.INCLUDED,
|
||||
value: 'some value',
|
||||
};
|
||||
const output: Array<
|
||||
ExceptionListItemSchema | CreateExceptionListItemSchema
|
||||
> = filterExceptionItems([
|
||||
{
|
||||
...rest,
|
||||
entries: [...entries, mockEmptyException],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]);
|
||||
});
|
||||
|
||||
test('it removes "match_any" entry items with "field" of empty string', () => {
|
||||
const { entries, ...rest } = { ...getExceptionListItemSchemaMock() };
|
||||
const mockEmptyException: EmptyEntry = {
|
||||
field: '',
|
||||
type: OperatorTypeEnum.MATCH_ANY,
|
||||
operator: OperatorEnum.INCLUDED,
|
||||
value: ['some value'],
|
||||
};
|
||||
const output: Array<
|
||||
ExceptionListItemSchema | CreateExceptionListItemSchema
|
||||
> = filterExceptionItems([
|
||||
{
|
||||
...rest,
|
||||
entries: [...entries, mockEmptyException],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]);
|
||||
});
|
||||
|
||||
test('it removes "nested" entry items with "field" of empty string', () => {
|
||||
const { entries, ...rest } = { ...getExceptionListItemSchemaMock() };
|
||||
const mockEmptyException: EntryNested = {
|
||||
field: '',
|
||||
type: OperatorTypeEnum.NESTED,
|
||||
entries: [{ ...getEntryMatchMock() }],
|
||||
};
|
||||
const output: Array<
|
||||
ExceptionListItemSchema | CreateExceptionListItemSchema
|
||||
> = filterExceptionItems([
|
||||
{
|
||||
...rest,
|
||||
entries: [...entries, mockEmptyException],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]);
|
||||
});
|
||||
|
||||
test('it removes `temporaryId` from items', () => {
|
||||
const { meta, ...rest } = getNewExceptionItem({
|
||||
listType: 'detection',
|
||||
|
@ -509,7 +590,7 @@ describe('Exception helpers', () => {
|
|||
});
|
||||
const exceptions = filterExceptionItems([{ ...rest, meta }]);
|
||||
|
||||
expect(exceptions).toEqual([{ ...rest, meta: undefined }]);
|
||||
expect(exceptions).toEqual([{ ...rest, entries: [], meta: undefined }]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
EntryNested,
|
||||
} from '../../../lists_plugin_deps';
|
||||
import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { validate } from '../../../../common/validate';
|
||||
import { TimelineNonEcsData } from '../../../graphql/types';
|
||||
import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard';
|
||||
|
||||
|
@ -348,11 +349,22 @@ export const filterExceptionItems = (
|
|||
): Array<ExceptionListItemSchema | CreateExceptionListItemSchema> => {
|
||||
return exceptions.reduce<Array<ExceptionListItemSchema | CreateExceptionListItemSchema>>(
|
||||
(acc, exception) => {
|
||||
const entries = exception.entries.filter((t) => entry.is(t) || entriesNested.is(t));
|
||||
const entries = exception.entries.filter((t) => {
|
||||
const [validatedEntry] = validate(t, entry);
|
||||
const [validatedNestedEntry] = validate(t, entriesNested);
|
||||
|
||||
if (validatedEntry != null || validatedNestedEntry != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const item = { ...exception, entries };
|
||||
|
||||
if (exceptionListItemSchema.is(item)) {
|
||||
return [...acc, item];
|
||||
} else if (createExceptionListItemSchema.is(item) && item.meta != null) {
|
||||
} else if (createExceptionListItemSchema.is(item)) {
|
||||
const { meta, ...rest } = item;
|
||||
const itemSansMetaId: CreateExceptionListItemSchema = { ...rest, meta: undefined };
|
||||
return [...acc, itemSansMetaId];
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ExceptionListClient } from '../../../../../lists/server';
|
|||
import { listMock } from '../../../../../lists/server/mocks';
|
||||
import { getFoundExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
import { EntriesArray, EntryList } from '../../../../../lists/common/schemas/types/entries';
|
||||
import { EntriesArray, EntryList } from '../../../../../lists/common/schemas/types';
|
||||
import { buildArtifact, getFullEndpointExceptionList } from './lists';
|
||||
import { TranslatedEntry, TranslatedExceptionListItem } from '../../schemas/artifacts';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { deflate } from 'zlib';
|
|||
import { ExceptionListItemSchema } from '../../../../../lists/common/schemas';
|
||||
import { validate } from '../../../../common/validate';
|
||||
|
||||
import { Entry, EntryNested } from '../../../../../lists/common/schemas/types/entries';
|
||||
import { Entry, EntryNested } from '../../../../../lists/common/schemas/types';
|
||||
import { FoundExceptionListItemSchema } from '../../../../../lists/common/schemas/response/found_exception_list_item_schema';
|
||||
import { ExceptionListClient } from '../../../../../lists/server';
|
||||
import { ENDPOINT_LIST_ID } from '../../../../common/shared_imports';
|
||||
|
|
Loading…
Reference in a new issue