Skip to content

Commit

Permalink
Add pre-response http interceptor (elastic#52366)
Browse files Browse the repository at this point in the history
* add onPreResponse interceptor

* expose registerPreResponse to plugins

* address comments

* regen docs
  • Loading branch information
mshustov authored and timductive committed Dec 16, 2019
1 parent b21fac0 commit 4bf86bd
Show file tree
Hide file tree
Showing 21 changed files with 517 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface HttpServiceSetup
| [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | <code>(handler: AuthenticationHandler) =&gt; void</code> | To define custom authentication and/or authorization mechanism for incoming requests. |
| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | <code>(handler: OnPostAuthHandler) =&gt; void</code> | To define custom logic to perform for incoming requests. |
| [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | <code>(handler: OnPreAuthHandler) =&gt; void</code> | To define custom logic to perform for incoming requests. |
| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | <code>(handler: OnPreResponseHandler) =&gt; void</code> | To define custom logic to perform for the server response. |
| [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <code>&lt;T extends keyof RequestHandlerContext&gt;(contextName: T, provider: RequestHandlerContextProvider&lt;T&gt;) =&gt; RequestHandlerContextContainer</code> | Register a context provider for a route handler. |

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

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) &gt; [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md)

## HttpServiceSetup.registerOnPreResponse property

To define custom logic to perform for the server response.

<b>Signature:</b>

```typescript
registerOnPreResponse: (handler: OnPreResponseHandler) => void;
```

## Remarks

Doesn't provide the whole response object. Supports extending response with custom headers. See [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md)<!-- -->.

4 changes: 4 additions & 0 deletions docs/development/core/server/kibana-plugin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. |
| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. |
| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. |
| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [PackageInfo](./kibana-plugin-server.packageinfo.md) | |
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. |
Expand Down Expand Up @@ -173,6 +176,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation |
| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md)<!-- -->. |
| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md)<!-- -->. |
| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md)<!-- -->. |
| [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. |
| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>server</code> directory should conform to this interface. |
| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) &gt; [headers](./kibana-plugin-server.onpreresponseextensions.headers.md)

## OnPreResponseExtensions.headers property

additional headers to attach to the response

<b>Signature:</b>

```typescript
headers?: ResponseHeaders;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

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

## OnPreResponseExtensions interface

Additional data to extend a response.

<b>Signature:</b>

```typescript
export interface OnPreResponseExtensions
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [headers](./kibana-plugin-server.onpreresponseextensions.headers.md) | <code>ResponseHeaders</code> | additional headers to attach to the response |

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

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

## OnPreResponseHandler type

See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md)<!-- -->.

<b>Signature:</b>

```typescript
export declare type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise<OnPreResponseResult>;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

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

## OnPreResponseInfo interface

Response status code.

<b>Signature:</b>

```typescript
export interface OnPreResponseInfo
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md) | <code>number</code> | |

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

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) &gt; [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md)

## OnPreResponseInfo.statusCode property

<b>Signature:</b>

```typescript
statusCode: number;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

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

## OnPreResponseToolkit interface

A tool set defining an outcome of OnPreAuth interceptor for incoming request.

<b>Signature:</b>

```typescript
export interface OnPreResponseToolkit
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) | <code>(responseExtensions?: OnPreResponseExtensions) =&gt; OnPreResponseResult</code> | To pass request to the next handler |

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

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) &gt; [next](./kibana-plugin-server.onpreresponsetoolkit.next.md)

## OnPreResponseToolkit.next property

To pass request to the next handler

<b>Signature:</b>

```typescript
next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult;
```
50 changes: 15 additions & 35 deletions src/core/server/http/http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/

import { Request, Server } from 'hapi';
import { Server } from 'hapi';
import url from 'url';

import { Logger, LoggerFactory } from '../logging';
Expand All @@ -26,8 +25,9 @@ import { createServer, getListenerOptions, getServerOptions } from './http_tools
import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth';
import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth';
import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth';
import { adoptToHapiOnPreResponseFormat, OnPreResponseHandler } from './lifecycle/on_pre_response';

import { ResponseHeaders, IRouter } from './router';
import { IRouter } from './router';
import {
SessionStorageCookieOptions,
createCookieSessionStorageFactory,
Expand All @@ -50,6 +50,7 @@ export interface HttpServerSetup {
registerAuth: HttpServiceSetup['registerAuth'];
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
registerOnPreResponse: HttpServiceSetup['registerOnPreResponse'];
isTlsEnabled: HttpServiceSetup['isTlsEnabled'];
auth: {
get: GetAuthState;
Expand Down Expand Up @@ -103,6 +104,7 @@ export class HttpServer {
registerRouter: this.registerRouter.bind(this),
registerOnPreAuth: this.registerOnPreAuth.bind(this),
registerOnPostAuth: this.registerOnPostAuth.bind(this),
registerOnPreResponse: this.registerOnPreResponse.bind(this),
createCookieSessionStorageFactory: <T>(cookieOptions: SessionStorageCookieOptions<T>) =>
this.createCookieSessionStorageFactory(cookieOptions, config.basePath),
registerAuth: this.registerAuth.bind(this),
Expand Down Expand Up @@ -232,6 +234,14 @@ export class HttpServer {
this.server.ext('onRequest', adoptToHapiOnPreAuthFormat(fn, this.log));
}

private registerOnPreResponse(fn: OnPreResponseHandler) {
if (this.server === undefined) {
throw new Error('Server is not created yet');
}

this.server.ext('onPreResponse', adoptToHapiOnPreResponseFormat(fn, this.log));
}

private async createCookieSessionStorageFactory<T>(
cookieOptions: SessionStorageCookieOptions<T>,
basePath?: string
Expand Down Expand Up @@ -289,39 +299,9 @@ export class HttpServer {
// https://github.com/hapijs/hapi/blob/master/API.md#-serverauthdefaultoptions
this.server.auth.default('session');

this.server.ext('onPreResponse', (request, t) => {
this.registerOnPreResponse((request, preResponseInfo, t) => {
const authResponseHeaders = this.authResponseHeaders.get(request);
this.extendResponseWithHeaders(request, authResponseHeaders);
return t.continue;
});
}

private extendResponseWithHeaders(request: Request, headers?: ResponseHeaders) {
const response = request.response;
if (!headers || !response) return;

if (response instanceof Error) {
this.findHeadersIntersection(response.output.headers, headers);
// hapi wraps all error response in Boom object internally
response.output.headers = {
...response.output.headers,
...(headers as any), // hapi types don't specify string[] as valid value
};
} else {
for (const [headerName, headerValue] of Object.entries(headers)) {
this.findHeadersIntersection(response.headers, headers);
response.header(headerName, headerValue as any); // hapi types don't specify string[] as valid value
}
}
}

// NOTE: responseHeaders contains not a full list of response headers, but only explicitly set on a response object.
// any headers added by hapi internally, like `content-type`, `content-length`, etc. do not present here.
private findHeadersIntersection(responseHeaders: ResponseHeaders, headers: ResponseHeaders) {
Object.keys(headers).forEach(headerName => {
if (responseHeaders[headerName] !== undefined) {
this.log.warn(`Server rewrites a response header [${headerName}].`);
}
return t.next({ headers: authResponseHeaders });
});
}
}
1 change: 1 addition & 0 deletions src/core/server/http/http_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const createSetupContractMock = () => {
registerAuth: jest.fn(),
registerOnPostAuth: jest.fn(),
registerRouteHandlerContext: jest.fn(),
registerOnPreResponse: jest.fn(),
createRouter: jest.fn().mockImplementation(() => mockRouter.create({})),
basePath: createBasePathMock(),
auth: {
Expand Down
6 changes: 6 additions & 0 deletions src/core/server/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export {
AuthResultType,
} from './lifecycle/auth';
export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth';
export {
OnPreResponseHandler,
OnPreResponseToolkit,
OnPreResponseExtensions,
OnPreResponseInfo,
} from './lifecycle/on_pre_response';
export { SessionStorageFactory, SessionStorage } from './session_storage';
export {
SessionStorageCookieOptions,
Expand Down

0 comments on commit 4bf86bd

Please sign in to comment.