[SIEM][Detection Engine][Lists] Adds conflict versioning and io-ts improvements to lists (#72337)

## Summary

* Adds conflict versioning by exposing the "_version" from the saved object system. It renames "version" to "_version" so that we can use regular "version" later for versioning things for pre-packaged lists abilities.
* Utilizes `t.OutputOf` in the requests and the data types to give us more correctly types
* Removes the `Identity` utility as that is adding confusion and can confuse vs code rather than improves things
* Removes extra types that were causing confusion which was an idiom from io-ts
* Changes the wording of `Partial` by removing that and instead focuses the request types on either client side or server side at this point.

NOTE: The UI can migrate to holding onto the `_version` and then push it back down when it wants to migrate to using the conflict resolution. If the UI does not push it down, then a value of undefined will be used which is indicating that no conflict errors are wanted.


Output example of posting an exception list:

❯ ./post_exception_list.sh
```ts
{
  "_tags": [
    "endpoint",
    "process",
    "malware",
    "os:linux"
  ],
  "_version": "Wzk4NiwxXQ==",
  "created_at": "2020-07-17T18:59:22.872Z",
  "created_by": "yo",
  "description": "This is a sample endpoint type exception",
  "id": "a08795b0-c85f-11ea-b1a6-c155df988a92",
  "list_id": "simple_list",
  "name": "Sample Endpoint Exception List",
  "namespace_type": "single",
  "tags": [
    "user added string for a tag",
    "malware"
  ],
  "tie_breaker_id": "b789ec05-3e0f-4344-a156-0c0f5b6e2f9c",
  "type": "detection",
  "updated_at": "2020-07-17T18:59:22.891Z",
  "updated_by": "yo"
}
```

Output example of posting an exception list item
❯ ./post_exception_list_item.sh
```ts
{
  "_tags": [
    "endpoint",
    "process",
    "malware",
    "os:linux"
  ],
  "_version": "Wzk4NywxXQ==",
  "comments": [],
  "created_at": "2020-07-17T18:59:30.286Z",
  "created_by": "yo",
  "description": "This is a sample endpoint type exception",
  "entries": [
    {
      "field": "actingProcess.file.signer",
      "operator": "excluded",
      "type": "exists"
    },
    {
      "field": "host.name",
      "operator": "included",
      "type": "match_any",
      "value": [
        "some host",
        "another host"
      ]
    }
  ],
  "id": "a4f2b800-c85f-11ea-b1a6-c155df988a92",
  "item_id": "simple_list_item",
  "list_id": "simple_list",
  "name": "Sample Endpoint Exception List",
  "namespace_type": "single",
  "tags": [
    "user added string for a tag",
    "malware"
  ],
  "tie_breaker_id": "1dc456bc-7aa9-44b4-bca3-131689cf729f",
  "type": "simple",
  "updated_at": "2020-07-17T18:59:30.304Z",
  "updated_by": "yo"
}
```

Output example of when you get an exception list:

❯ ./get_exception_list.sh simple_list
```ts
{
  "_tags": [
    "endpoint",
    "process",
    "malware",
    "os:linux"
  ],
  "_version": "WzEwNzcsMV0=",
  "created_at": "2020-07-17T18:59:22.872Z",
  "created_by": "yo",
  "description": "Different description",
  "id": "a08795b0-c85f-11ea-b1a6-c155df988a92",
  "list_id": "simple_list",
  "name": "Sample Endpoint Exception List",
  "namespace_type": "single",
  "tags": [
    "user added string for a tag",
    "malware"
  ],
  "tie_breaker_id": "b789ec05-3e0f-4344-a156-0c0f5b6e2f9c",
  "type": "endpoint",
  "updated_at": "2020-07-17T20:01:24.958Z",
  "updated_by": "yo"
}
```

Example of the error you get if you do an update of an exception list and someone else has changed it:
```ts
{
  "message": "[exception-list:a08795b0-c85f-11ea-b1a6-c155df988a92]: version conflict, required seqNo [1074], primary term [1]. current document has seqNo [1077] and primary term [1]: [version_conflict_engine_exception] [exception-list:a08795b0-c85f-11ea-b1a6-c155df988a92]: version conflict, required seqNo [1074], primary term [1]. current document has seqNo [1077] and primary term [1], with { index_uuid=\"a2mgXBO6Tl2ULDq-MTs1Tw\" & shard=\"0\" & index=\".kibana-hassanabad_1\" }",
  "status_code": 409
}
```

Lists are the same way and flavor, they encode the _version the same way that saved objects do. To see those work you run these scripts:

```ts
./post_list.sh
./post_list_item.sh
./find_list.sh
./find_list_item.sh
```



### Checklist

- [x] [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-07-20 11:00:06 -06:00 committed by GitHub
parent 75e4c7a2b7
commit afae94a85e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
128 changed files with 517 additions and 435 deletions

View file

@ -60,3 +60,4 @@ export const TAGS = [];
export const COMMENTS = [];
export const FILTER = 'name:Nicolas Bourbaki';
export const CURSOR = 'c29tZXN0cmluZ2ZvcnlvdQ==';
export const _VERSION = 'WzI5NywxXQ==';

View file

@ -307,3 +307,7 @@ export type Deserializer = t.TypeOf<typeof deserializer>;
export const deserializerOrUndefined = t.union([deserializer, t.undefined]);
export type DeserializerOrUndefined = t.TypeOf<typeof deserializerOrUndefined>;
export const _version = t.string;
export const _versionOrUndefined = t.union([_version, t.undefined]);
export type _VersionOrUndefined = t.TypeOf<typeof _versionOrUndefined>;

View file

@ -14,4 +14,4 @@ export const createEsBulkTypeSchema = t.exact(
})
);
export type CreateEsBulkTypeSchema = t.TypeOf<typeof createEsBulkTypeSchema>;
export type CreateEsBulkTypeSchema = t.OutputOf<typeof createEsBulkTypeSchema>;

View file

@ -38,4 +38,4 @@ export const indexEsListItemSchema = t.intersection([
esDataTypeUnion,
]);
export type IndexEsListItemSchema = t.TypeOf<typeof indexEsListItemSchema>;
export type IndexEsListItemSchema = t.OutputOf<typeof indexEsListItemSchema>;

View file

@ -38,4 +38,4 @@ export const indexEsListSchema = t.exact(
})
);
export type IndexEsListSchema = t.TypeOf<typeof indexEsListSchema>;
export type IndexEsListSchema = t.OutputOf<typeof indexEsListSchema>;

View file

@ -21,4 +21,4 @@ export const updateEsListItemSchema = t.intersection([
esDataTypeUnion,
]);
export type UpdateEsListItemSchema = t.TypeOf<typeof updateEsListItemSchema>;
export type UpdateEsListItemSchema = t.OutputOf<typeof updateEsListItemSchema>;

View file

@ -26,4 +26,4 @@ export const updateEsListSchema = t.exact(
})
);
export type UpdateEsListSchema = t.TypeOf<typeof updateEsListSchema>;
export type UpdateEsListSchema = t.OutputOf<typeof updateEsListSchema>;

View file

@ -19,7 +19,7 @@ import {
name,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
import { CreateCommentsArray, DefaultCreateCommentsArray, DefaultEntryArray } from '../types';
import { EntriesArray } from '../types/entries';
import { DefaultUuid } from '../../siem_common_deps';
@ -44,20 +44,16 @@ export const createEndpointListItemSchema = t.intersection([
),
]);
export type CreateEndpointListItemSchemaPartial = Identity<
t.TypeOf<typeof createEndpointListItemSchema>
>;
export type CreateEndpointListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof createEndpointListItemSchema>
>;
export type CreateEndpointListItemSchema = t.OutputOf<typeof createEndpointListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type CreateEndpointListItemSchemaDecoded = Identity<
Omit<CreateEndpointListItemSchema, '_tags' | 'tags' | 'item_id' | 'entries' | 'comments'> & {
_tags: _Tags;
comments: CreateCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
}
>;
export type CreateEndpointListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof createEndpointListItemSchema>>,
'_tags' | 'tags' | 'item_id' | 'entries' | 'comments'
> & {
_tags: _Tags;
comments: CreateCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
};

View file

@ -21,7 +21,7 @@ import {
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
import {
CreateCommentsArray,
DefaultCreateCommentsArray,
@ -53,24 +53,17 @@ export const createExceptionListItemSchema = t.intersection([
),
]);
export type CreateExceptionListItemSchemaPartial = Identity<
t.TypeOf<typeof createExceptionListItemSchema>
>;
export type CreateExceptionListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof createExceptionListItemSchema>
>;
export type CreateExceptionListItemSchema = t.OutputOf<typeof createExceptionListItemSchema>;
// 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' | 'namespace_type' | 'comments'
> & {
_tags: _Tags;
comments: CreateCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
namespace_type: NamespaceType;
}
>;
export type CreateExceptionListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof createExceptionListItemSchema>>,
'_tags' | 'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
> & {
_tags: _Tags;
comments: CreateCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
namespace_type: NamespaceType;
};

View file

@ -20,7 +20,7 @@ import {
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
import { DefaultUuid } from '../../siem_common_deps';
import { NamespaceType } from '../types';
@ -43,17 +43,15 @@ export const createExceptionListSchema = t.intersection([
),
]);
export type CreateExceptionListSchemaPartial = Identity<t.TypeOf<typeof createExceptionListSchema>>;
export type CreateExceptionListSchema = RequiredKeepUndefined<
t.TypeOf<typeof createExceptionListSchema>
>;
export type CreateExceptionListSchema = t.OutputOf<typeof createExceptionListSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type CreateExceptionListSchemaDecoded = Identity<
Omit<CreateExceptionListSchema, '_tags' | 'tags' | 'list_id' | 'namespace_type'> & {
_tags: _Tags;
tags: Tags;
list_id: ListId;
namespace_type: NamespaceType;
}
>;
export type CreateExceptionListSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof createExceptionListSchema>>,
'_tags' | 'tags' | 'list_id' | 'namespace_type'
> & {
_tags: _Tags;
tags: Tags;
list_id: ListId;
namespace_type: NamespaceType;
};

View file

@ -9,7 +9,7 @@
import * as t from 'io-ts';
import { id, list_id, meta, value } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
export const createListItemSchema = t.intersection([
t.exact(
@ -21,5 +21,7 @@ export const createListItemSchema = t.intersection([
t.exact(t.partial({ id, meta })),
]);
export type CreateListItemSchemaPartial = Identity<t.TypeOf<typeof createListItemSchema>>;
export type CreateListItemSchema = RequiredKeepUndefined<t.TypeOf<typeof createListItemSchema>>;
export type CreateListItemSchema = t.OutputOf<typeof createListItemSchema>;
export type CreateListItemSchemaDecoded = RequiredKeepUndefined<
t.TypeOf<typeof createListItemSchema>
>;

View file

@ -7,7 +7,7 @@
import * as t from 'io-ts';
import { description, deserializer, id, meta, name, serializer, type } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
export const createListSchema = t.intersection([
t.exact(
@ -20,5 +20,5 @@ export const createListSchema = t.intersection([
t.exact(t.partial({ deserializer, id, meta, serializer })),
]);
export type CreateListSchemaPartial = Identity<t.TypeOf<typeof createListSchema>>;
export type CreateListSchema = RequiredKeepUndefined<t.TypeOf<typeof createListSchema>>;
export type CreateListSchema = t.OutputOf<typeof createListSchema>;
export type CreateListSchemaDecoded = RequiredKeepUndefined<t.TypeOf<typeof createListSchema>>;

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import { id, item_id } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const deleteEndpointListItemSchema = t.exact(
t.partial({
@ -17,7 +18,9 @@ export const deleteEndpointListItemSchema = t.exact(
})
);
export type DeleteEndpointListItemSchema = t.TypeOf<typeof deleteEndpointListItemSchema>;
export type DeleteEndpointListItemSchema = t.OutputOf<typeof deleteEndpointListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type DeleteEndpointListItemSchemaDecoded = DeleteEndpointListItemSchema;
export type DeleteEndpointListItemSchemaDecoded = RequiredKeepUndefined<
t.TypeOf<typeof deleteEndpointListItemSchema>
>;

View file

@ -10,6 +10,7 @@ import * as t from 'io-ts';
import { id, item_id, namespace_type } from '../common/schemas';
import { NamespaceType } from '../types';
import { RequiredKeepUndefined } from '../../types';
export const deleteExceptionListItemSchema = t.exact(
t.partial({
@ -19,11 +20,11 @@ export const deleteExceptionListItemSchema = t.exact(
})
);
export type DeleteExceptionListItemSchema = t.TypeOf<typeof deleteExceptionListItemSchema>;
export type DeleteExceptionListItemSchema = t.OutputOf<typeof deleteExceptionListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type DeleteExceptionListItemSchemaDecoded = Omit<
DeleteExceptionListItemSchema,
RequiredKeepUndefined<t.TypeOf<typeof deleteExceptionListItemSchema>>,
'namespace_type'
> & {
namespace_type: NamespaceType;

View file

@ -10,6 +10,7 @@ import * as t from 'io-ts';
import { id, list_id, namespace_type } from '../common/schemas';
import { NamespaceType } from '../types';
import { RequiredKeepUndefined } from '../../types';
export const deleteExceptionListSchema = t.exact(
t.partial({
@ -19,9 +20,12 @@ export const deleteExceptionListSchema = t.exact(
})
);
export type DeleteExceptionListSchema = t.TypeOf<typeof deleteExceptionListSchema>;
export type DeleteExceptionListSchema = t.OutputOf<typeof deleteExceptionListSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type DeleteExceptionListSchemaDecoded = Omit<DeleteExceptionListSchema, 'namespace_type'> & {
export type DeleteExceptionListSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof deleteExceptionListSchema>>,
'namespace_type'
> & {
namespace_type: NamespaceType;
};

View file

@ -9,7 +9,7 @@
import * as t from 'io-ts';
import { id, list_id, valueOrUndefined } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
export const deleteListItemSchema = t.intersection([
t.exact(
@ -20,5 +20,7 @@ export const deleteListItemSchema = t.intersection([
t.exact(t.partial({ id, list_id })),
]);
export type DeleteListItemSchemaPartial = Identity<t.TypeOf<typeof deleteListItemSchema>>;
export type DeleteListItemSchema = RequiredKeepUndefined<t.TypeOf<typeof deleteListItemSchema>>;
export type DeleteListItemSchema = t.OutputOf<typeof deleteListItemSchema>;
export type DeleteListItemSchemaDecoded = RequiredKeepUndefined<
t.TypeOf<typeof deleteListItemSchema>
>;

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import { id } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const deleteListSchema = t.exact(
t.type({
@ -16,5 +17,5 @@ export const deleteListSchema = t.exact(
})
);
export type DeleteListSchema = t.TypeOf<typeof deleteListSchema>;
export type DeleteListSchema = RequiredKeepUndefined<t.TypeOf<typeof deleteListSchema>>;
export type DeleteListSchemaEncoded = t.OutputOf<typeof deleteListSchema>;

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import { list_id } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const exportListItemQuerySchema = t.exact(
t.type({
@ -17,5 +18,7 @@ export const exportListItemQuerySchema = t.exact(
})
);
export type ExportListItemQuerySchema = t.TypeOf<typeof exportListItemQuerySchema>;
export type ExportListItemQuerySchema = RequiredKeepUndefined<
t.TypeOf<typeof exportListItemQuerySchema>
>;
export type ExportListItemQuerySchemaEncoded = t.OutputOf<typeof exportListItemQuerySchema>;

View file

@ -7,11 +7,11 @@
import { FILTER } from '../../constants.mock';
import {
FindEndpointListItemSchemaPartial,
FindEndpointListItemSchemaPartialDecoded,
FindEndpointListItemSchema,
FindEndpointListItemSchemaDecoded,
} from './find_endpoint_list_item_schema';
export const getFindEndpointListItemSchemaMock = (): FindEndpointListItemSchemaPartial => ({
export const getFindEndpointListItemSchemaMock = (): FindEndpointListItemSchema => ({
filter: FILTER,
page: '1',
per_page: '25',
@ -19,7 +19,7 @@ export const getFindEndpointListItemSchemaMock = (): FindEndpointListItemSchemaP
sort_order: undefined,
});
export const getFindEndpointListItemSchemaDecodedMock = (): FindEndpointListItemSchemaPartialDecoded => ({
export const getFindEndpointListItemSchemaDecodedMock = (): FindEndpointListItemSchemaDecoded => ({
filter: FILTER,
page: 1,
per_page: 25,

View file

@ -14,7 +14,7 @@ import {
getFindEndpointListItemSchemaMock,
} from './find_endpoint_list_item_schema.mock';
import {
FindEndpointListItemSchemaPartial,
FindEndpointListItemSchema,
findEndpointListItemSchema,
} from './find_endpoint_list_item_schema';
@ -29,7 +29,7 @@ describe('find_endpoint_list_item_schema', () => {
});
test('it should validate and empty object since everything is optional and has defaults', () => {
const payload: FindEndpointListItemSchemaPartial = {};
const payload: FindEndpointListItemSchema = {};
const decoded = findEndpointListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
@ -98,7 +98,7 @@ describe('find_endpoint_list_item_schema', () => {
});
test('it should not allow an extra key to be sent in', () => {
const payload: FindEndpointListItemSchemaPartial & {
const payload: FindEndpointListItemSchema & {
extraKey: string;
} = { ...getFindEndpointListItemSchemaMock(), extraKey: 'some new value' };
const decoded = findEndpointListItemSchema.decode(payload);

View file

@ -22,16 +22,9 @@ export const findEndpointListItemSchema = t.exact(
})
);
export type FindEndpointListItemSchemaPartial = t.OutputOf<typeof findEndpointListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type FindEndpointListItemSchemaPartialDecoded = t.TypeOf<typeof findEndpointListItemSchema>;
export type FindEndpointListItemSchema = t.OutputOf<typeof findEndpointListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type FindEndpointListItemSchemaDecoded = RequiredKeepUndefined<
FindEndpointListItemSchemaPartialDecoded
>;
export type FindEndpointListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof findEndpointListItemSchema>
>;

View file

@ -7,11 +7,11 @@
import { FILTER, LIST_ID, NAMESPACE_TYPE } from '../../constants.mock';
import {
FindExceptionListItemSchemaPartial,
FindExceptionListItemSchemaPartialDecoded,
FindExceptionListItemSchema,
FindExceptionListItemSchemaDecoded,
} from './find_exception_list_item_schema';
export const getFindExceptionListItemSchemaMock = (): FindExceptionListItemSchemaPartial => ({
export const getFindExceptionListItemSchemaMock = (): FindExceptionListItemSchema => ({
filter: FILTER,
list_id: LIST_ID,
namespace_type: NAMESPACE_TYPE,
@ -21,7 +21,7 @@ export const getFindExceptionListItemSchemaMock = (): FindExceptionListItemSchem
sort_order: undefined,
});
export const getFindExceptionListItemSchemaMultipleMock = (): FindExceptionListItemSchemaPartial => ({
export const getFindExceptionListItemSchemaMultipleMock = (): FindExceptionListItemSchema => ({
filter: 'name:Sofia Kovalevskaya,name:Hypatia,name:Sophie Germain',
list_id: 'list-1,list-2,list-3',
namespace_type: 'single,single,agnostic',
@ -31,7 +31,7 @@ export const getFindExceptionListItemSchemaMultipleMock = (): FindExceptionListI
sort_order: undefined,
});
export const getFindExceptionListItemSchemaDecodedMock = (): FindExceptionListItemSchemaPartialDecoded => ({
export const getFindExceptionListItemSchemaDecodedMock = (): FindExceptionListItemSchemaDecoded => ({
filter: [FILTER],
list_id: [LIST_ID],
namespace_type: [NAMESPACE_TYPE],
@ -41,7 +41,7 @@ export const getFindExceptionListItemSchemaDecodedMock = (): FindExceptionListIt
sort_order: undefined,
});
export const getFindExceptionListItemSchemaDecodedMultipleMock = (): FindExceptionListItemSchemaPartialDecoded => ({
export const getFindExceptionListItemSchemaDecodedMultipleMock = (): FindExceptionListItemSchemaDecoded => ({
filter: ['name:Sofia Kovalevskaya', 'name:Hypatia', 'name:Sophie Germain'],
list_id: ['list-1', 'list-2', 'list-3'],
namespace_type: ['single', 'single', 'agnostic'],

View file

@ -17,8 +17,8 @@ import {
getFindExceptionListItemSchemaMultipleMock,
} from './find_exception_list_item_schema.mock';
import {
FindExceptionListItemSchemaPartial,
FindExceptionListItemSchemaPartialDecoded,
FindExceptionListItemSchema,
FindExceptionListItemSchemaDecoded,
findExceptionListItemSchema,
} from './find_exception_list_item_schema';
@ -42,15 +42,19 @@ describe('find_list_item_schema', () => {
});
test('it should validate just a list_id where it decodes into an array for list_id and adds a namespace_type of "single" as an array', () => {
const payload: FindExceptionListItemSchemaPartial = { list_id: LIST_ID };
const payload: FindExceptionListItemSchema = { list_id: LIST_ID };
const decoded = findExceptionListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: FindExceptionListItemSchemaPartialDecoded = {
const expected: FindExceptionListItemSchemaDecoded = {
filter: [],
list_id: [LIST_ID],
namespace_type: ['single'],
page: undefined,
per_page: undefined,
sort_field: undefined,
sort_order: undefined,
};
expect(message.schema).toEqual(expected);
});
@ -86,7 +90,7 @@ describe('find_list_item_schema', () => {
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: FindExceptionListItemSchemaPartialDecoded = {
const expected: FindExceptionListItemSchemaDecoded = {
...getFindExceptionListItemSchemaDecodedMock(),
filter: [],
};
@ -118,7 +122,7 @@ describe('find_list_item_schema', () => {
});
test('it should not allow an extra key to be sent in', () => {
const payload: FindExceptionListItemSchemaPartial & {
const payload: FindExceptionListItemSchema & {
extraKey: string;
} = { ...getFindExceptionListItemSchemaMock(), extraKey: 'some new value' };
const decoded = findExceptionListItemSchema.decode(payload);

View file

@ -36,22 +36,13 @@ export const findExceptionListItemSchema = t.intersection([
),
]);
export type FindExceptionListItemSchemaPartial = t.OutputOf<typeof findExceptionListItemSchema>;
export type FindExceptionListItemSchema = t.OutputOf<typeof findExceptionListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type FindExceptionListItemSchemaPartialDecoded = Omit<
t.TypeOf<typeof findExceptionListItemSchema>,
export type FindExceptionListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof findExceptionListItemSchema>>,
'namespace_type' | 'filter'
> & {
filter: EmptyStringArrayDecoded;
namespace_type: DefaultNamespaceArrayTypeDecoded;
};
// 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

@ -7,11 +7,11 @@
import { FILTER, NAMESPACE_TYPE } from '../../constants.mock';
import {
FindExceptionListSchemaPartial,
FindExceptionListSchemaPartialDecoded,
FindExceptionListSchema,
FindExceptionListSchemaDecoded,
} from './find_exception_list_schema';
export const getFindExceptionListSchemaMock = (): FindExceptionListSchemaPartial => ({
export const getFindExceptionListSchemaMock = (): FindExceptionListSchema => ({
filter: FILTER,
namespace_type: NAMESPACE_TYPE,
page: '1',
@ -20,7 +20,7 @@ export const getFindExceptionListSchemaMock = (): FindExceptionListSchemaPartial
sort_order: undefined,
});
export const getFindExceptionListSchemaDecodedMock = (): FindExceptionListSchemaPartialDecoded => ({
export const getFindExceptionListSchemaDecodedMock = (): FindExceptionListSchemaDecoded => ({
filter: FILTER,
namespace_type: NAMESPACE_TYPE,
page: 1,

View file

@ -14,8 +14,8 @@ import {
getFindExceptionListSchemaMock,
} from './find_exception_list_schema.mock';
import {
FindExceptionListSchemaPartial,
FindExceptionListSchemaPartialDecoded,
FindExceptionListSchema,
FindExceptionListSchemaDecoded,
findExceptionListSchema,
} from './find_exception_list_schema';
@ -30,13 +30,18 @@ describe('find_exception_list_schema', () => {
});
test('it should validate and empty object since everything is optional and will respond only with namespace_type filled out to be "single"', () => {
const payload: FindExceptionListSchemaPartial = {};
const payload: FindExceptionListSchema = {};
const decoded = findExceptionListSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: FindExceptionListSchemaPartialDecoded = {
const expected: FindExceptionListSchemaDecoded = {
filter: undefined,
namespace_type: 'single',
page: undefined,
per_page: undefined,
sort_field: undefined,
sort_order: undefined,
};
expect(message.schema).toEqual(expected);
});
@ -102,7 +107,7 @@ describe('find_exception_list_schema', () => {
});
test('it should not allow an extra key to be sent in', () => {
const payload: FindExceptionListSchemaPartial & {
const payload: FindExceptionListSchema & {
extraKey: string;
} = { ...getFindExceptionListSchemaMock(), extraKey: 'some new value' };
const decoded = findExceptionListSchema.decode(payload);

View file

@ -24,21 +24,12 @@ export const findExceptionListSchema = t.exact(
})
);
export type FindExceptionListSchemaPartial = t.OutputOf<typeof findExceptionListSchema>;
export type FindExceptionListSchema = t.OutputOf<typeof findExceptionListSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type FindExceptionListSchemaPartialDecoded = Omit<
t.TypeOf<typeof findExceptionListSchema>,
export type FindExceptionListSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof findExceptionListSchema>>,
'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

@ -6,12 +6,9 @@
import { CURSOR, FILTER, LIST_ID } from '../../constants.mock';
import {
FindListItemSchemaPartial,
FindListItemSchemaPartialDecoded,
} from './find_list_item_schema';
import { FindListItemSchema, FindListItemSchemaDecoded } from './find_list_item_schema';
export const getFindListItemSchemaMock = (): FindListItemSchemaPartial => ({
export const getFindListItemSchemaMock = (): FindListItemSchema => ({
cursor: CURSOR,
filter: FILTER,
list_id: LIST_ID,
@ -21,7 +18,7 @@ export const getFindListItemSchemaMock = (): FindListItemSchemaPartial => ({
sort_order: undefined,
});
export const getFindListItemSchemaDecodedMock = (): FindListItemSchemaPartialDecoded => ({
export const getFindListItemSchemaDecodedMock = (): FindListItemSchemaDecoded => ({
cursor: CURSOR,
filter: FILTER,
list_id: LIST_ID,

View file

@ -15,8 +15,8 @@ import {
getFindListItemSchemaMock,
} from './find_list_item_schema.mock';
import {
FindListItemSchemaPartial,
FindListItemSchemaPartialDecoded,
FindListItemSchema,
FindListItemSchemaDecoded,
findListItemSchema,
} from './find_list_item_schema';
@ -31,12 +31,12 @@ describe('find_list_item_schema', () => {
});
test('it should validate just a list_id where it decodes into an array for list_id and adds a namespace_type of "single"', () => {
const payload: FindListItemSchemaPartial = { list_id: LIST_ID };
const payload: FindListItemSchema = { list_id: LIST_ID };
const decoded = findListItemSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: FindListItemSchemaPartialDecoded = {
const expected: FindListItemSchemaDecoded = {
cursor: undefined,
filter: undefined,
list_id: LIST_ID,
@ -97,7 +97,7 @@ describe('find_list_item_schema', () => {
});
test('it should not allow an extra key to be sent in', () => {
const payload: FindListItemSchemaPartial & {
const payload: FindListItemSchema & {
extraKey: string;
} = { ...getFindListItemSchemaMock(), extraKey: 'some new value' };
const decoded = findListItemSchema.decode(payload);

View file

@ -9,7 +9,7 @@
import * as t from 'io-ts';
import { cursor, filter, list_id, sort_field, sort_order } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
import { StringToPositiveNumber } from '../types/string_to_positive_number';
export const findListItemSchema = t.intersection([
@ -26,9 +26,7 @@ export const findListItemSchema = t.intersection([
),
]);
export type FindListItemSchemaPartial = Identity<t.OutputOf<typeof findListItemSchema>>;
export type FindListItemSchema = t.OutputOf<typeof findListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type FindListItemSchemaPartialDecoded = RequiredKeepUndefined<
t.TypeOf<typeof findListItemSchema>
>;
export type FindListItemSchemaDecoded = RequiredKeepUndefined<t.TypeOf<typeof findListItemSchema>>;

View file

@ -17,6 +17,7 @@ export const getFindListSchemaMock = (): FindListSchemaEncoded => ({
});
export const getFindListSchemaDecodedMock = (): FindListSchema => ({
cursor: undefined,
filter: FILTER,
page: 1,
per_page: 25,

View file

@ -10,7 +10,7 @@ import { pipe } from 'fp-ts/lib/pipeable';
import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps';
import { getFindListSchemaDecodedMock, getFindListSchemaMock } from './find_list_schema.mock';
import { FindListSchema, FindListSchemaEncoded, findListSchema } from './find_list_schema';
import { FindListSchemaEncoded, findListSchema } from './find_list_schema';
describe('find_list_schema', () => {
test('it should validate a typical find item request', () => {
@ -28,8 +28,7 @@ describe('find_list_schema', () => {
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: FindListSchema = {};
expect(message.schema).toEqual(expected);
expect(message.schema).toEqual(payload);
});
test('it should validate with page missing', () => {

View file

@ -10,6 +10,7 @@ import * as t from 'io-ts';
import { cursor, filter, sort_field, sort_order } from '../common/schemas';
import { StringToPositiveNumber } from '../types/string_to_positive_number';
import { RequiredKeepUndefined } from '../../types';
export const findListSchema = t.exact(
t.partial({
@ -22,5 +23,5 @@ export const findListSchema = t.exact(
})
);
export type FindListSchema = t.TypeOf<typeof findListSchema>;
export type FindListSchema = RequiredKeepUndefined<t.TypeOf<typeof findListSchema>>;
export type FindListSchemaEncoded = t.OutputOf<typeof findListSchema>;

View file

@ -8,14 +8,14 @@
import * as t from 'io-ts';
import { RequiredKeepUndefined } from '../../types';
import { deserializer, list_id, serializer, type } from '../common/schemas';
import { Identity } from '../../types';
export const importListItemQuerySchema = t.exact(
t.partial({ deserializer, list_id, serializer, type })
);
export type ImportListItemQuerySchemaPartial = Identity<t.TypeOf<typeof importListItemQuerySchema>>;
export type ImportListItemQuerySchema = t.TypeOf<typeof importListItemQuerySchema>;
export type ImportListItemQuerySchema = RequiredKeepUndefined<
t.TypeOf<typeof importListItemQuerySchema>
>;
export type ImportListItemQuerySchemaEncoded = t.OutputOf<typeof importListItemQuerySchema>;

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import { file } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const importListItemSchema = t.exact(
t.type({
@ -16,5 +17,5 @@ export const importListItemSchema = t.exact(
})
);
export type ImportListItemSchema = t.TypeOf<typeof importListItemSchema>;
export type ImportListItemSchema = RequiredKeepUndefined<t.TypeOf<typeof importListItemSchema>>;
export type ImportListItemSchemaEncoded = t.OutputOf<typeof importListItemSchema>;

View file

@ -8,8 +8,8 @@
import * as t from 'io-ts';
import { id, meta, value } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { _version, id, meta, value } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const patchListItemSchema = t.intersection([
t.exact(
@ -17,8 +17,10 @@ export const patchListItemSchema = t.intersection([
id,
})
),
t.exact(t.partial({ meta, value })),
t.exact(t.partial({ _version, meta, value })),
]);
export type PatchListItemSchemaPartial = Identity<t.TypeOf<typeof patchListItemSchema>>;
export type PatchListItemSchema = RequiredKeepUndefined<t.TypeOf<typeof patchListItemSchema>>;
export type PatchListItemSchema = t.OutputOf<typeof patchListItemSchema>;
export type PatchListItemSchemaDecoded = RequiredKeepUndefined<
t.TypeOf<typeof patchListItemSchema>
>;

View file

@ -8,8 +8,8 @@
import * as t from 'io-ts';
import { description, id, meta, name } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { _version, description, id, meta, name } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const patchListSchema = t.intersection([
t.exact(
@ -17,8 +17,8 @@ export const patchListSchema = t.intersection([
id,
})
),
t.exact(t.partial({ description, meta, name })),
t.exact(t.partial({ _version, description, meta, name })),
]);
export type PatchListSchemaPartial = Identity<t.TypeOf<typeof patchListSchema>>;
export type PatchListSchema = RequiredKeepUndefined<Identity<t.TypeOf<typeof patchListSchema>>>;
export type PatchListSchema = t.OutputOf<typeof patchListSchema>;
export type PatchListSchemaDecoded = RequiredKeepUndefined<t.TypeOf<typeof patchListSchema>>;

View file

@ -18,14 +18,9 @@ export const readEndpointListItemSchema = t.exact(
})
);
export type ReadEndpointListItemSchemaPartial = t.TypeOf<typeof readEndpointListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type ReadEndpointListItemSchemaPartialDecoded = ReadEndpointListItemSchemaPartial;
export type ReadEndpointListItemSchema = t.OutputOf<typeof readEndpointListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type ReadEndpointListItemSchemaDecoded = RequiredKeepUndefined<
ReadEndpointListItemSchemaPartialDecoded
t.TypeOf<typeof readEndpointListItemSchema>
>;
export type ReadEndpointListItemSchema = RequiredKeepUndefined<ReadEndpointListItemSchemaPartial>;

View file

@ -20,19 +20,12 @@ export const readExceptionListItemSchema = t.exact(
})
);
export type ReadExceptionListItemSchemaPartial = t.TypeOf<typeof readExceptionListItemSchema>;
export type ReadExceptionListItemSchema = t.OutputOf<typeof readExceptionListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type ReadExceptionListItemSchemaPartialDecoded = Omit<
ReadExceptionListItemSchemaPartial,
export type ReadExceptionListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof readExceptionListItemSchema>>,
'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

@ -20,19 +20,12 @@ export const readExceptionListSchema = t.exact(
})
);
export type ReadExceptionListSchemaPartial = t.TypeOf<typeof readExceptionListSchema>;
export type ReadExceptionListSchema = t.OutputOf<typeof readExceptionListSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type ReadExceptionListSchemaPartialDecoded = Omit<
ReadExceptionListSchemaPartial,
export type ReadExceptionListSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof readExceptionListSchema>>,
'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,9 +9,9 @@
import * as t from 'io-ts';
import { id, list_id, value } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
export const readListItemSchema = t.exact(t.partial({ id, list_id, value }));
export type ReadListItemSchemaPartial = Identity<t.TypeOf<typeof readListItemSchema>>;
export type ReadListItemSchema = RequiredKeepUndefined<t.TypeOf<typeof readListItemSchema>>;
export type ReadListItemSchema = t.OutputOf<typeof readListItemSchema>;
export type ReadListItemSchemaDecoded = RequiredKeepUndefined<t.TypeOf<typeof readListItemSchema>>;

View file

@ -16,4 +16,4 @@ export const readListSchema = t.exact(
})
);
export type ReadListSchema = t.TypeOf<typeof readListSchema>;
export type ReadListSchema = t.OutputOf<typeof readListSchema>;

View file

@ -21,6 +21,7 @@ import { UpdateEndpointListItemSchema } from './update_endpoint_list_item_schema
export const getUpdateEndpointListItemSchemaMock = (): UpdateEndpointListItemSchema => ({
_tags: _TAGS,
_version: undefined,
comments: COMMENTS,
description: DESCRIPTION,
entries: ENTRIES,

View file

@ -12,6 +12,7 @@ import {
Tags,
_Tags,
_tags,
_version,
description,
exceptionListItemType,
id,
@ -19,7 +20,7 @@ import {
name,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
import {
DefaultEntryArray,
DefaultUpdateCommentsArray,
@ -38,6 +39,7 @@ export const updateEndpointListItemSchema = t.intersection([
t.exact(
t.partial({
_tags, // defaults to empty array if not set during decode
_version, // defaults to undefined if not set during decode
comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode
entries: DefaultEntryArray, // defaults to empty array if not set during decode
id, // defaults to undefined if not set during decode
@ -48,19 +50,15 @@ export const updateEndpointListItemSchema = t.intersection([
),
]);
export type UpdateEndpointListItemSchemaPartial = Identity<
t.TypeOf<typeof updateEndpointListItemSchema>
>;
export type UpdateEndpointListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof updateEndpointListItemSchema>
>;
export type UpdateEndpointListItemSchema = t.OutputOf<typeof updateEndpointListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type UpdateEndpointListItemSchemaDecoded = Identity<
Omit<UpdateEndpointListItemSchema, '_tags' | 'tags' | 'entries' | 'comments'> & {
_tags: _Tags;
comments: UpdateCommentsArray;
tags: Tags;
entries: EntriesArray;
}
>;
export type UpdateEndpointListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof updateEndpointListItemSchema>>,
'_tags' | 'tags' | 'entries' | 'comments'
> & {
_tags: _Tags;
comments: UpdateCommentsArray;
tags: Tags;
entries: EntriesArray;
};

View file

@ -22,6 +22,7 @@ import { UpdateExceptionListItemSchema } from './update_exception_list_item_sche
export const getUpdateExceptionListItemSchemaMock = (): UpdateExceptionListItemSchema => ({
_tags: _TAGS,
_version: undefined,
comments: COMMENTS,
description: DESCRIPTION,
entries: ENTRIES,

View file

@ -12,6 +12,7 @@ import {
Tags,
_Tags,
_tags,
_version,
description,
exceptionListItemType,
id,
@ -20,7 +21,7 @@ import {
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
import {
DefaultEntryArray,
DefaultUpdateCommentsArray,
@ -40,6 +41,7 @@ export const updateExceptionListItemSchema = t.intersection([
t.exact(
t.partial({
_tags, // defaults to empty array if not set during decode
_version, // defaults to undefined if not set during decode
comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode
entries: DefaultEntryArray, // defaults to empty array if not set during decode
id, // defaults to undefined if not set during decode
@ -51,23 +53,16 @@ export const updateExceptionListItemSchema = t.intersection([
),
]);
export type UpdateExceptionListItemSchemaPartial = Identity<
t.TypeOf<typeof updateExceptionListItemSchema>
>;
export type UpdateExceptionListItemSchema = RequiredKeepUndefined<
t.TypeOf<typeof updateExceptionListItemSchema>
>;
export type UpdateExceptionListItemSchema = t.OutputOf<typeof updateExceptionListItemSchema>;
// This type is used after a decode since some things are defaults after a decode.
export type UpdateExceptionListItemSchemaDecoded = Identity<
Omit<
UpdateExceptionListItemSchema,
'_tags' | 'tags' | 'entries' | 'namespace_type' | 'comments'
> & {
_tags: _Tags;
comments: UpdateCommentsArray;
tags: Tags;
entries: EntriesArray;
namespace_type: NamespaceType;
}
>;
export type UpdateExceptionListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof updateExceptionListItemSchema>>,
'_tags' | 'tags' | 'entries' | 'namespace_type' | 'comments'
> & {
_tags: _Tags;
comments: UpdateCommentsArray;
tags: Tags;
entries: EntriesArray;
namespace_type: NamespaceType;
};

View file

@ -10,6 +10,7 @@ import { UpdateExceptionListSchema } from './update_exception_list_schema';
export const getUpdateExceptionListSchemaMock = (): UpdateExceptionListSchema => ({
_tags: _TAGS,
_version: undefined,
description: DESCRIPTION,
id: ID,
list_id: LIST_ID,

View file

@ -12,14 +12,17 @@ import {
Tags,
_Tags,
_tags,
_version,
description,
exceptionListType,
id,
list_id,
meta,
name,
namespace_type,
tags,
} from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { RequiredKeepUndefined } from '../../types';
import { NamespaceType } from '../types';
export const updateExceptionListSchema = t.intersection([
@ -33,8 +36,9 @@ export const updateExceptionListSchema = t.intersection([
t.exact(
t.partial({
_tags, // defaults to empty array if not set during decode
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
_version, // defaults to undefined if not set during decode
id, // defaults to undefined if not set during decode
list_id, // 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
@ -42,16 +46,14 @@ export const updateExceptionListSchema = t.intersection([
),
]);
export type UpdateExceptionListSchemaPartial = Identity<t.TypeOf<typeof updateExceptionListSchema>>;
export type UpdateExceptionListSchema = RequiredKeepUndefined<
t.TypeOf<typeof updateExceptionListSchema>
>;
export type UpdateExceptionListSchema = t.OutputOf<typeof updateExceptionListSchema>;
// This type is used after a decode since the arrays turn into defaults of empty arrays.
export type UpdateExceptionListSchemaDecoded = Identity<
Omit<UpdateExceptionListSchema, '_tags | tags | namespace_type'> & {
_tags: _Tags;
tags: Tags;
namespace_type: NamespaceType;
}
>;
export type UpdateExceptionListSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof updateExceptionListSchema>>,
'_tags | tags | namespace_type'
> & {
_tags: _Tags;
tags: Tags;
namespace_type: NamespaceType;
};

View file

@ -8,8 +8,8 @@
import * as t from 'io-ts';
import { id, meta, value } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { _version, id, meta, value } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const updateListItemSchema = t.intersection([
t.exact(
@ -20,10 +20,13 @@ export const updateListItemSchema = t.intersection([
),
t.exact(
t.partial({
_version, // defaults to undefined if not set during decode
meta, // defaults to undefined if not set during decode
})
),
]);
export type UpdateListItemSchemaPartial = Identity<t.TypeOf<typeof updateListItemSchema>>;
export type UpdateListItemSchema = RequiredKeepUndefined<t.TypeOf<typeof updateListItemSchema>>;
export type UpdateListItemSchema = t.OutputOf<typeof updateListItemSchema>;
export type UpdateListItemSchemaDecoded = RequiredKeepUndefined<
t.TypeOf<typeof updateListItemSchema>
>;

View file

@ -8,8 +8,8 @@
import * as t from 'io-ts';
import { description, id, meta, name } from '../common/schemas';
import { Identity, RequiredKeepUndefined } from '../../types';
import { _version, description, id, meta, name } from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
export const updateListSchema = t.intersection([
t.exact(
@ -21,10 +21,11 @@ export const updateListSchema = t.intersection([
),
t.exact(
t.partial({
_version, // defaults to undefined if not set during decode
meta, // defaults to undefined if not set during decode
})
),
]);
export type UpdateListSchemaPartial = Identity<t.TypeOf<typeof updateListSchema>>;
export type UpdateListSchema = RequiredKeepUndefined<t.TypeOf<typeof updateListSchema>>;
export type UpdateListSchema = t.OutputOf<typeof updateListSchema>;
export type UpdateListSchemaDecoded = RequiredKeepUndefined<t.TypeOf<typeof updateListSchema>>;

View file

@ -41,7 +41,7 @@ describe('create_endpoint_list_schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'invalid keys "_tags,["endpoint","process","malware","os:linux"],created_at,created_by,description,id,meta,{},name,namespace_type,tags,["user added string for a tag","malware"],tie_breaker_id,type,updated_at,updated_by"',
'invalid keys "_tags,["endpoint","process","malware","os:linux"],_version,created_at,created_by,description,id,meta,{},name,namespace_type,tags,["user added string for a tag","malware"],tie_breaker_id,type,updated_at,updated_by"',
]);
expect(message.schema).toEqual({});
});

View file

@ -3,26 +3,38 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ENTRIES } from '../../constants.mock';
import {
COMMENTS,
DATE_NOW,
DESCRIPTION,
ENTRIES,
ITEM_TYPE,
META,
NAME,
NAMESPACE_TYPE,
TIE_BREAKER,
USER,
} from '../../constants.mock';
import { ExceptionListItemSchema } from './exception_list_item_schema';
export const getExceptionListItemSchemaMock = (): ExceptionListItemSchema => ({
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
comments: [],
created_at: '2020-04-23T00:19:13.289Z',
created_by: 'user_name',
description: 'This is a sample endpoint type exception',
_version: undefined,
comments: COMMENTS,
created_at: DATE_NOW,
created_by: USER,
description: DESCRIPTION,
entries: ENTRIES,
id: '1',
item_id: 'endpoint_list_item',
list_id: 'endpoint_list_id',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
meta: META,
name: NAME,
namespace_type: NAMESPACE_TYPE,
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',
tie_breaker_id: TIE_BREAKER,
type: ITEM_TYPE,
updated_at: DATE_NOW,
updated_by: USER,
});

View file

@ -10,6 +10,7 @@ import * as t from 'io-ts';
import {
_tags,
_versionOrUndefined,
created_at,
created_by,
description,
@ -30,6 +31,7 @@ import { commentsArray, entriesArray } from '../types';
export const exceptionListItemSchema = t.exact(
t.type({
_tags,
_version: _versionOrUndefined,
comments: commentsArray,
created_at,
created_by,

View file

@ -4,23 +4,32 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
DATE_NOW,
DESCRIPTION,
ENDPOINT_TYPE,
META,
TIE_BREAKER,
USER,
_VERSION,
} from '../../constants.mock';
import { ENDPOINT_LIST_ID } from '../..';
import { ExceptionListSchema } from './exception_list_schema';
export const getExceptionListSchemaMock = (): ExceptionListSchema => ({
_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',
_version: _VERSION,
created_at: DATE_NOW,
created_by: USER,
description: DESCRIPTION,
id: '1',
list_id: ENDPOINT_LIST_ID,
meta: {},
meta: META,
name: 'Sample Endpoint Exception List',
namespace_type: 'agnostic',
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',
tie_breaker_id: TIE_BREAKER,
type: ENDPOINT_TYPE,
updated_at: DATE_NOW,
updated_by: 'user_name',
});

View file

@ -10,6 +10,7 @@ import * as t from 'io-ts';
import {
_tags,
_versionOrUndefined,
created_at,
created_by,
description,
@ -28,6 +29,7 @@ import {
export const exceptionListSchema = t.exact(
t.type({
_tags,
_version: _versionOrUndefined,
created_at,
created_by,
description,

View file

@ -17,6 +17,7 @@ import {
} from '../../../common/constants.mock';
export const getListItemResponseMock = (): ListItemSchema => ({
_version: undefined,
created_at: DATE_NOW,
created_by: USER,
deserializer: undefined,

View file

@ -9,6 +9,7 @@ import * as t from 'io-ts';
/* eslint-disable @typescript-eslint/camelcase */
import {
_versionOrUndefined,
created_at,
created_by,
deserializerOrUndefined,
@ -25,6 +26,7 @@ import {
export const listItemSchema = t.exact(
t.type({
_version: _versionOrUndefined,
created_at,
created_by,
deserializer: deserializerOrUndefined,

View file

@ -17,6 +17,7 @@ import {
} from '../../../common/constants.mock';
export const getListResponseMock = (): ListSchema => ({
_version: undefined,
created_at: DATE_NOW,
created_by: USER,
description: DESCRIPTION,

View file

@ -9,6 +9,7 @@
import * as t from 'io-ts';
import {
_versionOrUndefined,
created_at,
created_by,
description,
@ -25,6 +26,7 @@ import {
export const listSchema = t.exact(
t.type({
_version: _versionOrUndefined,
created_at,
created_by,
description,

View file

@ -9,17 +9,11 @@ import { Either } from 'fp-ts/lib/Either';
import { CommentsArray, comments } from './comments';
export type DefaultCommentsArrayC = t.Type<CommentsArray, CommentsArray, unknown>;
/**
* Types the DefaultCommentsArray as:
* - If null or undefined, then a default array of type entry will be set
*/
export const DefaultCommentsArray: DefaultCommentsArrayC = new t.Type<
CommentsArray,
CommentsArray,
unknown
>(
export const DefaultCommentsArray = new t.Type<CommentsArray, CommentsArray, unknown>(
'DefaultCommentsArray',
t.array(comments).is,
(input): Either<t.Errors, CommentsArray> =>

View file

@ -9,13 +9,11 @@ import { Either } from 'fp-ts/lib/Either';
import { CreateCommentsArray, createComments } from './create_comments';
export type DefaultCreateCommentsArrayC = t.Type<CreateCommentsArray, CreateCommentsArray, unknown>;
/**
* Types the DefaultCreateComments as:
* - If null or undefined, then a default array of type entry will be set
*/
export const DefaultCreateCommentsArray: DefaultCreateCommentsArrayC = new t.Type<
export const DefaultCreateCommentsArray = new t.Type<
CreateCommentsArray,
CreateCommentsArray,
unknown

View file

@ -9,17 +9,11 @@ import { Either } from 'fp-ts/lib/Either';
import { EntriesArray, entriesArray } from './entries';
export type DefaultEntriesArrayC = t.Type<EntriesArray, EntriesArray, unknown>;
/**
* Types the DefaultEntriesArray as:
* - If null or undefined, then a default array of type entry will be set
*/
export const DefaultEntryArray: DefaultEntriesArrayC = new t.Type<
EntriesArray,
EntriesArray,
unknown
>(
export const DefaultEntryArray = new t.Type<EntriesArray, EntriesArray, unknown>(
'DefaultEntryArray',
entriesArray.is,
(input): Either<t.Errors, EntriesArray> =>

View file

@ -14,12 +14,10 @@ export type NamespaceType = t.TypeOf<typeof namespaceType>;
* Types the DefaultNamespace as:
* - If null or undefined, then a default string/enumeration of "single" will be used.
*/
export const DefaultNamespace = new t.Type<NamespaceType, NamespaceType, unknown>(
export const DefaultNamespace = new t.Type<NamespaceType, NamespaceType | undefined, unknown>(
'DefaultNamespace',
namespaceType.is,
(input, context): Either<t.Errors, NamespaceType> =>
input == null ? t.success('single') : namespaceType.validate(input, context),
t.identity
);
export type DefaultNamespaceC = typeof DefaultNamespace;

View file

@ -9,11 +9,11 @@ import { left } from 'fp-ts/lib/Either';
import { foldLeftRight, getPaths } from '../../siem_common_deps';
import { DefaultNamespaceArray, DefaultNamespaceArrayTypeEncoded } from './default_namespace_array';
import { DefaultNamespaceArray, DefaultNamespaceArrayType } from './default_namespace_array';
describe('default_namespace_array', () => {
test('it should validate "null" single item as an array with a "single" value', () => {
const payload: DefaultNamespaceArrayTypeEncoded = null;
const payload: DefaultNamespaceArrayType = null;
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -33,7 +33,7 @@ describe('default_namespace_array', () => {
});
test('it should validate "undefined" item as an array with a "single" value', () => {
const payload: DefaultNamespaceArrayTypeEncoded = undefined;
const payload: DefaultNamespaceArrayType = undefined;
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -42,7 +42,7 @@ describe('default_namespace_array', () => {
});
test('it should validate "single" as an array of a "single" value', () => {
const payload: DefaultNamespaceArrayTypeEncoded = 'single';
const payload: DefaultNamespaceArrayType = 'single';
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -51,7 +51,7 @@ describe('default_namespace_array', () => {
});
test('it should validate "agnostic" as an array of a "agnostic" value', () => {
const payload: DefaultNamespaceArrayTypeEncoded = 'agnostic';
const payload: DefaultNamespaceArrayType = 'agnostic';
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -60,7 +60,7 @@ describe('default_namespace_array', () => {
});
test('it should validate "single,agnostic" as an array of 2 values of ["single", "agnostic"] values', () => {
const payload: DefaultNamespaceArrayTypeEncoded = 'agnostic,single';
const payload: DefaultNamespaceArrayType = 'agnostic,single';
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -69,7 +69,7 @@ describe('default_namespace_array', () => {
});
test('it should validate 3 elements of "single,agnostic,single" as an array of 3 values of ["single", "agnostic", "single"] values', () => {
const payload: DefaultNamespaceArrayTypeEncoded = 'single,agnostic,single';
const payload: DefaultNamespaceArrayType = 'single,agnostic,single';
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -78,7 +78,7 @@ describe('default_namespace_array', () => {
});
test('it should validate 3 elements of "single,agnostic, single" as an array of 3 values of ["single", "agnostic", "single"] values when there are spaces', () => {
const payload: DefaultNamespaceArrayTypeEncoded = ' single, agnostic, single ';
const payload: DefaultNamespaceArrayType = ' single, agnostic, single ';
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -87,7 +87,7 @@ describe('default_namespace_array', () => {
});
test('it should not validate 3 elements of "single,agnostic,junk" since the 3rd value is junk', () => {
const payload: DefaultNamespaceArrayTypeEncoded = 'single,agnostic,junk';
const payload: DefaultNamespaceArrayType = 'single,agnostic,junk';
const decoded = DefaultNamespaceArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

View file

@ -39,7 +39,5 @@ export const DefaultNamespaceArray = new t.Type<
String
);
export type DefaultNamespaceC = typeof DefaultNamespaceArray;
export type DefaultNamespaceArrayTypeEncoded = t.OutputOf<typeof DefaultNamespaceArray>;
export type DefaultNamespaceArrayType = t.OutputOf<typeof DefaultNamespaceArray>;
export type DefaultNamespaceArrayTypeDecoded = t.TypeOf<typeof DefaultNamespaceArray>;

View file

@ -9,13 +9,11 @@ import { Either } from 'fp-ts/lib/Either';
import { UpdateCommentsArray, updateCommentsArray } from './update_comments';
export type DefaultUpdateCommentsArrayC = t.Type<UpdateCommentsArray, UpdateCommentsArray, unknown>;
/**
* Types the DefaultCommentsUpdate as:
* - If null or undefined, then a default array of type entry will be set
*/
export const DefaultUpdateCommentsArray: DefaultUpdateCommentsArrayC = new t.Type<
export const DefaultUpdateCommentsArray = new t.Type<
UpdateCommentsArray,
UpdateCommentsArray,
unknown

View file

@ -39,7 +39,5 @@ export const EmptyStringArray = new t.Type<string[], string | undefined | null,
String
);
export type EmptyStringArrayC = typeof EmptyStringArray;
export type EmptyStringArrayEncoded = t.OutputOf<typeof EmptyStringArray>;
export type EmptyStringArrayDecoded = t.TypeOf<typeof EmptyStringArray>;

View file

@ -9,11 +9,11 @@ import { left } from 'fp-ts/lib/Either';
import { foldLeftRight, getPaths } from '../../siem_common_deps';
import { NonEmptyStringArray, NonEmptyStringArrayEncoded } from './non_empty_string_array';
import { NonEmptyStringArray } from './non_empty_string_array';
describe('non_empty_string_array', () => {
test('it should NOT validate "null"', () => {
const payload: NonEmptyStringArrayEncoded | null = null;
const payload: NonEmptyStringArray | null = null;
const decoded = NonEmptyStringArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -24,7 +24,7 @@ describe('non_empty_string_array', () => {
});
test('it should NOT validate "undefined"', () => {
const payload: NonEmptyStringArrayEncoded | undefined = undefined;
const payload: NonEmptyStringArray | undefined = undefined;
const decoded = NonEmptyStringArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -35,7 +35,7 @@ describe('non_empty_string_array', () => {
});
test('it should NOT validate a single value of an empty string ""', () => {
const payload: NonEmptyStringArrayEncoded = '';
const payload: NonEmptyStringArray = '';
const decoded = NonEmptyStringArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -46,7 +46,7 @@ describe('non_empty_string_array', () => {
});
test('it should validate a single value of "a" into an array of size 1 of ["a"]', () => {
const payload: NonEmptyStringArrayEncoded = 'a';
const payload: NonEmptyStringArray = 'a';
const decoded = NonEmptyStringArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -55,7 +55,7 @@ describe('non_empty_string_array', () => {
});
test('it should validate 2 values of "a,b" into an array of size 2 of ["a", "b"]', () => {
const payload: NonEmptyStringArrayEncoded = 'a,b';
const payload: NonEmptyStringArray = 'a,b';
const decoded = NonEmptyStringArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -64,7 +64,7 @@ describe('non_empty_string_array', () => {
});
test('it should validate 3 values of "a,b,c" into an array of size 3 of ["a", "b", "c"]', () => {
const payload: NonEmptyStringArrayEncoded = 'a,b,c';
const payload: NonEmptyStringArray = 'a,b,c';
const decoded = NonEmptyStringArray.decode(payload);
const message = pipe(decoded, foldLeftRight);
@ -84,7 +84,7 @@ describe('non_empty_string_array', () => {
});
test('it should validate 3 values of " a, b, c " into an array of size 3 of ["a", "b", "c"] even though they have spaces', () => {
const payload: NonEmptyStringArrayEncoded = ' a, b, c ';
const payload: NonEmptyStringArray = ' a, b, c ';
const decoded = NonEmptyStringArray.decode(payload);
const message = pipe(decoded, foldLeftRight);

View file

@ -35,7 +35,5 @@ export const NonEmptyStringArray = new t.Type<string[], string, unknown>(
String
);
export type NonEmptyStringArrayC = typeof NonEmptyStringArray;
export type NonEmptyStringArrayEncoded = t.OutputOf<typeof NonEmptyStringArray>;
export type NonEmptyStringArray = t.OutputOf<typeof NonEmptyStringArray>;
export type NonEmptyStringArrayDecoded = t.TypeOf<typeof NonEmptyStringArray>;

View file

@ -20,11 +20,3 @@ export type RequiredKeepUndefined<T> = { [K in keyof T]-?: [T[K]] } extends infe
? { [K in keyof U]: U[K][0] }
: never
: never;
/**
* This is just a helper to cleanup nasty intersections and unions to make them
* readable from io.ts, it's an identity that strips away the uglyness of them.
*/
export type Identity<T> = {
[P in keyof T]: T[P];
};

View file

@ -10,7 +10,7 @@ import { LIST_ITEM_URL } from '../../common/constants';
import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps';
import { validate } from '../../common/siem_common_deps';
import {
FindListItemSchemaPartialDecoded,
FindListItemSchemaDecoded,
findListItemSchema,
foundListItemSchema,
} from '../../common/schemas';
@ -26,7 +26,7 @@ export const findListItemRoute = (router: IRouter): void => {
},
path: `${LIST_ITEM_URL}/_find`,
validate: {
query: buildRouteValidation<typeof findListItemSchema, FindListItemSchemaPartialDecoded>(
query: buildRouteValidation<typeof findListItemSchema, FindListItemSchemaDecoded>(
findListItemSchema
),
},

View file

@ -27,9 +27,10 @@ export const patchListItemRoute = (router: IRouter): void => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { value, id, meta } = request.body;
const { value, id, meta, _version } = request.body;
const lists = getListClient(context);
const listItem = await lists.updateListItem({
_version,
id,
meta,
value,

View file

@ -27,9 +27,9 @@ export const patchListRoute = (router: IRouter): void => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { name, description, id, meta } = request.body;
const { name, description, id, meta, _version } = request.body;
const lists = getListClient(context);
const list = await lists.updateList({ description, id, meta, name });
const list = await lists.updateList({ _version, description, id, meta, name });
if (list == null) {
return siemResponse.error({
body: `list id: "${id}" found found`,

View file

@ -41,6 +41,7 @@ export const updateEndpointListItemRoute = (router: IRouter): void => {
meta,
type,
_tags,
_version,
comments,
entries,
item_id: itemId,
@ -49,6 +50,7 @@ export const updateEndpointListItemRoute = (router: IRouter): void => {
const exceptionLists = getExceptionListClient(context);
const exceptionListItem = await exceptionLists.updateEndpointListItem({
_tags,
_version,
comments,
description,
entries,

View file

@ -41,6 +41,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => {
meta,
type,
_tags,
_version,
comments,
entries,
item_id: itemId,
@ -50,6 +51,7 @@ export const updateExceptionListItemRoute = (router: IRouter): void => {
const exceptionLists = getExceptionListClient(context);
const exceptionListItem = await exceptionLists.updateExceptionListItem({
_tags,
_version,
comments,
description,
entries,

View file

@ -36,6 +36,7 @@ export const updateExceptionListRoute = (router: IRouter): void => {
try {
const {
_tags,
_version,
tags,
name,
description,
@ -54,6 +55,7 @@ export const updateExceptionListRoute = (router: IRouter): void => {
} else {
const list = await exceptionLists.updateExceptionList({
_tags,
_version,
description,
id,
listId,

View file

@ -27,9 +27,10 @@ export const updateListItemRoute = (router: IRouter): void => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { value, id, meta } = request.body;
const { value, id, meta, _version } = request.body;
const lists = getListClient(context);
const listItem = await lists.updateListItem({
_version,
id,
meta,
value,

View file

@ -27,9 +27,9 @@ export const updateListRoute = (router: IRouter): void => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
try {
const { name, description, id, meta } = request.body;
const { name, description, id, meta, _version } = request.body;
const lists = getListClient(context);
const list = await lists.updateList({ description, id, meta, name });
const list = await lists.updateList({ _version, description, id, meta, name });
if (list == null) {
return siemResponse.error({
body: `list id: "${id}" found found`,

View file

@ -1,5 +1,5 @@
{
"list_id": "endpoint_list",
"list_id": "simple_list",
"_tags": ["endpoint", "process", "malware", "os:linux"],
"tags": ["user added string for a tag", "malware"],
"type": "endpoint",

View file

@ -131,6 +131,7 @@ export class ExceptionListClient {
*/
public updateEndpointListItem = async ({
_tags,
_version,
comments,
description,
entries,
@ -145,6 +146,7 @@ export class ExceptionListClient {
await this.createEndpointList();
return updateExceptionListItem({
_tags,
_version,
comments,
description,
entries,
@ -198,6 +200,7 @@ export class ExceptionListClient {
public updateExceptionList = async ({
_tags,
_version,
id,
description,
listId,
@ -210,6 +213,7 @@ export class ExceptionListClient {
const { savedObjectsClient, user } = this;
return updateExceptionList({
_tags,
_version,
description,
id,
listId,
@ -270,6 +274,7 @@ export class ExceptionListClient {
public updateExceptionListItem = async ({
_tags,
_version,
comments,
description,
entries,
@ -284,6 +289,7 @@ export class ExceptionListClient {
const { savedObjectsClient, user } = this;
return updateExceptionListItem({
_tags,
_version,
comments,
description,
entries,

View file

@ -38,6 +38,7 @@ import {
UpdateCommentsArray,
_Tags,
_TagsOrUndefined,
_VersionOrUndefined,
} from '../../../common/schemas';
export interface ConstructorOptions {
@ -64,6 +65,7 @@ export interface CreateExceptionListOptions {
export interface UpdateExceptionListOptions {
_tags: _TagsOrUndefined;
_version: _VersionOrUndefined;
id: IdOrUndefined;
listId: ListIdOrUndefined;
namespaceType: NamespaceType;
@ -130,6 +132,7 @@ export interface CreateEndpointListItemOptions {
export interface UpdateExceptionListItemOptions {
_tags: _TagsOrUndefined;
_version: _VersionOrUndefined;
comments: UpdateCommentsArray;
entries: EntriesArrayOrUndefined;
id: IdOrUndefined;
@ -144,6 +147,7 @@ export interface UpdateExceptionListItemOptions {
export interface UpdateEndpointListItemOptions {
_tags: _TagsOrUndefined;
_version: _VersionOrUndefined;
comments: UpdateCommentsArray;
entries: EntriesArrayOrUndefined;
id: IdOrUndefined;

View file

@ -18,6 +18,7 @@ import {
NamespaceType,
TagsOrUndefined,
_TagsOrUndefined,
_VersionOrUndefined,
} from '../../../common/schemas';
import { getSavedObjectType, transformSavedObjectUpdateToExceptionList } from './utils';
@ -26,6 +27,7 @@ import { getExceptionList } from './get_exception_list';
interface UpdateExceptionListOptions {
id: IdOrUndefined;
_tags: _TagsOrUndefined;
_version: _VersionOrUndefined;
name: NameOrUndefined;
description: DescriptionOrUndefined;
savedObjectsClient: SavedObjectsClientContract;
@ -40,6 +42,7 @@ interface UpdateExceptionListOptions {
export const updateExceptionList = async ({
_tags,
_version,
id,
savedObjectsClient,
namespaceType,
@ -67,6 +70,9 @@ export const updateExceptionList = async ({
tags,
type,
updated_by: user,
},
{
version: _version,
}
);
return transformSavedObjectUpdateToExceptionList({ exceptionList, savedObject });

View file

@ -20,6 +20,7 @@ import {
TagsOrUndefined,
UpdateCommentsArrayOrUndefined,
_TagsOrUndefined,
_VersionOrUndefined,
} from '../../../common/schemas';
import {
@ -33,6 +34,7 @@ interface UpdateExceptionListItemOptions {
id: IdOrUndefined;
comments: UpdateCommentsArrayOrUndefined;
_tags: _TagsOrUndefined;
_version: _VersionOrUndefined;
name: NameOrUndefined;
description: DescriptionOrUndefined;
entries: EntriesArrayOrUndefined;
@ -48,6 +50,7 @@ interface UpdateExceptionListItemOptions {
export const updateExceptionListItem = async ({
_tags,
_version,
comments,
entries,
id,
@ -89,6 +92,9 @@ export const updateExceptionListItem = async ({
tags,
type,
updated_by: user,
},
{
version: _version,
}
);
return transformSavedObjectUpdateToExceptionListItem({

View file

@ -72,6 +72,7 @@ export const transformSavedObjectToExceptionList = ({
}): ExceptionListSchema => {
const dateNow = new Date().toISOString();
const {
version: _version,
attributes: {
_tags,
created_at,
@ -93,6 +94,7 @@ export const transformSavedObjectToExceptionList = ({
// TODO: Do a throw if after the decode this is not the correct "list_type: list"
return {
_tags,
_version,
created_at,
created_by,
description,
@ -118,6 +120,7 @@ export const transformSavedObjectUpdateToExceptionList = ({
}): ExceptionListSchema => {
const dateNow = new Date().toISOString();
const {
version: _version,
attributes: { _tags, description, meta, name, tags, type, updated_by: updatedBy },
id,
updated_at: updatedAt,
@ -127,6 +130,7 @@ export const transformSavedObjectUpdateToExceptionList = ({
// TODO: Do a throw if after the decode this is not the correct "list_type: list"
return {
_tags: _tags ?? exceptionList._tags,
_version,
created_at: exceptionList.created_at,
created_by: exceptionList.created_by,
description: description ?? exceptionList.description,
@ -150,6 +154,7 @@ export const transformSavedObjectToExceptionListItem = ({
}): ExceptionListItemSchema => {
const dateNow = new Date().toISOString();
const {
version: _version,
attributes: {
_tags,
comments,
@ -174,6 +179,7 @@ export const transformSavedObjectToExceptionListItem = ({
// TODO: Do a throw if item_id or entries is not defined.
return {
_tags,
_version,
comments: comments ?? [],
created_at,
created_by,
@ -202,6 +208,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
}): ExceptionListItemSchema => {
const dateNow = new Date().toISOString();
const {
version: _version,
attributes: {
_tags,
comments,
@ -223,6 +230,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
// defaulting
return {
_tags: _tags ?? exceptionListItem._tags,
_version,
comments: comments ?? exceptionListItem.comments,
created_at: exceptionListItem.created_at,
created_by: exceptionListItem.created_by,

View file

@ -18,6 +18,7 @@ import {
Type,
} from '../../../common/schemas';
import { transformListItemToElasticQuery } from '../utils';
import { encodeHitVersion } from '../utils/encode_hit_version';
export interface CreateListItemOptions {
deserializer: DeserializerOrUndefined;
@ -75,6 +76,7 @@ export const createListItem = async ({
});
return {
_version: encodeHitVersion(response),
id: response._id,
type,
value,

View file

@ -5,6 +5,7 @@
*/
import { LegacyAPICaller } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import {
Filter,
@ -82,7 +83,10 @@ export const findListItem = async ({
});
if (scroll.validSearchAfterFound) {
const response = await callCluster<SearchEsListItemSchema>('search', {
// Note: This typing of response = await callCluster<SearchResponse<SearchEsListSchema>>
// is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have
// to explicitly define the type <T>.
const response = await callCluster<SearchResponse<SearchEsListItemSchema>>('search', {
body: {
query,
search_after: scroll.searchAfter,
@ -90,6 +94,7 @@ export const findListItem = async ({
},
ignoreUnavailable: true,
index: listItemIndex,
seq_no_primary_term: true,
size: perPage,
});
return {

View file

@ -5,6 +5,7 @@
*/
import { LegacyAPICaller } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { Id, ListItemSchema, SearchEsListItemSchema } from '../../../common/schemas';
import { transformElasticToListItem } from '../utils';
@ -21,7 +22,10 @@ export const getListItem = async ({
callCluster,
listItemIndex,
}: GetListItemOptions): Promise<ListItemSchema | null> => {
const listItemES = await callCluster<SearchEsListItemSchema>('search', {
// Note: This typing of response = await callCluster<SearchResponse<SearchEsListSchema>>
// is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have
// to explicitly define the type <T>.
const listItemES = await callCluster<SearchResponse<SearchEsListItemSchema>>('search', {
body: {
query: {
term: {
@ -31,6 +35,7 @@ export const getListItem = async ({
},
ignoreUnavailable: true,
index: listItemIndex,
seq_no_primary_term: true,
});
if (listItemES.hits.hits.length) {

View file

@ -15,6 +15,7 @@ import {
} from '../../../common/constants.mock';
export const getUpdateListItemOptionsMock = (): UpdateListItemOptions => ({
_version: undefined,
callCluster: getCallClusterMock(),
dateNow: DATE_NOW,
id: LIST_ITEM_ID,

View file

@ -12,12 +12,16 @@ import {
ListItemSchema,
MetaOrUndefined,
UpdateEsListItemSchema,
_VersionOrUndefined,
} from '../../../common/schemas';
import { transformListItemToElasticQuery } from '../utils';
import { decodeVersion } from '../utils/decode_version';
import { encodeHitVersion } from '../utils/encode_hit_version';
import { getListItem } from './get_list_item';
export interface UpdateListItemOptions {
_version: _VersionOrUndefined;
id: Id;
value: string | null | undefined;
callCluster: LegacyAPICaller;
@ -28,6 +32,7 @@ export interface UpdateListItemOptions {
}
export const updateListItem = async ({
_version,
id,
value,
callCluster,
@ -57,6 +62,7 @@ export const updateListItem = async ({
};
const response = await callCluster<CreateDocumentResponse>('update', {
...decodeVersion(_version),
body: {
doc,
},
@ -65,6 +71,7 @@ export const updateListItem = async ({
refresh: 'wait_for',
});
return {
_version: encodeHitVersion(response),
created_at: listItem.created_at,
created_by: listItem.created_by,
deserializer: listItem.deserializer,

View file

@ -8,6 +8,7 @@ import uuid from 'uuid';
import { CreateDocumentResponse } from 'elasticsearch';
import { LegacyAPICaller } from 'kibana/server';
import { encodeHitVersion } from '../utils/encode_hit_version';
import {
Description,
DeserializerOrUndefined,
@ -70,6 +71,7 @@ export const createList = async ({
refresh: 'wait_for',
});
return {
_version: encodeHitVersion(response),
id: response._id,
...body,
};

View file

@ -5,6 +5,7 @@
*/
import { LegacyAPICaller } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import {
Filter,
@ -71,7 +72,10 @@ export const findList = async ({
});
if (scroll.validSearchAfterFound) {
const response = await callCluster<SearchEsListSchema>('search', {
// Note: This typing of response = await callCluster<SearchResponse<SearchEsListSchema>>
// is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have
// to explicitly define the type <T>.
const response = await callCluster<SearchResponse<SearchEsListSchema>>('search', {
body: {
query,
search_after: scroll.searchAfter,
@ -79,6 +83,7 @@ export const findList = async ({
},
ignoreUnavailable: true,
index: listIndex,
seq_no_primary_term: true,
size: perPage,
});
return {

View file

@ -5,6 +5,7 @@
*/
import { LegacyAPICaller } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { Id, ListSchema, SearchEsListSchema } from '../../../common/schemas';
import { transformElasticToList } from '../utils/transform_elastic_to_list';
@ -20,7 +21,10 @@ export const getList = async ({
callCluster,
listIndex,
}: GetListOptions): Promise<ListSchema | null> => {
const response = await callCluster<SearchEsListSchema>('search', {
// Note: This typing of response = await callCluster<SearchResponse<SearchEsListSchema>>
// is because when you pass in seq_no_primary_term: true it does a "fall through" type and you have
// to explicitly define the type <T>.
const response = await callCluster<SearchResponse<SearchEsListSchema>>('search', {
body: {
query: {
term: {
@ -30,6 +34,7 @@ export const getList = async ({
},
ignoreUnavailable: true,
index: listIndex,
seq_no_primary_term: true,
});
const list = transformElasticToList({ response });
return list[0] ?? null;

View file

@ -395,6 +395,7 @@ export class ListClient {
};
public updateListItem = async ({
_version,
id,
value,
meta,
@ -402,6 +403,7 @@ export class ListClient {
const { callCluster, user } = this;
const listItemIndex = this.getListItemIndex();
return updateListItem({
_version,
callCluster,
id,
listItemIndex,
@ -412,6 +414,7 @@ export class ListClient {
};
public updateList = async ({
_version,
id,
name,
description,
@ -420,6 +423,7 @@ export class ListClient {
const { callCluster, user } = this;
const listIndex = this.getListIndex();
return updateList({
_version,
callCluster,
description,
id,

View file

@ -26,6 +26,7 @@ import {
SortFieldOrUndefined,
SortOrderOrUndefined,
Type,
_VersionOrUndefined,
} from '../../../common/schemas';
import { ConfigType } from '../../config';
@ -106,12 +107,14 @@ export interface CreateListItemOptions {
}
export interface UpdateListItemOptions {
_version: _VersionOrUndefined;
id: Id;
value: string | null | undefined;
meta: MetaOrUndefined;
}
export interface UpdateListOptions {
_version: _VersionOrUndefined;
id: Id;
name: NameOrUndefined;
description: DescriptionOrUndefined;

View file

@ -16,6 +16,7 @@ import {
} from '../../../common/constants.mock';
export const getUpdateListOptionsMock = (): UpdateListOptions => ({
_version: undefined,
callCluster: getCallClusterMock(),
dateNow: DATE_NOW,
description: DESCRIPTION,

View file

@ -7,6 +7,8 @@
import { CreateDocumentResponse } from 'elasticsearch';
import { LegacyAPICaller } from 'kibana/server';
import { decodeVersion } from '../utils/decode_version';
import { encodeHitVersion } from '../utils/encode_hit_version';
import {
DescriptionOrUndefined,
Id,
@ -14,11 +16,13 @@ import {
MetaOrUndefined,
NameOrUndefined,
UpdateEsListSchema,
_VersionOrUndefined,
} from '../../../common/schemas';
import { getList } from '.';
export interface UpdateListOptions {
_version: _VersionOrUndefined;
id: Id;
callCluster: LegacyAPICaller;
listIndex: string;
@ -30,6 +34,7 @@ export interface UpdateListOptions {
}
export const updateList = async ({
_version,
id,
name,
description,
@ -52,12 +57,14 @@ export const updateList = async ({
updated_by: user,
};
const response = await callCluster<CreateDocumentResponse>('update', {
...decodeVersion(_version),
body: { doc },
id,
index: listIndex,
refresh: 'wait_for',
});
return {
_version: encodeHitVersion(response),
created_at: list.created_at,
created_by: list.created_by,
description: description ?? list.description,

View file

@ -0,0 +1,37 @@
/*
* 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.
*/
// Similar to the src/core/server/saved_objects/version/decode_version.ts
// with the notable differences in that it is more tolerant and does not throw saved object specific errors
// but rather just returns an empty object if it cannot parse the version or cannot find one.
export const decodeVersion = (
version: string | undefined
):
| {
ifSeqNo: number;
ifPrimaryTerm: number;
}
| {} => {
if (version != null) {
try {
const decoded = Buffer.from(version, 'base64').toString('utf8');
const parsed = JSON.parse(decoded);
if (Array.isArray(parsed) && Number.isInteger(parsed[0]) && Number.isInteger(parsed[1])) {
return {
ifPrimaryTerm: parsed[1],
ifSeqNo: parsed[0],
};
} else {
return {};
}
} catch (err) {
// do nothing here, this is on purpose and we want to return any empty object when we can't parse.
return {};
}
} else {
return {};
}
};

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
/**
* Very similar to the encode_hit_version from saved object system from here:
* src/core/server/saved_objects/version/encode_hit_version.ts
*
* with the most notably change is that it doesn't do any throws but rather just returns undefined
* if _seq_no or _primary_term does not exist.
* @param response The response to encode into a version by using _seq_no and _primary_term
*/
export const encodeHitVersion = <T>(hit: T): string | undefined => {
// Have to do this "as cast" here as these two types aren't included in the SearchResponse hit type
const { _seq_no: seqNo, _primary_term: primaryTerm } = (hit as unknown) as {
_seq_no: number;
_primary_term: number;
};
if (seqNo == null || primaryTerm == null) {
return undefined;
} else {
return Buffer.from(JSON.stringify([seqNo, primaryTerm]), 'utf8').toString('base64');
}
};

View file

@ -8,6 +8,8 @@ import { SearchResponse } from 'elasticsearch';
import { ListArraySchema, SearchEsListSchema } from '../../../common/schemas';
import { encodeHitVersion } from './encode_hit_version';
export interface TransformElasticToListOptions {
response: SearchResponse<SearchEsListSchema>;
}
@ -17,6 +19,7 @@ export const transformElasticToList = ({
}: TransformElasticToListOptions): ListArraySchema => {
return response.hits.hits.map((hit) => {
return {
_version: encodeHitVersion(hit),
id: hit._id,
...hit._source,
};

View file

@ -9,6 +9,7 @@ import { SearchResponse } from 'elasticsearch';
import { ListItemArraySchema, SearchEsListItemSchema, Type } from '../../../common/schemas';
import { ErrorWithStatusCode } from '../../error_with_status_code';
import { encodeHitVersion } from './encode_hit_version';
import { findSourceValue } from './find_source_value';
export interface TransformElasticToListItemOptions {
@ -40,6 +41,7 @@ export const transformElasticToListItem = ({
throw new ErrorWithStatusCode(`Was expected ${type} to not be null/undefined`, 400);
} else {
return {
_version: encodeHitVersion(hit),
created_at,
created_by,
deserializer,

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