[SIEM][Lists] Adds _find to value lists

## Summary

Adds the REST and API routes for find and filter for exception lists and value lists 

* Fixes bugs with string parameters for the _find with exception lists
* Adds the _find for the value based lists
* More scripts for how to filter things for both list values and exception lists
* Misc type script fixes
* Adds a cursor to move from the previous page to the next page 
* Adds name space 'agnostic' vs. 'single' feature for exception_lists

**REST API's:**

```ts
POST /api/lists/_find
POST /api/lists/items/_find
POST /api/exception_lists/_find
POST /api/exception_lists/items/_find
```

**Parameters you can send:**

* sort
* sort_order
* filter
* page
* per_page 
* list_id (for list items only and required)
* cursor (for finding the next page or advancing to deep pages)

**See test scripts below:**
```sh
find_exception_list_items_by_filter.sh
find_exception_lists_by_filter.sh
find_list_items.sh
find_list_items_with_cursor.sh
find_list_items_with_sort.sh
find_list_items_with_sort_cursor.sh
find_lists.sh
find_lists_with_cursor.sh
find_lists_with_filter.sh
find_lists_with_sort.sh
find_lists_with_sort_cursor.sh
```

### Checklist

Note: Unit tests are left out as this is blocking people but I will be adding tests as this is being reviewed unless someone needs these features now. This is still all behind a feature flag and considered to be in the area of proof of concept and not production ready until more tests and end to tests are added.  

- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
This commit is contained in:
Frank Hassanabad 2020-05-28 12:35:24 -06:00 committed by GitHub
parent 6643b9c191
commit 19fe3461f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
109 changed files with 1921 additions and 196 deletions

View file

@ -28,3 +28,4 @@ export const META = {};
export const TYPE = 'ip';
export const VALUE = '127.0.0.1';
export const VALUE_2 = '255.255.255';
export const NAMESPACE_TYPE = 'single';

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import { DefaultStringArray, NonEmptyString } from '../types';
import { DefaultNamespace } from '../types/default_namespace';
export const name = t.string;
export type Name = t.TypeOf<typeof name>;
@ -97,8 +98,38 @@ export const itemIdOrUndefined = t.union([item_id, t.undefined]);
export type ItemIdOrUndefined = t.TypeOf<typeof itemIdOrUndefined>;
export const per_page = t.number; // TODO: Change this out for PositiveNumber from siem
export type PerPage = t.TypeOf<typeof per_page>;
export const perPageOrUndefined = t.union([per_page, t.undefined]);
export type PerPageOrUndefined = t.TypeOf<typeof perPageOrUndefined>;
export const total = t.number; // TODO: Change this out for PositiveNumber from siem
export const totalUndefined = t.union([total, t.undefined]);
export type TotalOrUndefined = t.TypeOf<typeof totalUndefined>;
export const page = t.number; // TODO: Change this out for PositiveNumber from siem
export type Page = t.TypeOf<typeof page>;
export const pageOrUndefined = t.union([page, t.undefined]);
export type PageOrUndefined = t.TypeOf<typeof pageOrUndefined>;
export const sort_field = t.string;
export const sortFieldOrUndefined = t.union([sort_field, t.undefined]);
export type SortFieldOrUndefined = t.TypeOf<typeof sortFieldOrUndefined>;
export const sort_order = t.keyof({ asc: null, desc: null });
export const sortOrderOrUndefined = t.union([sort_order, t.undefined]);
export type SortOrderOrUndefined = t.TypeOf<typeof sortOrderOrUndefined>;
export const filter = t.string;
export type Filter = t.TypeOf<typeof filter>;
export const filterOrUndefined = t.union([filter, t.undefined]);
export type FilterOrUndefined = t.TypeOf<typeof filterOrUndefined>;
export const cursor = t.string;
export type Cursor = t.TypeOf<typeof cursor>;
export const cursorOrUndefined = t.union([cursor, t.undefined]);
export type CursorOrUndefined = t.TypeOf<typeof cursorOrUndefined>;
export const namespace_type = DefaultNamespace;
export type NamespaceType = t.TypeOf<typeof namespace_type>;

View file

@ -10,6 +10,7 @@ import * as t from 'io-ts';
import {
ItemId,
NamespaceType,
Tags,
_Tags,
_tags,
@ -19,6 +20,7 @@ import {
list_id,
meta,
name,
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
@ -41,6 +43,7 @@ export const createExceptionListItemSchema = t.intersection([
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
tags, // defaults to empty array if not set during decode
})
),
@ -53,13 +56,16 @@ export type CreateExceptionListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof createExceptionListItemSchema>
>;
// This type is used after a decode since the arrays turn into defaults of empty arrays
// and if a item_id is not specified it turns into a default GUID
// This type is used after a decode since some things are defaults after a decode.
export type CreateExceptionListItemSchemaDecoded = Identity<
Omit<CreateExceptionListItemSchema, '_tags' | 'tags' | 'item_id' | 'entries'> & {
Omit<
CreateExceptionListItemSchema,
'_tags' | 'tags' | 'item_id' | 'entries' | 'namespace_type'
> & {
_tags: _Tags;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
namespace_type: NamespaceType;
}
>;

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { DESCRIPTION, LIST_ID, META, NAME, TYPE } from '../../constants.mock';
import { DESCRIPTION, LIST_ID, META, NAME, NAMESPACE_TYPE, TYPE } from '../../constants.mock';
import { CreateExceptionListSchema } from './create_exception_list_schema';
@ -14,6 +14,7 @@ export const getCreateExceptionListSchemaMock = (): CreateExceptionListSchema =>
list_id: LIST_ID,
meta: META,
name: NAME,
namespace_type: NAMESPACE_TYPE,
tags: [],
type: TYPE,
});

View file

@ -10,6 +10,7 @@ import * as t from 'io-ts';
import {
ListId,
NamespaceType,
Tags,
_Tags,
_tags,
@ -17,6 +18,7 @@ import {
exceptionListType,
meta,
name,
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
@ -35,6 +37,7 @@ export const createExceptionListSchema = t.intersection([
_tags, // defaults to empty array if not set during decode
list_id: DefaultUuid, // defaults to a GUID (UUID v4) string if not set during decode
meta, // defaults to undefined if not set during decode
namespace_type, // defaults to 'single' if not set during decode
tags, // defaults to empty array if not set during decode
})
),
@ -45,11 +48,12 @@ export type CreateExceptionListSchema = RequiredKeepUndefined<
t.TypeOf<typeof createExceptionListSchema>
>;
// This type is used after a decode since the arrays turn into defaults of empty arrays.
// This type is used after a decode since some things are defaults after a decode.
export type CreateExceptionListSchemaDecoded = Identity<
CreateExceptionListSchema & {
Omit<CreateExceptionListSchema, '_tags' | 'tags' | 'list_id' | 'namespace_type'> & {
_tags: _Tags;
tags: Tags;
list_id: ListId;
namespace_type: NamespaceType;
}
>;

View file

@ -8,13 +8,22 @@
import * as t from 'io-ts';
import { id, item_id } from '../common/schemas';
import { NamespaceType, id, item_id, namespace_type } from '../common/schemas';
export const deleteExceptionListItemSchema = t.exact(
t.partial({
id,
item_id,
namespace_type, // defaults to 'single' if not set during decode
})
);
export type DeleteExceptionListItemSchema = t.TypeOf<typeof deleteExceptionListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type DeleteExceptionListItemSchemaDecoded = Omit<
DeleteExceptionListItemSchema,
'namespace_type'
> & {
namespace_type: NamespaceType;
};

View file

@ -8,13 +8,19 @@
import * as t from 'io-ts';
import { id, list_id } from '../common/schemas';
import { NamespaceType, id, list_id, namespace_type } from '../common/schemas';
export const deleteExceptionListSchema = t.exact(
t.partial({
id,
list_id,
namespace_type, // defaults to 'single' if not set during decode
})
);
export type DeleteExceptionListSchema = t.TypeOf<typeof deleteExceptionListSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type DeleteExceptionListSchemaDecoded = Omit<DeleteExceptionListSchema, 'namespace_type'> & {
namespace_type: NamespaceType;
};

View file

@ -8,8 +8,16 @@
import * as t from 'io-ts';
import { filter, list_id, page, per_page, sort_field, sort_order } from '../common/schemas';
import {
NamespaceType,
filter,
list_id,
namespace_type,
sort_field,
sort_order,
} from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
import { StringToPositiveNumber } from '../types/string_to_positive_number';
export const findExceptionListItemSchema = t.intersection([
t.exact(
@ -20,8 +28,9 @@ export const findExceptionListItemSchema = t.intersection([
t.exact(
t.partial({
filter, // defaults to undefined if not set during decode
page, // defaults to undefined if not set during decode
per_page, // defaults to undefined if not set during decode
namespace_type, // defaults to 'single' if not set during decode
page: StringToPositiveNumber, // defaults to undefined if not set during decode
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode
sort_field, // defaults to undefined if not set during decode
sort_order, // defaults to undefined if not set during decode
})
@ -30,6 +39,19 @@ export const findExceptionListItemSchema = t.intersection([
export type FindExceptionListItemSchemaPartial = t.TypeOf<typeof findExceptionListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type FindExceptionListItemSchemaPartialDecoded = Omit<
FindExceptionListItemSchemaPartial,
'namespace_type'
> & {
namespace_type: NamespaceType;
};
// This type is used after a decode since some things are defaults after a decode.
export type FindExceptionListItemSchemaDecoded = RequiredKeepUndefined<
FindExceptionListItemSchemaPartialDecoded
>;
export type FindExceptionListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof findExceptionListItemSchema>
>;

View file

@ -8,14 +8,16 @@
import * as t from 'io-ts';
import { filter, page, per_page, sort_field, sort_order } from '../common/schemas';
import { NamespaceType, filter, namespace_type, sort_field, sort_order } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
import { StringToPositiveNumber } from '../types/string_to_positive_number';
export const findExceptionListSchema = t.exact(
t.partial({
filter, // defaults to undefined if not set during decode
page, // defaults to undefined if not set during decode
per_page, // defaults to undefined if not set during decode
namespace_type, // defaults to 'single' if not set during decode
page: StringToPositiveNumber, // defaults to undefined if not set during decode
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode
sort_field, // defaults to undefined if not set during decode
sort_order, // defaults to undefined if not set during decode
})
@ -23,6 +25,19 @@ export const findExceptionListSchema = t.exact(
export type FindExceptionListSchemaPartial = t.TypeOf<typeof findExceptionListSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type FindExceptionListSchemaPartialDecoded = Omit<
FindExceptionListSchemaPartial,
'namespace_type'
> & {
namespace_type: NamespaceType;
};
// This type is used after a decode since some things are defaults after a decode.
export type FindExceptionListSchemaDecoded = RequiredKeepUndefined<
FindExceptionListSchemaPartialDecoded
>;
export type FindExceptionListSchema = RequiredKeepUndefined<
t.TypeOf<typeof findExceptionListSchema>
>;

View file

@ -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.
*/
/* eslint-disable @typescript-eslint/camelcase */
import * as t from 'io-ts';
import { cursor, filter, list_id, sort_field, sort_order } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { StringToPositiveNumber } from '../types/string_to_positive_number';
export const findListItemSchema = t.intersection([
t.exact(t.type({ list_id })),
t.exact(
t.partial({
cursor, // defaults to undefined if not set during decode
filter, // defaults to undefined if not set during decode
page: StringToPositiveNumber, // defaults to undefined if not set during decode
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode
sort_field, // defaults to undefined if not set during decode
sort_order, // defaults to undefined if not set during decode
})
),
]);
export type FindListItemSchemaPartial = Identity<t.TypeOf<typeof findListItemSchema>>;
export type FindListItemSchema = RequiredKeepUndefined<t.TypeOf<typeof findListItemSchema>>;

View file

@ -0,0 +1,28 @@
/*
* 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 { cursor, filter, sort_field, sort_order } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
import { StringToPositiveNumber } from '../types/string_to_positive_number';
export const findListSchema = t.exact(
t.partial({
cursor, // defaults to undefined if not set during decode
filter, // defaults to undefined if not set during decode
page: StringToPositiveNumber, // defaults to undefined if not set during decode
per_page: StringToPositiveNumber, // defaults to undefined if not set during decode
sort_field, // defaults to undefined if not set during decode
sort_order, // defaults to undefined if not set during decode
})
);
export type FindListSchemaPartial = t.TypeOf<typeof findListSchema>;
export type FindListSchema = RequiredKeepUndefined<t.TypeOf<typeof findListSchema>>;

View file

@ -15,6 +15,8 @@ export * from './delete_list_schema';
export * from './export_list_item_query_schema';
export * from './find_exception_list_item_schema';
export * from './find_exception_list_schema';
export * from './find_list_item_schema';
export * from './find_list_schema';
export * from './import_list_item_schema';
export * from './patch_list_item_schema';
export * from './patch_list_schema';

View file

@ -8,13 +8,28 @@
import * as t from 'io-ts';
import { id, item_id } from '../common/schemas';
import { NamespaceType, id, item_id, namespace_type } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const readExceptionListItemSchema = t.partial({
id,
item_id,
namespace_type, // defaults to 'single' if not set during decode
});
export type ReadExceptionListItemSchemaPartial = t.TypeOf<typeof readExceptionListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type ReadExceptionListItemSchemaPartialDecoded = Omit<
ReadExceptionListItemSchemaPartial,
'namespace_type'
> & {
namespace_type: NamespaceType;
};
// This type is used after a decode since some things are defaults after a decode.
export type ReadExceptionListItemSchemaDecoded = RequiredKeepUndefined<
ReadExceptionListItemSchemaPartialDecoded
>;
export type ReadExceptionListItemSchema = RequiredKeepUndefined<ReadExceptionListItemSchemaPartial>;

View file

@ -8,13 +8,28 @@
import * as t from 'io-ts';
import { id, list_id } from '../common/schemas';
import { NamespaceType, id, list_id, namespace_type } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const readExceptionListSchema = t.partial({
id,
list_id,
namespace_type, // defaults to 'single' if not set during decode
});
export type ReadExceptionListSchemaPartial = t.TypeOf<typeof readExceptionListSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type ReadExceptionListSchemaPartialDecoded = Omit<
ReadExceptionListSchemaPartial,
'namespace_type'
> & {
namespace_type: NamespaceType;
};
// This type is used after a decode since some things are defaults after a decode.
export type ReadExceptionListSchemaDecoded = RequiredKeepUndefined<
ReadExceptionListSchemaPartialDecoded
>;
export type ReadExceptionListSchema = RequiredKeepUndefined<ReadExceptionListSchemaPartial>;

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import {
NamespaceType,
Tags,
_Tags,
_tags,
@ -18,6 +19,7 @@ import {
id,
meta,
name,
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
@ -40,6 +42,7 @@ export const updateExceptionListItemSchema = t.intersection([
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
namespace_type, // defaults to 'single' if not set during decode
tags, // defaults to empty array if not set during decode
})
),
@ -52,12 +55,12 @@ export type UpdateExceptionListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof updateExceptionListItemSchema>
>;
// This type is used after a decode since the arrays turn into defaults of empty arrays
// and if a item_id is not specified it turns into a default GUID
// This type is used after a decode since some things are defaults after a decode.
export type UpdateExceptionListItemSchemaDecoded = Identity<
Omit<UpdateExceptionListItemSchema, '_tags' | 'tags' | 'entries'> & {
Omit<UpdateExceptionListItemSchema, '_tags' | 'tags' | 'entries' | 'namespace_type'> & {
_tags: _Tags;
tags: Tags;
entries: EntriesArray;
namespace_type: NamespaceType;
}
>;

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import {
NamespaceType,
Tags,
_Tags,
_tags,
@ -16,6 +17,7 @@ import {
exceptionListType,
meta,
name,
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
@ -34,6 +36,7 @@ export const updateExceptionListSchema = t.intersection([
id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode
list_id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode
meta, // defaults to undefined if not set during decode
namespace_type, // defaults to 'single' if not set during decode
tags, // defaults to empty array if not set during decode
})
),
@ -46,8 +49,9 @@ export type UpdateExceptionListSchema = RequiredKeepUndefined<
// This type is used after a decode since the arrays turn into defaults of empty arrays.
export type UpdateExceptionListSchemaDecoded = Identity<
Omit<UpdateExceptionListSchema, '_tags | tags'> & {
Omit<UpdateExceptionListSchema, '_tags | tags | namespace_type'> & {
_tags: _Tags;
tags: Tags;
namespace_type: NamespaceType;
}
>;

View file

@ -20,6 +20,7 @@ import {
list_id,
metaOrUndefined,
name,
namespace_type,
tags,
tie_breaker_id,
updated_at,
@ -41,6 +42,7 @@ export const exceptionListItemSchema = t.exact(
list_id,
meta: metaOrUndefined,
name,
namespace_type,
tags,
tie_breaker_id,
type: exceptionListItemType,

View file

@ -18,6 +18,7 @@ import {
list_id,
metaOrUndefined,
name,
namespace_type,
tags,
tie_breaker_id,
updated_at,
@ -35,6 +36,7 @@ export const exceptionListSchema = t.exact(
list_id,
meta: metaOrUndefined,
name,
namespace_type,
tags,
tie_breaker_id,
type: exceptionListType,

View file

@ -0,0 +1,25 @@
/*
* 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 { cursor, page, per_page, total } from '../common/schemas';
import { listItemSchema } from './list_item_schema';
export const foundListItemSchema = t.exact(
t.type({
cursor,
data: t.array(listItemSchema),
page,
per_page,
total,
})
);
export type FoundListItemSchema = t.TypeOf<typeof foundListItemSchema>;

View file

@ -0,0 +1,25 @@
/*
* 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 { cursor, page, per_page, total } from '../common/schemas';
import { listSchema } from './list_schema';
export const foundListSchema = t.exact(
t.type({
cursor,
data: t.array(listSchema),
page,
per_page,
total,
})
);
export type FoundListSchema = t.TypeOf<typeof foundListSchema>;

View file

@ -4,11 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './list_item_schema';
export * from './list_schema';
export * from './acknowledge_schema';
export * from './list_item_index_exist_schema';
export * from './exception_list_schema';
export * from './exception_list_item_schema';
export * from './found_exception_list_item_schema';
export * from './found_exception_list_schema';
export * from './exception_list_item_schema';
export * from './found_list_item_schema';
export * from './found_list_schema';
export * from './list_item_schema';
export * from './list_schema';
export * from './list_item_index_exist_schema';

View file

@ -37,3 +37,6 @@ export const listSchema = t.exact(
);
export type ListSchema = t.TypeOf<typeof listSchema>;
export const listArraySchema = t.array(listSchema);
export type ListArraySchema = t.TypeOf<typeof listArraySchema>;

View file

@ -0,0 +1,30 @@
/*
* 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';
const namespaceType = t.keyof({ agnostic: null, single: null });
type NamespaceType = t.TypeOf<typeof namespaceType>;
export type DefaultNamespaceC = t.Type<NamespaceType, NamespaceType, unknown>;
/**
* Types the DefaultNamespace as:
* - If null or undefined, then a default string/enumeration of "single" will be used.
*/
export const DefaultNamespace: DefaultNamespaceC = new t.Type<
NamespaceType,
NamespaceType,
unknown
>(
'DefaultNamespace',
namespaceType.is,
(input): Either<t.Errors, NamespaceType> =>
input == null ? t.success('single') : namespaceType.decode(input),
t.identity
);

View file

@ -0,0 +1,35 @@
/*
* 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, either } from 'fp-ts/lib/Either';
export type StringToPositiveNumberC = t.Type<number, string, unknown>;
/**
* Types the StrongToPositiveNumber as:
* - If a string this converts the string into a number
* - Ensures it is a number (and not NaN)
* - Ensures it is positive number
*/
export const StringToPositiveNumber: StringToPositiveNumberC = new t.Type<number, string, unknown>(
'StringToPositiveNumber',
t.number.is,
(input, context): Either<t.Errors, number> => {
return either.chain(
t.string.validate(input, context),
(numberAsString): Either<t.Errors, number> => {
const stringAsNumber = +numberAsString;
if (numberAsString.trim().length === 0 || isNaN(stringAsNumber) || stringAsNumber <= 0) {
return t.failure(input, context);
} else {
return t.success(stringAsNumber);
}
}
);
},
String
);

View file

@ -68,7 +68,7 @@ describe('Exceptions Lists API', () => {
});
expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', {
body:
'{"_tags":["endpoint","process","malware","os:linux"],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","id":"1","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"endpoint","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}',
'{"_tags":["endpoint","process","malware","os:linux"],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","id":"1","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","namespace_type":"single","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"endpoint","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}',
method: 'PUT',
signal: abortCtrl.signal,
});
@ -112,7 +112,7 @@ describe('Exceptions Lists API', () => {
});
expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', {
body:
'{"_tags":["endpoint","process","malware","os:linux"],"comment":[],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","entries":[{"field":"actingProcess.file.signer","match":"Elastic, N.V.","operator":"included"},{"field":"event.category","match_any":["process","malware"],"operator":"included"}],"id":"1","item_id":"endpoint_list_item","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"simple","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}',
'{"_tags":["endpoint","process","malware","os:linux"],"comment":[],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","entries":[{"field":"actingProcess.file.signer","match":"Elastic, N.V.","operator":"included"},{"field":"event.category","match_any":["process","malware"],"operator":"included"}],"id":"1","item_id":"endpoint_list_item","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","namespace_type":"single","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"simple","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}',
method: 'PUT',
signal: abortCtrl.signal,
});

View file

@ -69,6 +69,7 @@ describe('useExceptionList', () => {
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'simple',
@ -84,6 +85,7 @@ describe('useExceptionList', () => {
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'endpoint',

View file

@ -19,6 +19,7 @@ export const mockExceptionList: ExceptionListSchema = {
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'endpoint',
@ -84,6 +85,7 @@ export const mockExceptionItem: ExceptionListItemSchema = {
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'simple',

View file

@ -39,6 +39,7 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
const siemResponse = buildSiemResponse(response);
try {
const {
namespace_type: namespaceType,
name,
_tags,
tags,
@ -54,8 +55,7 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
const exceptionList = await exceptionLists.getExceptionList({
id: undefined,
listId,
// TODO: Expose the name space type
namespaceType: 'single',
namespaceType,
});
if (exceptionList == null) {
return siemResponse.error({
@ -66,8 +66,7 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
const exceptionListItem = await exceptionLists.getExceptionListItem({
id: undefined,
itemId,
// TODO: Expose the name space type
namespaceType: 'single',
namespaceType,
});
if (exceptionListItem != null) {
return siemResponse.error({
@ -84,8 +83,7 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
listId,
meta,
name,
// TODO: Expose the name space type
namespaceType: 'single',
namespaceType,
tags,
type,
});

View file

@ -38,13 +38,21 @@ export const createExceptionListRoute = (router: IRouter): void => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { name, _tags, tags, meta, description, list_id: listId, type } = request.body;
const {
name,
_tags,
tags,
meta,
namespace_type: namespaceType,
description,
list_id: listId,
type,
} = request.body;
const exceptionLists = getExceptionListClient(context);
const exceptionList = await exceptionLists.getExceptionList({
id: undefined,
listId,
// TODO: Expose the name space type
namespaceType: 'single',
namespaceType,
});
if (exceptionList != null) {
return siemResponse.error({
@ -58,8 +66,7 @@ export const createExceptionListRoute = (router: IRouter): void => {
listId,
meta,
name,
// TODO: Expose the name space type
namespaceType: 'single',
namespaceType,
tags,
type,
});

View file

@ -13,7 +13,11 @@ import {
transformError,
validate,
} from '../siem_server_deps';
import { deleteExceptionListItemSchema, exceptionListItemSchema } from '../../common/schemas';
import {
DeleteExceptionListItemSchemaDecoded,
deleteExceptionListItemSchema,
exceptionListItemSchema,
} from '../../common/schemas';
import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils';
@ -25,14 +29,17 @@ export const deleteExceptionListItemRoute = (router: IRouter): void => {
},
path: EXCEPTION_LIST_ITEM_URL,
validate: {
query: buildRouteValidation(deleteExceptionListItemSchema),
query: buildRouteValidation<
typeof deleteExceptionListItemSchema,
DeleteExceptionListItemSchemaDecoded
>(deleteExceptionListItemSchema),
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const exceptionLists = getExceptionListClient(context);
const { item_id: itemId, id } = request.query;
const { item_id: itemId, id, namespace_type: namespaceType } = request.query;
if (itemId == null && id == null) {
return siemResponse.error({
body: 'Either "item_id" or "id" needs to be defined in the request',
@ -42,7 +49,7 @@ export const deleteExceptionListItemRoute = (router: IRouter): void => {
const deleted = await exceptionLists.deleteExceptionListItem({
id,
itemId,
namespaceType: 'single', // TODO: Bubble this up
namespaceType,
});
if (deleted == null) {
return siemResponse.error({

View file

@ -13,7 +13,11 @@ import {
transformError,
validate,
} from '../siem_server_deps';
import { deleteExceptionListSchema, exceptionListSchema } from '../../common/schemas';
import {
DeleteExceptionListSchemaDecoded,
deleteExceptionListSchema,
exceptionListSchema,
} from '../../common/schemas';
import { getErrorMessageExceptionList, getExceptionListClient } from './utils';
@ -25,25 +29,27 @@ export const deleteExceptionListRoute = (router: IRouter): void => {
},
path: EXCEPTION_LIST_URL,
validate: {
query: buildRouteValidation(deleteExceptionListSchema),
query: buildRouteValidation<
typeof deleteExceptionListSchema,
DeleteExceptionListSchemaDecoded
>(deleteExceptionListSchema),
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const exceptionLists = getExceptionListClient(context);
const { list_id: listId, id } = request.query;
const { list_id: listId, id, namespace_type: namespaceType } = request.query;
if (listId == null && id == null) {
return siemResponse.error({
body: 'Either "list_id" or "id" needs to be defined in the request',
statusCode: 400,
});
} else {
// TODO: At the moment this will delete the list but we need to delete all the list items before deleting the list
const deleted = await exceptionLists.deleteExceptionList({
id,
listId,
namespaceType: 'single',
namespaceType,
});
if (deleted == null) {
return siemResponse.error({

View file

@ -13,7 +13,11 @@ import {
transformError,
validate,
} from '../siem_server_deps';
import { findExceptionListItemSchema, foundExceptionListItemSchema } from '../../common/schemas';
import {
FindExceptionListItemSchemaDecoded,
findExceptionListItemSchema,
foundExceptionListItemSchema,
} from '../../common/schemas';
import { getExceptionListClient } from './utils';
@ -25,7 +29,10 @@ export const findExceptionListItemRoute = (router: IRouter): void => {
},
path: `${EXCEPTION_LIST_ITEM_URL}/_find`,
validate: {
query: buildRouteValidation(findExceptionListItemSchema),
query: buildRouteValidation<
typeof findExceptionListItemSchema,
FindExceptionListItemSchemaDecoded
>(findExceptionListItemSchema),
},
},
async (context, request, response) => {
@ -35,6 +42,7 @@ export const findExceptionListItemRoute = (router: IRouter): void => {
const {
filter,
list_id: listId,
namespace_type: namespaceType,
page,
per_page: perPage,
sort_field: sortField,
@ -43,7 +51,7 @@ export const findExceptionListItemRoute = (router: IRouter): void => {
const exceptionListItems = await exceptionLists.findExceptionListItem({
filter,
listId,
namespaceType: 'single', // TODO: Bubble this up
namespaceType,
page,
perPage,
sortField,

View file

@ -13,7 +13,11 @@ import {
transformError,
validate,
} from '../siem_server_deps';
import { findExceptionListSchema, foundExceptionListSchema } from '../../common/schemas';
import {
FindExceptionListSchemaDecoded,
findExceptionListSchema,
foundExceptionListSchema,
} from '../../common/schemas';
import { getExceptionListClient } from './utils';
@ -25,7 +29,9 @@ export const findExceptionListRoute = (router: IRouter): void => {
},
path: `${EXCEPTION_LIST_URL}/_find`,
validate: {
query: buildRouteValidation(findExceptionListSchema),
query: buildRouteValidation<typeof findExceptionListSchema, FindExceptionListSchemaDecoded>(
findExceptionListSchema
),
},
},
async (context, request, response) => {
@ -35,13 +41,14 @@ export const findExceptionListRoute = (router: IRouter): void => {
const {
filter,
page,
namespace_type: namespaceType,
per_page: perPage,
sort_field: sortField,
sort_order: sortOrder,
} = request.query;
const exceptionListItems = await exceptionLists.findExceptionList({
filter,
namespaceType: 'single', // TODO: Bubble this up
namespaceType,
page,
perPage,
sortField,

View file

@ -0,0 +1,99 @@
/*
* 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 { IRouter } from 'kibana/server';
import { LIST_ITEM_URL } from '../../common/constants';
import {
buildRouteValidation,
buildSiemResponse,
transformError,
validate,
} from '../siem_server_deps';
import { findListItemSchema, foundListItemSchema } from '../../common/schemas';
import { decodeCursor } from '../services/utils';
import { getListClient } from './utils';
export const findListItemRoute = (router: IRouter): void => {
router.get(
{
options: {
tags: ['access:lists'],
},
path: `${LIST_ITEM_URL}/_find`,
validate: {
query: buildRouteValidation(findListItemSchema),
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const lists = getListClient(context);
const {
cursor,
filter: filterOrUndefined,
list_id: listId,
page: pageOrUndefined,
per_page: perPageOrUndefined,
sort_field: sortField,
sort_order: sortOrder,
} = request.query;
const page = pageOrUndefined ?? 1;
const perPage = perPageOrUndefined ?? 20;
const filter = filterOrUndefined ?? '';
const {
isValid,
errorMessage,
cursor: [currentIndexPosition, searchAfter],
} = decodeCursor({
cursor,
page,
perPage,
sortField,
});
if (!isValid) {
return siemResponse.error({
body: errorMessage,
statusCode: 400,
});
} else {
const exceptionList = await lists.findListItem({
currentIndexPosition,
filter,
listId,
page,
perPage,
searchAfter,
sortField,
sortOrder,
});
if (exceptionList == null) {
return siemResponse.error({
body: `list id: "${listId}" does not exist`,
statusCode: 404,
});
} else {
const [validated, errors] = validate(exceptionList, foundListItemSchema);
if (errors != null) {
return siemResponse.error({ body: errors, statusCode: 500 });
} else {
return response.ok({ body: validated ?? {} });
}
}
}
} catch (err) {
const error = transformError(err);
return siemResponse.error({
body: error.message,
statusCode: error.statusCode,
});
}
}
);
};

View file

@ -0,0 +1,89 @@
/*
* 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 { IRouter } from 'kibana/server';
import { LIST_URL } from '../../common/constants';
import {
buildRouteValidation,
buildSiemResponse,
transformError,
validate,
} from '../siem_server_deps';
import { findListSchema, foundListSchema } from '../../common/schemas';
import { decodeCursor } from '../services/utils';
import { getListClient } from './utils';
export const findListRoute = (router: IRouter): void => {
router.get(
{
options: {
tags: ['access:lists'],
},
path: `${LIST_URL}/_find`,
validate: {
query: buildRouteValidation(findListSchema),
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const lists = getListClient(context);
const {
cursor,
filter: filterOrUndefined,
page: pageOrUndefined,
per_page: perPageOrUndefined,
sort_field: sortField,
sort_order: sortOrder,
} = request.query;
const page = pageOrUndefined ?? 1;
const perPage = perPageOrUndefined ?? 20;
const filter = filterOrUndefined ?? '';
const {
isValid,
errorMessage,
cursor: [currentIndexPosition, searchAfter],
} = decodeCursor({
cursor,
page,
perPage,
sortField,
});
if (!isValid) {
return siemResponse.error({
body: errorMessage,
statusCode: 400,
});
} else {
const exceptionList = await lists.findList({
currentIndexPosition,
filter,
page,
perPage,
searchAfter,
sortField,
sortOrder,
});
const [validated, errors] = validate(exceptionList, foundListSchema);
if (errors != null) {
return siemResponse.error({ body: errors, statusCode: 500 });
} else {
return response.ok({ body: validated ?? {} });
}
}
} catch (err) {
const error = transformError(err);
return siemResponse.error({
body: error.message,
statusCode: error.statusCode,
});
}
}
);
};

View file

@ -17,6 +17,8 @@ export * from './delete_list_route';
export * from './export_list_item_route';
export * from './find_exception_list_item_route';
export * from './find_exception_list_route';
export * from './find_list_item_route';
export * from './find_list_route';
export * from './import_list_item_route';
export * from './init_routes';
export * from './patch_list_item_route';

View file

@ -20,6 +20,8 @@ import {
exportListItemRoute,
findExceptionListItemRoute,
findExceptionListRoute,
findListItemRoute,
findListRoute,
importListItemRoute,
patchListItemRoute,
patchListRoute,
@ -41,6 +43,7 @@ export const initRoutes = (router: IRouter): void => {
updateListRoute(router);
deleteListRoute(router);
patchListRoute(router);
findListRoute(router);
// list items
createListItemRoute(router);
@ -50,6 +53,7 @@ export const initRoutes = (router: IRouter): void => {
patchListItemRoute(router);
exportListItemRoute(router);
importListItemRoute(router);
findListItemRoute(router);
// indexes of lists
createListIndexRoute(router);

View file

@ -13,7 +13,11 @@ import {
transformError,
validate,
} from '../siem_server_deps';
import { exceptionListItemSchema, readExceptionListItemSchema } from '../../common/schemas';
import {
ReadExceptionListItemSchemaDecoded,
exceptionListItemSchema,
readExceptionListItemSchema,
} from '../../common/schemas';
import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils';
@ -25,20 +29,22 @@ export const readExceptionListItemRoute = (router: IRouter): void => {
},
path: EXCEPTION_LIST_ITEM_URL,
validate: {
query: buildRouteValidation(readExceptionListItemSchema),
query: buildRouteValidation<
typeof readExceptionListItemSchema,
ReadExceptionListItemSchemaDecoded
>(readExceptionListItemSchema),
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { id, item_id: itemId } = request.query;
const { id, item_id: itemId, namespace_type: namespaceType } = request.query;
const exceptionLists = getExceptionListClient(context);
if (id != null || itemId != null) {
const exceptionListItem = await exceptionLists.getExceptionListItem({
id,
itemId,
// TODO: Bubble this up
namespaceType: 'single',
namespaceType,
});
if (exceptionListItem == null) {
return siemResponse.error({

View file

@ -13,7 +13,11 @@ import {
transformError,
validate,
} from '../siem_server_deps';
import { exceptionListSchema, readExceptionListSchema } from '../../common/schemas';
import {
ReadExceptionListSchemaDecoded,
exceptionListSchema,
readExceptionListSchema,
} from '../../common/schemas';
import { getErrorMessageExceptionList, getExceptionListClient } from './utils';
@ -25,20 +29,21 @@ export const readExceptionListRoute = (router: IRouter): void => {
},
path: EXCEPTION_LIST_URL,
validate: {
query: buildRouteValidation(readExceptionListSchema),
query: buildRouteValidation<typeof readExceptionListSchema, ReadExceptionListSchemaDecoded>(
readExceptionListSchema
),
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { id, list_id: listId } = request.query;
const { id, list_id: listId, namespace_type: namespaceType } = request.query;
const exceptionLists = getExceptionListClient(context);
if (id != null || listId != null) {
const exceptionList = await exceptionLists.getExceptionList({
id,
listId,
// TODO: Bubble this up
namespaceType: 'single',
namespaceType,
});
if (exceptionList == null) {
return siemResponse.error({

View file

@ -31,7 +31,7 @@ export const readListIndexRoute = (router: IRouter): void => {
if (listIndexExists || listItemIndexExists) {
const [validated, errors] = validate(
{ list_index: listIndexExists, lists_item_index: listItemIndexExists },
{ list_index: listIndexExists, list_item_index: listItemIndexExists },
listItemIndexExistSchema
);
if (errors != null) {

View file

@ -48,6 +48,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => {
comment,
entries,
item_id: itemId,
namespace_type: namespaceType,
tags,
} = request.body;
const exceptionLists = getExceptionListClient(context);
@ -60,7 +61,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => {
itemId,
meta,
name,
namespaceType: 'single', // TODO: Bubble this up
namespaceType,
tags,
type,
});

View file

@ -38,7 +38,17 @@ export const updateExceptionListRoute = (router: IRouter): void => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { _tags, tags, name, description, id, list_id: listId, meta, type } = request.body;
const {
_tags,
tags,
name,
description,
id,
list_id: listId,
meta,
namespace_type: namespaceType,
type,
} = request.body;
const exceptionLists = getExceptionListClient(context);
if (id == null && listId == null) {
return siemResponse.error({
@ -53,7 +63,7 @@ export const updateExceptionListRoute = (router: IRouter): void => {
listId,
meta,
name,
namespaceType: 'single', // TODO: Bubble this up
namespaceType,
tags,
type,
});

View file

@ -1,38 +0,0 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
# Example: ./delete_all_lists.sh
# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
# Delete all the main lists that have children items
curl -s -k \
-H "Content-Type: application/json" \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X POST ${ELASTICSEARCH_URL}/${KIBANA_INDEX}*/_delete_by_query \
--data '{
"query": {
"exists": { "field": "siem_list" }
}
}' \
| jq .
# Delete all the list children items as well
curl -s -k \
-H "Content-Type: application/json" \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X POST ${ELASTICSEARCH_URL}/${KIBANA_INDEX}*/_delete_by_query \
--data '{
"query": {
"exists": { "field": "siem_list_item" }
}
}' \
| jq .

View file

@ -9,8 +9,12 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${2-single}
# Example: ./delete_exception_list.sh ${list_id}
# Example: ./delete_exception_list.sh ${list_id} single
# Example: ./delete_exception_list.sh ${list_id} agnostic
curl -s -k \
-H 'kbn-xsrf: 123' \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id="$1" | jq .
-X DELETE "${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -9,8 +9,12 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${2-single}
# Example: ./delete_exception_list_by_id.sh ${list_id}
# Example: ./delete_exception_list_by_id.sh ${list_id} single
# Example: ./delete_exception_list_by_id.sh ${list_id} agnostic
curl -s -k \
-H 'kbn-xsrf: 123' \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists?id="$1" | jq .
-X DELETE "${KIBANA_URL}${SPACE_URL}/api/exception_lists?id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -9,8 +9,12 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${2-single}
# Example: ./delete_exception_list_item.sh ${item_id}
# Example: ./delete_exception_list_item.sh ${item_id} single
# Example: ./delete_exception_list_item.sh ${item_id} agnostic
curl -s -k \
-H 'kbn-xsrf: 123' \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id="$1" | jq .
-X DELETE "${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -9,8 +9,12 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${2-single}
# Example: ./delete_exception_list_item_by_id.sh ${list_id}
# Example: ./delete_exception_list_item_by_id.sh ${list_id} single
# Example: ./delete_exception_list_item_by_id.sh ${list_id} agnostic
curl -s -k \
-H 'kbn-xsrf: 123' \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id="$1" | jq .
-X DELETE "${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -13,4 +13,4 @@ set -e
curl -s -k \
-H 'kbn-xsrf: 123' \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X DELETE ${KIBANA_URL}${SPACE_URL}/api/lists?id="$1" | jq .
-X DELETE "${KIBANA_URL}${SPACE_URL}/api/lists?id=$1" | jq .

View file

@ -0,0 +1,9 @@
{
"list_id": "endpoint_list",
"_tags": ["endpoint", "process", "malware", "os:linux"],
"tags": ["user added string for a tag", "malware"],
"type": "endpoint",
"description": "This is a sample agnostic endpoint type exception",
"name": "Sample Endpoint Exception List",
"namespace_type": "agnostic"
}

View file

@ -0,0 +1,22 @@
{
"list_id": "endpoint_list",
"item_id": "endpoint_list_item",
"_tags": ["endpoint", "process", "malware", "os:linux"],
"tags": ["user added string for a tag", "malware"],
"type": "simple",
"description": "This is a sample agnostic endpoint type exception",
"name": "Sample Endpoint Exception List",
"namespace_type": "agnostic",
"entries": [
{
"field": "actingProcess.file.signer",
"operator": "included",
"match": "Elastic, N.V."
},
{
"field": "event.category",
"operator": "included",
"match_any": ["process", "malware"]
}
]
}

View file

@ -0,0 +1,16 @@
{
"item_id": "endpoint_list_item",
"_tags": ["endpoint", "process", "malware", "os:windows"],
"tags": ["user added string for a tag", "malware"],
"type": "simple",
"description": "This is a sample agnostic change here this list",
"name": "Sample Endpoint Exception List update change",
"namespace_type": "agnostic",
"entries": [
{
"field": "event.category",
"operator": "included",
"match_any": ["process", "malware"]
}
]
}

View file

@ -10,7 +10,11 @@ set -e
./check_env_variables.sh
LIST_ID=${1:-endpoint_list}
NAMESPACE_TYPE=${2-single}
# Example: ./find_exception_list_items.sh {list-id}
# Example: ./find_exception_list_items.sh {list-id} single
# Example: ./find_exception_list_items.sh {list-id} agnostic
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items/_find?list_id=${LIST_ID} | jq .
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/items/_find?list_id=${LIST_ID}&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -0,0 +1,29 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
LIST_ID=${1:-endpoint_list}
FILTER=${2:-'exception-list.attributes.name:%20Sample%20Endpoint%20Exception%20List'}
NAMESPACE_TYPE=${3-single}
# The %20 is just an encoded space that is typical of URL's.
# The %22 is just an encoded quote of "
# Table of them for testing if needed: https://www.w3schools.com/tags/ref_urlencode.asp
# Example: ./find_exception_list_items_by_filter.sh endpoint_list exception-list.attributes.name:%20Sample%20Endpoint%20Exception%20List
# Example: ./find_exception_list_items_by_filter.sh endpoint_list exception-list.attributes.name:%20Sample%20Endpoint%20Exception%20List single
# Example: ./find_exception_list_items_by_filter.sh endpoint_list exception-list.attributes.name:%20Sample%20Endpoint%20Exception%20List agnostic
#
# Example: ./find_exception_list_items_by_filter.sh endpoint_list exception-list.attributes.entries.field:actingProcess.file.signer
# Example: ./find_exception_list_items_by_filter.sh endpoint_list "exception-list.attributes.entries.field:actingProcess.file.signe*"
# Example: ./find_exception_list_items_by_filter.sh endpoint_list "exception-list.attributes.entries.match:Elastic*%20AND%20exception-list.attributes.entries.field:actingProcess.file.signe*"
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/items/_find?list_id=${LIST_ID}&filter=${FILTER}&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -9,7 +9,11 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${1-single}
# Example: ./find_exception_lists.sh {list-id}
# Example: ./find_exception_lists.sh {list-id} single
# Example: ./find_exception_lists.sh {list-id} agnostic
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/_find | jq .
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/_find?namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -0,0 +1,26 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
FILTER=${1:-'exception-list.attributes.name:%20Sample%20Endpoint%20Exception%20List'}
NAMESPACE_TYPE=${2-single}
# The %20 is just an encoded space that is typical of URL's.
# The %22 is just an encoded quote of "
# Table of them for testing if needed: https://www.w3schools.com/tags/ref_urlencode.asp
# Example get all lists by a particular name:
# ./find_exception_lists_by_filter.sh exception-list.attributes.name:%20Sample%20Endpoint%20Exception%20List
# ./find_exception_lists_by_filter.sh exception-list.attributes.tags:%20malware
# ./find_exception_lists_by_filter.sh exception-list.attributes.tags:%20malware single
# ./find_exception_lists_by_filter.sh exception-list.attributes.tags:%20malware agnostic
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/_find?filter=${FILTER}&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -0,0 +1,19 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
LIST_ID=${3-list-ip}
# Example: ./find_list_items.sh 1 20 list-ip
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/items/_find?list_id=${LIST_ID}&page=${PAGE}&per_page=${PER_PAGE}" | jq .

View file

@ -0,0 +1,23 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
LIST_ID=${3-list-ip}
CURSOR=${4-invalid}
# Example:
# ./find_list_items.sh 1 20 | jq .cursor
# Copy the cursor into the argument below like so
# ./find_list_items_with_cursor.sh 1 10 list-ip eyJwYWdlX2luZGV4IjoyMCwic2VhcmNoX2FmdGVyIjpbIjAyZDZlNGY3LWUzMzAtNGZkYi1iNTY0LTEzZjNiOTk1MjRiYSJdfQ==
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/items/_find?list_id=${LIST_ID}&page=${PAGE}&per_page=${PER_PAGE}&cursor=${CURSOR}" | jq .

View file

@ -0,0 +1,21 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
SORT_FIELD=${3-value}
SORT_ORDER=${4-asc}
LIST_ID=${5-list-ip}
# Example: ./find_list_items_with_sort.sh 1 20 value asc list-ip
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/items/_find?list_id=${LIST_ID}&page=${PAGE}&per_page=${PER_PAGE}&sort_field=${SORT_FIELD}&sort_order=${SORT_ORDER}" | jq .

View file

@ -0,0 +1,22 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
SORT_FIELD=${3-value}
SORT_ORDER=${4-asc}
LIST_ID=${5-list-ip}
CURSOR=${6-invalid}
# Example: ./find_list_items_with_sort_cursor.sh 1 20 value asc list-ip <cursor>
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/items/_find?list_id=${LIST_ID}&page=${PAGE}&per_page=${PER_PAGE}&sort_field=${SORT_FIELD}&sort_order=${SORT_ORDER}&cursor=${CURSOR}" | jq .

View file

@ -0,0 +1,18 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
# Example: ./find_lists.sh 1 20
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/_find?page=${PAGE}&per_page=${PER_PAGE}" | jq .

View file

@ -0,0 +1,22 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
CURSOR=${3-invalid}
# Example:
# ./find_lists.sh 1 20 | jq .cursor
# Copy the cursor into the argument below like so
# ./find_lists_with_cursor.sh 1 10 eyJwYWdlX2luZGV4IjoyMCwic2VhcmNoX2FmdGVyIjpbIjAyZDZlNGY3LWUzMzAtNGZkYi1iNTY0LTEzZjNiOTk1MjRiYSJdfQ==
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/_find?page=${PAGE}&per_page=${PER_PAGE}&cursor=${CURSOR}" | jq .

View file

@ -0,0 +1,18 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
FILTER=${3-type:ip}
# Example: ./find_lists_with_filter.sh 1 20 type:ip
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/_find?page=${PAGE}&per_page=${PER_PAGE}&filter=${FILTER}" | jq .

View file

@ -0,0 +1,20 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
SORT_FIELD=${3-name}
SORT_ORDER=${4-asc}
# Example: ./find_lists_with_sort.sh 1 20 name asc
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/_find?page=${PAGE}&per_page=${PER_PAGE}&sort_field=${SORT_FIELD}&sort_order=${SORT_ORDER}" | jq .

View file

@ -0,0 +1,21 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
PAGE=${1-1}
PER_PAGE=${2-20}
SORT_FIELD=${3-name}
SORT_ORDER=${4-asc}
CURSOR=${5-invalid}
# Example: ./find_lists_with_sort_cursor.sh 1 20 name asc <cursor>
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET "${KIBANA_URL}${SPACE_URL}/api/lists/_find?page=${PAGE}&per_page=${PER_PAGE}&sort_field=${SORT_FIELD}&sort_order=${SORT_ORDER}&cursor=${CURSOR}" | jq .

View file

@ -9,7 +9,10 @@
set -e
./check_env_variables.sh
# Example: ./get_exception_list.sh {id}
NAMESPACE_TYPE=${2-single}
# Example: ./get_exception_list.sh {id} single
# Example: ./get_exception_list.sh {id} agnostic
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id="$1" | jq .
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -9,7 +9,9 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${2-single}
# Example: ./get_exception_list_by_id.sh {id}
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists?id="$1" | jq .
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists?id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -9,7 +9,11 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${2-single}
# Example: ./get_exception_list_item.sh {id}
# Example: ./get_exception_list_item.sh {id} single
# Example: ./get_exception_list_item.sh {id} agnostic
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id="$1" | jq .
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -9,7 +9,11 @@
set -e
./check_env_variables.sh
NAMESPACE_TYPE=${2-single}
# Example: ./get_exception_list_item_by_id.sh {id}
# Example: ./get_exception_list_item_by_id.sh {id} single
# Example: ./get_exception_list_item_by_id.sh {id} agnostic
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id="$1" | jq .
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id=$1&namespace_type=${NAMESPACE_TYPE}" | jq .

View file

@ -0,0 +1,5 @@
{
"name": "Simple list with a type of ip and an auto created id",
"description": "list with an auto created id",
"type": "ip"
}

View file

@ -15,12 +15,12 @@ import {
ListId,
MetaOrUndefined,
Name,
NamespaceType,
Tags,
_Tags,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils';
import { NamespaceType } from './types';
interface CreateExceptionListOptions {
_tags: _Tags;
@ -68,5 +68,5 @@ export const createExceptionList = async ({
type,
updated_by: user,
});
return transformSavedObjectToExceptionList({ savedObject });
return transformSavedObjectToExceptionList({ namespaceType, savedObject });
};

View file

@ -18,12 +18,12 @@ import {
ListId,
MetaOrUndefined,
Name,
NamespaceType,
Tags,
_Tags,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils';
import { NamespaceType } from './types';
interface CreateExceptionListItemOptions {
_tags: _Tags;
@ -77,5 +77,5 @@ export const createExceptionListItem = async ({
type,
updated_by: user,
});
return transformSavedObjectToExceptionListItem({ savedObject });
return transformSavedObjectToExceptionListItem({ namespaceType, savedObject });
};

View file

@ -6,10 +6,14 @@
import { SavedObjectsClientContract } from 'kibana/server';
import { ExceptionListSchema, IdOrUndefined, ListIdOrUndefined } from '../../../common/schemas';
import {
ExceptionListSchema,
IdOrUndefined,
ListIdOrUndefined,
NamespaceType,
} from '../../../common/schemas';
import { getSavedObjectType } from './utils';
import { NamespaceType } from './types';
import { getExceptionList } from './get_exception_list';
import { deleteExceptionListItemByList } from './delete_exception_list_items_by_list';

View file

@ -6,10 +6,14 @@
import { SavedObjectsClientContract } from 'kibana/server';
import { ExceptionListItemSchema, IdOrUndefined, ItemIdOrUndefined } from '../../../common/schemas';
import {
ExceptionListItemSchema,
IdOrUndefined,
ItemIdOrUndefined,
NamespaceType,
} from '../../../common/schemas';
import { getSavedObjectType } from './utils';
import { NamespaceType } from './types';
import { getExceptionListItem } from './get_exception_list_item';
interface DeleteExceptionListItemOptions {

View file

@ -5,10 +5,9 @@
*/
import { SavedObjectsClientContract } from '../../../../../../src/core/server/';
import { ListId } from '../../../common/schemas';
import { ListId, NamespaceType } from '../../../common/schemas';
import { findExceptionListItem } from './find_exception_list_item';
import { NamespaceType } from './types';
import { getSavedObjectType } from './utils';
const PER_PAGE = 100;

View file

@ -7,6 +7,7 @@
import { SavedObjectsClientContract } from 'kibana/server';
import {
ExceptionListItemSchema,
ExceptionListSchema,
FoundExceptionListItemSchema,
FoundExceptionListSchema,
@ -59,7 +60,7 @@ export class ExceptionListClient {
itemId,
id,
namespaceType,
}: GetExceptionListItemOptions): Promise<ExceptionListSchema | null> => {
}: GetExceptionListItemOptions): Promise<ExceptionListItemSchema | null> => {
const { savedObjectsClient } = this;
return getExceptionListItem({ id, itemId, namespaceType, savedObjectsClient });
};
@ -142,7 +143,7 @@ export class ExceptionListClient {
namespaceType,
tags,
type,
}: CreateExceptionListItemOptions): Promise<ExceptionListSchema> => {
}: CreateExceptionListItemOptions): Promise<ExceptionListItemSchema> => {
const { savedObjectsClient, user } = this;
return createExceptionListItem({
_tags,
@ -173,7 +174,7 @@ export class ExceptionListClient {
namespaceType,
tags,
type,
}: UpdateExceptionListItemOptions): Promise<ExceptionListSchema | null> => {
}: UpdateExceptionListItemOptions): Promise<ExceptionListItemSchema | null> => {
const { savedObjectsClient, user } = this;
return updateExceptionListItem({
_tags,
@ -196,7 +197,7 @@ export class ExceptionListClient {
id,
itemId,
namespaceType,
}: DeleteExceptionListItemOptions): Promise<ExceptionListSchema | null> => {
}: DeleteExceptionListItemOptions): Promise<ExceptionListItemSchema | null> => {
const { savedObjectsClient } = this;
return deleteExceptionListItem({
id,

View file

@ -14,6 +14,7 @@ import {
EntriesArrayOrUndefined,
ExceptionListType,
ExceptionListTypeOrUndefined,
FilterOrUndefined,
IdOrUndefined,
ItemId,
ItemIdOrUndefined,
@ -22,14 +23,17 @@ import {
MetaOrUndefined,
Name,
NameOrUndefined,
NamespaceType,
PageOrUndefined,
PerPageOrUndefined,
SortFieldOrUndefined,
SortOrderOrUndefined,
Tags,
TagsOrUndefined,
_Tags,
_TagsOrUndefined,
} from '../../../common/schemas';
import { NamespaceType } from './types';
export interface ConstructorOptions {
user: string;
savedObjectsClient: SavedObjectsClientContract;
@ -113,18 +117,18 @@ export interface UpdateExceptionListItemOptions {
export interface FindExceptionListItemOptions {
listId: ListId;
namespaceType: NamespaceType;
filter: string | undefined;
perPage: number | undefined;
page: number | undefined;
sortField: string | undefined;
sortOrder: string | undefined;
filter: FilterOrUndefined;
perPage: PerPageOrUndefined;
page: PageOrUndefined;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
}
export interface FindExceptionListOptions {
namespaceType: NamespaceType;
filter: string | undefined;
perPage: number | undefined;
page: number | undefined;
sortField: string | undefined;
sortOrder: string | undefined;
filter: FilterOrUndefined;
perPage: PerPageOrUndefined;
page: PageOrUndefined;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
}

View file

@ -6,20 +6,28 @@
import { SavedObjectsClientContract } from 'kibana/server';
import { ExceptionListSoSchema, FoundExceptionListSchema } from '../../../common/schemas';
import {
ExceptionListSoSchema,
FilterOrUndefined,
FoundExceptionListSchema,
NamespaceType,
PageOrUndefined,
PerPageOrUndefined,
SortFieldOrUndefined,
SortOrderOrUndefined,
} from '../../../common/schemas';
import { SavedObjectType } from '../../saved_objects';
import { getSavedObjectType, transformSavedObjectsToFounExceptionList } from './utils';
import { NamespaceType } from './types';
interface FindExceptionListOptions {
namespaceType: NamespaceType;
savedObjectsClient: SavedObjectsClientContract;
filter: string | undefined;
perPage: number | undefined;
page: number | undefined;
sortField: string | undefined;
sortOrder: string | undefined;
filter: FilterOrUndefined;
perPage: PerPageOrUndefined;
page: PageOrUndefined;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
}
export const findExceptionList = async ({
@ -40,14 +48,14 @@ export const findExceptionList = async ({
sortOrder,
type: savedObjectType,
});
return transformSavedObjectsToFounExceptionList({ savedObjectsFindResponse });
return transformSavedObjectsToFounExceptionList({ namespaceType, savedObjectsFindResponse });
};
export const getExceptionListFilter = ({
filter,
savedObjectType,
}: {
filter: string | undefined;
filter: FilterOrUndefined;
savedObjectType: SavedObjectType;
}): string => {
if (filter == null) {

View file

@ -8,24 +8,29 @@ import { SavedObjectsClientContract } from 'kibana/server';
import {
ExceptionListSoSchema,
FilterOrUndefined,
FoundExceptionListItemSchema,
ListId,
NamespaceType,
PageOrUndefined,
PerPageOrUndefined,
SortFieldOrUndefined,
SortOrderOrUndefined,
} from '../../../common/schemas';
import { SavedObjectType } from '../../saved_objects';
import { getSavedObjectType, transformSavedObjectsToFounExceptionListItem } from './utils';
import { NamespaceType } from './types';
import { getExceptionList } from './get_exception_list';
interface FindExceptionListItemOptions {
listId: ListId;
namespaceType: NamespaceType;
savedObjectsClient: SavedObjectsClientContract;
filter: string | undefined;
perPage: number | undefined;
page: number | undefined;
sortField: string | undefined;
sortOrder: string | undefined;
filter: FilterOrUndefined;
perPage: PerPageOrUndefined;
page: PageOrUndefined;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
}
export const findExceptionListItem = async ({
@ -56,7 +61,10 @@ export const findExceptionListItem = async ({
sortOrder,
type: savedObjectType,
});
return transformSavedObjectsToFounExceptionListItem({ savedObjectsFindResponse });
return transformSavedObjectsToFounExceptionListItem({
namespaceType,
savedObjectsFindResponse,
});
}
};
@ -66,7 +74,7 @@ export const getExceptionListItemFilter = ({
savedObjectType,
}: {
listId: ListId;
filter: string | undefined;
filter: FilterOrUndefined;
savedObjectType: SavedObjectType;
}): string => {
if (filter == null) {

View file

@ -13,10 +13,10 @@ import {
ExceptionListSoSchema,
IdOrUndefined,
ListIdOrUndefined,
NamespaceType,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils';
import { NamespaceType } from './types';
interface GetExceptionListOptions {
id: IdOrUndefined;
@ -35,7 +35,7 @@ export const getExceptionList = async ({
if (id != null) {
try {
const savedObject = await savedObjectsClient.get<ExceptionListSoSchema>(savedObjectType, id);
return transformSavedObjectToExceptionList({ savedObject });
return transformSavedObjectToExceptionList({ namespaceType, savedObject });
} catch (err) {
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
return null;
@ -54,7 +54,10 @@ export const getExceptionList = async ({
type: savedObjectType,
});
if (savedObject.saved_objects[0] != null) {
return transformSavedObjectToExceptionList({ savedObject: savedObject.saved_objects[0] });
return transformSavedObjectToExceptionList({
namespaceType,
savedObject: savedObject.saved_objects[0],
});
} else {
return null;
}

View file

@ -13,10 +13,10 @@ import {
ExceptionListSoSchema,
IdOrUndefined,
ItemIdOrUndefined,
NamespaceType,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils';
import { NamespaceType } from './types';
interface GetExceptionListItemOptions {
id: IdOrUndefined;
@ -35,7 +35,7 @@ export const getExceptionListItem = async ({
if (id != null) {
try {
const savedObject = await savedObjectsClient.get<ExceptionListSoSchema>(savedObjectType, id);
return transformSavedObjectToExceptionListItem({ savedObject });
return transformSavedObjectToExceptionListItem({ namespaceType, savedObject });
} catch (err) {
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
return null;
@ -55,6 +55,7 @@ export const getExceptionListItem = async ({
});
if (savedObject.saved_objects[0] != null) {
return transformSavedObjectToExceptionListItem({
namespaceType,
savedObject: savedObject.saved_objects[0],
});
} else {

View file

@ -15,12 +15,12 @@ import {
ListIdOrUndefined,
MetaOrUndefined,
NameOrUndefined,
NamespaceType,
TagsOrUndefined,
_TagsOrUndefined,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectUpdateToExceptionList } from './utils';
import { NamespaceType } from './types';
import { getExceptionList } from './get_exception_list';
interface UpdateExceptionListOptions {
@ -69,6 +69,6 @@ export const updateExceptionList = async ({
updated_by: user,
}
);
return transformSavedObjectUpdateToExceptionList({ exceptionList, savedObject });
return transformSavedObjectUpdateToExceptionList({ exceptionList, namespaceType, savedObject });
}
};

View file

@ -17,12 +17,12 @@ import {
ItemIdOrUndefined,
MetaOrUndefined,
NameOrUndefined,
NamespaceType,
TagsOrUndefined,
_TagsOrUndefined,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectUpdateToExceptionListItem } from './utils';
import { NamespaceType } from './types';
import { getExceptionListItem } from './get_exception_list_item';
interface UpdateExceptionListItemOptions {
@ -82,6 +82,10 @@ export const updateExceptionListItem = async ({
updated_by: user,
}
);
return transformSavedObjectUpdateToExceptionListItem({ exceptionListItem, savedObject });
return transformSavedObjectUpdateToExceptionListItem({
exceptionListItem,
namespaceType,
savedObject,
});
}
};

View file

@ -12,6 +12,7 @@ import {
ExceptionListSoSchema,
FoundExceptionListItemSchema,
FoundExceptionListSchema,
NamespaceType,
} from '../../../common/schemas';
import {
SavedObjectType,
@ -19,8 +20,6 @@ import {
exceptionListSavedObjectType,
} from '../../saved_objects';
import { NamespaceType } from './types';
export const getSavedObjectType = ({
namespaceType,
}: {
@ -35,8 +34,10 @@ export const getSavedObjectType = ({
export const transformSavedObjectToExceptionList = ({
savedObject,
namespaceType,
}: {
savedObject: SavedObject<ExceptionListSoSchema>;
namespaceType: NamespaceType;
}): ExceptionListSchema => {
const dateNow = new Date().toISOString();
const {
@ -68,6 +69,7 @@ export const transformSavedObjectToExceptionList = ({
list_id,
meta,
name,
namespace_type: namespaceType,
tags,
tie_breaker_id,
type,
@ -79,9 +81,11 @@ export const transformSavedObjectToExceptionList = ({
export const transformSavedObjectUpdateToExceptionList = ({
exceptionList,
savedObject,
namespaceType,
}: {
exceptionList: ExceptionListSchema;
savedObject: SavedObjectsUpdateResponse<ExceptionListSoSchema>;
namespaceType: NamespaceType;
}): ExceptionListSchema => {
const dateNow = new Date().toISOString();
const {
@ -101,6 +105,7 @@ export const transformSavedObjectUpdateToExceptionList = ({
list_id: exceptionList.list_id,
meta: meta ?? exceptionList.meta,
name: name ?? exceptionList.name,
namespace_type: namespaceType,
tags: tags ?? exceptionList.tags,
tie_breaker_id: exceptionList.tie_breaker_id,
type: type ?? exceptionList.type,
@ -111,8 +116,10 @@ export const transformSavedObjectUpdateToExceptionList = ({
export const transformSavedObjectToExceptionListItem = ({
savedObject,
namespaceType,
}: {
savedObject: SavedObject<ExceptionListSoSchema>;
namespaceType: NamespaceType;
}): ExceptionListItemSchema => {
const dateNow = new Date().toISOString();
const {
@ -150,6 +157,7 @@ export const transformSavedObjectToExceptionListItem = ({
list_id,
meta,
name,
namespace_type: namespaceType,
tags,
tie_breaker_id,
type,
@ -161,9 +169,11 @@ export const transformSavedObjectToExceptionListItem = ({
export const transformSavedObjectUpdateToExceptionListItem = ({
exceptionListItem,
savedObject,
namespaceType,
}: {
exceptionListItem: ExceptionListItemSchema;
savedObject: SavedObjectsUpdateResponse<ExceptionListSoSchema>;
namespaceType: NamespaceType;
}): ExceptionListItemSchema => {
const dateNow = new Date().toISOString();
const {
@ -196,6 +206,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
list_id: exceptionListItem.list_id,
meta: meta ?? exceptionListItem.meta,
name: name ?? exceptionListItem.name,
namespace_type: namespaceType,
tags: tags ?? exceptionListItem.tags,
tie_breaker_id: exceptionListItem.tie_breaker_id,
type: type ?? exceptionListItem.type,
@ -206,12 +217,14 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
export const transformSavedObjectsToFounExceptionListItem = ({
savedObjectsFindResponse,
namespaceType,
}: {
savedObjectsFindResponse: SavedObjectsFindResponse<ExceptionListSoSchema>;
namespaceType: NamespaceType;
}): FoundExceptionListItemSchema => {
return {
data: savedObjectsFindResponse.saved_objects.map((savedObject) =>
transformSavedObjectToExceptionListItem({ savedObject })
transformSavedObjectToExceptionListItem({ namespaceType, savedObject })
),
page: savedObjectsFindResponse.page,
per_page: savedObjectsFindResponse.per_page,
@ -221,12 +234,14 @@ export const transformSavedObjectsToFounExceptionListItem = ({
export const transformSavedObjectsToFounExceptionList = ({
savedObjectsFindResponse,
namespaceType,
}: {
savedObjectsFindResponse: SavedObjectsFindResponse<ExceptionListSoSchema>;
namespaceType: NamespaceType;
}): FoundExceptionListSchema => {
return {
data: savedObjectsFindResponse.saved_objects.map((savedObject) =>
transformSavedObjectToExceptionList({ savedObject })
transformSavedObjectToExceptionList({ namespaceType, savedObject })
),
page: savedObjectsFindResponse.page,
per_page: savedObjectsFindResponse.per_page,

View file

@ -58,7 +58,7 @@ export const createListItem = async ({
...transformListItemToElasticQuery({ type, value }),
};
const response: CreateDocumentResponse = await callCluster('index', {
const response = await callCluster<CreateDocumentResponse>('index', {
body,
id,
index: listItemIndex,

View file

@ -0,0 +1,116 @@
/*
* 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 { APICaller } from 'kibana/server';
import {
Filter,
FoundListItemSchema,
ListId,
Page,
PerPage,
SearchEsListItemSchema,
SortFieldOrUndefined,
SortOrderOrUndefined,
} from '../../../common/schemas';
import { getList } from '../lists';
import {
encodeCursor,
getQueryFilter,
getSearchAfterWithTieBreaker,
getSortWithTieBreaker,
scrollToStartPage,
transformElasticToListItem,
} from '../utils';
interface FindListItemOptions {
listId: ListId;
filter: Filter;
currentIndexPosition: number;
searchAfter: string[] | undefined;
perPage: PerPage;
page: Page;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
callCluster: APICaller;
listIndex: string;
listItemIndex: string;
}
export const findListItem = async ({
callCluster,
currentIndexPosition,
filter,
listId,
page,
perPage,
searchAfter,
sortField: sortFieldWithPossibleValue,
listIndex,
listItemIndex,
sortOrder,
}: FindListItemOptions): Promise<FoundListItemSchema | null> => {
const query = getQueryFilter({ filter });
const list = await getList({ callCluster, id: listId, listIndex });
if (list == null) {
return null;
} else {
const sortField =
sortFieldWithPossibleValue === 'value' ? list.type : sortFieldWithPossibleValue;
const scroll = await scrollToStartPage({
callCluster,
currentIndexPosition,
filter,
hopSize: 100,
index: listItemIndex,
page,
perPage,
searchAfter,
sortField,
sortOrder,
});
const { count } = await callCluster('count', {
body: {
query,
},
ignoreUnavailable: true,
index: listItemIndex,
});
if (scroll.validSearchAfterFound) {
const response = await callCluster<SearchEsListItemSchema>('search', {
body: {
query,
search_after: scroll.searchAfter,
sort: getSortWithTieBreaker({ sortField, sortOrder }),
},
ignoreUnavailable: true,
index: listItemIndex,
size: perPage,
});
return {
cursor: encodeCursor({
page,
perPage,
searchAfter: getSearchAfterWithTieBreaker({ response, sortField }),
}),
data: transformElasticToListItem({ response, type: list.type }),
page,
per_page: perPage,
total: count,
};
} else {
return {
cursor: encodeCursor({ page, perPage, searchAfter: undefined }),
data: [],
page,
per_page: perPage,
total: count,
};
}
}
};

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SearchResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { Id, ListItemSchema, SearchEsListItemSchema } from '../../../common/schemas';
@ -21,7 +20,7 @@ export const getListItem = async ({
callCluster,
listItemIndex,
}: GetListItemOptions): Promise<ListItemSchema | null> => {
const listItemES: SearchResponse<SearchEsListItemSchema> = await callCluster('search', {
const listItemES = await callCluster<SearchEsListItemSchema>('search', {
body: {
query: {
term: {

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SearchResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas';
@ -25,7 +24,7 @@ export const getListItemByValues = async ({
type,
value,
}: GetListItemByValuesOptions): Promise<ListItemArraySchema> => {
const response: SearchResponse<SearchEsListItemSchema> = await callCluster('search', {
const response = await callCluster<SearchEsListItemSchema>('search', {
body: {
query: {
bool: {

View file

@ -8,12 +8,13 @@ export * from './buffer_lines';
export * from './create_list_item';
export * from './create_list_items_bulk';
export * from './delete_list_item_by_value';
export * from './delete_list_item';
export * from './find_list_item';
export * from './get_list_item_by_value';
export * from './get_list_item';
export * from './get_list_item_by_values';
export * from './get_list_item_template';
export * from './get_list_item_index';
export * from './update_list_item';
export * from './write_lines_to_bulk_list_items';
export * from './write_list_items_to_stream';
export * from './get_list_item_template';
export * from './delete_list_item';
export * from './get_list_item_index';

View file

@ -48,7 +48,7 @@ export const updateListItem = async ({
...transformListItemToElasticQuery({ type: listItem.type, value: value ?? listItem.value }),
};
const response: CreateDocumentResponse = await callCluster('update', {
const response = await callCluster<CreateDocumentResponse>('update', {
body: {
doc,
},

View file

@ -114,7 +114,7 @@ export const getResponse = async ({
listItemIndex,
size = SIZE,
}: GetResponseOptions): Promise<SearchResponse<SearchEsListItemSchema>> => {
return callCluster('search', {
return callCluster<SearchEsListItemSchema>('search', {
body: {
query: {
term: {

View file

@ -55,7 +55,7 @@ export const createList = async ({
updated_at: createdAt,
updated_by: user,
};
const response: CreateDocumentResponse = await callCluster('index', {
const response = await callCluster<CreateDocumentResponse>('index', {
body,
id,
index: listIndex,

View file

@ -0,0 +1,104 @@
/*
* 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 { APICaller } from 'kibana/server';
import {
Filter,
FoundListSchema,
Page,
PerPage,
SearchEsListSchema,
SortFieldOrUndefined,
SortOrderOrUndefined,
} from '../../../common/schemas';
import {
encodeCursor,
getQueryFilter,
getSearchAfterWithTieBreaker,
getSortWithTieBreaker,
scrollToStartPage,
transformElasticToList,
} from '../utils';
interface FindListOptions {
filter: Filter;
currentIndexPosition: number;
searchAfter: string[] | undefined;
perPage: PerPage;
page: Page;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
callCluster: APICaller;
listIndex: string;
}
export const findList = async ({
callCluster,
currentIndexPosition,
filter,
page,
perPage,
searchAfter,
sortField,
listIndex,
sortOrder,
}: FindListOptions): Promise<FoundListSchema> => {
const query = getQueryFilter({ filter });
const scroll = await scrollToStartPage({
callCluster,
currentIndexPosition,
filter,
hopSize: 100,
index: listIndex,
page,
perPage,
searchAfter,
sortField,
sortOrder,
});
const { count } = await callCluster('count', {
body: {
query,
},
ignoreUnavailable: true,
index: listIndex,
});
if (scroll.validSearchAfterFound) {
const response = await callCluster<SearchEsListSchema>('search', {
body: {
query,
search_after: scroll.searchAfter,
sort: getSortWithTieBreaker({ sortField, sortOrder }),
},
ignoreUnavailable: true,
index: listIndex,
size: perPage,
});
return {
cursor: encodeCursor({
page,
perPage,
searchAfter: getSearchAfterWithTieBreaker({ response, sortField }),
}),
data: transformElasticToList({ response }),
page,
per_page: perPage,
total: count,
};
} else {
return {
cursor: encodeCursor({ page, perPage, searchAfter: undefined }),
data: [],
page,
per_page: perPage,
total: count,
};
}
};

View file

@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SearchResponse } from 'elasticsearch';
import { APICaller } from 'kibana/server';
import { Id, ListSchema, SearchEsListSchema } from '../../../common/schemas';
import { transformElasticToList } from '../utils/transform_elastic_to_list';
interface GetListOptions {
id: Id;
@ -20,7 +20,7 @@ export const getList = async ({
callCluster,
listIndex,
}: GetListOptions): Promise<ListSchema | null> => {
const result: SearchResponse<SearchEsListSchema> = await callCluster('search', {
const response = await callCluster<SearchEsListSchema>('search', {
body: {
query: {
term: {
@ -31,12 +31,6 @@ export const getList = async ({
ignoreUnavailable: true,
index: listIndex,
});
if (result.hits.hits.length) {
return {
id: result.hits.hits[0]._id,
...result.hits.hits[0]._source,
};
} else {
return null;
}
const list = transformElasticToList({ response });
return list[0] ?? null;
};

View file

@ -6,6 +6,7 @@
export * from './create_list';
export * from './delete_list';
export * from './find_list';
export * from './get_list';
export * from './get_list_template';
export * from './update_list';

View file

@ -6,11 +6,18 @@
import { APICaller } from 'kibana/server';
import { ListItemArraySchema, ListItemSchema, ListSchema } from '../../../common/schemas';
import {
FoundListItemSchema,
FoundListSchema,
ListItemArraySchema,
ListItemSchema,
ListSchema,
} from '../../../common/schemas';
import { ConfigType } from '../../config';
import {
createList,
deleteList,
findList,
getList,
getListIndex,
getListTemplate,
@ -21,6 +28,7 @@ import {
deleteListItem,
deleteListItemByValue,
exportListItemsToStream,
findListItem,
getListItem,
getListItemByValue,
getListItemByValues,
@ -52,6 +60,8 @@ import {
DeleteListItemOptions,
DeleteListOptions,
ExportListItemsToStreamOptions,
FindListItemOptions,
FindListOptions,
GetListItemByValueOptions,
GetListItemOptions,
GetListItemsByValueOptions,
@ -410,4 +420,56 @@ export class ListClient {
value,
});
};
public findList = async ({
filter,
currentIndexPosition,
perPage,
page,
sortField,
sortOrder,
searchAfter,
}: FindListOptions): Promise<FoundListSchema> => {
const { callCluster } = this;
const listIndex = this.getListIndex();
return findList({
callCluster,
currentIndexPosition,
filter,
listIndex,
page,
perPage,
searchAfter,
sortField,
sortOrder,
});
};
public findListItem = async ({
listId,
filter,
currentIndexPosition,
perPage,
page,
sortField,
sortOrder,
searchAfter,
}: FindListItemOptions): Promise<FoundListItemSchema | null> => {
const { callCluster } = this;
const listIndex = this.getListIndex();
const listItemIndex = this.getListItemIndex();
return findListItem({
callCluster,
currentIndexPosition,
filter,
listId,
listIndex,
listItemIndex,
page,
perPage,
searchAfter,
sortField,
sortOrder,
});
};
}

View file

@ -11,11 +11,17 @@ import { APICaller } from 'kibana/server';
import {
Description,
DescriptionOrUndefined,
Filter,
Id,
IdOrUndefined,
ListId,
MetaOrUndefined,
Name,
NameOrUndefined,
Page,
PerPage,
SortFieldOrUndefined,
SortOrderOrUndefined,
Type,
} from '../../../common/schemas';
import { ConfigType } from '../../config';
@ -110,3 +116,24 @@ export interface GetListItemsByValueOptions {
listId: string;
value: string[];
}
export interface FindListOptions {
currentIndexPosition: number;
filter: Filter;
perPage: PerPage;
page: Page;
searchAfter: string[] | undefined;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
}
export interface FindListItemOptions {
currentIndexPosition: number;
filter: Filter;
listId: ListId;
perPage: PerPage;
page: Page;
searchAfter: string[] | undefined;
sortField: SortFieldOrUndefined;
sortOrder: SortOrderOrUndefined;
}

View file

@ -3,4 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export type NamespaceType = 'agnostic' | 'single';
interface Scroll {
searchAfter: string[] | undefined;
validSearchAfterFound: boolean;
}

View file

@ -51,7 +51,7 @@ export const updateList = async ({
updated_at: updatedAt,
updated_by: user,
};
const response: CreateDocumentResponse = await callCluster('update', {
const response = await callCluster<CreateDocumentResponse>('update', {
body: { doc },
id,
index: listIndex,

View file

@ -0,0 +1,41 @@
/*
* 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 { Page, PerPage } from '../../../common/schemas';
interface CalculateScrollMathOptions {
perPage: PerPage;
page: Page;
hopSize: number;
currentIndexPosition: number;
}
interface CalculateScrollMathReturn {
hops: number;
leftOverAfterHops: number;
}
export const calculateScrollMath = ({
currentIndexPosition,
page,
perPage,
hopSize,
}: CalculateScrollMathOptions): CalculateScrollMathReturn => {
const startPageIndex = (page - 1) * perPage - currentIndexPosition;
if (startPageIndex < 0) {
// This should never be hit but just in case I do a check. We do validate higher above this
// before the current index position gets to this point but to be safe we add this line.
throw new Error(
`page: ${page}, perPage ${perPage} and currentIndex ${currentIndexPosition} are less than zero`
);
}
const hops = Math.floor(startPageIndex / hopSize);
const leftOverAfterHops = startPageIndex - hops * hopSize;
return {
hops,
leftOverAfterHops,
};
};

View file

@ -0,0 +1,127 @@
/*
* 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 { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { CursorOrUndefined, SortFieldOrUndefined } from '../../../common/schemas';
import { exactCheck } from '../../../common/siem_common_deps';
/**
* Used only internally for this ad-hoc opaque cursor structure to keep track of the
* current page_index that the search_after is currently on. The format of an array
* is to be consistent with other compact forms of opaque nature such as a saved object versioning.
*
* The format is [index of item, search_after_array]
*/
// TODO: Use PositiveInteger from siem once that type is outside of server and in common
export const contextCursor = t.tuple([t.number, t.union([t.array(t.string), t.undefined])]);
export type ContextCursor = t.TypeOf<typeof contextCursor>;
export interface EncodeCursorOptions {
searchAfter: string[] | undefined;
page: number;
perPage: number;
}
export const encodeCursor = ({ searchAfter, page, perPage }: EncodeCursorOptions): string => {
const index = searchAfter != null ? page * perPage : 0;
const encodedCursor = searchAfter != null ? [index, searchAfter] : [index];
const scrollStringed = JSON.stringify(encodedCursor);
return Buffer.from(scrollStringed).toString('base64');
};
export interface DecodeCursorOptions {
cursor: CursorOrUndefined;
page: number;
perPage: number;
sortField: SortFieldOrUndefined;
}
export interface DecodeCursor {
cursor: ContextCursor;
isValid: boolean;
errorMessage: string;
}
export const decodeCursor = ({
cursor,
page,
perPage,
sortField,
}: DecodeCursorOptions): DecodeCursor => {
if (cursor == null) {
return {
cursor: [0, undefined],
errorMessage: '',
isValid: true,
};
} else {
const fromBuffer = Buffer.from(cursor, 'base64').toString();
const parsed = parseOrUndefined(fromBuffer);
if (parsed == null) {
return {
cursor: [0, undefined],
errorMessage: 'Error parsing JSON from base64 encoded cursor',
isValid: false,
};
} else {
const decodedCursor = contextCursor.decode(parsed);
const checked = exactCheck(parsed, decodedCursor);
const onLeft = (): ContextCursor | undefined => undefined;
const onRight = (schema: ContextCursor): ContextCursor | undefined => schema;
const cursorOrUndefined = pipe(checked, fold(onLeft, onRight));
const startPageIndex = (page - 1) * perPage;
if (cursorOrUndefined == null) {
return {
cursor: [0, undefined],
errorMessage: 'Error decoding cursor structure',
isValid: false,
};
} else {
const [index, searchAfter] = cursorOrUndefined;
if (index < 0) {
return {
cursor: [0, undefined],
errorMessage: 'index of cursor cannot be less 0',
isValid: false,
};
} else if (index > startPageIndex) {
return {
cursor: [0, undefined],
errorMessage: `index: ${index} of cursor cannot be greater than the start page index: ${startPageIndex}`,
isValid: false,
};
} else if (searchAfter != null && searchAfter.length > 1 && sortField == null) {
return {
cursor: [0, undefined],
errorMessage: '',
isValid: false,
};
} else {
return {
cursor: [index, searchAfter != null ? searchAfter : undefined],
errorMessage: '',
isValid: true,
};
}
}
}
}
};
export const parseOrUndefined = (input: string): ContextCursor | undefined => {
try {
return JSON.parse(input);
} catch (err) {
return undefined;
}
};

Some files were not shown because too many files have changed in this diff Show more