Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NP] Allow custom validations in HTTP Routes apart from @kbn/config-schema #51919

Merged
merged 44 commits into from
Dec 20, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
93cc5db
[NP] Allow custom validations in HTTP Routes apart from @kbn/config-s…
afharo Nov 29, 2019
318f827
API docs
afharo Nov 29, 2019
1b3088a
Merge branch 'master' into np-decouple-configSchema
elasticmachine Dec 2, 2019
f9ea319
Allow validate function in the route handler (run-code validation)
afharo Dec 2, 2019
d28e0aa
Prefix RouteXXX + Params and Body Validation Aliases
afharo Dec 2, 2019
4fddfd1
Fix test broken by lodash
afharo Dec 2, 2019
472a60f
Update API docs
afharo Dec 2, 2019
b2e06db
Add default types for simpler manual declaration
afharo Dec 2, 2019
3996c2a
Add run-time validation of the RouteValidateSpec
afharo Dec 2, 2019
f5af513
Expose RouteValidationError instead of SchemaTypeError
afharo Dec 2, 2019
8d02bbe
Merge branch 'master' into np-decouple-configSchema
elasticmachine Dec 2, 2019
46c670f
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 3, 2019
db62d75
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 3, 2019
c949643
RouteValidator as a class to match config-schema interface
afharo Dec 4, 2019
1b2165c
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 4, 2019
336844e
Test for not-inline handler (need to check IRouter for #47047)
afharo Dec 4, 2019
bf71f8a
Add preValidation of the input for a safer custom validation
afharo Dec 4, 2019
ada74fe
Better types for RouteHandlers
afharo Dec 4, 2019
97d4cdb
[NP] Move route validation to RouteValidator wrapper
afharo Dec 6, 2019
4daabcb
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 6, 2019
091125a
Use the class only internally but maintain the same API
afharo Dec 6, 2019
9bb014a
[NP] Move route validation to RouteValidator wrapper
afharo Dec 6, 2019
dfdcccb
Merge branch 'master' into np-decouple-configSchema
elasticmachine Dec 7, 2019
b30e913
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 8, 2019
df9d33a
Fix types
afharo Dec 8, 2019
cccfe15
Ensure RouteValidator instance in KibanaRequest.from
afharo Dec 8, 2019
f99566d
Fix validator.tests (Buffer.from instead of new Buffer)
afharo Dec 8, 2019
d0ac3b9
Default precheck should allow null values
afharo Dec 9, 2019
be34522
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 9, 2019
67bd356
Merge branch 'np-decouple-configSchema' of github.com:afharo/kibana i…
afharo Dec 9, 2019
16ee910
Also allow undefined in preChecks
afharo Dec 9, 2019
83b9da6
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 9, 2019
6dc23dc
Merge branch 'master' into np-decouple-configSchema
elasticmachine Dec 16, 2019
6a6b0ab
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 17, 2019
c10723a
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 18, 2019
5c45228
MR feedback fixes
afharo Dec 18, 2019
fb0e6ad
Provide RouteValidationResolver to the validation function
afharo Dec 18, 2019
be46a67
Add functional tests
afharo Dec 18, 2019
ce57c16
Fix new functional tests
afharo Dec 18, 2019
3b930f3
Merge branch 'master' of github.com:elastic/kibana into np-decouple-c…
afharo Dec 18, 2019
e394259
Fix validator additional test
afharo Dec 18, 2019
1ece254
Fix test with new resolver
afharo Dec 18, 2019
7d6ea02
Remove unused import
afharo Dec 18, 2019
c8c7939
Rename ValidationResolver to ValidationResultFactory and change the i…
afharo Dec 19, 2019
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
2 changes: 2 additions & 0 deletions docs/development/core/server/kibana-plugin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,6 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. |
| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. |
| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. |
| [ValidateFunction](./kibana-plugin-server.validatefunction.md) | Custom validate function (only if @<!-- -->kbn/config-schema is not a valid option in your plugin) |
| [ValidateFunctionReturn](./kibana-plugin-server.validatefunctionreturn.md) | Allowed returned format of the custom validate function |

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han
<b>Signature:</b>

```typescript
export declare type RequestHandler<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
export declare type RequestHandler<P extends ObjectType | ValidateFunction<unknown>, Q extends ObjectType | ValidateFunction<unknown>, B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<unknown>, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest<ValidatedType<P>, ValidatedType<Q>, ValidatedType<B>, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
```

## Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Route specific configuration.
<b>Signature:</b>

```typescript
export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>, Method extends RouteMethod>
export interface RouteConfig<P extends ObjectType | ValidateFunction<unknown>, Q extends ObjectType | ValidateFunction<unknown>, B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<unknown>, Method extends RouteMethod>
```

## Properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Route handler common definition
<b>Signature:</b>

```typescript
export declare type RouteRegistrar<Method extends RouteMethod> = <P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method>) => void;
export declare type RouteRegistrar<Method extends RouteMethod> = <P extends ObjectType | ValidateFunction<unknown>, Q extends ObjectType | ValidateFunction<unknown>, B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<unknown>>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method>) => void;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ RouteSchemas contains the schemas for validating the different parts of a reques
<b>Signature:</b>

```typescript
export interface RouteSchemas<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>>
export interface RouteSchemas<P extends ObjectType | ValidateFunction<unknown>, Q extends ObjectType | ValidateFunction<unknown>, B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<unknown>>
```

## Properties
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ValidateFunction](./kibana-plugin-server.validatefunction.md)

## ValidateFunction type

Custom validate function (only if @<!-- -->kbn/config-schema is not a valid option in your plugin)

<b>Signature:</b>

```typescript
export declare type ValidateFunction<T> = (data: any) => ValidateFunctionReturn<T>;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ValidateFunctionReturn](./kibana-plugin-server.validatefunctionreturn.md)

## ValidateFunctionReturn type

Allowed returned format of the custom validate function

<b>Signature:</b>

```typescript
export declare type ValidateFunctionReturn<T> = {
value: T;
error?: undefined;
} | {
value?: undefined;
error: SchemaTypeError;
};
```
1 change: 1 addition & 0 deletions packages/kbn-config-schema/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {

export { ObjectType, TypeOf, Type };
export { ByteSizeValue } from './byte_size_value';
export { SchemaTypeError, ValidationError } from './errors';

function any(options?: TypeOptions<any>) {
return new AnyType(options);
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export {
RouteConfigOptionsBody,
RouteContentType,
validBodyOutput,
ValidateFunctionReturn,
ValidateFunction,
} from './router';
export { BasePathProxyServer } from './base_path_proxy_server';
export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
Expand Down
12 changes: 9 additions & 3 deletions src/core/server/http/router/error_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,25 @@
*/

import Boom from 'boom';
import { ObjectType, TypeOf } from '@kbn/config-schema';
import { ObjectType, Type } from '@kbn/config-schema';
import { Stream } from 'stream';
import { KibanaRequest } from './request';
import { KibanaResponseFactory } from './response';
import { RequestHandler } from './router';
import { RequestHandlerContext } from '../../../server';
import { RouteMethod } from './route';
import { ValidateFunction, ValidatedType } from './validator';

export const wrapErrors = <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(
export const wrapErrors = <
P extends ObjectType | ValidateFunction<unknown>,
Q extends ObjectType | ValidateFunction<unknown>,
B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<unknown>
>(
handler: RequestHandler<P, Q, B, RouteMethod>
): RequestHandler<P, Q, B, RouteMethod> => {
return async (
context: RequestHandlerContext,
request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>, RouteMethod>,
request: KibanaRequest<ValidatedType<P>, ValidatedType<Q>, ValidatedType<B>, RouteMethod>,
response: KibanaResponseFactory
) => {
try {
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/http/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@ export {
} from './response';

export { IKibanaSocket } from './socket';

export { ValidateFunctionReturn, ValidateFunction } from './validator';
49 changes: 49 additions & 0 deletions src/core/server/http/router/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
import { KibanaRequest } from './request';
import { httpServerMock } from '../http_server.mocks';
import { schema, SchemaTypeError } from '@kbn/config-schema';

describe('KibanaRequest', () => {
describe('get all headers', () => {
Expand Down Expand Up @@ -64,4 +65,52 @@ describe('KibanaRequest', () => {
});
});
});

describe('RouteSchema type inferring', () => {
it('should work with config-schema', () => {
const body = new Buffer('body!');
const request = httpServerMock.createRawRequest({
params: { id: 'params' },
query: { search: 'query' },
payload: body,
});
const kibanaRequest = KibanaRequest.from(request, {
params: schema.object({ id: schema.string() }),
query: schema.object({ search: schema.string() }),
body: schema.buffer(),
});
expect(kibanaRequest.params).toStrictEqual({ id: 'params' });
expect(kibanaRequest.params.id.toUpperCase()).toEqual('PARAMS'); // infers it's a string
expect(kibanaRequest.query).toStrictEqual({ search: 'query' });
expect(kibanaRequest.query.search.toUpperCase()).toEqual('QUERY'); // infers it's a string
expect(kibanaRequest.body).toEqual(body);
expect(kibanaRequest.body.byteLength).toBeGreaterThan(0); // infers it's a buffer
});

it('should work with ValidationFunction', () => {
const body = new Buffer('body!');
const request = httpServerMock.createRawRequest({
params: { id: 'params' },
query: { search: 'query' },
payload: body,
});
const kibanaRequest = KibanaRequest.from(request, {
params: schema.object({ id: schema.string() }),
query: schema.object({ search: schema.string() }),
body: data => {
if (Buffer.isBuffer(data)) {
return { value: data };
} else {
return { error: new SchemaTypeError('It should be a Buffer', []) };
}
},
});
expect(kibanaRequest.params).toStrictEqual({ id: 'params' });
expect(kibanaRequest.params.id.toUpperCase()).toEqual('PARAMS'); // infers it's a string
expect(kibanaRequest.query).toStrictEqual({ search: 'query' });
expect(kibanaRequest.query.search.toUpperCase()).toEqual('QUERY'); // infers it's a string
expect(kibanaRequest.body).toEqual(body);
expect(kibanaRequest.body.byteLength).toBeGreaterThan(0); // infers it's a buffer
});
});
});
39 changes: 20 additions & 19 deletions src/core/server/http/router/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
import { Url } from 'url';
import { Request } from 'hapi';

import { ObjectType, Type, TypeOf } from '@kbn/config-schema';
import { ObjectType, Type } from '@kbn/config-schema';

import { Stream } from 'stream';
import { deepFreeze, RecursiveReadonly } from '../../../utils';
import { Headers } from './headers';
import { RouteMethod, RouteSchemas, RouteConfigOptions, validBodyOutput } from './route';
import { KibanaSocket, IKibanaSocket } from './socket';
import { validate, ValidateFunction, ValidatedType } from './validator';

const requestSymbol = Symbol('request');

Expand Down Expand Up @@ -71,9 +72,9 @@ export class KibanaRequest<
* @internal
*/
public static from<
P extends ObjectType,
Q extends ObjectType,
B extends ObjectType | Type<Buffer> | Type<Stream>
P extends ObjectType | ValidateFunction<any>,
Q extends ObjectType | ValidateFunction<any>,
B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<any>
>(req: Request, routeSchemas?: RouteSchemas<P, Q, B>, withoutSecretHeaders: boolean = true) {
const requestParts = KibanaRequest.validate(req, routeSchemas);
return new KibanaRequest(
Expand All @@ -92,39 +93,39 @@ export class KibanaRequest<
* @internal
*/
private static validate<
P extends ObjectType,
Q extends ObjectType,
B extends ObjectType | Type<Buffer> | Type<Stream>
P extends ObjectType | ValidateFunction<any>,
Q extends ObjectType | ValidateFunction<any>,
B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<any>
>(
req: Request,
routeSchemas: RouteSchemas<P, Q, B> | undefined
): {
params: TypeOf<P>;
query: TypeOf<Q>;
body: TypeOf<B>;
params: ValidatedType<P>;
query: ValidatedType<Q>;
body: ValidatedType<B>;
} {
if (routeSchemas === undefined) {
return {
body: {},
params: {},
query: {},
params: {} as ValidatedType<P>,
query: {} as ValidatedType<Q>,
body: {} as ValidatedType<B>,
};
}

const params =
routeSchemas.params === undefined
? {}
: routeSchemas.params.validate(req.params, {}, 'request params');
? ({} as ValidatedType<P>)
: validate<ValidatedType<P>>(routeSchemas.params, req.params, 'request params');

const query =
routeSchemas.query === undefined
? {}
: routeSchemas.query.validate(req.query, {}, 'request query');
? ({} as ValidatedType<Q>)
: validate<ValidatedType<Q>>(routeSchemas.query, req.query, 'request query');

const body =
routeSchemas.body === undefined
? {}
: routeSchemas.body.validate(req.payload, {}, 'request body');
? ({} as ValidatedType<B>)
: validate<ValidatedType<B>>(routeSchemas.body as any, req.payload, 'request body');

return { query, params, body };
}
Expand Down
13 changes: 7 additions & 6 deletions src/core/server/http/router/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import { ObjectType, Type } from '@kbn/config-schema';
import { Stream } from 'stream';
import { ValidateFunction } from './validator';

/**
* The set of common HTTP methods supported by Kibana routing.
Expand Down Expand Up @@ -125,9 +126,9 @@ export interface RouteConfigOptions<Method extends RouteMethod> {
* @public
*/
export interface RouteConfig<
P extends ObjectType,
Q extends ObjectType,
B extends ObjectType | Type<Buffer> | Type<Stream>,
P extends ObjectType | ValidateFunction<unknown>,
Q extends ObjectType | ValidateFunction<unknown>,
B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<unknown>,
Method extends RouteMethod
> {
/**
Expand Down Expand Up @@ -215,9 +216,9 @@ export interface RouteConfig<
* @public
*/
export interface RouteSchemas<
P extends ObjectType,
Q extends ObjectType,
B extends ObjectType | Type<Buffer> | Type<Stream>
P extends ObjectType | ValidateFunction<unknown>,
Q extends ObjectType | ValidateFunction<unknown>,
B extends ObjectType | Type<Buffer> | Type<Stream> | ValidateFunction<unknown>
> {
params?: P;
query?: Q;
Expand Down
Loading