From 78e3a3c350da9837759c619c941e273986aff16a Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 16 Apr 2020 16:09:39 +0200 Subject: [PATCH] [NP] add http resources sub-service (#61797) * add HttpResources basic implementation * expose http resources to plugins * add mocks * move http resources to a separate service * hide rendering service * adopt internal types * expose HttpResources service to plugins * update platform mocks * plugins start using HttpResources API * remove RenderingServiceSetup export * RenderingServiceSetup --> InternalRenderingServiceSetup * improve types * remove httpRespources leftovers from http service * remove rendering types from RequestHanlderContext * fix security plugin tests * add unit tests for httpResources service * add unit tests * remove outdated cache-control header * restructure http resources service * merge getUiPlugins and discover * static route declaration shouldnt require auth & validate * update docs * use HttpResources service instad of rendering * address comments * update docs * roll back unnecessary changes * use getVars for rendering * dont pass app. it is not public API * remove static registers * update migration guide --- ...ibana-plugin-core-server.coresetup.http.md | 4 +- .../kibana-plugin-core-server.coresetup.md | 2 +- ...kibana-plugin-core-server.httpresources.md | 20 ++ ...ugin-core-server.httpresources.register.md | 13 + ...rver.httpresourcesrenderoptions.headers.md | 18 ++ ...-core-server.httpresourcesrenderoptions.md | 20 ++ ...core-server.httpresourcesrequesthandler.md | 18 ++ ...ore-server.httpresourcesresponseoptions.md | 13 + ...core-server.httpresourcesservicetoolkit.md | 23 ++ ...esservicetoolkit.renderanonymouscoreapp.md | 13 + ...tpresourcesservicetoolkit.rendercoreapp.md | 13 + ....httpresourcesservicetoolkit.renderhtml.md | 13 + ...er.httpresourcesservicetoolkit.renderjs.md | 13 + ...-core-server.irouter.handlelegacyerrors.md | 2 +- .../kibana-plugin-core-server.irouter.md | 2 +- ...ugin-core-server.iscopedrenderingclient.md | 19 -- ...re-server.iscopedrenderingclient.render.md | 41 --- ...ugin-core-server.legacyservicesetupdeps.md | 1 + ...server.legacyservicesetupdeps.uiplugins.md | 11 + .../core/server/kibana-plugin-core-server.md | 9 +- ...ibana-plugin-core-server.requesthandler.md | 2 +- ...-core-server.requesthandlercontext.core.md | 1 - ...lugin-core-server.requesthandlercontext.md | 4 +- ...lugin-core-server.requesthandlerwrapper.md | 27 ++ ...bana-plugin-core-server.responseheaders.md | 6 +- src/core/MIGRATION.md | 38 +-- src/core/MIGRATION_EXAMPLES.md | 32 +-- src/core/server/http/index.ts | 1 + .../server/http/lifecycle/on_pre_response.ts | 2 +- src/core/server/http/router/error_wrapper.ts | 16 +- src/core/server/http/router/headers.ts | 6 +- src/core/server/http/router/index.ts | 2 +- src/core/server/http/router/router.ts | 34 ++- .../http_resources_service.mock.ts | 50 ++++ .../http_resources_service.test.ts | 258 ++++++++++++++++++ .../http_resources/http_resources_service.ts | 130 +++++++++ src/core/server/http_resources/index.ts | 29 ++ .../http_resources_service.test.ts | 203 ++++++++++++++ src/core/server/http_resources/types.ts | 116 ++++++++ src/core/server/index.ts | 24 +- src/core/server/internal_types.ts | 4 + src/core/server/legacy/legacy_service.test.ts | 28 +- src/core/server/legacy/legacy_service.ts | 6 +- src/core/server/legacy/types.ts | 7 +- src/core/server/mocks.ts | 10 +- src/core/server/plugins/index.ts | 7 +- src/core/server/plugins/plugin_context.ts | 5 +- .../server/plugins/plugins_service.mock.ts | 15 +- .../server/plugins/plugins_service.test.ts | 29 +- src/core/server/plugins/plugins_service.ts | 55 ++-- src/core/server/rendering/__mocks__/params.ts | 4 +- .../rendering/__mocks__/rendering_service.ts | 4 +- .../rendering/rendering_service.mock.ts | 31 +++ .../rendering/rendering_service.test.ts | 17 +- .../server/rendering/rendering_service.tsx | 42 +-- src/core/server/rendering/types.ts | 29 +- src/core/server/server.api.md | 63 +++-- src/core/server/server.test.ts | 10 +- src/core/server/server.ts | 49 ++-- src/legacy/server/kbn_server.d.ts | 5 +- src/plugins/share/server/routes/goto.ts | 10 +- .../plugins/rendering_plugin/server/plugin.ts | 20 +- x-pack/plugins/security/server/plugin.ts | 2 +- .../server/routes/authentication/index.ts | 11 - .../server/routes/authentication/oidc.ts | 47 ++-- .../server/routes/authentication/saml.ts | 34 +-- .../security/server/routes/index.mock.ts | 2 + .../plugins/security/server/routes/index.ts | 10 +- .../server/routes/views/account_management.ts | 11 +- .../server/routes/views/index.test.ts | 33 ++- .../server/routes/views/logged_out.test.ts | 37 +-- .../server/routes/views/logged_out.ts | 10 +- .../server/routes/views/login.test.ts | 51 ++-- .../security/server/routes/views/login.ts | 9 +- .../security/server/routes/views/logout.ts | 17 +- .../routes/views/overwritten_session.ts | 11 +- x-pack/plugins/spaces/server/plugin.ts | 4 +- .../spaces/server/routes/views/index.ts | 21 +- 78 files changed, 1486 insertions(+), 523 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresources.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresources.register.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.headers.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesresponseoptions.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderhtml.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderjs.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.render.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.uiplugins.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md create mode 100644 src/core/server/http_resources/http_resources_service.mock.ts create mode 100644 src/core/server/http_resources/http_resources_service.test.ts create mode 100644 src/core/server/http_resources/http_resources_service.ts create mode 100644 src/core/server/http_resources/index.ts create mode 100644 src/core/server/http_resources/integration_tests/http_resources_service.test.ts create mode 100644 src/core/server/http_resources/types.ts create mode 100644 src/core/server/rendering/rendering_service.mock.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.http.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.http.md index dcc1d754feb7cd..a8028827cc0a46 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.http.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.http.md @@ -9,5 +9,7 @@ Signature: ```typescript -http: HttpServiceSetup; +http: HttpServiceSetup & { + resources: HttpResources; + }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index c10b460da8b4f7..30c054345928bf 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -20,7 +20,7 @@ export interface CoreSetupContextSetup | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | | [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | -| [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | +| [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup & {
resources: HttpResources;
} | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | | [metrics](./kibana-plugin-core-server.coresetup.metrics.md) | MetricsServiceSetup | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | | [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | | [status](./kibana-plugin-core-server.coresetup.status.md) | StatusServiceSetup | [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresources.md b/docs/development/core/server/kibana-plugin-core-server.httpresources.md new file mode 100644 index 00000000000000..cb3170e989e172 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresources.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResources](./kibana-plugin-core-server.httpresources.md) + +## HttpResources interface + +HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. Provides API allowing plug-ins to respond with: - a pre-configured HTML page bootstrapping Kibana client app - custom HTML page - custom JS script file. + +Signature: + +```typescript +export interface HttpResources +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [register](./kibana-plugin-core-server.httpresources.register.md) | <P, Q, B>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B>) => void | To register a route handler executing passed function to form response. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresources.register.md b/docs/development/core/server/kibana-plugin-core-server.httpresources.register.md new file mode 100644 index 00000000000000..fe3803a6ffe52a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresources.register.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResources](./kibana-plugin-core-server.httpresources.md) > [register](./kibana-plugin-core-server.httpresources.register.md) + +## HttpResources.register property + +To register a route handler executing passed function to form response. + +Signature: + +```typescript +register: (route: RouteConfig, handler: HttpResourcesRequestHandler) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.headers.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.headers.md new file mode 100644 index 00000000000000..bb6dec504ff425 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.headers.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesRenderOptions](./kibana-plugin-core-server.httpresourcesrenderoptions.md) > [headers](./kibana-plugin-core-server.httpresourcesrenderoptions.headers.md) + +## HttpResourcesRenderOptions.headers property + +HTTP Headers with additional information about response. + +Signature: + +```typescript +headers?: ResponseHeaders; +``` + +## Remarks + +All HTML pages are already pre-configured with `content-security-policy` header that cannot be overridden. + diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.md new file mode 100644 index 00000000000000..6563e3c636a99e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrenderoptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesRenderOptions](./kibana-plugin-core-server.httpresourcesrenderoptions.md) + +## HttpResourcesRenderOptions interface + +Allows to configure HTTP response parameters + +Signature: + +```typescript +export interface HttpResourcesRenderOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [headers](./kibana-plugin-core-server.httpresourcesrenderoptions.headers.md) | ResponseHeaders | HTTP Headers with additional information about response. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md new file mode 100644 index 00000000000000..20f930382955e4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesRequestHandler](./kibana-plugin-core-server.httpresourcesrequesthandler.md) + +## HttpResourcesRequestHandler type + +Extended version of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) having access to [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) to respond with HTML or JS resources. + +Signature: + +```typescript +export declare type HttpResourcesRequestHandler

= RequestHandler; +``` + +## Example + +\`\`\`typescript httpResources.register({ path: '/login', validate: { params: schema.object({ id: schema.string() }), }, }, async (context, request, response) => { //.. return response.renderCoreApp(); }); + diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesresponseoptions.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesresponseoptions.md new file mode 100644 index 00000000000000..2ea3ea7e58c786 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesresponseoptions.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesResponseOptions](./kibana-plugin-core-server.httpresourcesresponseoptions.md) + +## HttpResourcesResponseOptions type + +HTTP Resources response parameters + +Signature: + +```typescript +export declare type HttpResourcesResponseOptions = HttpResponseOptions; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.md new file mode 100644 index 00000000000000..1c221e13f534fd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) + +## HttpResourcesServiceToolkit interface + +Extended set of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) helpers used to respond with HTML or JS resource. + +Signature: + +```typescript +export interface HttpResourcesServiceToolkit +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [renderAnonymousCoreApp](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md) | (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse> | To respond with HTML page bootstrapping Kibana application without retrieving user-specific information. | +| [renderCoreApp](./kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md) | (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse> | To respond with HTML page bootstrapping Kibana application. | +| [renderHtml](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderhtml.md) | (options: HttpResourcesResponseOptions) => IKibanaResponse | To respond with a custom HTML page. | +| [renderJs](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderjs.md) | (options: HttpResourcesResponseOptions) => IKibanaResponse | To respond with a custom JS script file. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md new file mode 100644 index 00000000000000..3dce9d88c80363 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) > [renderAnonymousCoreApp](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md) + +## HttpResourcesServiceToolkit.renderAnonymousCoreApp property + +To respond with HTML page bootstrapping Kibana application without retrieving user-specific information. + +Signature: + +```typescript +renderAnonymousCoreApp: (options?: HttpResourcesRenderOptions) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md new file mode 100644 index 00000000000000..eb4f095bc19bed --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) > [renderCoreApp](./kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md) + +## HttpResourcesServiceToolkit.renderCoreApp property + +To respond with HTML page bootstrapping Kibana application. + +Signature: + +```typescript +renderCoreApp: (options?: HttpResourcesRenderOptions) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderhtml.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderhtml.md new file mode 100644 index 00000000000000..325d19625df449 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderhtml.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) > [renderHtml](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderhtml.md) + +## HttpResourcesServiceToolkit.renderHtml property + +To respond with a custom HTML page. + +Signature: + +```typescript +renderHtml: (options: HttpResourcesResponseOptions) => IKibanaResponse; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderjs.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderjs.md new file mode 100644 index 00000000000000..f8d4418fc6cba0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderjs.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) > [renderJs](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderjs.md) + +## HttpResourcesServiceToolkit.renderJs property + +To respond with a custom JS script file. + +Signature: + +```typescript +renderJs: (options: HttpResourcesResponseOptions) => IKibanaResponse; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.handlelegacyerrors.md b/docs/development/core/server/kibana-plugin-core-server.irouter.handlelegacyerrors.md index 94cf3c94187b0d..35d109975c83a6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.handlelegacyerrors.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.handlelegacyerrors.md @@ -9,5 +9,5 @@ Wrap a router handler to catch and converts legacy boom errors to proper custom Signature: ```typescript -handleLegacyErrors: (handler: RequestHandler) => RequestHandler; +handleLegacyErrors: RequestHandlerWrapper; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.md b/docs/development/core/server/kibana-plugin-core-server.irouter.md index 073f02f1a4191b..4bade638a65a57 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.md @@ -18,7 +18,7 @@ export interface IRouter | --- | --- | --- | | [delete](./kibana-plugin-core-server.irouter.delete.md) | RouteRegistrar<'delete'> | Register a route handler for DELETE request. | | [get](./kibana-plugin-core-server.irouter.get.md) | RouteRegistrar<'get'> | Register a route handler for GET request. | -| [handleLegacyErrors](./kibana-plugin-core-server.irouter.handlelegacyerrors.md) | <P, Q, B>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | +| [handleLegacyErrors](./kibana-plugin-core-server.irouter.handlelegacyerrors.md) | RequestHandlerWrapper | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | | [patch](./kibana-plugin-core-server.irouter.patch.md) | RouteRegistrar<'patch'> | Register a route handler for PATCH request. | | [post](./kibana-plugin-core-server.irouter.post.md) | RouteRegistrar<'post'> | Register a route handler for POST request. | | [put](./kibana-plugin-core-server.irouter.put.md) | RouteRegistrar<'put'> | Register a route handler for PUT request. | diff --git a/docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.md b/docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.md deleted file mode 100644 index 0632b5e5e2297a..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IScopedRenderingClient](./kibana-plugin-core-server.iscopedrenderingclient.md) - -## IScopedRenderingClient interface - - -Signature: - -```typescript -export interface IScopedRenderingClient -``` - -## Methods - -| Method | Description | -| --- | --- | -| [render(options)](./kibana-plugin-core-server.iscopedrenderingclient.render.md) | Generate a KibanaResponse which renders an HTML page bootstrapped with the core bundle. Intended as a response body for HTTP route handlers. | - diff --git a/docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.render.md b/docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.render.md deleted file mode 100644 index ca114bed211491..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.render.md +++ /dev/null @@ -1,41 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IScopedRenderingClient](./kibana-plugin-core-server.iscopedrenderingclient.md) > [render](./kibana-plugin-core-server.iscopedrenderingclient.render.md) - -## IScopedRenderingClient.render() method - -Generate a `KibanaResponse` which renders an HTML page bootstrapped with the `core` bundle. Intended as a response body for HTTP route handlers. - -Signature: - -```typescript -render(options?: Pick): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | Pick<IRenderOptions, 'includeUserSettings'> | | - -Returns: - -`Promise` - -## Example - - -```ts -router.get( - { path: '/', validate: false }, - (context, request, response) => - response.ok({ - body: await context.core.rendering.render(), - headers: { - 'content-security-policy': context.core.http.csp.header, - }, - }) -); - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.md b/docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.md index f037b7b3e7cb29..a5c1d59be06d35 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.md @@ -20,4 +20,5 @@ export interface LegacyServiceSetupDeps | --- | --- | --- | | [core](./kibana-plugin-core-server.legacyservicesetupdeps.core.md) | LegacyCoreSetup | | | [plugins](./kibana-plugin-core-server.legacyservicesetupdeps.plugins.md) | Record<string, unknown> | | +| [uiPlugins](./kibana-plugin-core-server.legacyservicesetupdeps.uiplugins.md) | UiPlugins | | diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.uiplugins.md b/docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.uiplugins.md new file mode 100644 index 00000000000000..d19a7dfcbfcfad --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyservicesetupdeps.uiplugins.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyServiceSetupDeps](./kibana-plugin-core-server.legacyservicesetupdeps.md) > [uiPlugins](./kibana-plugin-core-server.legacyservicesetupdeps.uiplugins.md) + +## LegacyServiceSetupDeps.uiPlugins property + +Signature: + +```typescript +uiPlugins: UiPlugins; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 5c0f10cac51793..5450e84417f89b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -80,6 +80,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | +| [HttpResources](./kibana-plugin-core-server.httpresources.md) | HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. Provides API allowing plug-ins to respond with: - a pre-configured HTML page bootstrapping Kibana client app - custom HTML page - custom JS script file. | +| [HttpResourcesRenderOptions](./kibana-plugin-core-server.httpresourcesrenderoptions.md) | Allows to configure HTTP response parameters | +| [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) | Extended set of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) helpers used to respond with HTML or JS resource. | | [HttpResponseOptions](./kibana-plugin-core-server.httpresponseoptions.md) | HTTP response parameters | | [HttpServerInfo](./kibana-plugin-core-server.httpserverinfo.md) | | | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | @@ -92,7 +95,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IndexSettingsDeprecationInfo](./kibana-plugin-core-server.indexsettingsdeprecationinfo.md) | | | [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) | | | [IRouter](./kibana-plugin-core-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-core-server.routeconfig.md) and [RequestHandler](./kibana-plugin-core-server.requesthandler.md) for more information about arguments to route registrations. | -| [IScopedRenderingClient](./kibana-plugin-core-server.iscopedrenderingclient.md) | | | [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | | [KibanaRequestEvents](./kibana-plugin-core-server.kibanarequestevents.md) | Request events. | | [KibanaRequestRoute](./kibana-plugin-core-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | @@ -118,7 +120,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginConfigDescriptor](./kibana-plugin-core-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | | [PluginInitializerContext](./kibana-plugin-core-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | | [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | -| [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [rendering](./kibana-plugin-core-server.iscopedrenderingclient.md) - Rendering client which uses the data of the incoming request - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.dataClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | +| [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.dataClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | | [RouteConfig](./kibana-plugin-core-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-core-server.routeconfigoptions.md) | Additional route options. | | [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md) | Additional body options for a route | @@ -216,6 +218,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HandlerFunction](./kibana-plugin-core-server.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-core-server.icontextcontainer.md) | | [HandlerParameters](./kibana-plugin-core-server.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-core-server.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-core-server.handlercontexttype.md). | | [Headers](./kibana-plugin-core-server.headers.md) | Http request headers to read. | +| [HttpResourcesRequestHandler](./kibana-plugin-core-server.httpresourcesrequesthandler.md) | Extended version of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) having access to [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) to respond with HTML or JS resources. | +| [HttpResourcesResponseOptions](./kibana-plugin-core-server.httpresourcesresponseoptions.md) | HTTP Resources response parameters | | [HttpResponsePayload](./kibana-plugin-core-server.httpresponsepayload.md) | Data send to the client as a response payload. | | [IBasePath](./kibana-plugin-core-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-core-server.basepath.md) | | [IClusterClient](./kibana-plugin-core-server.iclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-core-server.clusterclient.md). | @@ -245,6 +249,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RequestHandler](./kibana-plugin-core-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. | | [RequestHandlerContextContainer](./kibana-plugin-core-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | | [RequestHandlerContextProvider](./kibana-plugin-core-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | +| [RequestHandlerWrapper](./kibana-plugin-core-server.requesthandlerwrapper.md) | Type-safe wrapper for [RequestHandler](./kibana-plugin-core-server.requesthandler.md) function. | | [ResponseError](./kibana-plugin-core-server.responseerror.md) | Error message and optional data send to the client in case of error. | | [ResponseErrorAttributes](./kibana-plugin-core-server.responseerrorattributes.md) | Additional data to provide error details. | | [ResponseHeaders](./kibana-plugin-core-server.responseheaders.md) | Http response headers to set. | diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md index 156f38fab0983b..cecef7c923568e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han Signature: ```typescript -export declare type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export declare type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md index 3c6bee114b6ab3..0d640e52c3a03f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md @@ -8,7 +8,6 @@ ```typescript core: { - rendering: IScopedRenderingClient; savedObjects: { client: SavedObjectsClientContract; typeRegistry: ISavedObjectTypeRegistry; diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md index b65ae47f0e0c11..0966b91a4ebf21 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md @@ -6,7 +6,7 @@ Plugin specific context passed to a route handler. -Provides the following clients and services: - [rendering](./kibana-plugin-core-server.iscopedrenderingclient.md) - Rendering client which uses the data of the incoming request - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.dataClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request +Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.dataClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-core-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request Signature: @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
rendering: IScopedRenderingClient;
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
} | | +| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md new file mode 100644 index 00000000000000..a9fe188ee2bffd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RequestHandlerWrapper](./kibana-plugin-core-server.requesthandlerwrapper.md) + +## RequestHandlerWrapper type + +Type-safe wrapper for [RequestHandler](./kibana-plugin-core-server.requesthandler.md) function. + +Signature: + +```typescript +export declare type RequestHandlerWrapper = (handler: RequestHandler) => RequestHandler; +``` + +## Example + + +```typescript +export const wrapper: RequestHandlerWrapper = handler => { + return async (context, request, response) => { + // do some logic + ... + }; +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.responseheaders.md b/docs/development/core/server/kibana-plugin-core-server.responseheaders.md index 4551d1cab86322..fb7d6a10c6b6c5 100644 --- a/docs/development/core/server/kibana-plugin-core-server.responseheaders.md +++ b/docs/development/core/server/kibana-plugin-core-server.responseheaders.md @@ -9,9 +9,5 @@ Http response headers to set. Signature: ```typescript -export declare type ResponseHeaders = { - [header in KnownHeaders]?: string | string[]; -} & { - [header: string]: string | string[]; -}; +export declare type ResponseHeaders = Record | Record; ``` diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 368d1f47e9c3fa..80f12dd78214d1 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1252,26 +1252,27 @@ import { npStart: { plugins } } from 'ui/new_platform'; In server code, `core` can be accessed from either `server.newPlatform` or `kbnServer.newPlatform`. There are not currently very many services available on the server-side: -| Legacy Platform | New Platform | Notes | -| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -| `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | -| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | -| `server.renderApp()` / `server.renderAppWithDefaultConfig()` | [`context.rendering.render()`](/docs/development/core/server/kibana-plugin-core-server.iscopedrenderingclient.render.md) | [Examples](./MIGRATION_EXAMPLES.md#render-html-content) | -| `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.basepath.md) | | +| Legacy Platform | New Platform | Notes | +| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | +| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | +| `server.renderApp()` | [`response.renderCoreApp()`](docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md) | [Examples](./MIGRATION_EXAMPLES.md#render-html-content) | +| `server.renderAppWithDefaultConfig()` | [`response.renderAnonymousCoreApp()`](docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md) | [Examples](./MIGRATION_EXAMPLES.md#render-html-content) | +| `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`context.core.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md) | | | `server.plugins.elasticsearch.getCluster('admin')` | [`context.core.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md) | | -| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.legacy.createClient`](/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | | -| `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactoryProvider`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.setclientfactoryprovider.md) | | -| `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.addclientwrapper.md) | | +| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.legacy.createClient`](/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | | +| `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactoryProvider`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.setclientfactoryprovider.md) | | +| `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.addclientwrapper.md) | | | `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md) | | -| `server.savedObjects.getScopedSavedObjectsClient` | [`core.savedObjects.getScopedClient`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.getscopedclient.md) | | -| `request.getSavedObjectsClient` | [`context.core.savedObjects.client`](/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md) | | +| `server.savedObjects.getScopedSavedObjectsClient` | [`core.savedObjects.getScopedClient`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.getscopedclient.md) | | +| `request.getSavedObjectsClient` | [`context.core.savedObjects.client`](/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md) | | | `request.getUiSettingsService` | [`context.core.uiSettings.client`](/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md) | | -| `kibana.Plugin.deprecations` | [Handle plugin configuration deprecations](#handle-plugin-config-deprecations) and [`PluginConfigDescriptor.deprecations`](docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.md) | Deprecations from New Platform are not applied to legacy configuration | -| `kibana.Plugin.savedObjectSchemas` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | -| `kibana.Plugin.mappings` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | -| `kibana.Plugin.migrations` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | -| `kibana.Plugin.savedObjectsManagement` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | +| `kibana.Plugin.deprecations` | [Handle plugin configuration deprecations](#handle-plugin-config-deprecations) and [`PluginConfigDescriptor.deprecations`](docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.md) | Deprecations from New Platform are not applied to legacy configuration | +| `kibana.Plugin.savedObjectSchemas` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | +| `kibana.Plugin.mappings` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | +| `kibana.Plugin.migrations` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | +| `kibana.Plugin.savedObjectsManagement` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-core-server.coresetup.md)_ @@ -1494,8 +1495,9 @@ The above example looks in the new platform as ``` The [request handler context](/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md) exposed the next scoped **core** services: -| Legacy Platform | New Platform | -| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------| + +| Legacy Platform | New Platform | +| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `request.getSavedObjectsClient` | [`context.savedObjects.client`](/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md) | | `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md) | | `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md) | diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 37d0b9297ed3cc..8c5fe4875aaea2 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -700,21 +700,15 @@ application.register({ ## Render HTML Content You can return a blank HTML page bootstrapped with the core application bundle from an HTTP route handler -via the `rendering` context. You may wish to do this if you are rendering a chromeless application with a +via the `httpResources` service. You may wish to do this if you are rendering a chromeless application with a custom application route or have other custom rendering needs. -```ts -router.get( +```typescript +httpResources.register( { path: '/chromeless', validate: false }, (context, request, response) => { - const { http, rendering } = context.core; - - return response.ok({ - body: await rendering.render(), // generates an HTML document - headers: { - 'content-security-policy': http.csp.header, - }, - }); + //... some logic + return response.renderCoreApp(); } ); ``` @@ -724,18 +718,12 @@ comprises all UI Settings that are *user provided*, then injected into the page. You may wish to exclude fetching this data if not authorized or to slim the page size. -```ts -router.get( - { path: '/', validate: false }, +```typescript +httpResources.register( + { path: '/', validate: false, options: { authRequired: false } }, (context, request, response) => { - const { http, rendering } = context.core; - - return response.ok({ - body: await rendering.render({ includeUserSettings: false }), - headers: { - 'content-security-policy': http.csp.header, - }, - }); + //... some logic + return response.renderAnonymousCoreApp(); } ); ``` diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index a75eb04fa01204..ca9dfde2e71dce 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -38,6 +38,7 @@ export { LifecycleResponseFactory, RedirectResponseOptions, RequestHandler, + RequestHandlerWrapper, ResponseError, ResponseErrorAttributes, ResponseHeaders, diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts index 50d3d7b47bf8d6..050881472bc805 100644 --- a/src/core/server/http/lifecycle/on_pre_response.ts +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -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}].`); } }); diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index 8f895753c38c32..af99812eff4b37 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -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 = ( - handler: RequestHandler -): RequestHandler => { - return async ( - context: RequestHandlerContext, - request: KibanaRequest, - response: KibanaResponseFactory - ) => { +export const wrapErrors: RequestHandlerWrapper = handler => { + return async (context, request, response) => { try { return await handler(context, request, response); } catch (e) { diff --git a/src/core/server/http/router/headers.ts b/src/core/server/http/router/headers.ts index 19eaee50819966..b79cc0d325f1e7 100644 --- a/src/core/server/http/router/headers.ts +++ b/src/core/server/http/router/headers.ts @@ -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 + | Record; const normalizeHeaderField = (field: string) => field.trim().toLowerCase(); diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts index d254f391ca5e41..83ceff4a25d86d 100644 --- a/src/core/server/http/router/index.ts +++ b/src/core/server/http/router/index.ts @@ -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, diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index bb56ee3727d1a2..b4e7fc2a989b69 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -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: (handler: RequestHandler) => RequestHandler; + handleLegacyErrors: RequestHandlerWrapper; /** * Returns all routes registered with this router. @@ -237,9 +237,7 @@ export class Router implements IRouter { return [...this.routes]; } - public handleLegacyErrors(handler: RequestHandler): RequestHandler { - return wrapErrors(handler); - } + public handleLegacyErrors = wrapErrors; private async handle({ routeSchemas, @@ -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, - response: KibanaResponseFactory + response: ResponseFactory ) => IKibanaResponse | Promise>; + +/** + * Type-safe wrapper for {@link RequestHandler} function. + * @example + * ```typescript + * export const wrapper: RequestHandlerWrapper = handler => { + * return async (context, request, response) => { + * // do some logic + * ... + * }; + * } + * ``` + * @public + */ +export type RequestHandlerWrapper = < + P, + Q, + B, + Method extends RouteMethod = any, + ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory +>( + handler: RequestHandler +) => RequestHandler; diff --git a/src/core/server/http_resources/http_resources_service.mock.ts b/src/core/server/http_resources/http_resources_service.mock.ts new file mode 100644 index 00000000000000..4536b0898cad9b --- /dev/null +++ b/src/core/server/http_resources/http_resources_service.mock.ts @@ -0,0 +1,50 @@ +/* + * 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 => ({ + register: jest.fn(), +}); + +function createInternalHttpResourcesSetup() { + return { + createRegistrar: createHttpResourcesMock, + }; +} + +function createHttpResourcesResponseFactory() { + const mocked: jest.Mocked = { + 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, +}; diff --git a/src/core/server/http_resources/http_resources_service.test.ts b/src/core/server/http_resources/http_resources_service.test.ts new file mode 100644 index 00000000000000..e6f129ba12d78d --- /dev/null +++ b/src/core/server/http_resources/http_resources_service.test.ts @@ -0,0 +1,258 @@ +/* + * 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; + const kibanaRequest = httpServerMock.createKibanaRequest(); + const context = { core: coreMock.createRequestHandlerContext() }; + describe('#createRegistrar', () => { + beforeEach(() => { + setupDeps = { + http: httpServiceMock.createSetupContract(), + rendering: renderingMock.createSetupContract(), + }; + service = new HttpResourcesService(coreContext); + router = httpServiceMock.createRouter(); + }); + + describe('register', () => { + describe('renderCoreApp', () => { + it('formats successful response', async () => { + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderCoreApp(); + }); + 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, + } + ); + }); + + it('can attach headers, except the CSP header', async () => { + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderCoreApp({ + 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: '', + headers: { + 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); + }); + }); + describe('renderAnonymousCoreApp', () => { + it('formats successful response', async () => { + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderAnonymousCoreApp(); + }); + 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, + } + ); + }); + + it('can attach headers, except the CSP header', async () => { + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderAnonymousCoreApp({ + 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: '', + headers: { + 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); + }); + }); + describe('renderHtml', () => { + it('formats successful response', async () => { + const htmlBody = ''; + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderHtml({ body: htmlBody }); + }); + const [[, routeHandler]] = router.get.mock.calls; + + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); + expect(responseFactory.ok).toHaveBeenCalledWith({ + body: htmlBody, + headers: { + 'content-type': 'text/html', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); + }); + + it('can attach headers, except the CSP & "content-type" headers', async () => { + const htmlBody = ''; + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderHtml({ + body: htmlBody, + headers: { + 'content-type': 'text/html5', + '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: htmlBody, + headers: { + 'content-type': 'text/html', + 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); + }); + }); + describe('renderJs', () => { + it('formats successful response', async () => { + const jsBody = 'alert(1);'; + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderJs({ body: jsBody }); + }); + const [[, routeHandler]] = router.get.mock.calls; + + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); + expect(responseFactory.ok).toHaveBeenCalledWith({ + body: jsBody, + headers: { + 'content-type': 'text/javascript', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); + }); + + it('can attach headers, except the CSP & "content-type" headers', async () => { + const jsBody = 'alert(1);'; + const routeConfig: RouteConfig = { path: '/', validate: false }; + const { createRegistrar } = await service.setup(setupDeps); + const { register } = createRegistrar(router); + register(routeConfig, async (ctx, req, res) => { + return res.renderJs({ + body: jsBody, + headers: { + 'content-type': 'text/html', + '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: jsBody, + headers: { + 'content-type': 'text/javascript', + 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); + }); + }); + }); + }); +}); diff --git a/src/core/server/http_resources/http_resources_service.ts b/src/core/server/http_resources/http_resources_service.ts new file mode 100644 index 00000000000000..bc79ad68f40998 --- /dev/null +++ b/src/core/server/http_resources/http_resources_service.ts @@ -0,0 +1,130 @@ +/* + * 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 { RequestHandlerContext } from 'src/core/server'; + +import { CoreContext } from '../core_context'; +import { + IRouter, + RouteConfig, + InternalHttpServiceSetup, + KibanaRequest, + KibanaResponseFactory, +} from '../http'; + +import { Logger } from '../logging'; +import { InternalRenderingServiceSetup } from '../rendering'; +import { CoreService } from '../../types'; + +import { + InternalHttpResourcesSetup, + HttpResources, + HttpResourcesResponseOptions, + HttpResourcesRenderOptions, + HttpResourcesRequestHandler, + HttpResourcesServiceToolkit, +} from './types'; + +export interface SetupDeps { + http: InternalHttpServiceSetup; + rendering: InternalRenderingServiceSetup; +} + +export class HttpResourcesService implements CoreService { + private readonly logger: Logger; + constructor(core: CoreContext) { + this.logger = core.logger.get('http-resources'); + } + + setup(deps: SetupDeps) { + this.logger.debug('setting up HttpResourcesService'); + return { + createRegistrar: this.createRegistrar.bind(this, deps), + }; + } + + start() {} + stop() {} + + private createRegistrar(deps: SetupDeps, router: IRouter): HttpResources { + return { + register: ( + route: RouteConfig, + handler: HttpResourcesRequestHandler + ) => { + return router.get(route, (context, request, response) => { + return handler(context, request, { + ...response, + ...this.createResponseToolkit(deps, context, request, response), + }); + }); + }, + }; + } + + private createResponseToolkit( + deps: SetupDeps, + context: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ): HttpResourcesServiceToolkit { + const cspHeader = deps.http.csp.header; + return { + async renderCoreApp(options: HttpResourcesRenderOptions = {}) { + const body = await deps.rendering.render(request, context.core.uiSettings.client, { + includeUserSettings: true, + }); + + return response.ok({ + body, + headers: { ...options.headers, 'content-security-policy': cspHeader }, + }); + }, + async renderAnonymousCoreApp(options: HttpResourcesRenderOptions = {}) { + const body = await deps.rendering.render(request, context.core.uiSettings.client, { + includeUserSettings: false, + }); + + return response.ok({ + body, + headers: { ...options.headers, 'content-security-policy': cspHeader }, + }); + }, + renderHtml(options: HttpResourcesResponseOptions) { + return response.ok({ + body: options.body, + headers: { + ...options.headers, + 'content-type': 'text/html', + 'content-security-policy': cspHeader, + }, + }); + }, + renderJs(options: HttpResourcesResponseOptions) { + return response.ok({ + body: options.body, + headers: { + ...options.headers, + 'content-type': 'text/javascript', + 'content-security-policy': cspHeader, + }, + }); + }, + }; + } +} diff --git a/src/core/server/http_resources/index.ts b/src/core/server/http_resources/index.ts new file mode 100644 index 00000000000000..b373c6a9efa89b --- /dev/null +++ b/src/core/server/http_resources/index.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +export { HttpResourcesService } from './http_resources_service'; + +export { + HttpResourcesRenderOptions, + HttpResourcesResponseOptions, + HttpResourcesServiceToolkit, + HttpResourcesRequestHandler, + HttpResources, + InternalHttpResourcesSetup, +} from './types'; diff --git a/src/core/server/http_resources/integration_tests/http_resources_service.test.ts b/src/core/server/http_resources/integration_tests/http_resources_service.test.ts new file mode 100644 index 00000000000000..0a5daa02e17e9b --- /dev/null +++ b/src/core/server/http_resources/integration_tests/http_resources_service.test.ts @@ -0,0 +1,203 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import * as kbnTestServer from '../../../../test_utils/kbn_server'; + +describe('http resources service', () => { + describe('register', () => { + let root: ReturnType; + const defaultCspRules = "script-src 'self'"; + beforeEach(async () => { + root = kbnTestServer.createRoot({ + csp: { + rules: [defaultCspRules], + }, + }); + }, 30000); + + afterEach(async () => { + await root.shutdown(); + }); + + describe('renderAnonymousCoreApp', () => { + it('renders core application', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + resources.register({ path: '/render-core', validate: false }, (context, req, res) => + res.renderAnonymousCoreApp() + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/render-core').expect(200); + + expect(response.text.length).toBeGreaterThan(0); + }); + + it('attaches CSP header', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + resources.register({ path: '/render-core', validate: false }, (context, req, res) => + res.renderAnonymousCoreApp() + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/render-core').expect(200); + + expect(response.header['content-security-policy']).toBe(defaultCspRules); + }); + + it('can attach headers, except the CSP header', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + resources.register({ path: '/render-core', validate: false }, (context, req, res) => + res.renderAnonymousCoreApp({ + headers: { + 'content-security-policy': "script-src 'unsafe-eval'", + 'x-kibana': '42', + }, + }) + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/render-core').expect(200); + + expect(response.header['content-security-policy']).toBe(defaultCspRules); + expect(response.header['x-kibana']).toBe('42'); + }); + }); + + describe('custom renders', () => { + it('renders html', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + const htmlBody = ` + + + +

HTML body

+ + + `; + resources.register({ path: '/render-html', validate: false }, (context, req, res) => + res.renderHtml({ body: htmlBody }) + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/render-html').expect(200); + + expect(response.text).toBe(htmlBody); + expect(response.header['content-type']).toBe('text/html; charset=utf-8'); + }); + + it('renders javascript', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + const jsBody = 'window.alert("from js body");'; + resources.register({ path: '/render-js', validate: false }, (context, req, res) => + res.renderJs({ body: jsBody }) + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/render-js').expect(200); + + expect(response.text).toBe(jsBody); + expect(response.header['content-type']).toBe('text/javascript; charset=utf-8'); + }); + + it('attaches CSP header', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + const htmlBody = ` + + + +

HTML body

+ + + `; + resources.register({ path: '/render-html', validate: false }, (context, req, res) => + res.renderHtml({ body: htmlBody }) + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/render-html').expect(200); + + expect(response.header['content-security-policy']).toBe(defaultCspRules); + }); + + it('can attach headers, except the CSP & "content-type" headers', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + resources.register({ path: '/render-core', validate: false }, (context, req, res) => + res.renderHtml({ + body: '

Hi

', + headers: { + 'content-security-policy': "script-src 'unsafe-eval'", + 'content-type': 'text/html', + 'x-kibana': '42', + }, + }) + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/render-core').expect(200); + + expect(response.header['content-security-policy']).toBe(defaultCspRules); + expect(response.header['x-kibana']).toBe('42'); + }); + + it('can adjust route config', async () => { + const { http, httpResources } = await root.setup(); + + const router = http.createRouter(''); + const resources = httpResources.createRegistrar(router); + const validate = { + params: schema.object({ + id: schema.string(), + }), + }; + + resources.register({ path: '/render-js-with-param/{id}', validate }, (context, req, res) => + res.renderJs({ body: `window.alert(${req.params.id});` }) + ); + + await root.start(); + const response = await kbnTestServer.request + .get(root, '/render-js-with-param/42') + .expect(200); + + expect(response.text).toBe('window.alert(42);'); + }); + }); + }); +}); diff --git a/src/core/server/http_resources/types.ts b/src/core/server/http_resources/types.ts new file mode 100644 index 00000000000000..d761e2def10231 --- /dev/null +++ b/src/core/server/http_resources/types.ts @@ -0,0 +1,116 @@ +/* + * 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, + IKibanaResponse, + ResponseHeaders, + HttpResponseOptions, + KibanaResponseFactory, + RequestHandler, +} from '../http'; + +/** + * Allows to configure HTTP response parameters + * @public + */ +export interface HttpResourcesRenderOptions { + /** + * HTTP Headers with additional information about response. + * @remarks + * All HTML pages are already pre-configured with `content-security-policy` header that cannot be overridden. + * */ + headers?: ResponseHeaders; +} + +/** + * HTTP Resources response parameters + * @public + */ +export type HttpResourcesResponseOptions = HttpResponseOptions; + +/** + * Extended set of {@link KibanaResponseFactory} helpers used to respond with HTML or JS resource. + * @public + */ +export interface HttpResourcesServiceToolkit { + /** To respond with HTML page bootstrapping Kibana application. */ + renderCoreApp: (options?: HttpResourcesRenderOptions) => Promise; + /** To respond with HTML page bootstrapping Kibana application without retrieving user-specific information. */ + renderAnonymousCoreApp: (options?: HttpResourcesRenderOptions) => Promise; + /** To respond with a custom HTML page. */ + renderHtml: (options: HttpResourcesResponseOptions) => IKibanaResponse; + /** To respond with a custom JS script file. */ + renderJs: (options: HttpResourcesResponseOptions) => IKibanaResponse; +} + +/** + * Extended version of {@link RequestHandler} having access to {@link HttpResourcesServiceToolkit} + * to respond with HTML or JS resources. + * @param context {@link RequestHandlerContext} - the core context exposed for this request. + * @param request {@link KibanaRequest} - object containing information about requested resource, + * such as path, method, headers, parameters, query, body, etc. + * @param response {@link KibanaResponseFactory} {@libk HttpResourcesServiceToolkit} - a set of helper functions used to respond to a request. + * + * @example + * ```typescript + * httpResources.register({ + * path: '/login', + * validate: { + * params: schema.object({ id: schema.string() }), + * }, + * }, + * async (context, request, response) => { + * //.. + * return response.renderCoreApp(); + * }); + * @public + */ +export type HttpResourcesRequestHandler

= RequestHandler< + P, + Q, + B, + 'get', + KibanaResponseFactory & HttpResourcesServiceToolkit +>; + +/** + * Allows to configure HTTP response parameters + * @internal + */ +export interface InternalHttpResourcesSetup { + createRegistrar(router: IRouter): HttpResources; +} + +/** + * HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. + * Provides API allowing plug-ins to respond with: + * - a pre-configured HTML page bootstrapping Kibana client app + * - custom HTML page + * - custom JS script file. + * @public + */ +export interface HttpResources { + /** To register a route handler executing passed function to form response. */ + register: ( + route: RouteConfig, + handler: HttpResourcesRequestHandler + ) => void; +} diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 039988fa089680..ef57fae159d7ec 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -47,7 +47,8 @@ import { } from './elasticsearch'; import { HttpServiceSetup } from './http'; -import { IScopedRenderingClient } from './rendering'; +import { HttpResources } from './http_resources'; + import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; import { ContextSetup } from './context'; import { IUiSettingsClient, UiSettingsServiceSetup, UiSettingsServiceStart } from './ui_settings'; @@ -146,6 +147,7 @@ export { OnPreResponseInfo, RedirectResponseOptions, RequestHandler, + RequestHandlerWrapper, RequestHandlerContextContainer, RequestHandlerContextProvider, ResponseError, @@ -175,7 +177,15 @@ export { DestructiveRouteMethod, SafeRouteMethod, } from './http'; -export { RenderingServiceSetup, IRenderOptions } from './rendering'; + +export { + HttpResourcesRenderOptions, + HttpResourcesResponseOptions, + HttpResourcesServiceToolkit, + HttpResourcesRequestHandler, +} from './http_resources'; + +export { IRenderOptions } from './rendering'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; export { @@ -313,8 +323,6 @@ export { * Plugin specific context passed to a route handler. * * Provides the following clients and services: - * - {@link IScopedRenderingClient | rendering} - Rendering client - * which uses the data of the incoming request * - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client * which uses the credentials of the incoming request * - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing @@ -330,7 +338,6 @@ export { */ export interface RequestHandlerContext { core: { - rendering: IScopedRenderingClient; savedObjects: { client: SavedObjectsClientContract; typeRegistry: ISavedObjectTypeRegistry; @@ -362,7 +369,10 @@ export interface CoreSetup { getAuthHeaders: () => undefined, } as any, }, + httpResources: httpResourcesMock.createSetupContract(), savedObjects: savedObjectsServiceMock.createInternalSetupContract(), plugins: { initialized: true, contracts: new Map([['plugin-id', 'plugin-value']]), - uiPlugins: { - public: new Map([['plugin-id', {} as DiscoveredPlugin]]), - internal: new Map([ - [ - 'plugin-id', - { - publicTargetDir: 'path/to/target/public', - publicAssetsDir: '/plugins/name/assets/', - }, - ], - ]), - browserConfigs: new Map(), - }, }, rendering: renderingServiceMock, metrics: metricsServiceMock.createInternalSetupContract(), @@ -110,6 +99,19 @@ beforeEach(() => { status: statusServiceMock.createInternalSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, + uiPlugins: { + public: new Map([['plugin-id', {} as DiscoveredPlugin]]), + internal: new Map([ + [ + 'plugin-id', + { + publicTargetDir: 'path/to/target/public', + publicAssetsDir: '/plugins/name/assets/', + }, + ], + ]), + browserConfigs: new Map(), + }, }; startDeps = { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index f77230301ce023..fd3ebfe120599c 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -269,6 +269,7 @@ export class LegacyService implements CoreService { uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient }, }; + const router = setupDeps.core.http.createRouter('', this.legacyId); const coreSetup: CoreSetup = { capabilities: setupDeps.core.capabilities, context: setupDeps.core.context, @@ -283,7 +284,8 @@ export class LegacyService implements CoreService { null, this.legacyId ), - createRouter: () => setupDeps.core.http.createRouter('', this.legacyId), + createRouter: () => router, + resources: setupDeps.core.httpResources.createRegistrar(router), registerOnPreAuth: setupDeps.core.http.registerOnPreAuth, registerAuth: setupDeps.core.http.registerAuth, registerOnPostAuth: setupDeps.core.http.registerOnPostAuth, @@ -342,7 +344,7 @@ export class LegacyService implements CoreService { }, hapiServer: setupDeps.core.http.server, kibanaMigrator: startDeps.core.savedObjects.migrator, - uiPlugins: setupDeps.core.plugins.uiPlugins, + uiPlugins: setupDeps.uiPlugins, elasticsearch: setupDeps.core.elasticsearch, rendering: setupDeps.core.rendering, uiSettings: setupDeps.core.uiSettings, diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 0c1a7730f92a77..38cb9e6835ce99 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -22,8 +22,8 @@ import { Server } from 'hapi'; import { ChromeNavLink } from '../../public'; import { KibanaRequest, LegacyRequest } from '../http'; import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; -import { PluginsServiceSetup, PluginsServiceStart } from '../plugins'; -import { RenderingServiceSetup } from '../rendering'; +import { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from '../plugins'; +import { InternalRenderingServiceSetup } from '../rendering'; import { SavedObjectsLegacyUiExports } from '../types'; /** @@ -34,7 +34,7 @@ export type LegacyVars = Record; type LegacyCoreSetup = InternalCoreSetup & { plugins: PluginsServiceSetup; - rendering: RenderingServiceSetup; + rendering: InternalRenderingServiceSetup; }; type LegacyCoreStart = InternalCoreStart & { plugins: PluginsServiceStart }; @@ -173,6 +173,7 @@ export type LegacyUiExports = SavedObjectsLegacyUiExports & { export interface LegacyServiceSetupDeps { core: LegacyCoreSetup; plugins: Record; + uiPlugins: UiPlugins; } /** diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index faf73044cac4d1..3b9a39db722789 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -23,10 +23,12 @@ import { CspConfig } from './csp'; import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; +import { httpResourcesMock } from './http_resources/http_resources_service.mock'; import { contextServiceMock } from './context/context_service.mock'; import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; import { savedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; import { typeRegistryMock as savedObjectsTypeRegistryMock } from './saved_objects/saved_objects_type_registry.mock'; +import { renderingMock } from './rendering/rendering_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { SharedGlobalConfig } from './plugins'; import { InternalCoreSetup, InternalCoreStart } from './internal_types'; @@ -36,6 +38,7 @@ import { uuidServiceMock } from './uuid/uuid_service.mock'; import { statusServiceMock } from './status/status_service.mock'; export { httpServerMock } from './http/http_server.mocks'; +export { httpResourcesMock } from './http_resources/http_resources_service.mock'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; export { configServiceMock } from './config/config_service.mock'; export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; @@ -45,6 +48,7 @@ export { savedObjectsRepositoryMock } from './saved_objects/service/lib/reposito export { typeRegistryMock as savedObjectsTypeRegistryMock } from './saved_objects/saved_objects_type_registry.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { metricsServiceMock } from './metrics/metrics_service.mock'; +export { renderingMock } from './rendering/rendering_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { @@ -120,6 +124,7 @@ function createCoreSetupMock({ get: httpService.auth.get, isAuthenticated: httpService.auth.isAuthenticated, }, + resources: httpResourcesMock.createRegistrar(), getServerInfo: httpService.getServerInfo, }; httpMock.createRouter.mockImplementation(() => httpService.createRouter('')); @@ -167,6 +172,8 @@ function createInternalCoreSetupMock() { savedObjects: savedObjectsServiceMock.createInternalSetupContract(), status: statusServiceMock.createInternalSetupContract(), uuid: uuidServiceMock.createSetupContract(), + httpResources: httpResourcesMock.createSetupContract(), + rendering: renderingMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), }; return setupDeps; @@ -184,9 +191,6 @@ function createInternalCoreStartMock() { function createCoreRequestHandlerContextMock() { return { - rendering: { - render: jest.fn(), - }, savedObjects: { client: savedObjectsClientMock.create(), typeRegistry: savedObjectsTypeRegistryMock.create(), diff --git a/src/core/server/plugins/index.ts b/src/core/server/plugins/index.ts index c7ef213c8f1875..e480de750bb1a5 100644 --- a/src/core/server/plugins/index.ts +++ b/src/core/server/plugins/index.ts @@ -17,7 +17,12 @@ * under the License. */ -export { PluginsService, PluginsServiceSetup, PluginsServiceStart } from './plugins_service'; +export { + PluginsService, + PluginsServiceSetup, + PluginsServiceStart, + UiPlugins, +} from './plugins_service'; export { config } from './plugins_config'; /** @internal */ export { isNewPlatformPlugin } from './discovery'; diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 61d97aea97459c..ab18a9cbbc0626 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -136,6 +136,8 @@ export function createPluginSetupContext( deps: PluginsServiceSetupDeps, plugin: PluginWrapper ): CoreSetup { + const router = deps.http.createRouter('', plugin.opaqueId); + return { capabilities: { registerProvider: deps.capabilities.registerProvider, @@ -155,7 +157,8 @@ export function createPluginSetupContext( null, plugin.opaqueId ), - createRouter: () => deps.http.createRouter('', plugin.opaqueId), + createRouter: () => router, + resources: deps.httpResources.createRegistrar(router), registerOnPreAuth: deps.http.registerOnPreAuth, registerAuth: deps.http.registerAuth, registerOnPostAuth: deps.http.registerOnPostAuth, diff --git a/src/core/server/plugins/plugins_service.mock.ts b/src/core/server/plugins/plugins_service.mock.ts index 29e5b83b2e4c71..a40566767ddaef 100644 --- a/src/core/server/plugins/plugins_service.mock.ts +++ b/src/core/server/plugins/plugins_service.mock.ts @@ -23,14 +23,10 @@ type PluginsServiceMock = jest.Mocked>; const createSetupContractMock = (): PluginsServiceSetup => ({ contracts: new Map(), - uiPlugins: { - browserConfigs: new Map(), - internal: new Map(), - public: new Map(), - }, initialized: true, }); const createStartContractMock = () => ({ contracts: new Map() }); + const createServiceMock = (): PluginsServiceMock => ({ discover: jest.fn(), setup: jest.fn().mockResolvedValue(createSetupContractMock()), @@ -38,8 +34,17 @@ const createServiceMock = (): PluginsServiceMock => ({ stop: jest.fn(), }); +function createUiPlugins() { + return { + browserConfigs: new Map(), + internal: new Map(), + public: new Map(), + }; +} + export const pluginServiceMock = { create: createServiceMock, createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, + createUiPlugins, }; diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 14147ab9f2a8dd..38fda12bd290f6 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -120,6 +120,7 @@ describe('PluginsService', () => { pluginsService = new PluginsService({ coreId, env, logger, configService }); [mockPluginSystem] = MockPluginsSystem.mock.instances as any; + mockPluginSystem.uiPlugins.mockReturnValue(new Map()); }); afterEach(() => { @@ -202,7 +203,6 @@ describe('PluginsService', () => { .mockImplementation(path => Promise.resolve(!path.includes('disabled'))); mockPluginSystem.setupPlugins.mockResolvedValue(new Map()); - mockPluginSystem.uiPlugins.mockReturnValue(new Map()); mockDiscover.mockReturnValue({ error$: from([]), @@ -234,8 +234,6 @@ describe('PluginsService', () => { const setup = await pluginsService.setup(setupDeps); expect(setup.contracts).toBeInstanceOf(Map); - expect(setup.uiPlugins.public).toBeInstanceOf(Map); - expect(setup.uiPlugins.internal).toBeInstanceOf(Map); expect(mockPluginSystem.addPlugin).not.toHaveBeenCalled(); expect(mockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); expect(mockPluginSystem.setupPlugins).toHaveBeenCalledWith(setupDeps); @@ -273,7 +271,8 @@ describe('PluginsService', () => { plugin$: from([firstPlugin, secondPlugin]), }); - await expect(pluginsService.discover()).resolves.toBeUndefined(); + const { pluginTree } = await pluginsService.discover(); + expect(pluginTree).toBeUndefined(); expect(mockDiscover).toHaveBeenCalledTimes(1); expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); @@ -308,7 +307,8 @@ describe('PluginsService', () => { plugin$: from([firstPlugin, secondPlugin, thirdPlugin, lastPlugin, missingDepsPlugin]), }); - await expect(pluginsService.discover()).resolves.toBeUndefined(); + const { pluginTree } = await pluginsService.discover(); + expect(pluginTree).toBeUndefined(); expect(mockDiscover).toHaveBeenCalledTimes(1); expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(4); @@ -466,12 +466,8 @@ describe('PluginsService', () => { }); mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)])); - await pluginsService.discover(); - const { - uiPlugins: { browserConfigs }, - } = await pluginsService.setup(setupDeps); - - const uiConfig$ = browserConfigs.get('plugin-with-expose'); + const { uiPlugins } = await pluginsService.discover(); + const uiConfig$ = uiPlugins.browserConfigs.get('plugin-with-expose'); expect(uiConfig$).toBeDefined(); const uiConfig = await uiConfig$!.pipe(take(1)).toPromise(); @@ -506,12 +502,8 @@ describe('PluginsService', () => { }); mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)])); - await pluginsService.discover(); - const { - uiPlugins: { browserConfigs }, - } = await pluginsService.setup(setupDeps); - - expect([...browserConfigs.entries()]).toHaveLength(0); + const { uiPlugins } = await pluginsService.discover(); + expect([...uiPlugins.browserConfigs.entries()]).toHaveLength(0); }); }); @@ -539,8 +531,7 @@ describe('PluginsService', () => { describe('uiPlugins.internal', () => { it('includes disabled plugins', async () => { config$.next({ plugins: { initialize: true }, plugin1: { enabled: false } }); - await pluginsService.discover(); - const { uiPlugins } = await pluginsService.setup(setupDeps); + const { uiPlugins } = await pluginsService.discover(); expect(uiPlugins.internal).toMatchInlineSnapshot(` Map { "plugin-1" => Object { diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index a0ecee47c675f2..d7a348affe94ff 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -39,23 +39,25 @@ export interface PluginsServiceSetup { initialized: boolean; /** Setup contracts returned by plugins. */ contracts: Map; - uiPlugins: { - /** - * Paths to all discovered ui plugin entrypoints on the filesystem, even if - * disabled. - */ - internal: Map; - - /** - * Information needed by client-side to load plugins and wire dependencies. - */ - public: Map; - - /** - * Configuration for plugins to be exposed to the client-side. - */ - browserConfigs: Map>; - }; +} + +/** @internal */ +export interface UiPlugins { + /** + * Paths to all discovered ui plugin entrypoints on the filesystem, even if + * disabled. + */ + internal: Map; + + /** + * Information needed by client-side to load plugins and wire dependencies. + */ + public: Map; + + /** + * Configuration for plugins to be exposed to the client-side. + */ + browserConfigs: Map>; } /** @internal */ @@ -97,8 +99,17 @@ export class PluginsService implements CoreService; -export const setupMock: jest.Mocked = { +export const setupMock: jest.Mocked = { render: jest.fn(), }; export const mockSetup = jest.fn().mockResolvedValue(setupMock); diff --git a/src/core/server/rendering/rendering_service.mock.ts b/src/core/server/rendering/rendering_service.mock.ts new file mode 100644 index 00000000000000..7eba332512386d --- /dev/null +++ b/src/core/server/rendering/rendering_service.mock.ts @@ -0,0 +1,31 @@ +/* + * 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 { InternalRenderingServiceSetup } from './types'; + +function createRenderingSetup() { + const mocked: jest.Mocked = { + render: jest.fn().mockResolvedValue(''), + }; + return mocked; +} + +export const renderingMock = { + createSetupContract: createRenderingSetup, +}; diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 43ff4f633085c9..d1c527aca4dbaf 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -22,7 +22,7 @@ import { load } from 'cheerio'; import { httpServerMock } from '../http/http_server.mocks'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { mockRenderingServiceParams, mockRenderingSetupDeps } from './__mocks__/params'; -import { RenderingServiceSetup } from './types'; +import { InternalRenderingServiceSetup } from './types'; import { RenderingService } from './rendering_service'; const INJECTED_METADATA = { @@ -62,15 +62,9 @@ describe('RenderingService', () => { }); describe('setup()', () => { - it('creates instance of RenderingServiceSetup', async () => { - const rendering = await service.setup(mockRenderingSetupDeps); - - expect(rendering.render).toBeInstanceOf(Function); - }); - describe('render()', () => { let uiSettings: ReturnType; - let render: RenderingServiceSetup['render']; + let render: InternalRenderingServiceSetup['render']; beforeEach(async () => { uiSettings = uiSettingsServiceMock.createClient(); @@ -78,6 +72,13 @@ describe('RenderingService', () => { registered: { name: 'title' }, }); render = (await service.setup(mockRenderingSetupDeps)).render; + await service.start({ + legacy: { + legacyInternals: { + getVars: () => ({}), + }, + }, + } as any); }); it('renders "core" page', async () => { diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index dbafd5806bd74a..a02d85d22b2cbb 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -23,41 +23,37 @@ import { take } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; +import { UiPlugins } from '../plugins'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Template } from './views'; +import { LegacyService } from '../legacy'; import { IRenderOptions, RenderingSetupDeps, - RenderingServiceSetup, + InternalRenderingServiceSetup, RenderingMetadata, } from './types'; /** @internal */ -export class RenderingService implements CoreService { +export class RenderingService implements CoreService { + private legacyInternals?: LegacyService['legacyInternals']; constructor(private readonly coreContext: CoreContext) {} public async setup({ http, legacyPlugins, - plugins, - }: RenderingSetupDeps): Promise { - async function getUiConfig(pluginId: string) { - const browserConfig = plugins.uiPlugins.browserConfigs.get(pluginId); - - return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record; - } - + uiPlugins, + }: RenderingSetupDeps): Promise { return { render: async ( request, uiSettings, - { - app = { getId: () => 'core' }, - includeUserSettings = true, - vars = {}, - }: IRenderOptions = {} + { app = { getId: () => 'core' }, includeUserSettings = true, vars }: IRenderOptions = {} ) => { + if (!this.legacyInternals) { + throw new Error('Cannot render before "start"'); + } const { env } = this.coreContext; const basePath = http.basePath.get(request); const serverBasePath = http.basePath.serverBasePath; @@ -87,12 +83,12 @@ export class RenderingService implements CoreService { translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`, }, csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers }, - vars, + vars: vars ?? (await this.legacyInternals!.getVars('core', request)), uiPlugins: await Promise.all( - [...plugins.uiPlugins.public].map(async ([id, plugin]) => ({ + [...uiPlugins.public].map(async ([id, plugin]) => ({ id, plugin, - config: await getUiConfig(id), + config: await this.getUiConfig(uiPlugins, id), })) ), legacyMetadata: { @@ -116,7 +112,15 @@ export class RenderingService implements CoreService { }; } - public async start() {} + public async start({ legacy }: { legacy: LegacyService }) { + this.legacyInternals = legacy.legacyInternals; + } public async stop() {} + + private async getUiConfig(uiPlugins: UiPlugins, pluginId: string) { + const browserConfig = uiPlugins.browserConfigs.get(pluginId); + + return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record; + } } diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index cfaa23d4911396..2a3be930550062 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -23,7 +23,7 @@ import { Env } from '../config'; import { ICspConfig } from '../csp'; import { InternalHttpServiceSetup, KibanaRequest, LegacyRequest } from '../http'; import { LegacyNavLink, LegacyServiceDiscoverPlugins } from '../legacy'; -import { PluginsServiceSetup, DiscoveredPlugin } from '../plugins'; +import { UiPlugins, DiscoveredPlugin } from '../plugins'; import { IUiSettingsClient, UserProvidedValues } from '../ui_settings'; /** @internal */ @@ -75,7 +75,7 @@ export interface RenderingMetadata { export interface RenderingSetupDeps { http: InternalHttpServiceSetup; legacyPlugins: LegacyServiceDiscoverPlugins; - plugins: PluginsServiceSetup; + uiPlugins: UiPlugins; } /** @public */ @@ -102,31 +102,8 @@ export interface IRenderOptions { vars?: Record; } -/** @public */ -export interface IScopedRenderingClient { - /** - * Generate a `KibanaResponse` which renders an HTML page bootstrapped - * with the `core` bundle. Intended as a response body for HTTP route handlers. - * - * @example - * ```ts - * router.get( - * { path: '/', validate: false }, - * (context, request, response) => - * response.ok({ - * body: await context.core.rendering.render(), - * headers: { - * 'content-security-policy': context.core.http.csp.header, - * }, - * }) - * ); - * ``` - */ - render(options?: Pick): Promise; -} - /** @internal */ -export interface RenderingServiceSetup { +export interface InternalRenderingServiceSetup { /** * Generate a `KibanaResponse` which renders an HTML page bootstrapped * with the `core` bundle or the ID of another specified legacy bundle. diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 37051da4b17da4..e2af94437c9f33 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -632,7 +632,9 @@ export interface CoreSetup; // (undocumented) - http: HttpServiceSetup; + http: HttpServiceSetup & { + resources: HttpResources; + }; // (undocumented) metrics: MetricsServiceSetup; // (undocumented) @@ -861,6 +863,30 @@ export type Headers = { [header: string]: string | string[] | undefined; }; +// @public +export interface HttpResources { + register: (route: RouteConfig, handler: HttpResourcesRequestHandler) => void; +} + +// @public +export interface HttpResourcesRenderOptions { + headers?: ResponseHeaders; +} + +// @public +export type HttpResourcesRequestHandler

= RequestHandler; + +// @public +export type HttpResourcesResponseOptions = HttpResponseOptions; + +// @public +export interface HttpResourcesServiceToolkit { + renderAnonymousCoreApp: (options?: HttpResourcesRenderOptions) => Promise; + renderCoreApp: (options?: HttpResourcesRenderOptions) => Promise; + renderHtml: (options: HttpResourcesResponseOptions) => IKibanaResponse; + renderJs: (options: HttpResourcesResponseOptions) => IKibanaResponse; +} + // @public export interface HttpResponseOptions { body?: HttpResponsePayload; @@ -989,7 +1015,7 @@ export interface IRouter { // // @internal getRoutes: () => RouterRoute[]; - handleLegacyErrors: (handler: RequestHandler) => RequestHandler; + handleLegacyErrors: RequestHandlerWrapper; patch: RouteRegistrar<'patch'>; post: RouteRegistrar<'post'>; put: RouteRegistrar<'put'>; @@ -1008,11 +1034,6 @@ export type ISavedObjectTypeRegistry = Omit; -// @public (undocumented) -export interface IScopedRenderingClient { - render(options?: Pick): Promise; -} - // @public export interface IUiSettingsClient { get: (key: string) => Promise; @@ -1150,6 +1171,10 @@ export interface LegacyServiceSetupDeps { core: LegacyCoreSetup; // (undocumented) plugins: Record; + // Warning: (ae-forgotten-export) The symbol "UiPlugins" needs to be exported by the entry point index.d.ts + // + // (undocumented) + uiPlugins: UiPlugins; } // @public @deprecated (undocumented) @@ -1466,12 +1491,6 @@ export type PluginOpaqueId = symbol; export interface PluginsServiceSetup { contracts: Map; initialized: boolean; - // (undocumented) - uiPlugins: { - internal: Map; - public: Map; - browserConfigs: Map>; - }; } // @internal (undocumented) @@ -1496,19 +1515,13 @@ export type RedirectResponseOptions = HttpResponseOptions & { }; }; -// @internal (undocumented) -export interface RenderingServiceSetup { - render(request: R, uiSettings: IUiSettingsClient, options?: IRenderOptions): Promise; -} - // @public -export type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; // @public export interface RequestHandlerContext { // (undocumented) core: { - rendering: IScopedRenderingClient; savedObjects: { client: SavedObjectsClientContract; typeRegistry: ISavedObjectTypeRegistry; @@ -1529,6 +1542,9 @@ export type RequestHandlerContextContainer = IContextContainer = IContextProvider, TContextName>; +// @public +export type RequestHandlerWrapper = (handler: RequestHandler) => RequestHandler; + // @public export function resolveSavedObjectsImportErrors({ readStream, objectLimit, retries, savedObjectsClient, supportedTypes, namespace, }: SavedObjectsResolveImportErrorsOptions): Promise; @@ -1542,11 +1558,7 @@ export type ResponseError = string | Error | { export type ResponseErrorAttributes = Record; // @public -export type ResponseHeaders = { - [header in KnownHeaders]?: string | string[]; -} & { - [header: string]: string | string[]; -}; +export type ResponseHeaders = Record | Record; // @public export interface RouteConfig { @@ -2463,7 +2475,6 @@ export const validBodyOutput: readonly ["data", "stream"]; // src/core/server/legacy/types.ts:164:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:165:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:166:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/plugins_service.ts:47:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:230:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:230:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:232:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 24c41d511180a1..1e3e1638cf2a04 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -46,7 +46,10 @@ const rawConfigService = rawConfigServiceMock.create({}); beforeEach(() => { mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true })); - mockPluginsService.discover.mockResolvedValue(new Map()); + mockPluginsService.discover.mockResolvedValue({ + pluginTree: new Map(), + uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, + }); }); afterEach(() => { @@ -88,7 +91,10 @@ test('injects legacy dependency to context#setup()', async () => { [pluginA, []], [pluginB, [pluginA]], ]); - mockPluginsService.discover.mockResolvedValue(pluginDependencies); + mockPluginsService.discover.mockResolvedValue({ + pluginTree: pluginDependencies, + uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, + }); await server.setup(); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 07ea431dd3a0df..e78f77f6fbdbda 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -29,7 +29,8 @@ import { import { CoreApp } from './core_app'; import { ElasticsearchService } from './elasticsearch'; import { HttpService } from './http'; -import { RenderingService, RenderingServiceSetup } from './rendering'; +import { HttpResourcesService } from './http_resources'; +import { RenderingService } from './rendering'; import { LegacyService, ensureValidConfiguration } from './legacy'; import { Logger, LoggerFactory } from './logging'; import { UiSettingsService } from './ui_settings'; @@ -71,6 +72,7 @@ export class Server { private readonly uiSettings: UiSettingsService; private readonly uuid: UuidService; private readonly metrics: MetricsService; + private readonly httpResources: HttpResourcesService; private readonly status: StatusService; private readonly coreApp: CoreApp; @@ -99,13 +101,14 @@ export class Server { this.metrics = new MetricsService(core); this.status = new StatusService(core); this.coreApp = new CoreApp(core); + this.httpResources = new HttpResourcesService(core); } public async setup() { this.log.debug('setting up server'); // Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph. - const pluginDependencies = await this.plugins.discover(); + const { pluginTree, uiPlugins } = await this.plugins.discover(); const legacyPlugins = await this.legacy.discoverPlugins(); // Immediately terminate in case of invalid configuration @@ -117,10 +120,7 @@ export class Server { // 1) Can access context from any NP plugin // 2) Can register context providers that will only be available to other legacy plugins and will not leak into // New Platform plugins. - pluginDependencies: new Map([ - ...pluginDependencies, - [this.legacy.legacyId, [...pluginDependencies.keys()]], - ]), + pluginDependencies: new Map([...pluginTree, [this.legacy.legacyId, [...pluginTree.keys()]]]), }); const uuidSetup = await this.uuid.setup(); @@ -148,6 +148,17 @@ export class Server { const metricsSetup = await this.metrics.setup({ http: httpSetup }); + const renderingSetup = await this.rendering.setup({ + http: httpSetup, + legacyPlugins, + uiPlugins, + }); + + const httpResourcesSetup = this.httpResources.setup({ + http: httpSetup, + rendering: renderingSetup, + }); + const statusSetup = this.status.setup({ elasticsearch: elasticsearchServiceSetup, savedObjects: savedObjectsSetup, @@ -158,28 +169,25 @@ export class Server { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, - metrics: metricsSetup, savedObjects: savedObjectsSetup, status: statusSetup, uiSettings: uiSettingsSetup, uuid: uuidSetup, + metrics: metricsSetup, + rendering: renderingSetup, + httpResources: httpResourcesSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); this.pluginsInitialized = pluginsSetup.initialized; - const renderingSetup = await this.rendering.setup({ - http: httpSetup, - legacyPlugins, - plugins: pluginsSetup, - }); - await this.legacy.setup({ core: { ...coreSetup, plugins: pluginsSetup, rendering: renderingSetup }, plugins: mapToObject(pluginsSetup.contracts), + uiPlugins, }); - this.registerCoreContext(coreSetup, renderingSetup); + this.registerCoreContext(coreSetup); this.coreApp.setup(coreSetup); return coreSetup; @@ -212,7 +220,9 @@ export class Server { }); await this.http.start(); - await this.rendering.start(); + await this.rendering.start({ + legacy: this.legacy, + }); await this.metrics.start(); return this.coreStart; @@ -232,7 +242,7 @@ export class Server { await this.status.stop(); } - private registerCoreContext(coreSetup: InternalCoreSetup, rendering: RenderingServiceSetup) { + private registerCoreContext(coreSetup: InternalCoreSetup) { coreSetup.http.registerRouteHandlerContext( coreId, 'core', @@ -241,13 +251,6 @@ export class Server { const uiSettingsClient = coreSetup.uiSettings.asScopedToClient(savedObjectsClient); return { - rendering: { - render: async (options = {}) => - rendering.render(req, uiSettingsClient, { - ...options, - vars: await this.legacy.legacyInternals!.getVars('core', req), - }), - }, savedObjects: { client: savedObjectsClient, typeRegistry: this.coreStart!.savedObjects.getTypeRegistry(), diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 04f369ac26d7e8..ee8b25a5de8f19 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -41,10 +41,11 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { LegacyConfig, ILegacyService, ILegacyInternals } from '../../core/server/legacy'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { UiPlugins } from '../../core/server/plugins'; import { ApmOssPlugin } from '../core_plugins/apm_oss'; import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch'; import { UsageCollectionSetup } from '../../plugins/usage_collection/server'; -import { Capabilities } from '../../core/server'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; import { HomeServerPluginSetup } from '../../plugins/home/server'; @@ -111,7 +112,7 @@ export interface KibanaCore { kibanaMigrator: LegacyServiceStartDeps['core']['savedObjects']['migrator']; legacy: ILegacyInternals; rendering: LegacyServiceSetupDeps['core']['rendering']; - uiPlugins: LegacyServiceSetupDeps['core']['plugins']['uiPlugins']; + uiPlugins: UiPlugins; uiSettings: LegacyServiceSetupDeps['core']['uiSettings']; savedObjectsClientProvider: LegacyServiceStartDeps['core']['savedObjects']['clientProvider']; }; diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts index 0c5b74915e58a6..747af3b9e57df1 100644 --- a/src/plugins/share/server/routes/goto.ts +++ b/src/plugins/share/server/routes/goto.ts @@ -34,7 +34,7 @@ export const createGotoRoute = ({ shortUrlLookup: ShortUrlLookupService; http: CoreSetup['http']; }) => { - router.get( + http.resources.register( { path: getGotoPath('{urlId}'), validate: { @@ -63,14 +63,8 @@ export const createGotoRoute = ({ }, }); } - const body = await context.core.rendering.render(); - return response.ok({ - headers: { - 'content-security-policy': http.csp.header, - }, - body, - }); + return response.renderCoreApp(); }) ); }; diff --git a/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts b/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts index 3f6a8e8773e04d..f0b1cde24c6fe7 100644 --- a/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts +++ b/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts @@ -17,15 +17,13 @@ * under the License. */ -import { Plugin, CoreSetup, IRenderOptions } from 'kibana/server'; +import { Plugin, CoreSetup } from 'kibana/server'; import { schema } from '@kbn/config-schema'; export class RenderingPlugin implements Plugin { public setup(core: CoreSetup) { - const router = core.http.createRouter(); - - router.get( + core.http.resources.register( { path: '/render/{id}', validate: { @@ -41,18 +39,12 @@ export class RenderingPlugin implements Plugin { }, }, async (context, req, res) => { - const { id } = req.params; const { includeUserSettings } = req.query; - const app = { getId: () => id! }; - const options: Partial = { app, includeUserSettings }; - const body = await context.core.rendering.render(options); - return res.ok({ - body, - headers: { - 'content-security-policy': core.http.csp.header, - }, - }); + if (includeUserSettings) { + return res.renderCoreApp(); + } + return res.renderAnonymousCoreApp(); } ); } diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 68acf68f46109d..9dd4aaafa3494f 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -157,12 +157,12 @@ export class Plugin { defineRoutes({ router: core.http.createRouter(), basePath: core.http.basePath, + httpResources: core.http.resources, logger: this.initializerContext.logger.get('routes'), clusterClient: this.clusterClient, config, authc, authz, - csp: core.http.csp, license, }); diff --git a/x-pack/plugins/security/server/routes/authentication/index.ts b/x-pack/plugins/security/server/routes/authentication/index.ts index 7e9eb75bbf7530..d09f65525f44e0 100644 --- a/x-pack/plugins/security/server/routes/authentication/index.ts +++ b/x-pack/plugins/security/server/routes/authentication/index.ts @@ -11,17 +11,6 @@ import { defineCommonRoutes } from './common'; import { defineOIDCRoutes } from './oidc'; import { RouteDefinitionParams } from '..'; -export function createCustomResourceResponse(body: string, contentType: string, cspHeader: string) { - return { - body, - headers: { - 'content-type': contentType, - 'content-security-policy': cspHeader, - }, - statusCode: 200, - }; -} - export function defineAuthenticationRoutes(params: RouteDefinitionParams) { defineSessionRoutes(params); defineCommonRoutes(params); diff --git a/x-pack/plugins/security/server/routes/authentication/oidc.ts b/x-pack/plugins/security/server/routes/authentication/oidc.ts index d325a453af9d1b..5d8a7ae7bdfea8 100644 --- a/x-pack/plugins/security/server/routes/authentication/oidc.ts +++ b/x-pack/plugins/security/server/routes/authentication/oidc.ts @@ -8,7 +8,6 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { KibanaRequest, KibanaResponseFactory } from '../../../../../../src/core/server'; import { OIDCLogin } from '../../authentication'; -import { createCustomResourceResponse } from '.'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../errors'; import { @@ -20,7 +19,13 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for SAML authentication. */ -export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: RouteDefinitionParams) { +export function defineOIDCRoutes({ + router, + httpResources, + logger, + authc, + basePath, +}: RouteDefinitionParams) { // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. for (const path of ['/api/security/oidc/implicit', '/api/security/v1/oidc/implicit']) { /** @@ -28,7 +33,7 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route * is used, so that we can extract authentication response from URL fragment and send it to * the `/api/security/oidc/callback` route. */ - router.get( + httpResources.register( { path, validate: false, @@ -42,18 +47,14 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route { tags: ['deprecation'] } ); } - return response.custom( - createCustomResourceResponse( - ` - - Kibana OpenID Connect Login - - - `, - 'text/html', - csp.header - ) - ); + return response.renderHtml({ + body: ` + + Kibana OpenID Connect Login + + + `, + }); } ); } @@ -63,7 +64,7 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route * that extracts fragment part from the URL and send it to the `/api/security/oidc/callback` route. * We need this separate endpoint because of default CSP policy that forbids inline scripts. */ - router.get( + httpResources.register( { path: '/internal/security/oidc/implicit.js', validate: false, @@ -71,17 +72,13 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route }, (context, request, response) => { const serverBasePath = basePath.serverBasePath; - return response.custom( - createCustomResourceResponse( - ` + return response.renderJs({ + body: ` window.location.replace( '${serverBasePath}/api/security/oidc/callback?authenticationResponseURI=' + encodeURIComponent(window.location.href) ); `, - 'text/javascript', - csp.header - ) - ); + }); } ); @@ -155,7 +152,9 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route } if (!loginAttempt) { - return response.badRequest({ body: 'Unrecognized login attempt.' }); + return response.badRequest({ + body: 'Unrecognized login attempt.', + }); } return performOIDCLogin(request, response, loginAttempt); diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 3ea92c0d8c4c16..688793d0806c4f 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -7,14 +7,19 @@ import { schema } from '@kbn/config-schema'; import { SAMLLogin } from '../../authentication'; import { SAMLAuthenticationProvider } from '../../authentication/providers'; -import { createCustomResourceResponse } from '.'; import { RouteDefinitionParams } from '..'; /** * Defines routes required for SAML authentication. */ -export function defineSAMLRoutes({ router, logger, authc, csp, basePath }: RouteDefinitionParams) { - router.get( +export function defineSAMLRoutes({ + router, + httpResources, + logger, + authc, + basePath, +}: RouteDefinitionParams) { + httpResources.register( { path: '/internal/security/saml/capture-url-fragment', validate: false, @@ -22,39 +27,30 @@ export function defineSAMLRoutes({ router, logger, authc, csp, basePath }: Route }, (context, request, response) => { // We're also preventing `favicon.ico` request since it can cause new SAML handshake. - return response.custom( - createCustomResourceResponse( - ` + return response.renderHtml({ + body: ` Kibana SAML Login `, - 'text/html', - csp.header - ) - ); + }); } ); - - router.get( + httpResources.register( { path: '/internal/security/saml/capture-url-fragment.js', validate: false, options: { authRequired: false }, }, (context, request, response) => { - return response.custom( - createCustomResourceResponse( - ` + return response.renderJs({ + body: ` window.location.replace( '${basePath.serverBasePath}/internal/security/saml/start?redirectURLFragment=' + encodeURIComponent(window.location.hash) ); `, - 'text/javascript', - csp.header - ) - ); + }); } ); diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index aaefdad6c221aa..b0c74b98ee19bf 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -8,6 +8,7 @@ import { elasticsearchServiceMock, httpServiceMock, loggingServiceMock, + httpResourcesMock, } from '../../../../../src/core/server/mocks'; import { authenticationMock } from '../authentication/index.mock'; import { authorizationMock } from '../authorization/index.mock'; @@ -27,5 +28,6 @@ export const routeDefinitionParamsMock = { authc: authenticationMock.create(), authz: authorizationMock.create(), license: licenseMock.create(), + httpResources: httpResourcesMock.createRegistrar(), }), }; diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index a372fcf092707e..e43072b95c9067 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -4,7 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, IClusterClient, IRouter, Logger } from '../../../../../src/core/server'; +import { + CoreSetup, + HttpResources, + IClusterClient, + IRouter, + Logger, +} from '../../../../../src/core/server'; import { SecurityLicense } from '../../common/licensing'; import { Authentication } from '../authentication'; import { Authorization } from '../authorization'; @@ -24,7 +30,7 @@ import { defineViewRoutes } from './views'; export interface RouteDefinitionParams { router: IRouter; basePath: CoreSetup['http']['basePath']; - csp: CoreSetup['http']['csp']; + httpResources: HttpResources; logger: Logger; clusterClient: IClusterClient; config: ConfigType; diff --git a/x-pack/plugins/security/server/routes/views/account_management.ts b/x-pack/plugins/security/server/routes/views/account_management.ts index 3c84483d8f494d..696a5e12b64c13 100644 --- a/x-pack/plugins/security/server/routes/views/account_management.ts +++ b/x-pack/plugins/security/server/routes/views/account_management.ts @@ -9,11 +9,8 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for the Account Management view. */ -export function defineAccountManagementRoutes({ router, csp }: RouteDefinitionParams) { - router.get({ path: '/security/account', validate: false }, async (context, request, response) => { - return response.ok({ - body: await context.core.rendering.render({ includeUserSettings: true }), - headers: { 'content-security-policy': csp.header }, - }); - }); +export function defineAccountManagementRoutes({ httpResources }: RouteDefinitionParams) { + httpResources.register({ path: '/security/account', validate: false }, (context, req, res) => + res.renderCoreApp() + ); } diff --git a/x-pack/plugins/security/server/routes/views/index.test.ts b/x-pack/plugins/security/server/routes/views/index.test.ts index 80f7f62a5ff432..a8e7e905b119af 100644 --- a/x-pack/plugins/security/server/routes/views/index.test.ts +++ b/x-pack/plugins/security/server/routes/views/index.test.ts @@ -17,7 +17,8 @@ describe('View routes', () => { defineViewRoutes(routeParamsMock); - expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(` + expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path)) + .toMatchInlineSnapshot(` Array [ "/security/account", "/security/logged_out", @@ -25,6 +26,9 @@ describe('View routes', () => { "/security/overwritten_session", ] `); + expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot( + `Array []` + ); }); it('registers Login routes if `basic` provider is enabled', () => { @@ -35,16 +39,21 @@ describe('View routes', () => { defineViewRoutes(routeParamsMock); - expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(` + expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path)) + .toMatchInlineSnapshot(` Array [ "/login", - "/internal/security/login_state", "/security/account", "/security/logged_out", "/logout", "/security/overwritten_session", ] `); + expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(` + Array [ + "/internal/security/login_state", + ] + `); }); it('registers Login routes if `token` provider is enabled', () => { @@ -55,16 +64,21 @@ describe('View routes', () => { defineViewRoutes(routeParamsMock); - expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(` + expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path)) + .toMatchInlineSnapshot(` Array [ "/login", - "/internal/security/login_state", "/security/account", "/security/logged_out", "/logout", "/security/overwritten_session", ] `); + expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(` + Array [ + "/internal/security/login_state", + ] + `); }); it('registers Login routes if Login Selector is enabled even if both `token` and `basic` providers are not enabled', () => { @@ -75,15 +89,20 @@ describe('View routes', () => { defineViewRoutes(routeParamsMock); - expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(` + expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path)) + .toMatchInlineSnapshot(` Array [ "/login", - "/internal/security/login_state", "/security/account", "/security/logged_out", "/logout", "/security/overwritten_session", ] `); + expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(` + Array [ + "/internal/security/login_state", + ] + `); }); }); diff --git a/x-pack/plugins/security/server/routes/views/logged_out.test.ts b/x-pack/plugins/security/server/routes/views/logged_out.test.ts index 822802b62d8740..3ff05d242d9dde 100644 --- a/x-pack/plugins/security/server/routes/views/logged_out.test.ts +++ b/x-pack/plugins/security/server/routes/views/logged_out.test.ts @@ -4,20 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - RequestHandler, - RouteConfig, - kibanaResponseFactory, -} from '../../../../../../src/core/server'; +import { HttpResourcesRequestHandler, RouteConfig } from '../../../../../../src/core/server'; import { Authentication } from '../../authentication'; import { defineLoggedOutRoutes } from './logged_out'; -import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { httpServerMock, httpResourcesMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; describe('LoggedOut view routes', () => { let authc: jest.Mocked; - let routeHandler: RequestHandler; + let routeHandler: HttpResourcesRequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); @@ -28,7 +24,7 @@ describe('LoggedOut view routes', () => { const [ loggedOutRouteConfig, loggedOutRouteHandler, - ] = routeParamsMock.router.get.mock.calls.find( + ] = routeParamsMock.httpResources.register.mock.calls.find( ([{ path }]) => path === '/security/logged_out' )!; @@ -51,9 +47,11 @@ describe('LoggedOut view routes', () => { const request = httpServerMock.createKibanaRequest(); - await expect(routeHandler({} as any, request, kibanaResponseFactory)).resolves.toEqual({ - options: { headers: { location: '/mock-server-basepath/' } }, - status: 302, + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler({} as any, request, responseFactory); + + expect(responseFactory.redirected).toHaveBeenCalledWith({ + headers: { location: '/mock-server-basepath/' }, }); expect(authc.getSessionInfo).toHaveBeenCalledWith(request); @@ -63,21 +61,10 @@ describe('LoggedOut view routes', () => { authc.getSessionInfo.mockResolvedValue(null); const request = httpServerMock.createKibanaRequest(); - const contextMock = coreMock.createRequestHandlerContext(); - - await expect( - routeHandler({ core: contextMock } as any, request, kibanaResponseFactory) - ).resolves.toEqual({ - options: { - headers: { - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, - }, - status: 200, - }); + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler({} as any, request, responseFactory); expect(authc.getSessionInfo).toHaveBeenCalledWith(request); - expect(contextMock.rendering.render).toHaveBeenCalledWith({ includeUserSettings: false }); + expect(responseFactory.renderAnonymousCoreApp).toHaveBeenCalledWith(); }); }); diff --git a/x-pack/plugins/security/server/routes/views/logged_out.ts b/x-pack/plugins/security/server/routes/views/logged_out.ts index 2f69d8c35f03ed..43c2f01b1b53d4 100644 --- a/x-pack/plugins/security/server/routes/views/logged_out.ts +++ b/x-pack/plugins/security/server/routes/views/logged_out.ts @@ -16,13 +16,12 @@ import { RouteDefinitionParams } from '..'; * Defines routes required for the Logged Out view. */ export function defineLoggedOutRoutes({ - router, logger, authc, - csp, + httpResources, basePath, }: RouteDefinitionParams) { - router.get( + httpResources.register( { path: '/security/logged_out', validate: false, @@ -39,10 +38,7 @@ export function defineLoggedOutRoutes({ }); } - return response.ok({ - body: await context.core.rendering.render({ includeUserSettings: false }), - headers: { 'content-security-policy': csp.header }, - }); + return response.renderAnonymousCoreApp(); } ); } diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index 7751f9a952c090..d43319efbdfb9f 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -7,26 +7,34 @@ import { URL } from 'url'; import { Type } from '@kbn/config-schema'; import { + HttpResources, + HttpResourcesRequestHandler, + IRouter, RequestHandler, - RouteConfig, kibanaResponseFactory, - IRouter, + RouteConfig, } from '../../../../../../src/core/server'; import { SecurityLicense } from '../../../common/licensing'; import { LoginState } from '../../../common/login_state'; import { ConfigType } from '../../config'; import { defineLoginRoutes } from './login'; -import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { + coreMock, + httpServerMock, + httpResourcesMock, +} from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; describe('Login view routes', () => { + let httpResources: jest.Mocked; let router: jest.Mocked; let license: jest.Mocked; let config: ConfigType; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; + httpResources = routeParamsMock.httpResources; license = routeParamsMock.license; config = routeParamsMock.config; @@ -34,10 +42,10 @@ describe('Login view routes', () => { }); describe('View route', () => { - let routeHandler: RequestHandler; + let routeHandler: HttpResourcesRequestHandler; let routeConfig: RouteConfig; beforeEach(() => { - const [loginRouteConfig, loginRouteHandler] = router.get.mock.calls.find( + const [loginRouteConfig, loginRouteHandler] = httpResources.register.mock.calls.find( ([{ path }]) => path === '/login' )!; @@ -96,9 +104,11 @@ describe('Login view routes', () => { 'https://kibana.co' ); license.getFeatures.mockReturnValue({ showLogin: true } as any); - await expect(routeHandler({} as any, request, kibanaResponseFactory)).resolves.toEqual({ - options: { headers: { location: `${expectedLocation}` } }, - status: 302, + const responseFactory = httpResourcesMock.createResponseFactory(); + + await routeHandler({} as any, request, responseFactory); + expect(responseFactory.redirected).toHaveBeenCalledWith({ + headers: { location: `${expectedLocation}` }, }); // Redirect if `showLogin` is `false` even if user is not authenticated. @@ -108,9 +118,12 @@ describe('Login view routes', () => { 'https://kibana.co' ); license.getFeatures.mockReturnValue({ showLogin: false } as any); - await expect(routeHandler({} as any, request, kibanaResponseFactory)).resolves.toEqual({ - options: { headers: { location: `${expectedLocation}` } }, - status: 302, + responseFactory.redirected.mockClear(); + + await routeHandler({} as any, request, responseFactory); + + expect(responseFactory.redirected).toHaveBeenCalledWith({ + headers: { location: `${expectedLocation}` }, }); } }); @@ -121,19 +134,9 @@ describe('Login view routes', () => { const request = httpServerMock.createKibanaRequest({ auth: { isAuthenticated: false } }); const contextMock = coreMock.createRequestHandlerContext(); - await expect( - routeHandler({ core: contextMock } as any, request, kibanaResponseFactory) - ).resolves.toEqual({ - options: { - headers: { - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, - }, - status: 200, - }); - - expect(contextMock.rendering.render).toHaveBeenCalledWith({ includeUserSettings: false }); + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler({ core: contextMock } as any, request, responseFactory); + expect(responseFactory.renderAnonymousCoreApp).toHaveBeenCalledWith(); }); }); diff --git a/x-pack/plugins/security/server/routes/views/login.ts b/x-pack/plugins/security/server/routes/views/login.ts index 4cabd4337971ce..4d6747de713f76 100644 --- a/x-pack/plugins/security/server/routes/views/login.ts +++ b/x-pack/plugins/security/server/routes/views/login.ts @@ -16,11 +16,11 @@ export function defineLoginRoutes({ config, router, logger, - csp, + httpResources, basePath, license, }: RouteDefinitionParams) { - router.get( + httpResources.register( { path: '/login', validate: { @@ -45,10 +45,7 @@ export function defineLoginRoutes({ }); } - return response.ok({ - body: await context.core.rendering.render({ includeUserSettings: false }), - headers: { 'content-security-policy': csp.header }, - }); + return response.renderAnonymousCoreApp(); } ); diff --git a/x-pack/plugins/security/server/routes/views/logout.ts b/x-pack/plugins/security/server/routes/views/logout.ts index 8fa8e689a1c38e..370cb069096a33 100644 --- a/x-pack/plugins/security/server/routes/views/logout.ts +++ b/x-pack/plugins/security/server/routes/views/logout.ts @@ -9,18 +9,9 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for the Logout out view. */ -export function defineLogoutRoutes({ router, csp }: RouteDefinitionParams) { - router.get( - { - path: '/logout', - validate: false, - options: { authRequired: false }, - }, - async (context, request, response) => { - return response.ok({ - body: await context.core.rendering.render({ includeUserSettings: false }), - headers: { 'content-security-policy': csp.header }, - }); - } +export function defineLogoutRoutes({ httpResources }: RouteDefinitionParams) { + httpResources.register( + { path: '/logout', validate: false, options: { authRequired: false } }, + (context, request, response) => response.renderAnonymousCoreApp() ); } diff --git a/x-pack/plugins/security/server/routes/views/overwritten_session.ts b/x-pack/plugins/security/server/routes/views/overwritten_session.ts index c21ab1c207362f..ee4988cb122ccc 100644 --- a/x-pack/plugins/security/server/routes/views/overwritten_session.ts +++ b/x-pack/plugins/security/server/routes/views/overwritten_session.ts @@ -9,14 +9,9 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for the Overwritten Session view. */ -export function defineOverwrittenSessionRoutes({ router, csp }: RouteDefinitionParams) { - router.get( +export function defineOverwrittenSessionRoutes({ httpResources }: RouteDefinitionParams) { + httpResources.register( { path: '/security/overwritten_session', validate: false }, - async (context, request, response) => { - return response.ok({ - body: await context.core.rendering.render({ includeUserSettings: true }), - headers: { 'content-security-policy': csp.header }, - }); - } + (context, req, res) => res.renderCoreApp() ); } diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index a24d626c2a85d3..09b38adb706825 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -115,10 +115,8 @@ export class Plugin { const savedObjectsService = new SpacesSavedObjectsService(); savedObjectsService.setup({ core, spacesService }); - const viewRouter = core.http.createRouter(); initSpacesViewsRoutes({ - viewRouter, - cspHeader: core.http.csp.header, + httpResources: core.http.resources, }); const externalRouter = core.http.createRouter(); diff --git a/x-pack/plugins/spaces/server/routes/views/index.ts b/x-pack/plugins/spaces/server/routes/views/index.ts index 2a346c7e5241ae..57ad8872ce558f 100644 --- a/x-pack/plugins/spaces/server/routes/views/index.ts +++ b/x-pack/plugins/spaces/server/routes/views/index.ts @@ -4,26 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import { HttpResources } from 'src/core/server'; export interface ViewRouteDeps { - viewRouter: IRouter; - cspHeader: string; + httpResources: HttpResources; } export function initSpacesViewsRoutes(deps: ViewRouteDeps) { - deps.viewRouter.get( - { - path: '/spaces/space_selector', - validate: false, - }, - async (context, request, response) => { - return response.ok({ - headers: { - 'Content-Security-Policy': deps.cspHeader, - }, - body: await context.core.rendering.render({ includeUserSettings: true }), - }); - } + deps.httpResources.register( + { path: '/spaces/space_selector', validate: false }, + (context, request, response) => response.renderCoreApp() ); }