mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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
This commit is contained in:
parent
721e4fae1b
commit
af09fedaf2
78 changed files with 1485 additions and 522 deletions
|
@ -9,5 +9,7 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
http: HttpServiceSetup;
|
||||
http: HttpServiceSetup & {
|
||||
resources: HttpResources;
|
||||
};
|
||||
```
|
||||
|
|
|
@ -20,7 +20,7 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
|
|||
| [context](./kibana-plugin-core-server.coresetup.context.md) | <code>ContextSetup</code> | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) |
|
||||
| [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | <code>ElasticsearchServiceSetup</code> | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) |
|
||||
| [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | <code>StartServicesAccessor<TPluginsStart, TStart></code> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) |
|
||||
| [http](./kibana-plugin-core-server.coresetup.http.md) | <code>HttpServiceSetup</code> | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) |
|
||||
| [http](./kibana-plugin-core-server.coresetup.http.md) | <code>HttpServiceSetup & {</code><br/><code> resources: HttpResources;</code><br/><code> }</code> | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) |
|
||||
| [metrics](./kibana-plugin-core-server.coresetup.metrics.md) | <code>MetricsServiceSetup</code> | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) |
|
||||
| [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | <code>SavedObjectsServiceSetup</code> | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) |
|
||||
| [status](./kibana-plugin-core-server.coresetup.status.md) | <code>StatusServiceSetup</code> | [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) |
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface HttpResources
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [register](./kibana-plugin-core-server.httpresources.register.md) | <code><P, Q, B>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B>) => void</code> | To register a route handler executing passed function to form response. |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
register: <P, Q, B>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B>) => void;
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
headers?: ResponseHeaders;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
All HTML pages are already pre-configured with `content-security-policy` header that cannot be overridden.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface HttpResourcesRenderOptions
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [headers](./kibana-plugin-core-server.httpresourcesrenderoptions.headers.md) | <code>ResponseHeaders</code> | HTTP Headers with additional information about response. |
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type HttpResourcesRequestHandler<P = unknown, Q = unknown, B = unknown> = RequestHandler<P, Q, B, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>;
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
\`\`\`<!-- -->typescript httpResources.register(<!-- -->{ path: '/login', validate: { params: schema.object(<!-- -->{ id: schema.string() }<!-- -->), }<!-- -->, }<!-- -->, async (context, request, response) =<!-- -->> { //.. return response.renderCoreApp(); }<!-- -->);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type HttpResourcesResponseOptions = HttpResponseOptions;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface HttpResourcesServiceToolkit
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [renderAnonymousCoreApp](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md) | <code>(options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse></code> | To respond with HTML page bootstrapping Kibana application without retrieving user-specific information. |
|
||||
| [renderCoreApp](./kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md) | <code>(options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse></code> | To respond with HTML page bootstrapping Kibana application. |
|
||||
| [renderHtml](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderhtml.md) | <code>(options: HttpResourcesResponseOptions) => IKibanaResponse</code> | To respond with a custom HTML page. |
|
||||
| [renderJs](./kibana-plugin-core-server.httpresourcesservicetoolkit.renderjs.md) | <code>(options: HttpResourcesResponseOptions) => IKibanaResponse</code> | To respond with a custom JS script file. |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
renderAnonymousCoreApp: (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse>;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
renderCoreApp: (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse>;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
renderHtml: (options: HttpResourcesResponseOptions) => IKibanaResponse;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
renderJs: (options: HttpResourcesResponseOptions) => IKibanaResponse;
|
||||
```
|
|
@ -9,5 +9,5 @@ Wrap a router handler to catch and converts legacy boom errors to proper custom
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
handleLegacyErrors: <P, Q, B>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B>;
|
||||
handleLegacyErrors: RequestHandlerWrapper;
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ export interface IRouter
|
|||
| --- | --- | --- |
|
||||
| [delete](./kibana-plugin-core-server.irouter.delete.md) | <code>RouteRegistrar<'delete'></code> | Register a route handler for <code>DELETE</code> request. |
|
||||
| [get](./kibana-plugin-core-server.irouter.get.md) | <code>RouteRegistrar<'get'></code> | Register a route handler for <code>GET</code> request. |
|
||||
| [handleLegacyErrors](./kibana-plugin-core-server.irouter.handlelegacyerrors.md) | <code><P, Q, B>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B></code> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. |
|
||||
| [handleLegacyErrors](./kibana-plugin-core-server.irouter.handlelegacyerrors.md) | <code>RequestHandlerWrapper</code> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. |
|
||||
| [patch](./kibana-plugin-core-server.irouter.patch.md) | <code>RouteRegistrar<'patch'></code> | Register a route handler for <code>PATCH</code> request. |
|
||||
| [post](./kibana-plugin-core-server.irouter.post.md) | <code>RouteRegistrar<'post'></code> | Register a route handler for <code>POST</code> request. |
|
||||
| [put](./kibana-plugin-core-server.irouter.put.md) | <code>RouteRegistrar<'put'></code> | Register a route handler for <code>PUT</code> request. |
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IScopedRenderingClient](./kibana-plugin-core-server.iscopedrenderingclient.md)
|
||||
|
||||
## IScopedRenderingClient interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface IScopedRenderingClient
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [render(options)](./kibana-plugin-core-server.iscopedrenderingclient.render.md) | Generate a <code>KibanaResponse</code> which renders an HTML page bootstrapped with the <code>core</code> bundle. Intended as a response body for HTTP route handlers. |
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
render(options?: Pick<IRenderOptions, 'includeUserSettings'>): Promise<string>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| options | <code>Pick<IRenderOptions, 'includeUserSettings'></code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`Promise<string>`
|
||||
|
||||
## 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,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
```
|
||||
|
|
@ -20,4 +20,5 @@ export interface LegacyServiceSetupDeps
|
|||
| --- | --- | --- |
|
||||
| [core](./kibana-plugin-core-server.legacyservicesetupdeps.core.md) | <code>LegacyCoreSetup</code> | |
|
||||
| [plugins](./kibana-plugin-core-server.legacyservicesetupdeps.plugins.md) | <code>Record<string, unknown></code> | |
|
||||
| [uiPlugins](./kibana-plugin-core-server.legacyservicesetupdeps.uiplugins.md) | <code>UiPlugins</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
uiPlugins: UiPlugins;
|
||||
```
|
|
@ -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 <code>hapi</code> 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 <code>asScoped(...)</code>).<!-- -->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. |
|
||||
|
|
|
@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest<P, Q, B, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
export declare type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory> = (context: RequestHandlerContext, request: KibanaRequest<P, Q, B, Method>, response: ResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
```
|
||||
|
||||
## Example
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
```typescript
|
||||
core: {
|
||||
rendering: IScopedRenderingClient;
|
||||
savedObjects: {
|
||||
client: SavedObjectsClientContract;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
|
|
|
@ -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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
@ -18,5 +18,5 @@ export interface RequestHandlerContext
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | <code>{</code><br/><code> rendering: IScopedRenderingClient;</code><br/><code> savedObjects: {</code><br/><code> client: SavedObjectsClientContract;</code><br/><code> typeRegistry: ISavedObjectTypeRegistry;</code><br/><code> };</code><br/><code> elasticsearch: {</code><br/><code> dataClient: IScopedClusterClient;</code><br/><code> adminClient: IScopedClusterClient;</code><br/><code> };</code><br/><code> uiSettings: {</code><br/><code> client: IUiSettingsClient;</code><br/><code> };</code><br/><code> }</code> | |
|
||||
| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | <code>{</code><br/><code> savedObjects: {</code><br/><code> client: SavedObjectsClientContract;</code><br/><code> typeRegistry: ISavedObjectTypeRegistry;</code><br/><code> };</code><br/><code> elasticsearch: {</code><br/><code> dataClient: IScopedClusterClient;</code><br/><code> adminClient: IScopedClusterClient;</code><br/><code> };</code><br/><code> uiSettings: {</code><br/><code> client: IUiSettingsClient;</code><br/><code> };</code><br/><code> }</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type RequestHandlerWrapper = <P, Q, B, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory>(handler: RequestHandler<P, Q, B, Method, ResponseFactory>) => RequestHandler<P, Q, B, Method, ResponseFactory>;
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
```typescript
|
||||
export const wrapper: RequestHandlerWrapper = handler => {
|
||||
return async (context, request, response) => {
|
||||
// do some logic
|
||||
...
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
|
@ -9,9 +9,5 @@ Http response headers to set.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type ResponseHeaders = {
|
||||
[header in KnownHeaders]?: string | string[];
|
||||
} & {
|
||||
[header: string]: string | string[];
|
||||
};
|
||||
export declare type ResponseHeaders = Record<KnownHeaders, string | string[]> | Record<string, string | string[]>;
|
||||
```
|
||||
|
|
|
@ -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) |
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
```
|
||||
|
|
|
@ -38,6 +38,7 @@ export {
|
|||
LifecycleResponseFactory,
|
||||
RedirectResponseOptions,
|
||||
RequestHandler,
|
||||
RequestHandlerWrapper,
|
||||
ResponseError,
|
||||
ResponseErrorAttributes,
|
||||
ResponseHeaders,
|
||||
|
|
|
@ -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}].`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,20 +18,10 @@
|
|||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import { KibanaRequest } from './request';
|
||||
import { KibanaResponseFactory } from './response';
|
||||
import { RequestHandler } from './router';
|
||||
import { RequestHandlerContext } from '../../../server';
|
||||
import { RouteMethod } from './route';
|
||||
import { RequestHandlerWrapper } from './router';
|
||||
|
||||
export const wrapErrors = <P, Q, B>(
|
||||
handler: RequestHandler<P, Q, B, RouteMethod>
|
||||
): RequestHandler<P, Q, B, RouteMethod> => {
|
||||
return async (
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest<P, Q, B, RouteMethod>,
|
||||
response: KibanaResponseFactory
|
||||
) => {
|
||||
export const wrapErrors: RequestHandlerWrapper = handler => {
|
||||
return async (context, request, response) => {
|
||||
try {
|
||||
return await handler(context, request, response);
|
||||
} catch (e) {
|
||||
|
|
|
@ -56,9 +56,9 @@ export type Headers = { [header in KnownHeaders]?: string | string[] | undefined
|
|||
* Http response headers to set.
|
||||
* @public
|
||||
*/
|
||||
export type ResponseHeaders = { [header in KnownHeaders]?: string | string[] } & {
|
||||
[header: string]: string | string[];
|
||||
};
|
||||
export type ResponseHeaders =
|
||||
| Record<KnownHeaders, string | string[]>
|
||||
| Record<string, string | string[]>;
|
||||
|
||||
const normalizeHeaderField = (field: string) => field.trim().toLowerCase();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -98,7 +98,7 @@ export interface IRouter {
|
|||
* Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
|
||||
* @param handler {@link RequestHandler} - a route handler to wrap
|
||||
*/
|
||||
handleLegacyErrors: <P, Q, B>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B>;
|
||||
handleLegacyErrors: RequestHandlerWrapper;
|
||||
|
||||
/**
|
||||
* Returns all routes registered with this router.
|
||||
|
@ -237,9 +237,7 @@ export class Router implements IRouter {
|
|||
return [...this.routes];
|
||||
}
|
||||
|
||||
public handleLegacyErrors<P, Q, B>(handler: RequestHandler<P, Q, B>): RequestHandler<P, Q, B> {
|
||||
return wrapErrors(handler);
|
||||
}
|
||||
public handleLegacyErrors = wrapErrors;
|
||||
|
||||
private async handle<P, Q, B>({
|
||||
routeSchemas,
|
||||
|
@ -316,9 +314,33 @@ export type RequestHandler<
|
|||
P = unknown,
|
||||
Q = unknown,
|
||||
B = unknown,
|
||||
Method extends RouteMethod = any
|
||||
Method extends RouteMethod = any,
|
||||
ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory
|
||||
> = (
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest<P, Q, B, Method>,
|
||||
response: KibanaResponseFactory
|
||||
response: ResponseFactory
|
||||
) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for {@link RequestHandler} function.
|
||||
* @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<P, Q, B, Method, ResponseFactory>
|
||||
) => RequestHandler<P, Q, B, Method, ResponseFactory>;
|
||||
|
|
|
@ -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<HttpResources> => ({
|
||||
register: jest.fn(),
|
||||
});
|
||||
|
||||
function createInternalHttpResourcesSetup() {
|
||||
return {
|
||||
createRegistrar: createHttpResourcesMock,
|
||||
};
|
||||
}
|
||||
|
||||
function createHttpResourcesResponseFactory() {
|
||||
const mocked: jest.Mocked<HttpResourcesServiceToolkit> = {
|
||||
renderCoreApp: jest.fn(),
|
||||
renderAnonymousCoreApp: jest.fn(),
|
||||
renderHtml: jest.fn(),
|
||||
renderJs: jest.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
...httpServerMock.createResponseFactory(),
|
||||
...mocked,
|
||||
};
|
||||
}
|
||||
|
||||
export const httpResourcesMock = {
|
||||
createRegistrar: createHttpResourcesMock,
|
||||
createSetupContract: createInternalHttpResourcesSetup,
|
||||
createResponseFactory: createHttpResourcesResponseFactory,
|
||||
};
|
258
src/core/server/http_resources/http_resources_service.test.ts
Normal file
258
src/core/server/http_resources/http_resources_service.test.ts
Normal file
|
@ -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<IRouter>;
|
||||
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<any, any, any, 'get'> = { 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<any, any, any, 'get'> = { 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: '<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<any, any, any, 'get'> = { 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<any, any, any, 'get'> = { 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: '<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 = '<html><body /></html>';
|
||||
const routeConfig: RouteConfig<any, any, any, 'get'> = { 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 = '<html><body /></html>';
|
||||
const routeConfig: RouteConfig<any, any, any, 'get'> = { 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<any, any, any, 'get'> = { 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<any, any, any, 'get'> = { 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'",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
130
src/core/server/http_resources/http_resources_service.ts
Normal file
130
src/core/server/http_resources/http_resources_service.ts
Normal file
|
@ -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<InternalHttpResourcesSetup> {
|
||||
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: <P, Q, B>(
|
||||
route: RouteConfig<P, Q, B, 'get'>,
|
||||
handler: HttpResourcesRequestHandler<P, Q, B>
|
||||
) => {
|
||||
return router.get<P, Q, B>(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,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
29
src/core/server/http_resources/index.ts
Normal file
29
src/core/server/http_resources/index.ts
Normal file
|
@ -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';
|
|
@ -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<typeof kbnTestServer.createRoot>;
|
||||
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 = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<p>HTML body</p>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
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 = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<p>HTML body</p>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
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: '<html><p>Hi</p></html>',
|
||||
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);');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
116
src/core/server/http_resources/types.ts
Normal file
116
src/core/server/http_resources/types.ts
Normal file
|
@ -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<IKibanaResponse>;
|
||||
/** To respond with HTML page bootstrapping Kibana application without retrieving user-specific information. */
|
||||
renderAnonymousCoreApp: (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse>;
|
||||
/** 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<P = unknown, Q = unknown, B = unknown> = 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: <P, Q, B>(
|
||||
route: RouteConfig<P, Q, B, 'get'>,
|
||||
handler: HttpResourcesRequestHandler<P, Q, B>
|
||||
) => void;
|
||||
}
|
|
@ -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<TPluginsStart extends object = object, TStart = unkno
|
|||
/** {@link ElasticsearchServiceSetup} */
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
/** {@link HttpServiceSetup} */
|
||||
http: HttpServiceSetup;
|
||||
http: HttpServiceSetup & {
|
||||
/** {@link HttpResources} */
|
||||
resources: HttpResources;
|
||||
};
|
||||
/** {@link MetricsServiceSetup} */
|
||||
metrics: MetricsServiceSetup;
|
||||
/** {@link SavedObjectsServiceSetup} */
|
||||
|
@ -410,7 +420,7 @@ export {
|
|||
CapabilitiesSetup,
|
||||
CapabilitiesStart,
|
||||
ContextSetup,
|
||||
IScopedRenderingClient,
|
||||
HttpResources,
|
||||
PluginsServiceSetup,
|
||||
PluginsServiceStart,
|
||||
PluginOpaqueId,
|
||||
|
|
|
@ -31,6 +31,8 @@ import {
|
|||
import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings';
|
||||
import { UuidServiceSetup } from './uuid';
|
||||
import { InternalMetricsServiceSetup } from './metrics';
|
||||
import { InternalRenderingServiceSetup } from './rendering';
|
||||
import { InternalHttpResourcesSetup } from './http_resources';
|
||||
import { InternalStatusServiceSetup } from './status';
|
||||
|
||||
/** @internal */
|
||||
|
@ -44,6 +46,8 @@ export interface InternalCoreSetup {
|
|||
status: InternalStatusServiceSetup;
|
||||
uiSettings: InternalUiSettingsServiceSetup;
|
||||
uuid: UuidServiceSetup;
|
||||
rendering: InternalRenderingServiceSetup;
|
||||
httpResources: InternalHttpResourcesSetup;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -41,6 +41,7 @@ import { httpServiceMock } from '../http/http_service.mock';
|
|||
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
|
||||
import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock';
|
||||
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
|
||||
import { httpResourcesMock } from '../http_resources/http_resources_service.mock';
|
||||
import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service';
|
||||
import { uuidServiceMock } from '../uuid/uuid_service.mock';
|
||||
import { metricsServiceMock } from '../metrics/metrics_service.mock';
|
||||
|
@ -86,23 +87,11 @@ beforeEach(() => {
|
|||
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 = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<string, any>;
|
|||
|
||||
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<string, unknown>;
|
||||
uiPlugins: UiPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<T>(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(),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -136,6 +136,8 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
deps: PluginsServiceSetupDeps,
|
||||
plugin: PluginWrapper<TPlugin, TPluginDependencies>
|
||||
): CoreSetup {
|
||||
const router = deps.http.createRouter('', plugin.opaqueId);
|
||||
|
||||
return {
|
||||
capabilities: {
|
||||
registerProvider: deps.capabilities.registerProvider,
|
||||
|
@ -155,7 +157,8 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
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,
|
||||
|
|
|
@ -23,14 +23,10 @@ type PluginsServiceMock = jest.Mocked<PublicMethodsOf<PluginsService>>;
|
|||
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -39,23 +39,25 @@ export interface PluginsServiceSetup {
|
|||
initialized: boolean;
|
||||
/** Setup contracts returned by plugins. */
|
||||
contracts: Map<PluginName, unknown>;
|
||||
uiPlugins: {
|
||||
/**
|
||||
* Paths to all discovered ui plugin entrypoints on the filesystem, even if
|
||||
* disabled.
|
||||
*/
|
||||
internal: Map<PluginName, InternalPluginInfo>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information needed by client-side to load plugins and wire dependencies.
|
||||
*/
|
||||
public: Map<PluginName, DiscoveredPlugin>;
|
||||
/** @internal */
|
||||
export interface UiPlugins {
|
||||
/**
|
||||
* Paths to all discovered ui plugin entrypoints on the filesystem, even if
|
||||
* disabled.
|
||||
*/
|
||||
internal: Map<PluginName, InternalPluginInfo>;
|
||||
|
||||
/**
|
||||
* Configuration for plugins to be exposed to the client-side.
|
||||
*/
|
||||
browserConfigs: Map<PluginName, Observable<unknown>>;
|
||||
};
|
||||
/**
|
||||
* Information needed by client-side to load plugins and wire dependencies.
|
||||
*/
|
||||
public: Map<PluginName, DiscoveredPlugin>;
|
||||
|
||||
/**
|
||||
* Configuration for plugins to be exposed to the client-side.
|
||||
*/
|
||||
browserConfigs: Map<PluginName, Observable<unknown>>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -97,8 +99,17 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
|
|||
await this.handleDiscoveryErrors(error$);
|
||||
await this.handleDiscoveredPlugins(plugin$);
|
||||
|
||||
// Return dependency tree
|
||||
return this.pluginsSystem.getPluginDependencies();
|
||||
const uiPlugins = this.pluginsSystem.uiPlugins();
|
||||
|
||||
return {
|
||||
// Return dependency tree
|
||||
pluginTree: this.pluginsSystem.getPluginDependencies(),
|
||||
uiPlugins: {
|
||||
internal: this.uiPluginInternalInfo,
|
||||
public: uiPlugins,
|
||||
browserConfigs: this.generateUiPluginsConfigs(uiPlugins),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async setup(deps: PluginsServiceSetupDeps) {
|
||||
|
@ -115,15 +126,9 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
|
|||
this.log.info('Plugin initialization disabled.');
|
||||
}
|
||||
|
||||
const uiPlugins = this.pluginsSystem.uiPlugins();
|
||||
return {
|
||||
initialized: initialize,
|
||||
contracts,
|
||||
uiPlugins: {
|
||||
internal: this.uiPluginInternalInfo,
|
||||
public: uiPlugins,
|
||||
browserConfigs: this.generateUiPluginsConfigs(uiPlugins),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -24,12 +24,12 @@ import { legacyServiceMock } from '../../legacy/legacy_service.mock';
|
|||
|
||||
const context = mockCoreContext.create();
|
||||
const http = httpServiceMock.createSetupContract();
|
||||
const plugins = pluginServiceMock.createSetupContract();
|
||||
const uiPlugins = pluginServiceMock.createUiPlugins();
|
||||
const legacyPlugins = legacyServiceMock.createDiscoverPlugins();
|
||||
|
||||
export const mockRenderingServiceParams = context;
|
||||
export const mockRenderingSetupDeps = {
|
||||
http,
|
||||
legacyPlugins,
|
||||
plugins,
|
||||
uiPlugins,
|
||||
};
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
*/
|
||||
|
||||
import { RenderingService as Service } from '../rendering_service';
|
||||
import { RenderingServiceSetup } from '../types';
|
||||
import { InternalRenderingServiceSetup } from '../types';
|
||||
import { mockRenderingServiceParams } from './params';
|
||||
|
||||
type IRenderingService = PublicMethodsOf<Service>;
|
||||
|
||||
export const setupMock: jest.Mocked<RenderingServiceSetup> = {
|
||||
export const setupMock: jest.Mocked<InternalRenderingServiceSetup> = {
|
||||
render: jest.fn(),
|
||||
};
|
||||
export const mockSetup = jest.fn().mockResolvedValue(setupMock);
|
||||
|
|
31
src/core/server/rendering/rendering_service.mock.ts
Normal file
31
src/core/server/rendering/rendering_service.mock.ts
Normal file
|
@ -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<InternalRenderingServiceSetup> = {
|
||||
render: jest.fn().mockResolvedValue('<body />'),
|
||||
};
|
||||
return mocked;
|
||||
}
|
||||
|
||||
export const renderingMock = {
|
||||
createSetupContract: createRenderingSetup,
|
||||
};
|
|
@ -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<typeof uiSettingsServiceMock.createClient>;
|
||||
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 () => {
|
||||
|
|
|
@ -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<RenderingServiceSetup> {
|
||||
export class RenderingService implements CoreService<InternalRenderingServiceSetup> {
|
||||
private legacyInternals?: LegacyService['legacyInternals'];
|
||||
constructor(private readonly coreContext: CoreContext) {}
|
||||
|
||||
public async setup({
|
||||
http,
|
||||
legacyPlugins,
|
||||
plugins,
|
||||
}: RenderingSetupDeps): Promise<RenderingServiceSetup> {
|
||||
async function getUiConfig(pluginId: string) {
|
||||
const browserConfig = plugins.uiPlugins.browserConfigs.get(pluginId);
|
||||
|
||||
return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record<string, any>;
|
||||
}
|
||||
|
||||
uiPlugins,
|
||||
}: RenderingSetupDeps): Promise<InternalRenderingServiceSetup> {
|
||||
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<RenderingServiceSetup> {
|
|||
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<RenderingServiceSetup> {
|
|||
};
|
||||
}
|
||||
|
||||
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<string, any>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string, any>;
|
||||
}
|
||||
|
||||
/** @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<IRenderOptions, 'includeUserSettings'>): Promise<string>;
|
||||
}
|
||||
|
||||
/** @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.
|
||||
|
|
|
@ -632,7 +632,9 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
|
|||
// (undocumented)
|
||||
getStartServices: StartServicesAccessor<TPluginsStart, TStart>;
|
||||
// (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: <P, Q, B>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B>) => void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HttpResourcesRenderOptions {
|
||||
headers?: ResponseHeaders;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type HttpResourcesRequestHandler<P = unknown, Q = unknown, B = unknown> = RequestHandler<P, Q, B, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>;
|
||||
|
||||
// @public
|
||||
export type HttpResourcesResponseOptions = HttpResponseOptions;
|
||||
|
||||
// @public
|
||||
export interface HttpResourcesServiceToolkit {
|
||||
renderAnonymousCoreApp: (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse>;
|
||||
renderCoreApp: (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse>;
|
||||
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: <P, Q, B>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B>;
|
||||
handleLegacyErrors: RequestHandlerWrapper;
|
||||
patch: RouteRegistrar<'patch'>;
|
||||
post: RouteRegistrar<'post'>;
|
||||
put: RouteRegistrar<'put'>;
|
||||
|
@ -1008,11 +1034,6 @@ export type ISavedObjectTypeRegistry = Omit<SavedObjectTypeRegistry, 'registerTy
|
|||
// @public
|
||||
export type IScopedClusterClient = Pick<ScopedClusterClient, 'callAsCurrentUser' | 'callAsInternalUser'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface IScopedRenderingClient {
|
||||
render(options?: Pick<IRenderOptions, 'includeUserSettings'>): Promise<string>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface IUiSettingsClient {
|
||||
get: <T = any>(key: string) => Promise<T>;
|
||||
|
@ -1150,6 +1171,10 @@ export interface LegacyServiceSetupDeps {
|
|||
core: LegacyCoreSetup;
|
||||
// (undocumented)
|
||||
plugins: Record<string, unknown>;
|
||||
// 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<PluginName, unknown>;
|
||||
initialized: boolean;
|
||||
// (undocumented)
|
||||
uiPlugins: {
|
||||
internal: Map<PluginName, InternalPluginInfo>;
|
||||
public: Map<PluginName, DiscoveredPlugin>;
|
||||
browserConfigs: Map<PluginName, Observable<unknown>>;
|
||||
};
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
|
@ -1496,19 +1515,13 @@ export type RedirectResponseOptions = HttpResponseOptions & {
|
|||
};
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export interface RenderingServiceSetup {
|
||||
render<R extends KibanaRequest | LegacyRequest>(request: R, uiSettings: IUiSettingsClient, options?: IRenderOptions): Promise<string>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest<P, Q, B, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
export type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory> = (context: RequestHandlerContext, request: KibanaRequest<P, Q, B, Method>, response: ResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
|
||||
// @public
|
||||
export interface RequestHandlerContext {
|
||||
// (undocumented)
|
||||
core: {
|
||||
rendering: IScopedRenderingClient;
|
||||
savedObjects: {
|
||||
client: SavedObjectsClientContract;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
|
@ -1529,6 +1542,9 @@ export type RequestHandlerContextContainer = IContextContainer<RequestHandler<an
|
|||
// @public
|
||||
export type RequestHandlerContextProvider<TContextName extends keyof RequestHandlerContext> = IContextProvider<RequestHandler<any, any, any>, TContextName>;
|
||||
|
||||
// @public
|
||||
export type RequestHandlerWrapper = <P, Q, B, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory>(handler: RequestHandler<P, Q, B, Method, ResponseFactory>) => RequestHandler<P, Q, B, Method, ResponseFactory>;
|
||||
|
||||
// @public
|
||||
export function resolveSavedObjectsImportErrors({ readStream, objectLimit, retries, savedObjectsClient, supportedTypes, namespace, }: SavedObjectsResolveImportErrorsOptions): Promise<SavedObjectsImportResponse>;
|
||||
|
||||
|
@ -1542,11 +1558,7 @@ export type ResponseError = string | Error | {
|
|||
export type ResponseErrorAttributes = Record<string, any>;
|
||||
|
||||
// @public
|
||||
export type ResponseHeaders = {
|
||||
[header in KnownHeaders]?: string | string[];
|
||||
} & {
|
||||
[header: string]: string | string[];
|
||||
};
|
||||
export type ResponseHeaders = Record<KnownHeaders, string | string[]> | Record<string, string | string[]>;
|
||||
|
||||
// @public
|
||||
export interface RouteConfig<P, Q, B, Method extends RouteMethod> {
|
||||
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
5
src/legacy/server/kbn_server.d.ts
vendored
5
src/legacy/server/kbn_server.d.ts
vendored
|
@ -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'];
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<IRenderOptions> = { 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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<title>Kibana OpenID Connect Login</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<script src="${serverBasePath}/internal/security/oidc/implicit.js"></script>
|
||||
`,
|
||||
'text/html',
|
||||
csp.header
|
||||
)
|
||||
);
|
||||
return response.renderHtml({
|
||||
body: `
|
||||
<!DOCTYPE html>
|
||||
<title>Kibana OpenID Connect Login</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<script src="${serverBasePath}/internal/security/oidc/implicit.js"></script>
|
||||
`,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
|
|
@ -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: `
|
||||
<!DOCTYPE html>
|
||||
<title>Kibana SAML Login</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<script src="${basePath.serverBasePath}/internal/security/saml/capture-url-fragment.js"></script>
|
||||
`,
|
||||
'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
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<Authentication>;
|
||||
let routeHandler: RequestHandler<any, any, any, 'get'>;
|
||||
let routeHandler: HttpResourcesRequestHandler<any, any, any>;
|
||||
let routeConfig: RouteConfig<any, any, any, 'get'>;
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,26 +7,34 @@
|
|||
import { URL } from 'url';
|
||||
import { Type } from '@kbn/config-schema';
|
||||
import {
|
||||
RequestHandler,
|
||||
RouteConfig,
|
||||
kibanaResponseFactory,
|
||||
HttpResources,
|
||||
HttpResourcesRequestHandler,
|
||||
IRouter,
|
||||
RequestHandler,
|
||||
kibanaResponseFactory,
|
||||
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<HttpResources>;
|
||||
let router: jest.Mocked<IRouter>;
|
||||
let license: jest.Mocked<SecurityLicense>;
|
||||
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<any, any, any, 'get'>;
|
||||
let routeHandler: HttpResourcesRequestHandler<any, any, any>;
|
||||
let routeConfig: RouteConfig<any, any, any, 'get'>;
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue