Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a141531
Add strict payload validation to invntory_views endpoint
crespocarlos Jun 29, 2023
bf4a41c
Update README.md
crespocarlos Jun 29, 2023
6e1fe5e
Fix update endpoint payload type
crespocarlos Jun 29, 2023
d621f6b
Refactor saved view types
crespocarlos Jun 29, 2023
58b71bb
Revert some type changes and adjust toolbar_control component
crespocarlos Jun 30, 2023
c70fb9b
Merge branch 'main' into 157505-inventory-view-strict-input-validation
kibanamachine Jun 30, 2023
10c042d
Merge branch 'main' of github.com:elastic/kibana into 157505-inventor…
crespocarlos Jun 30, 2023
ed95718
Decouple Saved View schema
crespocarlos Jun 30, 2023
4e0e533
Inventory Views API type refactoring
crespocarlos Jun 30, 2023
c35542b
Fix after changing the find_inventory_view type
crespocarlos Jun 30, 2023
c71f345
Fix after changing the find_inventory_view type
crespocarlos Jun 30, 2023
663b389
Merge branch '157505-inventory-view-strict-input-validation' of githu…
crespocarlos Jun 30, 2023
fc0256f
Clean up
crespocarlos Jun 30, 2023
1fe79b5
More clean up
crespocarlos Jun 30, 2023
f343285
Fix missing attributes
crespocarlos Jun 30, 2023
3cc8ba2
Improve mapper
crespocarlos Jun 30, 2023
4f9933a
Merge branch 'main' into 157505-inventory-view-strict-input-validation
kibanamachine Jun 30, 2023
43cc290
Typo
crespocarlos Jul 3, 2023
5eac15f
Merge branch 'main' into 157505-inventory-view-strict-input-validation
kibanamachine Jul 3, 2023
2d24158
Merge branch 'main' into 157505-inventory-view-strict-input-validation
kibanamachine Jul 3, 2023
c00ae1b
Merge branch 'main' into 157505-inventory-view-strict-input-validation
kibanamachine Jul 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/kbn-io-ts-utils/src/in_range_rt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ export interface InRangeBrand {
export type InRange = rt.Branded<number, InRangeBrand>;

export const inRangeRt = (start: number, end: number) =>
rt.brand(
rt.number, // codec
(n): n is InRange => n >= start && n <= end,
// refinement of the number type
'InRange' // name of this codec
new rt.Type<number, number>(
'InRange',
(input: unknown): input is number =>
typeof input === 'number' && input >= start && input <= end,
(input: unknown, context: rt.Context) =>
typeof input === 'number' && input >= start && input <= end
? rt.success(input)
: rt.failure(input, context),
rt.identity
);

export const inRangeFromStringRt = (start: number, end: number) => {
Expand Down
31 changes: 5 additions & 26 deletions x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { nonEmptyStringRt } from '@kbn/io-ts-utils';

import * as rt from 'io-ts';
import { either } from 'fp-ts/Either';
import { inventoryViewRT } from '../../../inventory_views';

export const INVENTORY_VIEW_URL = '/api/infra/inventory_views';
export const INVENTORY_VIEW_URL_ENTITY = `${INVENTORY_VIEW_URL}/{inventoryViewId}`;
Expand All @@ -33,30 +34,8 @@ export const inventoryViewRequestQueryRT = rt.partial({
sourceId: rt.string,
});

export type InventoryViewRequestQuery = rt.TypeOf<typeof inventoryViewRequestQueryRT>;

const inventoryViewAttributesResponseRT = rt.intersection([
rt.strict({
name: nonEmptyStringRt,
isDefault: rt.boolean,
isStatic: rt.boolean,
}),
rt.UnknownRecord,
]);

const inventoryViewResponseRT = rt.exact(
rt.intersection([
rt.type({
id: rt.string,
attributes: inventoryViewAttributesResponseRT,
}),
rt.partial({
updatedAt: rt.number,
version: rt.string,
}),
])
);

export const inventoryViewResponsePayloadRT = rt.type({
data: inventoryViewResponseRT,
data: inventoryViewRT,
});

export type InventoryViewRequestQuery = rt.TypeOf<typeof inventoryViewRequestQueryRT>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
* 2.0.
*/

import { nonEmptyStringRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';
import { inventoryViewAttributesRT, inventoryViewRT } from '../../../inventory_views';

export const createInventoryViewAttributesRequestPayloadRT = rt.intersection([
rt.type({
name: nonEmptyStringRt,
}),
rt.UnknownRecord,
rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })),
]);
export const createInventoryViewAttributesRequestPayloadRT = rt.exact(
rt.intersection([
inventoryViewAttributesRT,
rt.partial({
isDefault: rt.undefined,
isStatic: rt.undefined,
}),
])
);

export type CreateInventoryViewAttributesRequestPayload = rt.TypeOf<
typeof createInventoryViewAttributesRequestPayloadRT
Expand All @@ -23,3 +25,5 @@ export type CreateInventoryViewAttributesRequestPayload = rt.TypeOf<
export const createInventoryViewRequestPayloadRT = rt.type({
attributes: createInventoryViewAttributesRequestPayloadRT,
});

export type CreateInventoryViewResponsePayload = rt.TypeOf<typeof inventoryViewRT>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,11 @@
* 2.0.
*/

import { nonEmptyStringRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';

export const findInventoryViewAttributesResponseRT = rt.strict({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I remembered this was a strict type to strip out the non-essential properties for this endpoint when the response is encoded in the server, I think doing so it will now leak all SO attributes on this response for every saved view, could you verify that please?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see any weird attributes. I'll check it again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it. thanks!

name: nonEmptyStringRt,
isDefault: rt.boolean,
isStatic: rt.boolean,
});

const findInventoryViewResponseRT = rt.exact(
rt.intersection([
rt.type({
id: rt.string,
attributes: findInventoryViewAttributesResponseRT,
}),
rt.partial({
updatedAt: rt.number,
version: rt.string,
}),
])
);
import { singleInventoryViewRT } from '../../../inventory_views';

export const findInventoryViewResponsePayloadRT = rt.type({
data: rt.array(findInventoryViewResponseRT),
data: rt.array(singleInventoryViewRT),
});

export type FindInventoryViewResponsePayload = rt.TypeOf<typeof findInventoryViewResponsePayloadRT>;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
*/

import * as rt from 'io-ts';
import { inventoryViewRT } from '../../../inventory_views';

export const getInventoryViewRequestParamsRT = rt.type({
inventoryViewId: rt.string,
});

export type GetInventoryViewResposePayload = rt.TypeOf<typeof inventoryViewRT>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
* 2.0.
*/

import { nonEmptyStringRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';
import { inventoryViewAttributesRT, inventoryViewRT } from '../../../inventory_views';

export const updateInventoryViewAttributesRequestPayloadRT = rt.intersection([
rt.type({
name: nonEmptyStringRt,
}),
rt.UnknownRecord,
rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })),
]);
export const updateInventoryViewAttributesRequestPayloadRT = rt.exact(
rt.intersection([
inventoryViewAttributesRT,
rt.partial({
isDefault: rt.undefined,
isStatic: rt.undefined,
}),
])
);

export type UpdateInventoryViewAttributesRequestPayload = rt.TypeOf<
typeof updateInventoryViewAttributesRequestPayloadRT
Expand All @@ -23,3 +25,5 @@ export type UpdateInventoryViewAttributesRequestPayload = rt.TypeOf<
export const updateInventoryViewRequestPayloadRT = rt.type({
attributes: updateInventoryViewAttributesRequestPayloadRT,
});

export type UpdateInventoryViewResponsePayload = rt.TypeOf<typeof inventoryViewRT>;
107 changes: 99 additions & 8 deletions x-pack/plugins/infra/common/inventory_views/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,89 @@
* 2.0.
*/

import { nonEmptyStringRt } from '@kbn/io-ts-utils';
import { isoToEpochRt, nonEmptyStringRt, inRangeRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';
import {
SnapshotCustomMetricInputRT,
SnapshotGroupByRT,
SnapshotMetricInputRT,
} from '../http_api/snapshot_api';
import { ItemTypeRT } from '../inventory_models/types';

export const inventoryColorPaletteRT = rt.keyof({
status: null,
temperature: null,
cool: null,
warm: null,
positive: null,
negative: null,
});

const inventoryLegendOptionsRT = rt.type({
palette: inventoryColorPaletteRT,
steps: inRangeRt(2, 18),
reverseColors: rt.boolean,
});

export const inventorySortOptionRT = rt.type({
by: rt.keyof({ name: null, value: null }),
direction: rt.keyof({ asc: null, desc: null }),
});

export const inventoryViewOptionsRT = rt.keyof({ table: null, map: null });

export const inventoryMapBoundsRT = rt.type({
min: inRangeRt(0, 1),
max: inRangeRt(0, 1),
});

export const inventoryFiltersStateRT = rt.type({
kind: rt.literal('kuery'),
expression: rt.string,
});

export const inventoryOptionsStateRT = rt.intersection([
rt.type({
accountId: rt.string,
autoBounds: rt.boolean,
boundsOverride: inventoryMapBoundsRT,
customMetrics: rt.array(SnapshotCustomMetricInputRT),
customOptions: rt.array(
rt.type({
text: rt.string,
field: rt.string,
})
),
groupBy: SnapshotGroupByRT,
metric: SnapshotMetricInputRT,
nodeType: ItemTypeRT,
region: rt.string,
sort: inventorySortOptionRT,
view: inventoryViewOptionsRT,
}),
rt.partial({ legend: inventoryLegendOptionsRT, source: rt.string, timelineOpen: rt.boolean }),
]);

export const inventoryViewBasicAttributesRT = rt.type({
name: nonEmptyStringRt,
});

const inventoryViewFlagsRT = rt.partial({ isDefault: rt.boolean, isStatic: rt.boolean });

export const inventoryViewAttributesRT = rt.intersection([
rt.strict({
name: nonEmptyStringRt,
isDefault: rt.boolean,
isStatic: rt.boolean,
inventoryOptionsStateRT,
inventoryViewBasicAttributesRT,
inventoryViewFlagsRT,
rt.type({
autoReload: rt.boolean,
filterQuery: inventoryFiltersStateRT,
}),
rt.UnknownRecord,
rt.partial({ time: rt.number }),
]);

export type InventoryViewAttributes = rt.TypeOf<typeof inventoryViewAttributesRT>;
const singleInventoryViewAttributesRT = rt.exact(
rt.intersection([inventoryViewBasicAttributesRT, inventoryViewFlagsRT])
);

export const inventoryViewRT = rt.exact(
rt.intersection([
Expand All @@ -26,10 +96,31 @@ export const inventoryViewRT = rt.exact(
attributes: inventoryViewAttributesRT,
}),
rt.partial({
updatedAt: rt.number,
updatedAt: isoToEpochRt,
version: rt.string,
}),
])
);

export const singleInventoryViewRT = rt.exact(
rt.intersection([
rt.type({
id: rt.string,
attributes: singleInventoryViewAttributesRT,
}),
rt.partial({
updatedAt: isoToEpochRt,
version: rt.string,
}),
])
);

export type InventoryColorPalette = rt.TypeOf<typeof inventoryColorPaletteRT>;
export type InventoryFiltersState = rt.TypeOf<typeof inventoryFiltersStateRT>;
export type InventoryLegendOptions = rt.TypeOf<typeof inventoryLegendOptionsRT>;
export type InventoryMapBounds = rt.TypeOf<typeof inventoryMapBoundsRT>;
export type InventoryOptionsState = rt.TypeOf<typeof inventoryOptionsStateRT>;
export type InventorySortOption = rt.TypeOf<typeof inventorySortOptionRT>;
export type InventoryView = rt.TypeOf<typeof inventoryViewRT>;
export type InventoryViewAttributes = rt.TypeOf<typeof inventoryViewAttributesRT>;
export type InventoryViewOptions = rt.TypeOf<typeof inventoryViewOptionsRT>;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { nonEmptyStringRt } from '@kbn/io-ts-utils';
import * as rt from 'io-ts';

export const metricsExplorerViewAttributesRT = rt.intersection([
rt.strict({
rt.type({
name: nonEmptyStringRt,
isDefault: rt.boolean,
isStatic: rt.boolean,
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/infra/common/saved_views/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './types';
70 changes: 70 additions & 0 deletions x-pack/plugins/infra/common/saved_views/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import {
QueryObserverBaseResult,
UseMutateAsyncFunction,
UseMutateFunction,
} from '@tanstack/react-query';

import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';

export type ServerError = IHttpFetchError<ResponseErrorBody>;

export interface SavedViewState<TView> {
views?: SavedViewItem[];
currentView?: TView | null;
isCreatingView: boolean;
isFetchingCurrentView: boolean;
isFetchingViews: boolean;
isUpdatingView: boolean;
}

export interface SavedViewOperations<
TView extends { id: TView['id'] },
TId extends TView['id'] = TView['id'],
TPayload = any,
TConfig = any
> {
createView: UseMutateAsyncFunction<TView, ServerError, TPayload>;
deleteViewById: UseMutateFunction<null, ServerError, string, MutationContext<TView>>;
fetchViews: QueryObserverBaseResult<SavedViewItem[]>['refetch'];
updateViewById: UseMutateAsyncFunction<TView, ServerError, UpdateViewParams<TPayload>>;
switchViewById: (id: TId) => void;
setDefaultViewById: UseMutateFunction<TConfig, ServerError, string, MutationContext<TView>>;
}

export interface SavedViewResult<
TView extends {
id: TView['id'];
},
TId extends string = '',
TPayload = any,
TConfig = any
> extends SavedViewState<TView>,
SavedViewOperations<TView, TId, TPayload, TConfig> {}

export interface UpdateViewParams<TRequestPayload> {
id: string;
attributes: TRequestPayload;
}

export interface MutationContext<TView> {
id?: string;
previousViews?: TView[];
}

export interface BasicAttributes {
name?: string;
time?: number;
isDefault?: boolean;
isStatic?: boolean;
}
export interface SavedViewItem {
id: string;
attributes: BasicAttributes;
}
Loading