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] add http resources sub-service #61797

Merged
merged 36 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
601ee0f
add HttpResources basic implementation
mshustov Mar 30, 2020
543ef00
expose http resources to plugins
mshustov Mar 30, 2020
03f4219
add mocks
mshustov Mar 30, 2020
b514e7b
move http resources to a separate service
mshustov Apr 1, 2020
9e34b0e
hide rendering service
mshustov Apr 1, 2020
4c276bf
adopt internal types
mshustov Apr 1, 2020
8a1e10f
expose HttpResources service to plugins
mshustov Apr 1, 2020
1b4ef8e
update platform mocks
mshustov Apr 1, 2020
18309fc
plugins start using HttpResources API
mshustov Apr 1, 2020
5e3d085
remove RenderingServiceSetup export
mshustov Apr 1, 2020
5fc5beb
RenderingServiceSetup --> InternalRenderingServiceSetup
mshustov Apr 1, 2020
6ca04a0
Merge branch 'master' into issue-50654-http-resources
mshustov Apr 1, 2020
b96f741
improve types
mshustov Apr 1, 2020
900d40e
remove httpRespources leftovers from http service
mshustov Apr 1, 2020
4700564
remove rendering types from RequestHanlderContext
mshustov Apr 1, 2020
4daa02c
fix security plugin tests
mshustov Apr 1, 2020
b9f3099
add unit tests for httpResources service
mshustov Apr 1, 2020
cebf397
Merge branch 'master' into issue-50654-http-resources
mshustov Apr 6, 2020
d988c76
add unit tests
mshustov Apr 7, 2020
8150a9e
Merge branch 'master' into issue-50654-http-resources
mshustov Apr 8, 2020
7326679
remove outdated cache-control header
mshustov Apr 8, 2020
6e8ce80
restructure http resources service
mshustov Apr 8, 2020
457cbf9
merge getUiPlugins and discover
mshustov Apr 8, 2020
1301220
static route declaration shouldnt require auth & validate
mshustov Apr 8, 2020
262a465
update docs
mshustov Apr 8, 2020
02d8315
use HttpResources service instad of rendering
mshustov Apr 8, 2020
6d0de20
Merge branch 'master' into issue-50654-http-resources
mshustov Apr 14, 2020
40a9b7f
address comments
mshustov Apr 14, 2020
7f52fb3
update docs
mshustov Apr 14, 2020
02776eb
roll back unnecessary changes
mshustov Apr 14, 2020
c7712ac
use getVars for rendering
mshustov Apr 14, 2020
e01d8ff
dont pass app. it is not public API
mshustov Apr 14, 2020
54d3a70
Merge branch 'master' into issue-50654-http-resources
mshustov Apr 14, 2020
be8380e
Merge branch 'master' into issue-50654-http-resources
mshustov Apr 16, 2020
9e58fe0
remove static registers
mshustov Apr 16, 2020
ce0b74e
update migration guide
mshustov Apr 16, 2020
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
1 change: 1 addition & 0 deletions src/core/server/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export {
LifecycleResponseFactory,
RedirectResponseOptions,
RequestHandler,
RequestHandlerWrapper,
ResponseError,
ResponseErrorAttributes,
ResponseHeaders,
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/http/lifecycle/on_pre_response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ function findHeadersIntersection(
log: Logger
) {
Object.keys(headers).forEach(headerName => {
if (responseHeaders[headerName] !== undefined) {
if (Reflect.has(responseHeaders, headerName)) {
log.warn(`onPreResponseHandler rewrote a response header [${headerName}].`);
}
});
Expand Down
16 changes: 3 additions & 13 deletions src/core/server/http/router/error_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,10 @@
*/

import Boom from 'boom';
import { KibanaRequest } from './request';
import { KibanaResponseFactory } from './response';
import { RequestHandler } from './router';
import { RequestHandlerContext } from '../../../server';
import { RouteMethod } from './route';
import { RequestHandlerWrapper } from './router';

export const wrapErrors = <P, Q, B>(
handler: RequestHandler<P, Q, B, RouteMethod>
): RequestHandler<P, Q, B, RouteMethod> => {
return async (
context: RequestHandlerContext,
request: KibanaRequest<P, Q, B, RouteMethod>,
response: KibanaResponseFactory
) => {
export const wrapErrors: RequestHandlerWrapper = handler => {
return async (context, request, response) => {
try {
return await handler(context, request, response);
} catch (e) {
Expand Down
6 changes: 3 additions & 3 deletions src/core/server/http/router/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export type Headers = { [header in KnownHeaders]?: string | string[] | undefined
* Http response headers to set.
* @public
*/
export type ResponseHeaders = { [header in KnownHeaders]?: string | string[] } & {
[header: string]: string | string[];
};
export type ResponseHeaders =
| Record<KnownHeaders, string | string[]>
| Record<string, string | string[]>;

const normalizeHeaderField = (field: string) => field.trim().toLowerCase();

Expand Down
2 changes: 1 addition & 1 deletion src/core/server/http/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

export { Headers, filterHeaders, ResponseHeaders, KnownHeaders } from './headers';
export { Router, RequestHandler, IRouter, RouteRegistrar } from './router';
export { Router, RequestHandler, RequestHandlerWrapper, IRouter, RouteRegistrar } from './router';
export {
KibanaRequest,
KibanaRequestEvents,
Expand Down
34 changes: 28 additions & 6 deletions src/core/server/http/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export interface IRouter {
* Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
* @param handler {@link RequestHandler} - a route handler to wrap
*/
handleLegacyErrors: <P, Q, B>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B>;
handleLegacyErrors: RequestHandlerWrapper;

/**
* Returns all routes registered with this router.
Expand Down Expand Up @@ -237,9 +237,7 @@ export class Router implements IRouter {
return [...this.routes];
}

public handleLegacyErrors<P, Q, B>(handler: RequestHandler<P, Q, B>): RequestHandler<P, Q, B> {
return wrapErrors(handler);
}
public handleLegacyErrors = wrapErrors;

private async handle<P, Q, B>({
routeSchemas,
Expand Down Expand Up @@ -316,9 +314,33 @@ export type RequestHandler<
P = unknown,
Q = unknown,
B = unknown,
Method extends RouteMethod = any
Method extends RouteMethod = any,
ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory
> = (
context: RequestHandlerContext,
request: KibanaRequest<P, Q, B, Method>,
response: KibanaResponseFactory
response: ResponseFactory
) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;

/**
* Type-safe wrapper for {@link RequestHandler} function.
* @public
* @example
* ```typescript
* export const wrapper: RequestHandlerWrapper = handler => {
* return async (context, request, response) => {
* // do some logic
* ...
* };
* }
* ```
*/
export type RequestHandlerWrapper = <
P,
Q,
B,
Method extends RouteMethod = any,
ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory
>(
handler: RequestHandler<P, Q, B, Method, ResponseFactory>
) => RequestHandler<P, Q, B, Method, ResponseFactory>;
52 changes: 52 additions & 0 deletions src/core/server/http_resources/http_resources_service.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { httpServerMock } from '../http/http_server.mocks';
import { HttpResources, HttpResourcesServiceToolkit } from './types';

const createHttpResourcesMock = (): jest.Mocked<HttpResources> => ({
registerCoreApp: jest.fn(),
registerAnonymousCoreApp: jest.fn(),
register: jest.fn(),
});

function createInternalHttpResourcesSetup() {
return {
createRegistrar: createHttpResourcesMock,
};
}

function createHttpResourcesResponseFactory() {
const mocked: jest.Mocked<HttpResourcesServiceToolkit> = {
renderCoreApp: jest.fn(),
renderAnonymousCoreApp: jest.fn(),
renderHtml: jest.fn(),
renderJs: jest.fn(),
};

return {
...httpServerMock.createResponseFactory(),
...mocked,
};
}

export const httpResourcesMock = {
createRegistrar: createHttpResourcesMock,
createSetupContract: createInternalHttpResourcesSetup,
createResponseFactory: createHttpResourcesResponseFactory,
};
178 changes: 178 additions & 0 deletions src/core/server/http_resources/http_resources_service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { IRouter, RouteConfig } from '../http';

import { coreMock } from '../mocks';
import { mockCoreContext } from '../core_context.mock';
import { httpServiceMock } from '../http/http_service.mock';
import { httpServerMock } from '../http/http_server.mocks';
import { renderingMock } from '../rendering/rendering_service.mock';
import { HttpResourcesService, SetupDeps } from './http_resources_service';
import { httpResourcesMock } from './http_resources_service.mock';

const coreContext = mockCoreContext.create();

describe('HttpResources service', () => {
let service: HttpResourcesService;
let setupDeps: SetupDeps;
let router: jest.Mocked<IRouter>;
const kibanaRequest = httpServerMock.createKibanaRequest();
const context = { core: coreMock.createRequestHandlerContext() };
beforeEach(() => {
setupDeps = {
http: httpServiceMock.createSetupContract(),
rendering: renderingMock.createSetupContract(),
};
service = new HttpResourcesService(coreContext);
router = httpServiceMock.createRouter();
});
describe('#createRegistrar', () => {
describe('registerCoreApp', () => {
it('registers core app with route config', async () => {
const routeConfig: RouteConfig<any, any, any, 'get'> = { path: '/', validate: false };
const { createRegistrar } = await service.setup(setupDeps);
const { registerCoreApp } = createRegistrar(router);
registerCoreApp(routeConfig);

const [[routerConfig]] = router.get.mock.calls;
expect(routerConfig).toBe(routeConfig);
});

it('renders page with user settings and CSP header', async () => {
const routeConfig: RouteConfig<any, any, any, 'get'> = { path: '/', validate: false };
const { createRegistrar } = await service.setup(setupDeps);
const { registerCoreApp } = createRegistrar(router);
registerCoreApp(routeConfig);

const [[, routeHandler]] = router.get.mock.calls;

const responseFactory = httpResourcesMock.createResponseFactory();
await routeHandler(context, kibanaRequest, responseFactory);

expect(setupDeps.rendering.render).toHaveBeenCalledWith(
kibanaRequest,
context.core.uiSettings.client,
{
includeUserSettings: true,
}
);

expect(responseFactory.ok).toHaveBeenCalledWith({
body: '<body />',
headers: {
'content-security-policy':
"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
},
});
});

it('can attach headers, except the CSP header', async () => {
const routeConfig: RouteConfig<any, any, any, 'get'> = { path: '/', validate: false };
const { createRegistrar } = await service.setup(setupDeps);
const { registerCoreApp } = createRegistrar(router);
registerCoreApp(routeConfig, {
headers: {
'content-security-policy': "script-src 'unsafe-eval'",
'x-kibana': '42',
},
});

const [[, routeHandler]] = router.get.mock.calls;

const responseFactory = httpResourcesMock.createResponseFactory();
await routeHandler(context, kibanaRequest, responseFactory);

expect(responseFactory.ok).toHaveBeenCalledWith({
body: '<body />',
headers: {
'x-kibana': '42',
'content-security-policy':
"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
},
});
});
});
describe('registerAnonymousCoreApp', () => {
it('registers core app with route config', async () => {
const routeConfig: RouteConfig<any, any, any, 'get'> = { path: '/', validate: false };
const { createRegistrar } = await service.setup(setupDeps);
const { registerAnonymousCoreApp } = createRegistrar(router);
registerAnonymousCoreApp(routeConfig);

const [[routerConfig]] = router.get.mock.calls;
expect(routerConfig).toBe(routeConfig);
});

it('renders page with user settings and CSP header', async () => {
const routeConfig: RouteConfig<any, any, any, 'get'> = { path: '/', validate: false };
const { createRegistrar } = await service.setup(setupDeps);
const { registerAnonymousCoreApp } = createRegistrar(router);
registerAnonymousCoreApp(routeConfig);

const [[, routeHandler]] = router.get.mock.calls;

const responseFactory = httpResourcesMock.createResponseFactory();
await routeHandler(context, kibanaRequest, responseFactory);

expect(setupDeps.rendering.render).toHaveBeenCalledWith(
kibanaRequest,
context.core.uiSettings.client,
{
includeUserSettings: false,
}
);

expect(responseFactory.ok).toHaveBeenCalledWith({
body: '<body />',
headers: {
'content-security-policy':
"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
},
});
});

it('can attach headers, except the CSP header', async () => {
const routeConfig: RouteConfig<any, any, any, 'get'> = { path: '/', validate: false };
const { createRegistrar } = await service.setup(setupDeps);
const { registerAnonymousCoreApp } = createRegistrar(router);
registerAnonymousCoreApp(routeConfig, {
headers: {
'content-security-policy': "script-src 'unsafe-eval'",
'x-kibana': '42',
},
});

const [[, routeHandler]] = router.get.mock.calls;

const responseFactory = httpResourcesMock.createResponseFactory();
await routeHandler(context, kibanaRequest, responseFactory);

expect(responseFactory.ok).toHaveBeenCalledWith({
body: '<body />',
headers: {
'x-kibana': '42',
'content-security-policy':
"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
},
});
});
});
it.todo('register');
});
});
Loading