Skip to content

Commit

Permalink
[Security Solution][Exceptions] - Require non empty entries and non e…
Browse files Browse the repository at this point in the history
…mpty string values in exception list items (elastic#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
  • Loading branch information
yctercero committed Jul 22, 2020
1 parent a5d9840 commit 3d7731c
Show file tree
Hide file tree
Showing 52 changed files with 1,481 additions and 570 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ 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';

export const createEndpointListItemSchema = t.intersection([
t.exact(
t.type({
description,
entries: nonEmptyEntriesArray,
name,
type: exceptionListItemType,
})
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -35,6 +35,7 @@ export const createExceptionListItemSchema = t.intersection([
t.exact(
t.type({
description,
entries: nonEmptyEntriesArray,
list_id,
name,
type: exceptionListItemType,
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,18 @@ 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;
outputPayload.entries = [];
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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,18 @@ 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;
outputPayload.entries = [];
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"', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand All @@ -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
Expand Down

This file was deleted.

22 changes: 0 additions & 22 deletions x-pack/plugins/lists/common/schemas/types/default_entries_array.ts

This file was deleted.

70 changes: 11 additions & 59 deletions x-pack/plugins/lists/common/schemas/types/entries.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() },
];
Loading

0 comments on commit 3d7731c

Please sign in to comment.