From ddd5fe58da724dbe13996c31746b5b163defddb2 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 20 May 2019 15:51:28 +0200 Subject: [PATCH 01/18] introduce pre-,post-auth stages --- src/core/server/http/http_server.test.ts | 14 +- src/core/server/http/http_server.ts | 41 ++--- src/core/server/http/http_service.mock.ts | 3 +- src/core/server/http/index.ts | 3 +- .../integration_tests/http_service.test.ts | 19 +-- ...n_request.test.ts => on_post_auth.test.ts} | 18 +-- .../{on_request.ts => on_post_auth.ts} | 83 +++++----- .../server/http/lifecycle/on_pre_auth.test.ts | 108 +++++++++++++ src/core/server/http/lifecycle/on_pre_auth.ts | 147 ++++++++++++++++++ src/core/server/http/router/request.ts | 3 + src/core/server/index.ts | 6 +- src/core/server/plugins/plugin_context.ts | 6 +- 12 files changed, 363 insertions(+), 88 deletions(-) rename src/core/server/http/lifecycle/{on_request.test.ts => on_post_auth.test.ts} (81%) rename src/core/server/http/lifecycle/{on_request.ts => on_post_auth.ts} (57%) create mode 100644 src/core/server/http/lifecycle/on_pre_auth.test.ts create mode 100644 src/core/server/http/lifecycle/on_pre_auth.ts diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 21e11934719725..4979768785de31 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -505,7 +505,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => { res.ok({ key: 'value:/foo' }) ); - const { registerRouter, server: innerServer } = await server.setup(config); + const { registerRouter, server: innerServer } = await server.setup(configWithBasePath); registerRouter(router); await server.start(configWithBasePath); @@ -602,8 +602,8 @@ test('registers auth request interceptor only once', async () => { }); test('registers onRequest interceptor several times', async () => { - const { registerOnRequest } = await server.setup(config); - const doRegister = () => registerOnRequest(() => null as any); + const { registerOnPostAuth } = await server.setup(config); + const doRegister = () => registerOnPostAuth(() => null as any); doRegister(); expect(doRegister).not.toThrowError(); @@ -621,11 +621,11 @@ test('#getBasePathFor() returns base path associated with an incoming request', setBasePathFor, registerRouter, server: innerServer, - registerOnRequest, + registerOnPostAuth, } = await server.setup(config); const path = '/base-path'; - registerOnRequest((req, t) => { + registerOnPostAuth((req, t) => { setBasePathFor(req, path); return t.next(); }); @@ -653,11 +653,11 @@ test('#getBasePathFor() is based on server base path', async () => { setBasePathFor, registerRouter, server: innerServer, - registerOnRequest, + registerOnPostAuth, } = await server.setup(configWithBasePath); const path = '/base-path'; - registerOnRequest((req, t) => { + registerOnPostAuth((req, t) => { setBasePathFor(req, path); return t.next(); }); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 6dbae8a14d6015..91e53524e872fa 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -24,7 +24,8 @@ import { Logger } from '../logging'; import { HttpConfig } from './http_config'; import { createServer, getServerOptions } from './http_tools'; import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth'; -import { adoptToHapiOnRequestFormat, OnRequestHandler } from './lifecycle/on_request'; +import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth'; +import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth'; import { Router, KibanaRequest } from './router'; import { SessionStorageCookieOptions, @@ -48,7 +49,8 @@ export interface HttpServerSetup { * Applied to all resources by default. * Can register any number of OnRequestHandlers, which are called in sequence (from the first registered to the last) */ - registerOnRequest: (requestHandler: OnRequestHandler) => void; + registerOnPreAuth: (requestHandler: OnPreAuthHandler) => void; + registerOnPostAuth: (requestHandler: OnPostAuthHandler) => void; getBasePathFor: (request: KibanaRequest | Request) => string; setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void; } @@ -103,10 +105,13 @@ export class HttpServer { const serverOptions = getServerOptions(config); this.server = createServer(serverOptions); + this.setupBasePathRewrite(config); + return { options: serverOptions, registerRouter: this.registerRouter.bind(this), - registerOnRequest: this.registerOnRequest.bind(this), + registerOnPreAuth: this.registerOnPreAuth.bind(this), + registerOnPostAuth: this.registerOnPostAuth.bind(this), registerAuth: ( fn: AuthenticationHandler, cookieOptions: SessionStorageCookieOptions @@ -126,8 +131,6 @@ export class HttpServer { } this.log.debug('starting http server'); - this.setupBasePathRewrite(this.server, config); - for (const router of this.registeredRouters) { for (const route of router.getRoutes()) { this.server.route({ @@ -157,13 +160,13 @@ export class HttpServer { this.server = undefined; } - private setupBasePathRewrite(server: Server, config: HttpConfig) { + private setupBasePathRewrite(config: HttpConfig) { if (config.basePath === undefined || !config.rewriteBasePath) { return; } const basePath = config.basePath; - server.ext('onRequest', (request, responseToolkit) => { + this.registerOnPreAuth((request, toolkit) => { const newURL = modifyUrl(request.url.href!, urlParts => { if (urlParts.pathname != null && urlParts.pathname.startsWith(basePath)) { urlParts.pathname = urlParts.pathname.replace(basePath, '') || '/'; @@ -173,18 +176,10 @@ export class HttpServer { }); if (!newURL) { - return responseToolkit - .response('Not Found') - .code(404) - .takeover(); + return toolkit.rejected(new Error('not found'), { statusCode: 404 }); } - request.setUrl(newURL); - // We should update raw request as well since it can be proxied to the old platform - // where base path isn't expected. - request.raw.req.url = request.url.href; - - return responseToolkit.continue; + return toolkit.redirected(newURL, { forward: true }); }); } @@ -195,12 +190,20 @@ export class HttpServer { return `${routerPath}${routePath.slice(routePathStartIndex)}`; } - private registerOnRequest(fn: OnRequestHandler) { + private registerOnPostAuth(fn: OnPostAuthHandler) { + if (this.server === undefined) { + throw new Error('Server is not created yet'); + } + + this.server.ext('onPostAuth', adoptToHapiOnPostAuthFormat(fn)); + } + + private registerOnPreAuth(fn: OnPreAuthHandler) { if (this.server === undefined) { throw new Error('Server is not created yet'); } - this.server.ext('onRequest', adoptToHapiOnRequestFormat(fn)); + this.server.ext('onRequest', adoptToHapiOnPreAuthFormat(fn)); } private async registerAuth( diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 289eae0990531f..3e06f4b1f4fa78 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -23,8 +23,9 @@ import { HttpService } from './http_service'; const createSetupContractMock = () => { const setupContract = { options: {} as ServerOptions, + registerOnPreAuth: jest.fn(), registerAuth: jest.fn(), - registerOnRequest: jest.fn(), + registerOnPostAuth: jest.fn(), registerRouter: jest.fn(), getBasePathFor: jest.fn(), setBasePathFor: jest.fn(), diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 465c5cb6a859b0..ece61579314b1e 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -21,5 +21,6 @@ export { config, HttpConfig, HttpConfigType } from './http_config'; export { HttpService, HttpServiceSetup, HttpServiceStart } from './http_service'; export { Router, KibanaRequest } from './router'; export { BasePathProxyServer } from './base_path_proxy_server'; +export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth'; export { AuthenticationHandler, AuthToolkit } from './lifecycle/auth'; -export { OnRequestHandler, OnRequestToolkit } from './lifecycle/on_request'; +export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth'; diff --git a/src/core/server/http/integration_tests/http_service.test.ts b/src/core/server/http/integration_tests/http_service.test.ts index 9913f9914a0a8f..802101fe9774f0 100644 --- a/src/core/server/http/integration_tests/http_service.test.ts +++ b/src/core/server/http/integration_tests/http_service.test.ts @@ -16,9 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - -import { parse } from 'url'; - import request from 'request'; import Boom from 'boom'; @@ -174,7 +171,7 @@ describe('http service', () => { }); }); - describe('#registerOnRequest()', () => { + describe('#registerOnPostAuth()', () => { let root: ReturnType; beforeEach(async () => { root = kbnTestServer.createRoot(); @@ -259,7 +256,7 @@ describe('http service', () => { }); }); - describe('#registerOnRequest() toolkit', () => { + describe('#registerOnPostAuth() toolkit', () => { let root: ReturnType; beforeEach(async () => { root = kbnTestServer.createRoot(); @@ -268,9 +265,8 @@ describe('http service', () => { afterEach(async () => await root.shutdown()); it('supports Url change on the flight', async () => { const { http } = await root.setup(); - http.registerOnRequest((req, t) => { - t.setUrl(parse('/new-url')); - return t.next(); + http.registerOnPreAuth((req, t) => { + return t.redirected('/new-url', { forward: true }); }); const router = new Router('/'); @@ -287,9 +283,8 @@ describe('http service', () => { it('url re-write works for legacy server as well', async () => { const { http } = await root.setup(); const newUrl = '/new-url'; - http.registerOnRequest((req, t) => { - t.setUrl(newUrl); - return t.next(); + http.registerOnPreAuth((req, t) => { + return t.redirected(newUrl, { forward: true }); }); await root.start(); @@ -314,7 +309,7 @@ describe('http service', () => { it('basePath information for an incoming request is available in legacy server', async () => { const reqBasePath = '/requests-specific-base-path'; const { http } = await root.setup(); - http.registerOnRequest((req, t) => { + http.registerOnPreAuth((req, t) => { http.setBasePathFor(req, reqBasePath); return t.next(); }); diff --git a/src/core/server/http/lifecycle/on_request.test.ts b/src/core/server/http/lifecycle/on_post_auth.test.ts similarity index 81% rename from src/core/server/http/lifecycle/on_request.test.ts rename to src/core/server/http/lifecycle/on_post_auth.test.ts index bc4410c7732888..8dd0d1a20f4e17 100644 --- a/src/core/server/http/lifecycle/on_request.test.ts +++ b/src/core/server/http/lifecycle/on_post_auth.test.ts @@ -18,15 +18,15 @@ */ import Boom from 'boom'; -import { adoptToHapiOnRequestFormat } from './on_request'; +import { adoptToHapiOnPostAuthFormat } from './on_post_auth'; const requestMock = {} as any; const createResponseToolkit = (customization = {}): any => ({ ...customization }); -describe('adoptToHapiOnRequestFormat', () => { +describe('adoptToHapiOnPostAuthFormat', () => { it('Should allow passing request to the next handler', async () => { const continueSymbol = {}; - const onRequest = adoptToHapiOnRequestFormat((req, t) => t.next()); + const onRequest = adoptToHapiOnPostAuthFormat((req, t) => t.next()); const result = await onRequest( requestMock, createResponseToolkit({ @@ -39,7 +39,7 @@ describe('adoptToHapiOnRequestFormat', () => { it('Should support redirecting to specified url', async () => { const redirectUrl = '/docs'; - const onRequest = adoptToHapiOnRequestFormat((req, t) => t.redirected(redirectUrl)); + const onRequest = adoptToHapiOnPostAuthFormat((req, t) => t.redirected(redirectUrl)); const takeoverSymbol = {}; const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol })); const result = await onRequest( @@ -54,7 +54,7 @@ describe('adoptToHapiOnRequestFormat', () => { }); it('Should support specifying statusCode and message for Boom error', async () => { - const onRequest = adoptToHapiOnRequestFormat((req, t) => { + const onRequest = adoptToHapiOnPostAuthFormat((req, t) => { return t.rejected(new Error('unexpected result'), { statusCode: 501 }); }); const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; @@ -65,7 +65,7 @@ describe('adoptToHapiOnRequestFormat', () => { }); it('Should return Boom.internal error if interceptor throws', async () => { - const onRequest = adoptToHapiOnRequestFormat((req, t) => { + const onRequest = adoptToHapiOnPostAuthFormat((req, t) => { throw new Error('unknown error'); }); const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; @@ -76,12 +76,12 @@ describe('adoptToHapiOnRequestFormat', () => { }); it('Should return Boom.internal error if interceptor returns unexpected result', async () => { - const onRequest = adoptToHapiOnRequestFormat((req, toolkit) => undefined as any); + const onRequest = adoptToHapiOnPostAuthFormat((req, toolkit) => undefined as any); const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; expect(result).toBeInstanceOf(Boom); - expect(result.message).toBe( - 'Unexpected result from OnRequest. Expected OnRequestResult, but given: undefined.' + expect(result.message).toMatchInlineSnapshot( + `"Unexpected result from OnPostAuth. Expected OnPostAuthResult, but given: undefined."` ); expect(result.output.statusCode).toBe(500); }); diff --git a/src/core/server/http/lifecycle/on_request.ts b/src/core/server/http/lifecycle/on_post_auth.ts similarity index 57% rename from src/core/server/http/lifecycle/on_request.ts rename to src/core/server/http/lifecycle/on_post_auth.ts index 168b4f513400fe..01be34c1f6208b 100644 --- a/src/core/server/http/lifecycle/on_request.ts +++ b/src/core/server/http/lifecycle/on_post_auth.ts @@ -17,7 +17,6 @@ * under the License. */ -import { Url } from 'url'; import Boom from 'boom'; import { Lifecycle, Request, ResponseToolkit } from 'hapi'; import { KibanaRequest } from '../router'; @@ -28,90 +27,104 @@ enum ResultType { rejected = 'rejected', } +interface NextParams { + type: ResultType.next; +} + +interface RedirectedParams { + type: ResultType.redirected; + url: string; +} + +interface RejectedParams { + type: ResultType.rejected; + error: Error; + statusCode?: number; +} + /** @internal */ -class OnRequestResult { +class OnPostAuthResult { public static next() { - return new OnRequestResult(ResultType.next); + return new OnPostAuthResult({ type: ResultType.next }); } public static redirected(url: string) { - return new OnRequestResult(ResultType.redirected, url); + return new OnPostAuthResult({ type: ResultType.redirected, url }); } public static rejected(error: Error, options: { statusCode?: number } = {}) { - return new OnRequestResult(ResultType.rejected, { error, statusCode: options.statusCode }); + return new OnPostAuthResult({ + type: ResultType.rejected, + error, + statusCode: options.statusCode, + }); } public static isValidResult(candidate: any) { - return candidate instanceof OnRequestResult; + return candidate instanceof OnPostAuthResult; } - constructor(private readonly type: ResultType, public readonly payload?: any) {} + constructor(public readonly params: NextParams | RejectedParams | RedirectedParams) {} public isNext() { - return this.type === ResultType.next; + return this.params.type === ResultType.next; } public isRedirected() { - return this.type === ResultType.redirected; + return this.params.type === ResultType.redirected; } public isRejected() { - return this.type === ResultType.rejected; + return this.params.type === ResultType.rejected; } } /** * @public - * A tool set defining an outcome of OnRequest interceptor for incoming request. + * A tool set defining an outcome of OnPostAuth interceptor for incoming request. */ -export interface OnRequestToolkit { +export interface OnPostAuthToolkit { /** To pass request to the next handler */ - next: () => OnRequestResult; + next: () => OnPostAuthResult; /** To interrupt request handling and redirect to a configured url */ - redirected: (url: string) => OnRequestResult; + redirected: (url: string) => OnPostAuthResult; /** Fail the request with specified error. */ - rejected: (error: Error, options?: { statusCode?: number }) => OnRequestResult; - /** Change url for an incoming request. */ - setUrl: (newUrl: string | Url) => void; + rejected: (error: Error, options?: { statusCode?: number }) => OnPostAuthResult; } /** @public */ -export type OnRequestHandler = ( +export type OnPostAuthHandler = ( req: KibanaRequest, - t: OnRequestToolkit -) => OnRequestResult | Promise; + t: OnPostAuthToolkit +) => OnPostAuthResult | Promise; +const toolkit: OnPostAuthToolkit = { + next: OnPostAuthResult.next, + redirected: OnPostAuthResult.redirected, + rejected: OnPostAuthResult.rejected, +}; /** * @public * Adopt custom request interceptor to Hapi lifecycle system. * @param fn - an extension point allowing to perform custom logic for * incoming HTTP requests. */ -export function adoptToHapiOnRequestFormat(fn: OnRequestHandler) { +export function adoptToHapiOnPostAuthFormat(fn: OnPostAuthHandler) { return async function interceptRequest( request: Request, h: ResponseToolkit ): Promise { try { - const result = await fn(KibanaRequest.from(request, undefined), { - next: OnRequestResult.next, - redirected: OnRequestResult.redirected, - rejected: OnRequestResult.rejected, - setUrl: (newUrl: string | Url) => { - request.setUrl(newUrl); - // We should update raw request as well since it can be proxied to the old platform - request.raw.req.url = typeof newUrl === 'string' ? newUrl : newUrl.href; - }, - }); - if (OnRequestResult.isValidResult(result)) { + const result = await fn(KibanaRequest.from(request, undefined), toolkit); + if (OnPostAuthResult.isValidResult(result)) { if (result.isNext()) { return h.continue; } if (result.isRedirected()) { - return h.redirect(result.payload).takeover(); + const { url } = result.params as RedirectedParams; + return h.redirect(url).takeover(); } if (result.isRejected()) { - const { error, statusCode } = result.payload; + const { error, statusCode } = result.params as RejectedParams; return Boom.boomify(error, { statusCode }); } } throw new Error( - `Unexpected result from OnRequest. Expected OnRequestResult, but given: ${result}.` + `Unexpected result from OnPostAuth. Expected OnPostAuthResult, but given: ${result}.` ); } catch (error) { return Boom.internal(error.message, { statusCode: 500 }); diff --git a/src/core/server/http/lifecycle/on_pre_auth.test.ts b/src/core/server/http/lifecycle/on_pre_auth.test.ts new file mode 100644 index 00000000000000..00cbd5365fb998 --- /dev/null +++ b/src/core/server/http/lifecycle/on_pre_auth.test.ts @@ -0,0 +1,108 @@ +/* + * 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 Boom from 'boom'; +import { adoptToHapiOnPreAuthFormat } from './on_pre_auth'; + +const requestMock = {} as any; +const createResponseToolkit = (customization = {}): any => ({ ...customization }); + +describe('adoptToHapiOnPreAuthFormat', () => { + it('Should allow passing request to the next handler', async () => { + const continueSymbol = {}; + const onRequest = adoptToHapiOnPreAuthFormat((req, t) => t.next()); + const result = await onRequest( + requestMock, + createResponseToolkit({ + ['continue']: continueSymbol, + }) + ); + + expect(result).toBe(continueSymbol); + }); + + it('Should support redirecting to specified url', async () => { + const redirectUrl = '/docs'; + const onRequest = adoptToHapiOnPreAuthFormat((req, t) => t.redirected(redirectUrl)); + const takeoverSymbol = {}; + const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol })); + const result = await onRequest( + requestMock, + createResponseToolkit({ + redirect: redirectMock, + }) + ); + + expect(redirectMock).toBeCalledWith(redirectUrl); + expect(result).toBe(takeoverSymbol); + }); + + it('Should support request forwarding to specified url', async () => { + const redirectUrl = '/docs'; + const onRequest = adoptToHapiOnPreAuthFormat((req, t) => + t.redirected(redirectUrl, { forward: true }) + ); + const continueSymbol = {}; + const setUrl = jest.fn(); + const reqMock = { setUrl, raw: { req: {} } } as any; + const result = await onRequest( + reqMock as any, + createResponseToolkit({ + ['continue']: continueSymbol, + }) + ); + + expect(setUrl).toBeCalledWith(redirectUrl); + expect(reqMock.raw.req.url).toBe(redirectUrl); + expect(result).toBe(continueSymbol); + }); + + it('Should support specifying statusCode and message for Boom error', async () => { + const onRequest = adoptToHapiOnPreAuthFormat((req, t) => { + return t.rejected(new Error('unexpected result'), { statusCode: 501 }); + }); + const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + + expect(result).toBeInstanceOf(Boom); + expect(result.message).toBe('unexpected result'); + expect(result.output.statusCode).toBe(501); + }); + + it('Should return Boom.internal error if interceptor throws', async () => { + const onRequest = adoptToHapiOnPreAuthFormat((req, t) => { + throw new Error('unknown error'); + }); + const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + + expect(result).toBeInstanceOf(Boom); + expect(result.message).toBe('unknown error'); + expect(result.output.statusCode).toBe(500); + }); + + it('Should return Boom.internal error if interceptor returns unexpected result', async () => { + const onRequest = adoptToHapiOnPreAuthFormat((req, toolkit) => undefined as any); + const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + + expect(result).toBeInstanceOf(Boom); + expect(result.message).toMatchInlineSnapshot( + `"Unexpected result from OnPreAuth. Expected OnPreAuthResult, but given: undefined."` + ); + expect(result.output.statusCode).toBe(500); + }); +}); diff --git a/src/core/server/http/lifecycle/on_pre_auth.ts b/src/core/server/http/lifecycle/on_pre_auth.ts new file mode 100644 index 00000000000000..3b29439baa92a4 --- /dev/null +++ b/src/core/server/http/lifecycle/on_pre_auth.ts @@ -0,0 +1,147 @@ +/* + * 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 Boom from 'boom'; +import { Lifecycle, Request, ResponseToolkit } from 'hapi'; +import { KibanaRequest } from '../router'; + +enum ResultType { + next = 'next', + redirected = 'redirected', + rejected = 'rejected', +} + +interface NextParams { + type: ResultType.next; +} + +interface RedirectedParams { + type: ResultType.redirected; + url: string; + forward?: boolean; +} + +interface RejectedParams { + type: ResultType.rejected; + error: Error; + statusCode?: number; +} + +/** @internal */ +class OnPreAuthResult { + public static next() { + return new OnPreAuthResult({ type: ResultType.next }); + } + public static redirected(url: string, options: { forward?: boolean } = {}) { + return new OnPreAuthResult({ type: ResultType.redirected, url, forward: options.forward }); + } + public static rejected(error: Error, options: { statusCode?: number } = {}) { + return new OnPreAuthResult({ + type: ResultType.rejected, + error, + statusCode: options.statusCode, + }); + } + public static isValidResult(candidate: any) { + return candidate instanceof OnPreAuthResult; + } + constructor(public readonly params: NextParams | RejectedParams | RedirectedParams) {} + public isNext() { + return this.params.type === ResultType.next; + } + public isRedirected() { + return this.params.type === ResultType.redirected; + } + public isRejected() { + return this.params.type === ResultType.rejected; + } +} + +/** + * @public + * A tool set defining an outcome of OnPreAuth interceptor for incoming request. + */ +export interface OnPreAuthToolkit { + /** To pass request to the next handler */ + next: () => OnPreAuthResult; + /** + * To interrupt request handling and redirect to a configured url. + * If "options.forwarded" = true, request will be forwarded to another url right on the server. + * */ + redirected: (url: string, options?: { forward: boolean }) => OnPreAuthResult; + /** Fail the request with specified error. */ + rejected: (error: Error, options?: { statusCode?: number }) => OnPreAuthResult; +} + +const toolkit: OnPreAuthToolkit = { + next: OnPreAuthResult.next, + redirected: OnPreAuthResult.redirected, + rejected: OnPreAuthResult.rejected, +}; + +/** @public */ +export type OnPreAuthHandler = ( + req: KibanaRequest, + t: OnPreAuthToolkit +) => OnPreAuthResult | Promise; + +/** + * @public + * Adopt custom request interceptor to Hapi lifecycle system. + * @param fn - an extension point allowing to perform custom logic for + * incoming HTTP requests. + */ +export function adoptToHapiOnPreAuthFormat(fn: OnPreAuthHandler) { + return async function interceptPreAuthRequest( + request: Request, + h: ResponseToolkit + ): Promise { + try { + const result = await fn(KibanaRequest.from(request, undefined), toolkit); + + if (OnPreAuthResult.isValidResult(result)) { + if (result.isNext()) { + return h.continue; + } + + if (result.isRedirected()) { + const { url, forward } = result.params as RedirectedParams; + if (forward) { + request.setUrl(url); + // We should update raw request as well since it can be proxied to the old platform + request.raw.req.url = request.url.href; + return h.continue; + } + return h.redirect(url).takeover(); + } + + if (result.isRejected()) { + const { error, statusCode } = result.params as RejectedParams; + return Boom.boomify(error, { statusCode }); + } + } + + throw new Error( + `Unexpected result from OnPreAuth. Expected OnPreAuthResult, but given: ${result}.` + ); + } catch (error) { + return Boom.internal(error.message, { statusCode: 500 }); + } + }; +} diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 03b62f4948306d..4b7c3193e2ea74 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -17,6 +17,7 @@ * under the License. */ +import { Url } from 'url'; import { ObjectType, TypeOf } from '@kbn/config-schema'; import { Request } from 'hapi'; @@ -70,6 +71,7 @@ export class KibanaRequest { public readonly headers: Headers; public readonly path: string; + public readonly url: Url; constructor( private readonly request: Request, @@ -79,6 +81,7 @@ export class KibanaRequest { ) { this.headers = request.headers; this.path = request.path; + this.url = request.url; } public getFilteredHeaders(headersToKeep: string[]) { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 8d8b38d2e78def..e089ecdec08f2b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -34,8 +34,10 @@ export { AuthenticationHandler, AuthToolkit, KibanaRequest, - OnRequestHandler, - OnRequestToolkit, + OnPreAuthHandler, + OnPreAuthToolkit, + OnPostAuthHandler, + OnPostAuthToolkit, Router, } from './http'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 1c1fe03161e62e..4630d60317f344 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -51,8 +51,9 @@ export interface PluginSetupContext { dataClient$: Observable; }; http: { + registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; - registerOnRequest: HttpServiceSetup['registerOnRequest']; + registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; getBasePathFor: HttpServiceSetup['getBasePathFor']; setBasePathFor: HttpServiceSetup['setBasePathFor']; }; @@ -143,8 +144,9 @@ export function createPluginSetupContext( dataClient$: deps.elasticsearch.dataClient$, }, http: { + registerOnPreAuth: deps.http.registerOnPreAuth, registerAuth: deps.http.registerAuth, - registerOnRequest: deps.http.registerOnRequest, + registerOnPostAuth: deps.http.registerOnPostAuth, getBasePathFor: deps.http.getBasePathFor, setBasePathFor: deps.http.setBasePathFor, }, From 112f30e87e1129a055c7a8e2fefa5b6ba742d124 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 21 May 2019 10:44:39 +0200 Subject: [PATCH 02/18] cleanup integration_tests. now contracts available in tests --- .../http/integration_tests/http_service.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/server/http/integration_tests/http_service.test.ts b/src/core/server/http/integration_tests/http_service.test.ts index 802101fe9774f0..226d264b74d26a 100644 --- a/src/core/server/http/integration_tests/http_service.test.ts +++ b/src/core/server/http/integration_tests/http_service.test.ts @@ -183,8 +183,8 @@ describe('http service', () => { router.get({ path: '/', validate: false }, async (req, res) => res.ok({ content: 'ok' })); const { http } = await root.setup(); - http.registerOnRequest((req, t) => t.next()); - http.registerOnRequest(async (req, t) => { + http.registerOnPostAuth((req, t) => t.next()); + http.registerOnPostAuth(async (req, t) => { await Promise.resolve(); return t.next(); }); @@ -197,7 +197,7 @@ describe('http service', () => { it('Should support redirecting to configured url', async () => { const redirectTo = '/redirect-url'; const { http } = await root.setup(); - http.registerOnRequest(async (req, t) => t.redirected(redirectTo)); + http.registerOnPostAuth(async (req, t) => t.redirected(redirectTo)); await root.start(); const response = await kbnTestServer.request.get(root, '/').expect(302); @@ -206,7 +206,7 @@ describe('http service', () => { it('Should failing a request with configured error and status code', async () => { const { http } = await root.setup(); - http.registerOnRequest(async (req, t) => + http.registerOnPostAuth(async (req, t) => t.rejected(new Error('unexpected error'), { statusCode: 400 }) ); await root.start(); @@ -218,7 +218,7 @@ describe('http service', () => { it(`Shouldn't expose internal error details`, async () => { const { http } = await root.setup(); - http.registerOnRequest(async (req, t) => { + http.registerOnPostAuth(async (req, t) => { throw new Error('sensitive info'); }); await root.start(); @@ -232,12 +232,12 @@ describe('http service', () => { it(`Shouldn't share request object between interceptors`, async () => { const { http } = await root.setup(); - http.registerOnRequest(async (req, t) => { + http.registerOnPostAuth(async (req, t) => { // @ts-ignore. don't complain customField is not defined on Request type req.customField = { value: 42 }; return t.next(); }); - http.registerOnRequest((req, t) => { + http.registerOnPostAuth((req, t) => { // @ts-ignore don't complain customField is not defined on Request type if (typeof req.customField !== 'undefined') { throw new Error('Request object was mutated'); From 4ce4967bb80dd1046819ee73a49ef10b703f8415 Mon Sep 17 00:00:00 2001 From: restrry Date: Thu, 23 May 2019 12:34:06 +0200 Subject: [PATCH 03/18] auth per route is configurable --- src/core/server/http/http_server.test.ts | 43 ++++++++++++++++++++++++ src/core/server/http/router/route.ts | 9 +++++ src/core/server/http/router/router.ts | 17 +++++++--- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 4979768785de31..0cd697eef0ec89 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -708,3 +708,46 @@ test('#setBasePathFor() cannot be set twice for one request', async () => { `"Request basePath was previously set. Setting multiple times is not supported."` ); }); +const cookieOptions = { + name: 'sid', + encryptionKey: 'something_at_least_32_characters', + validate: () => true, + isSecure: false, +}; + +test('Should enable auth for a route by default if registerAuth has been called', async () => { + const { registerAuth, registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router(''); + router.get({ path: '/', validate: false }, async (req, res) => res.ok({})); + registerRouter(router); + + const authenticate = jest + .fn() + .mockImplementation((req, sessionStorage, t) => t.authenticated({})); + await registerAuth(authenticate, cookieOptions); + + await server.start(config); + await supertest(innerServer.listener) + .get('/') + .expect(200); + + expect(authenticate).toHaveBeenCalledTimes(1); +}); + +test('Should support disabling auth for a route', async () => { + const { registerAuth, registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router(''); + router.get({ path: '/', validate: false, authRequired: false }, async (req, res) => res.ok({})); + registerRouter(router); + const authenticate = jest.fn(); + await registerAuth(authenticate, cookieOptions); + + await server.start(config); + await supertest(innerServer.listener) + .get('/') + .expect(200); + + expect(authenticate).not.toHaveBeenCalled(); +}); diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index 64ed67e8f940b1..bbc45258b1e1af 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -35,6 +35,15 @@ export interface RouteConfig

| false; + + /** + * A flag shows that authentication for a route: + * enabled when true + * disabled when false + * + * Enabled by default. + */ + authRequired?: boolean; } export type RouteValidateFactory< diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index a640a413fd81b0..2a1a169e0931dd 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -27,6 +27,7 @@ import { RouteConfig, RouteMethod, RouteSchemas } from './route'; export interface RouterRoute { method: 'GET' | 'POST' | 'PUT' | 'DELETE'; path: string; + authRequired: boolean; handler: (req: Request, responseToolkit: ResponseToolkit) => Promise; } @@ -43,12 +44,14 @@ export class Router { route: RouteConfig, handler: RequestHandler ) { + const { path, authRequired = true } = route; const routeSchemas = this.routeSchemasFromRouteConfig(route, 'GET'); this.routes.push({ handler: async (req, responseToolkit) => await this.handle(routeSchemas, req, responseToolkit, handler), method: 'GET', - path: route.path, + path, + authRequired, }); } @@ -59,12 +62,14 @@ export class Router { route: RouteConfig, handler: RequestHandler ) { + const { path, authRequired = true } = route; const routeSchemas = this.routeSchemasFromRouteConfig(route, 'POST'); this.routes.push({ handler: async (req, responseToolkit) => await this.handle(routeSchemas, req, responseToolkit, handler), method: 'POST', - path: route.path, + path, + authRequired, }); } @@ -75,12 +80,14 @@ export class Router { route: RouteConfig, handler: RequestHandler ) { + const { path, authRequired = true } = route; const routeSchemas = this.routeSchemasFromRouteConfig(route, 'POST'); this.routes.push({ handler: async (req, responseToolkit) => await this.handle(routeSchemas, req, responseToolkit, handler), method: 'PUT', - path: route.path, + path, + authRequired, }); } @@ -91,12 +98,14 @@ export class Router { route: RouteConfig, handler: RequestHandler ) { + const { path, authRequired = true } = route; const routeSchemas = this.routeSchemasFromRouteConfig(route, 'DELETE'); this.routes.push({ handler: async (req, responseToolkit) => await this.handle(routeSchemas, req, responseToolkit, handler), method: 'DELETE', - path: route.path, + path, + authRequired, }); } From 013d8152c9810cbbdb20428306bf2928f8195926 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 23 May 2019 13:44:36 +0200 Subject: [PATCH 04/18] Unify lifecycle results structure --- src/core/server/http/lifecycle/auth.ts | 99 ++++++++++++------- .../http/lifecycle/on_post_auth.test.ts | 20 ++-- .../server/http/lifecycle/on_post_auth.ts | 84 ++++++++-------- .../server/http/lifecycle/on_pre_auth.test.ts | 24 ++--- src/core/server/http/lifecycle/on_pre_auth.ts | 85 ++++++++-------- 5 files changed, 168 insertions(+), 144 deletions(-) diff --git a/src/core/server/http/lifecycle/auth.ts b/src/core/server/http/lifecycle/auth.ts index 8205d21c5ff59e..4ab1b8e39a0cb5 100644 --- a/src/core/server/http/lifecycle/auth.ts +++ b/src/core/server/http/lifecycle/auth.ts @@ -17,6 +17,7 @@ * under the License. */ import Boom from 'boom'; +import { noop } from 'lodash'; import { Lifecycle, Request, ResponseToolkit } from 'hapi'; import { SessionStorage, SessionStorageFactory } from '../session_storage'; @@ -26,39 +27,60 @@ enum ResultType { rejected = 'rejected', } -/** @internal */ -class AuthResult { - public static authenticated(credentials: any) { - return new AuthResult(ResultType.authenticated, credentials); - } - public static redirected(url: string) { - return new AuthResult(ResultType.redirected, url); - } - public static rejected(error: Error, options: { statusCode?: number } = {}) { - return new AuthResult(ResultType.rejected, { error, statusCode: options.statusCode }); - } - public static isValidResult(candidate: any) { - return candidate instanceof AuthResult; - } - constructor(private readonly type: ResultType, public readonly payload: any) {} - public isAuthenticated() { - return this.type === ResultType.authenticated; - } - public isRedirected() { - return this.type === ResultType.redirected; - } - public isRejected() { - return this.type === ResultType.rejected; - } +interface Authenticated { + type: ResultType.authenticated; + state: object; } +interface Redirected { + type: ResultType.redirected; + url: string; +} + +interface Rejected { + type: ResultType.rejected; + error: Error; + statusCode?: number; +} + +type AuthResult = Authenticated | Rejected | Redirected; + +const authResult = { + authenticated(state: object): AuthResult { + return { type: ResultType.authenticated, state }; + }, + redirected(url: string): AuthResult { + return { type: ResultType.redirected, url }; + }, + rejected(error: Error, options: { statusCode?: number } = {}): AuthResult { + return { type: ResultType.rejected, error, statusCode: options.statusCode }; + }, + isValid(candidate: any): candidate is AuthResult { + return ( + candidate && + (candidate.type === ResultType.authenticated || + candidate.type === ResultType.rejected || + candidate.type === ResultType.redirected) + ); + }, + isAuthenticated(result: AuthResult): result is Authenticated { + return result.type === ResultType.authenticated; + }, + isRedirected(result: AuthResult): result is Redirected { + return result.type === ResultType.redirected; + }, + isRejected(result: AuthResult): result is Rejected { + return result.type === ResultType.rejected; + }, +}; + /** * @public * A tool set defining an outcome of Auth interceptor for incoming request. */ export interface AuthToolkit { /** Authentication is successful with given credentials, allow request to pass through */ - authenticated: (credentials: any) => AuthResult; + authenticated: (state: object) => AuthResult; /** Authentication requires to interrupt request handling and redirect to a configured url */ redirected: (url: string) => AuthResult; /** Authentication is unsuccessful, fail the request with specified error. */ @@ -66,9 +88,9 @@ export interface AuthToolkit { } const toolkit: AuthToolkit = { - authenticated: AuthResult.authenticated, - redirected: AuthResult.redirected, - rejected: AuthResult.rejected, + authenticated: authResult.authenticated, + redirected: authResult.redirected, + rejected: authResult.rejected, }; /** @public */ @@ -76,12 +98,13 @@ export type AuthenticationHandler = ( request: Request, sessionStorage: SessionStorage, t: AuthToolkit -) => Promise; +) => AuthResult | Promise; /** @public */ export function adoptToHapiAuthFormat( fn: AuthenticationHandler, - sessionStorage: SessionStorageFactory + sessionStorage: SessionStorageFactory, + onSuccess: (req: Request, state: unknown) => void = noop ) { return async function interceptAuth( req: Request, @@ -89,16 +112,16 @@ export function adoptToHapiAuthFormat( ): Promise { try { const result = await fn(req, sessionStorage.asScoped(req), toolkit); - - if (AuthResult.isValidResult(result)) { - if (result.isAuthenticated()) { - return h.authenticated({ credentials: result.payload }); + if (authResult.isValid(result)) { + if (authResult.isAuthenticated(result)) { + onSuccess(req, result.state); + return h.authenticated({ credentials: result.state }); } - if (result.isRedirected()) { - return h.redirect(result.payload).takeover(); + if (authResult.isRedirected(result)) { + return h.redirect(result.url).takeover(); } - if (result.isRejected()) { - const { error, statusCode } = result.payload; + if (authResult.isRejected(result)) { + const { error, statusCode } = result; return Boom.boomify(error, { statusCode }); } } diff --git a/src/core/server/http/lifecycle/on_post_auth.test.ts b/src/core/server/http/lifecycle/on_post_auth.test.ts index 8dd0d1a20f4e17..7644b8a35ef772 100644 --- a/src/core/server/http/lifecycle/on_post_auth.test.ts +++ b/src/core/server/http/lifecycle/on_post_auth.test.ts @@ -26,8 +26,8 @@ const createResponseToolkit = (customization = {}): any => ({ ...customization } describe('adoptToHapiOnPostAuthFormat', () => { it('Should allow passing request to the next handler', async () => { const continueSymbol = {}; - const onRequest = adoptToHapiOnPostAuthFormat((req, t) => t.next()); - const result = await onRequest( + const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => t.next()); + const result = await onPostAuth( requestMock, createResponseToolkit({ ['continue']: continueSymbol, @@ -39,10 +39,10 @@ describe('adoptToHapiOnPostAuthFormat', () => { it('Should support redirecting to specified url', async () => { const redirectUrl = '/docs'; - const onRequest = adoptToHapiOnPostAuthFormat((req, t) => t.redirected(redirectUrl)); + const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => t.redirected(redirectUrl)); const takeoverSymbol = {}; const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol })); - const result = await onRequest( + const result = await onPostAuth( requestMock, createResponseToolkit({ redirect: redirectMock, @@ -54,10 +54,10 @@ describe('adoptToHapiOnPostAuthFormat', () => { }); it('Should support specifying statusCode and message for Boom error', async () => { - const onRequest = adoptToHapiOnPostAuthFormat((req, t) => { + const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => { return t.rejected(new Error('unexpected result'), { statusCode: 501 }); }); - const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom; expect(result).toBeInstanceOf(Boom); expect(result.message).toBe('unexpected result'); @@ -65,10 +65,10 @@ describe('adoptToHapiOnPostAuthFormat', () => { }); it('Should return Boom.internal error if interceptor throws', async () => { - const onRequest = adoptToHapiOnPostAuthFormat((req, t) => { + const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => { throw new Error('unknown error'); }); - const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom; expect(result).toBeInstanceOf(Boom); expect(result.message).toBe('unknown error'); @@ -76,8 +76,8 @@ describe('adoptToHapiOnPostAuthFormat', () => { }); it('Should return Boom.internal error if interceptor returns unexpected result', async () => { - const onRequest = adoptToHapiOnPostAuthFormat((req, toolkit) => undefined as any); - const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + const onPostAuth = adoptToHapiOnPostAuthFormat((req, toolkit) => undefined as any); + const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom; expect(result).toBeInstanceOf(Boom); expect(result.message).toMatchInlineSnapshot( diff --git a/src/core/server/http/lifecycle/on_post_auth.ts b/src/core/server/http/lifecycle/on_post_auth.ts index 01be34c1f6208b..e8d3ba33387475 100644 --- a/src/core/server/http/lifecycle/on_post_auth.ts +++ b/src/core/server/http/lifecycle/on_post_auth.ts @@ -27,50 +27,51 @@ enum ResultType { rejected = 'rejected', } -interface NextParams { +interface Next { type: ResultType.next; } -interface RedirectedParams { +interface Redirected { type: ResultType.redirected; url: string; } -interface RejectedParams { +interface Rejected { type: ResultType.rejected; error: Error; statusCode?: number; } -/** @internal */ -class OnPostAuthResult { - public static next() { - return new OnPostAuthResult({ type: ResultType.next }); - } - public static redirected(url: string) { - return new OnPostAuthResult({ type: ResultType.redirected, url }); - } - public static rejected(error: Error, options: { statusCode?: number } = {}) { - return new OnPostAuthResult({ - type: ResultType.rejected, - error, - statusCode: options.statusCode, - }); - } - public static isValidResult(candidate: any) { - return candidate instanceof OnPostAuthResult; - } - constructor(public readonly params: NextParams | RejectedParams | RedirectedParams) {} - public isNext() { - return this.params.type === ResultType.next; - } - public isRedirected() { - return this.params.type === ResultType.redirected; - } - public isRejected() { - return this.params.type === ResultType.rejected; - } -} +type OnPostAuthResult = Next | Rejected | Redirected; + +const postAuthResult = { + next(): OnPostAuthResult { + return { type: ResultType.next }; + }, + redirected(url: string): OnPostAuthResult { + return { type: ResultType.redirected, url }; + }, + rejected(error: Error, options: { statusCode?: number } = {}): OnPostAuthResult { + return { type: ResultType.rejected, error, statusCode: options.statusCode }; + }, + isValid(candidate: any): candidate is OnPostAuthResult { + return ( + candidate && + (candidate.type === ResultType.next || + candidate.type === ResultType.rejected || + candidate.type === ResultType.redirected) + ); + }, + isNext(result: OnPostAuthResult): result is Next { + return result.type === ResultType.next; + }, + isRedirected(result: OnPostAuthResult): result is Redirected { + return result.type === ResultType.redirected; + }, + isRejected(result: OnPostAuthResult): result is Rejected { + return result.type === ResultType.rejected; + }, +}; /** * @public @@ -92,9 +93,9 @@ export type OnPostAuthHandler = ( ) => OnPostAuthResult | Promise; const toolkit: OnPostAuthToolkit = { - next: OnPostAuthResult.next, - redirected: OnPostAuthResult.redirected, - rejected: OnPostAuthResult.rejected, + next: postAuthResult.next, + redirected: postAuthResult.redirected, + rejected: postAuthResult.rejected, }; /** * @public @@ -109,16 +110,15 @@ export function adoptToHapiOnPostAuthFormat(fn: OnPostAuthHandler) { ): Promise { try { const result = await fn(KibanaRequest.from(request, undefined), toolkit); - if (OnPostAuthResult.isValidResult(result)) { - if (result.isNext()) { + if (postAuthResult.isValid(result)) { + if (postAuthResult.isNext(result)) { return h.continue; } - if (result.isRedirected()) { - const { url } = result.params as RedirectedParams; - return h.redirect(url).takeover(); + if (postAuthResult.isRedirected(result)) { + return h.redirect(result.url).takeover(); } - if (result.isRejected()) { - const { error, statusCode } = result.params as RejectedParams; + if (postAuthResult.isRejected(result)) { + const { error, statusCode } = result; return Boom.boomify(error, { statusCode }); } } diff --git a/src/core/server/http/lifecycle/on_pre_auth.test.ts b/src/core/server/http/lifecycle/on_pre_auth.test.ts index 00cbd5365fb998..83900ba5ad89d2 100644 --- a/src/core/server/http/lifecycle/on_pre_auth.test.ts +++ b/src/core/server/http/lifecycle/on_pre_auth.test.ts @@ -26,8 +26,8 @@ const createResponseToolkit = (customization = {}): any => ({ ...customization } describe('adoptToHapiOnPreAuthFormat', () => { it('Should allow passing request to the next handler', async () => { const continueSymbol = {}; - const onRequest = adoptToHapiOnPreAuthFormat((req, t) => t.next()); - const result = await onRequest( + const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => t.next()); + const result = await onPreAuth( requestMock, createResponseToolkit({ ['continue']: continueSymbol, @@ -39,10 +39,10 @@ describe('adoptToHapiOnPreAuthFormat', () => { it('Should support redirecting to specified url', async () => { const redirectUrl = '/docs'; - const onRequest = adoptToHapiOnPreAuthFormat((req, t) => t.redirected(redirectUrl)); + const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => t.redirected(redirectUrl)); const takeoverSymbol = {}; const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol })); - const result = await onRequest( + const result = await onPreAuth( requestMock, createResponseToolkit({ redirect: redirectMock, @@ -55,13 +55,13 @@ describe('adoptToHapiOnPreAuthFormat', () => { it('Should support request forwarding to specified url', async () => { const redirectUrl = '/docs'; - const onRequest = adoptToHapiOnPreAuthFormat((req, t) => + const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => t.redirected(redirectUrl, { forward: true }) ); const continueSymbol = {}; const setUrl = jest.fn(); const reqMock = { setUrl, raw: { req: {} } } as any; - const result = await onRequest( + const result = await onPreAuth( reqMock as any, createResponseToolkit({ ['continue']: continueSymbol, @@ -74,10 +74,10 @@ describe('adoptToHapiOnPreAuthFormat', () => { }); it('Should support specifying statusCode and message for Boom error', async () => { - const onRequest = adoptToHapiOnPreAuthFormat((req, t) => { + const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => { return t.rejected(new Error('unexpected result'), { statusCode: 501 }); }); - const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + const result = (await onPreAuth(requestMock, createResponseToolkit())) as Boom; expect(result).toBeInstanceOf(Boom); expect(result.message).toBe('unexpected result'); @@ -85,10 +85,10 @@ describe('adoptToHapiOnPreAuthFormat', () => { }); it('Should return Boom.internal error if interceptor throws', async () => { - const onRequest = adoptToHapiOnPreAuthFormat((req, t) => { + const onPreAuth = adoptToHapiOnPreAuthFormat((req, t) => { throw new Error('unknown error'); }); - const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + const result = (await onPreAuth(requestMock, createResponseToolkit())) as Boom; expect(result).toBeInstanceOf(Boom); expect(result.message).toBe('unknown error'); @@ -96,8 +96,8 @@ describe('adoptToHapiOnPreAuthFormat', () => { }); it('Should return Boom.internal error if interceptor returns unexpected result', async () => { - const onRequest = adoptToHapiOnPreAuthFormat((req, toolkit) => undefined as any); - const result = (await onRequest(requestMock, createResponseToolkit())) as Boom; + const onPreAuth = adoptToHapiOnPreAuthFormat((req, toolkit) => undefined as any); + const result = (await onPreAuth(requestMock, createResponseToolkit())) as Boom; expect(result).toBeInstanceOf(Boom); expect(result.message).toMatchInlineSnapshot( diff --git a/src/core/server/http/lifecycle/on_pre_auth.ts b/src/core/server/http/lifecycle/on_pre_auth.ts index 3b29439baa92a4..6902117ad82faf 100644 --- a/src/core/server/http/lifecycle/on_pre_auth.ts +++ b/src/core/server/http/lifecycle/on_pre_auth.ts @@ -27,51 +27,52 @@ enum ResultType { rejected = 'rejected', } -interface NextParams { +interface Next { type: ResultType.next; } -interface RedirectedParams { +interface Redirected { type: ResultType.redirected; url: string; forward?: boolean; } -interface RejectedParams { +interface Rejected { type: ResultType.rejected; error: Error; statusCode?: number; } -/** @internal */ -class OnPreAuthResult { - public static next() { - return new OnPreAuthResult({ type: ResultType.next }); - } - public static redirected(url: string, options: { forward?: boolean } = {}) { - return new OnPreAuthResult({ type: ResultType.redirected, url, forward: options.forward }); - } - public static rejected(error: Error, options: { statusCode?: number } = {}) { - return new OnPreAuthResult({ - type: ResultType.rejected, - error, - statusCode: options.statusCode, - }); - } - public static isValidResult(candidate: any) { - return candidate instanceof OnPreAuthResult; - } - constructor(public readonly params: NextParams | RejectedParams | RedirectedParams) {} - public isNext() { - return this.params.type === ResultType.next; - } - public isRedirected() { - return this.params.type === ResultType.redirected; - } - public isRejected() { - return this.params.type === ResultType.rejected; - } -} +type OnPreAuthResult = Next | Rejected | Redirected; + +const preAuthResult = { + next(): OnPreAuthResult { + return { type: ResultType.next }; + }, + redirected(url: string, options: { forward?: boolean } = {}): OnPreAuthResult { + return { type: ResultType.redirected, url, forward: options.forward }; + }, + rejected(error: Error, options: { statusCode?: number } = {}): OnPreAuthResult { + return { type: ResultType.rejected, error, statusCode: options.statusCode }; + }, + isValid(candidate: any): candidate is OnPreAuthResult { + return ( + candidate && + (candidate.type === ResultType.next || + candidate.type === ResultType.rejected || + candidate.type === ResultType.redirected) + ); + }, + isNext(result: OnPreAuthResult): result is Next { + return result.type === ResultType.next; + }, + isRedirected(result: OnPreAuthResult): result is Redirected { + return result.type === ResultType.redirected; + }, + isRejected(result: OnPreAuthResult): result is Rejected { + return result.type === ResultType.rejected; + }, +}; /** * @public @@ -90,9 +91,9 @@ export interface OnPreAuthToolkit { } const toolkit: OnPreAuthToolkit = { - next: OnPreAuthResult.next, - redirected: OnPreAuthResult.redirected, - rejected: OnPreAuthResult.rejected, + next: preAuthResult.next, + redirected: preAuthResult.redirected, + rejected: preAuthResult.rejected, }; /** @public */ @@ -115,24 +116,24 @@ export function adoptToHapiOnPreAuthFormat(fn: OnPreAuthHandler) { try { const result = await fn(KibanaRequest.from(request, undefined), toolkit); - if (OnPreAuthResult.isValidResult(result)) { - if (result.isNext()) { + if (preAuthResult.isValid(result)) { + if (preAuthResult.isNext(result)) { return h.continue; } - if (result.isRedirected()) { - const { url, forward } = result.params as RedirectedParams; + if (preAuthResult.isRedirected(result)) { + const { url, forward } = result; if (forward) { request.setUrl(url); // We should update raw request as well since it can be proxied to the old platform - request.raw.req.url = request.url.href; + request.raw.req.url = url; return h.continue; } return h.redirect(url).takeover(); } - if (result.isRejected()) { - const { error, statusCode } = result.params as RejectedParams; + if (preAuthResult.isRejected(result)) { + const { error, statusCode } = result; return Boom.boomify(error, { statusCode }); } } From 84c5c1d81f2eebbe391dd5fc429f3af348659895 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 23 May 2019 13:45:33 +0200 Subject: [PATCH 05/18] expose api to store auth state and status via http service --- src/core/server/http/http_server.ts | 51 +++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 91e53524e872fa..1c7f18c750ec05 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -32,6 +32,12 @@ import { createCookieSessionStorageFactory, } from './cookie_session_storage'; +enum AuthStatus { + authenticated = 'authenticated', + unauthenticated = 'unauthenticated', + unknown = 'unknown', +} + export interface HttpServerSetup { server: Server; options: ServerOptions; @@ -43,7 +49,7 @@ export interface HttpServerSetup { registerAuth: ( authenticationHandler: AuthenticationHandler, cookieOptions: SessionStorageCookieOptions - ) => void; + ) => Promise; /** * Define custom logic to perform for incoming requests. * Applied to all resources by default. @@ -53,6 +59,15 @@ export interface HttpServerSetup { registerOnPostAuth: (requestHandler: OnPostAuthHandler) => void; getBasePathFor: (request: KibanaRequest | Request) => string; setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void; + auth: { + get: ( + request: KibanaRequest | Request + ) => { + status: AuthStatus; + state: unknown; + }; + isAuthenticated: (request: KibanaRequest | Request) => boolean; + }; } export class HttpServer { @@ -64,6 +79,11 @@ export class HttpServer { string >(); + private authState = new WeakMap< + ReturnType, + unknown + >(); + constructor(private readonly log: Logger) {} public isListening() { @@ -118,6 +138,10 @@ export class HttpServer { ) => this.registerAuth(fn, cookieOptions, config.basePath), getBasePathFor: this.getBasePathFor.bind(this, config), setBasePathFor: this.setBasePathFor.bind(this), + auth: { + get: this.getAuthData.bind(this), + isAuthenticated: this.isAuthenticated.bind(this), + }, // Return server instance with the connection options so that we can properly // bridge core and the "legacy" Kibana internally. Once this bridge isn't // needed anymore we shouldn't return the instance from this method. @@ -133,10 +157,14 @@ export class HttpServer { for (const router of this.registeredRouters) { for (const route of router.getRoutes()) { + const isAuthRequired = Boolean(this.authRegistered && route.authRequired); this.server.route({ handler: route.handler, method: route.method, path: this.getRouteFullPath(router.path, route.path), + options: { + auth: isAuthRequired ? undefined : false, + }, }); } } @@ -226,7 +254,9 @@ export class HttpServer { ); this.server.auth.scheme('login', () => ({ - authenticate: adoptToHapiAuthFormat(fn, sessionStorage), + authenticate: adoptToHapiAuthFormat(fn, sessionStorage, (req, state) => { + this.authState.set(req.raw.req, state); + }), })); this.server.auth.strategy('session', 'login'); @@ -236,4 +266,21 @@ export class HttpServer { // https://github.com/hapijs/hapi/blob/master/API.md#-serverauthdefaultoptions this.server.auth.default('session'); } + private getAuthData(request: KibanaRequest | Request) { + const incomingMessage = + request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; + + const hasState = this.authState.has(incomingMessage); + const state = this.authState.get(incomingMessage); + const status: AuthStatus = hasState + ? AuthStatus.authenticated + : this.authRegistered + ? AuthStatus.unauthenticated + : AuthStatus.unknown; + + return { status, state }; + } + private isAuthenticated(request: KibanaRequest | Request) { + return this.getAuthData(request).status === AuthStatus.authenticated; + } } From e0a607b14e9360a344d8090bdbe3076fa2fa311c Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 23 May 2019 13:45:48 +0200 Subject: [PATCH 06/18] update tests --- src/core/server/http/http_server.test.ts | 105 +++++++++++++++++- src/core/server/http/http_service.mock.ts | 4 + .../integration_tests/http_service.test.ts | 46 ++++++-- 3 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 0cd697eef0ec89..0f1a85a336b048 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -601,7 +601,7 @@ test('registers auth request interceptor only once', async () => { expect(doRegister()).rejects.toThrowError('Auth interceptor was already registered'); }); -test('registers onRequest interceptor several times', async () => { +test('registers registerOnPostAuth interceptor several times', async () => { const { registerOnPostAuth } = await server.setup(config); const doRegister = () => registerOnPostAuth(() => null as any); @@ -751,3 +751,106 @@ test('Should support disabling auth for a route', async () => { expect(authenticate).not.toHaveBeenCalled(); }); + +describe('#auth.isAuthenticated()', () => { + it('returns true if has been authorized', async () => { + const { registerAuth, registerRouter, server: innerServer, auth } = await server.setup(config); + + const router = new Router(''); + router.get({ path: '/', validate: false }, async (req, res) => + res.ok({ isAuthenticated: auth.isAuthenticated(req) }) + ); + registerRouter(router); + + await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions); + + await server.start(config); + await supertest(innerServer.listener) + .get('/') + .expect(200, { isAuthenticated: true }); + }); + + it('returns false if has not been authorized', async () => { + const { registerAuth, registerRouter, server: innerServer, auth } = await server.setup(config); + + const router = new Router(''); + router.get({ path: '/', validate: false, authRequired: false }, async (req, res) => + res.ok({ isAuthenticated: auth.isAuthenticated(req) }) + ); + registerRouter(router); + + await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions); + + await server.start(config); + await supertest(innerServer.listener) + .get('/') + .expect(200, { isAuthenticated: false }); + }); + + it('returns false if no authorization mechanism has been registered', async () => { + const { registerRouter, server: innerServer, auth } = await server.setup(config); + + const router = new Router(''); + router.get({ path: '/', validate: false, authRequired: false }, async (req, res) => + res.ok({ isAuthenticated: auth.isAuthenticated(req) }) + ); + registerRouter(router); + + await server.start(config); + await supertest(innerServer.listener) + .get('/') + .expect(200, { isAuthenticated: false }); + }); +}); + +describe('#auth.get()', () => { + it('Should return authenticated status and allow associate auth state with request', async () => { + const user = { id: '42' }; + const { registerRouter, registerAuth, server: innerServer, auth } = await server.setup(config); + await registerAuth((req, sessionStorage, t) => { + sessionStorage.set({ value: user }); + return t.authenticated(user); + }, cookieOptions); + + const router = new Router(''); + router.get({ path: '/', validate: false }, async (req, res) => res.ok(auth.get(req))); + registerRouter(router); + await server.start(config); + + await supertest(innerServer.listener) + .get('/') + .expect(200, { state: user, status: 'authenticated' }); + }); + + it('Should return correct authentication unknown status', async () => { + const { registerRouter, server: innerServer, auth } = await server.setup(config); + const router = new Router(''); + router.get({ path: '/', validate: false }, async (req, res) => res.ok(auth.get(req))); + + registerRouter(router); + await server.start(config); + await supertest(innerServer.listener) + .get('/') + .expect(200, { status: 'unknown' }); + }); + + it('Should return correct unauthenticated status', async () => { + const authenticate = jest.fn(); + + const { registerRouter, registerAuth, server: innerServer, auth } = await server.setup(config); + await registerAuth(authenticate, cookieOptions); + const router = new Router(''); + router.get({ path: '/', validate: false, authRequired: false }, async (req, res) => + res.ok(auth.get(req)) + ); + + registerRouter(router); + await server.start(config); + + await supertest(innerServer.listener) + .get('/') + .expect(200, { status: 'unauthenticated' }); + + expect(authenticate).not.toHaveBeenCalled(); + }); +}); diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 3e06f4b1f4fa78..6d49ac3010ef02 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -31,6 +31,10 @@ const createSetupContractMock = () => { setBasePathFor: jest.fn(), // we can mock some hapi server method when we need it server: {} as Server, + auth: { + get: jest.fn(), + isAuthenticated: jest.fn(), + }, }; return setupContract; }; diff --git a/src/core/server/http/integration_tests/http_service.test.ts b/src/core/server/http/integration_tests/http_service.test.ts index 226d264b74d26a..93fe20a80e1209 100644 --- a/src/core/server/http/integration_tests/http_service.test.ts +++ b/src/core/server/http/integration_tests/http_service.test.ts @@ -61,14 +61,14 @@ describe('http service', () => { if (req.headers.authorization) { const user = { id: '42' }; sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs }); - return t.authenticated({ credentials: user }); + return t.authenticated(user); } else { return t.rejected(Boom.unauthorized()); } }; const { http } = await root.setup(); - http.registerAuth(authenticate, cookieOptions); + await http.registerAuth(authenticate, cookieOptions); http.registerRouter(router); await root.start(); @@ -94,14 +94,14 @@ describe('http service', () => { if (req.headers.authorization) { const user = { id: '42' }; sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs }); - return t.authenticated({ credentials: user }); + return t.authenticated(user); } else { return t.rejected(Boom.unauthorized()); } }; const { http } = await root.setup(); - http.registerAuth(authenticate, cookieOptions); + await http.registerAuth(authenticate, cookieOptions); await root.start(); await kbnTestServer.request @@ -117,7 +117,7 @@ describe('http service', () => { }; const { http } = await root.setup(); - http.registerAuth(authenticate, cookieOptions); + await http.registerAuth(authenticate, cookieOptions); await root.start(); const response = await kbnTestServer.request.get(root, '/').expect(302); @@ -129,14 +129,14 @@ describe('http service', () => { if (req.headers.authorization) { const user = { id: '42' }; sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs }); - return t.authenticated({ credentials: user }); + return t.authenticated(user); } else { return t.rejected(Boom.unauthorized()); } }; const { http } = await root.setup(); - http.registerAuth(authenticate, cookieOptions); + await http.registerAuth(authenticate, cookieOptions); await root.start(); const legacyUrl = '/legacy'; @@ -154,13 +154,43 @@ describe('http service', () => { expect(response.header['set-cookie']).toBe(undefined); }); + it('Should pass associated auth state to Legacy platform', async () => { + const user = { id: '42' }; + const authenticate: AuthenticationHandler = async (req, sessionStorage, t) => { + if (req.headers.authorization) { + sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs }); + return t.authenticated(user); + } else { + return t.rejected(Boom.unauthorized()); + } + }; + + const { http } = await root.setup(); + await http.registerAuth(authenticate, cookieOptions); + await root.start(); + + const legacyUrl = '/legacy'; + const kbnServer = kbnTestServer.getKbnServer(root); + kbnServer.server.route({ + method: 'GET', + path: legacyUrl, + handler: kbnServer.newPlatform.setup.core.http.auth.get, + }); + + const response = await kbnTestServer.request.get(root, legacyUrl).expect(200); + expect(response.body.state).toEqual(user); + expect(response.body.status).toEqual('authenticated'); + + expect(response.header['set-cookie']).toBe(undefined); + }); + it(`Shouldn't expose internal error details`, async () => { const authenticate: AuthenticationHandler = async (req, sessionStorage, t) => { throw new Error('sensitive info'); }; const { http } = await root.setup(); - http.registerAuth(authenticate, cookieOptions); + await http.registerAuth(authenticate, cookieOptions); await root.start(); await kbnTestServer.request.get(root, '/').expect({ From 4f75b7197867e34c67e0d02464ab81b725e88f94 Mon Sep 17 00:00:00 2001 From: restrry Date: Thu, 23 May 2019 15:47:36 +0200 Subject: [PATCH 07/18] update docs --- ...ana-plugin-server.authenticationhandler.md | 2 +- ...plugin-server.authtoolkit.authenticated.md | 2 +- .../kibana-plugin-server.authtoolkit.md | 2 +- .../kibana-plugin-server.kibanarequest.md | 1 + .../kibana-plugin-server.kibanarequest.url.md | 11 ++++++ .../core/server/kibana-plugin-server.md | 6 ++- .../kibana-plugin-server.onpostauthhandler.md | 12 ++++++ .../kibana-plugin-server.onpostauthtoolkit.md | 22 +++++++++++ ...na-plugin-server.onpostauthtoolkit.next.md | 13 +++++++ ...gin-server.onpostauthtoolkit.redirected.md | 13 +++++++ ...lugin-server.onpostauthtoolkit.rejected.md | 15 ++++++++ .../kibana-plugin-server.onpreauthhandler.md | 12 ++++++ .../kibana-plugin-server.onpreauthtoolkit.md | 22 +++++++++++ ...na-plugin-server.onpreauthtoolkit.next.md} | 6 +-- ...ugin-server.onpreauthtoolkit.redirected.md | 15 ++++++++ ...lugin-server.onpreauthtoolkit.rejected.md} | 6 +-- .../kibana-plugin-server.onrequesthandler.md | 12 ------ .../kibana-plugin-server.onrequesttoolkit.md | 23 ----------- ...ugin-server.onrequesttoolkit.redirected.md | 13 ------- ...a-plugin-server.onrequesttoolkit.seturl.md | 13 ------- ...a-plugin-server.pluginsetupcontext.http.md | 3 +- ...kibana-plugin-server.pluginsetupcontext.md | 2 +- src/core/server/server.api.md | 38 ++++++++++++++----- 23 files changed, 180 insertions(+), 84 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.kibanarequest.url.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpostauthhandler.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.next.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.redirected.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.rejected.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpreauthhandler.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.md rename docs/development/core/server/{kibana-plugin-server.onrequesttoolkit.next.md => kibana-plugin-server.onpreauthtoolkit.next.md} (52%) create mode 100644 docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.redirected.md rename docs/development/core/server/{kibana-plugin-server.onrequesttoolkit.rejected.md => kibana-plugin-server.onpreauthtoolkit.rejected.md} (59%) delete mode 100644 docs/development/core/server/kibana-plugin-server.onrequesthandler.md delete mode 100644 docs/development/core/server/kibana-plugin-server.onrequesttoolkit.md delete mode 100644 docs/development/core/server/kibana-plugin-server.onrequesttoolkit.redirected.md delete mode 100644 docs/development/core/server/kibana-plugin-server.onrequesttoolkit.seturl.md diff --git a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md index cb09dc0cd6051d..7ec9fd347baa4c 100644 --- a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md +++ b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => Promise; +export declare type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.authtoolkit.authenticated.md b/docs/development/core/server/kibana-plugin-server.authtoolkit.authenticated.md index d0f1e07c474846..eabee65cb14519 100644 --- a/docs/development/core/server/kibana-plugin-server.authtoolkit.authenticated.md +++ b/docs/development/core/server/kibana-plugin-server.authtoolkit.authenticated.md @@ -9,5 +9,5 @@ Authentication is successful with given credentials, allow request to pass throu Signature: ```typescript -authenticated: (credentials: any) => AuthResult; +authenticated: (state: object) => AuthResult; ``` diff --git a/docs/development/core/server/kibana-plugin-server.authtoolkit.md b/docs/development/core/server/kibana-plugin-server.authtoolkit.md index d7e2e39b44d4cb..cc9f14c57fe4c1 100644 --- a/docs/development/core/server/kibana-plugin-server.authtoolkit.md +++ b/docs/development/core/server/kibana-plugin-server.authtoolkit.md @@ -16,7 +16,7 @@ export interface AuthToolkit | Property | Type | Description | | --- | --- | --- | -| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | (credentials: any) => AuthResult | Authentication is successful with given credentials, allow request to pass through | +| [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md) | (state: object) => AuthResult | Authentication is successful with given credentials, allow request to pass through | | [redirected](./kibana-plugin-server.authtoolkit.redirected.md) | (url: string) => AuthResult | Authentication requires to interrupt request handling and redirect to a configured url | | [rejected](./kibana-plugin-server.authtoolkit.rejected.md) | (error: Error, options?: {`

` statusCode?: number;`

` }) => AuthResult | Authentication is unsuccessful, fail the request with specified error. | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index f7f7707b5657e2..f93e4c073eb210 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.md @@ -20,6 +20,7 @@ export declare class KibanaRequestParams | | | [path](./kibana-plugin-server.kibanarequest.path.md) | | string | | | [query](./kibana-plugin-server.kibanarequest.query.md) | | Query | | +| [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | | ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.url.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.url.md new file mode 100644 index 00000000000000..d552ba55a2b0e6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.url.md @@ -0,0 +1,11 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [url](./kibana-plugin-server.kibanarequest.url.md) + +## KibanaRequest.url property + +Signature: + +```typescript +readonly url: Url; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 027aef60265a2b..e2f43765c68c09 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -27,7 +27,8 @@ | [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. | | [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | | [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | -| [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) | A tool set defining an outcome of OnRequest interceptor for incoming request. | +| [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. | | [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | | [PluginSetupContext](./kibana-plugin-server.pluginsetupcontext.md) | Context passed to the plugins setup method. | @@ -44,7 +45,8 @@ | [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | | [Headers](./kibana-plugin-server.headers.md) | | | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | | -| [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md) | | +| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | | +| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server 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. | diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md new file mode 100644 index 00000000000000..3eca8c743169ad --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md @@ -0,0 +1,12 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) + +## OnPostAuthHandler type + + +Signature: + +```typescript +export declare type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.md b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.md new file mode 100644 index 00000000000000..276643c4f9d1a9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.md @@ -0,0 +1,22 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) + +## OnPostAuthToolkit interface + +A tool set defining an outcome of OnPostAuth interceptor for incoming request. + +Signature: + +```typescript +export interface OnPostAuthToolkit +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [next](./kibana-plugin-server.onpostauthtoolkit.next.md) | () => OnPostAuthResult | To pass request to the next handler | +| [redirected](./kibana-plugin-server.onpostauthtoolkit.redirected.md) | (url: string) => OnPostAuthResult | To interrupt request handling and redirect to a configured url | +| [rejected](./kibana-plugin-server.onpostauthtoolkit.rejected.md) | (error: Error, options?: {`

` statusCode?: number;`

` }) => OnPostAuthResult | Fail the request with specified error. | + diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.next.md b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.next.md new file mode 100644 index 00000000000000..26b4562974e412 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.next.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) > [next](./kibana-plugin-server.onpostauthtoolkit.next.md) + +## OnPostAuthToolkit.next property + +To pass request to the next handler + +Signature: + +```typescript +next: () => OnPostAuthResult; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.redirected.md b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.redirected.md new file mode 100644 index 00000000000000..23cef2f97e32b2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.redirected.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) > [redirected](./kibana-plugin-server.onpostauthtoolkit.redirected.md) + +## OnPostAuthToolkit.redirected property + +To interrupt request handling and redirect to a configured url + +Signature: + +```typescript +redirected: (url: string) => OnPostAuthResult; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.rejected.md b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.rejected.md new file mode 100644 index 00000000000000..a7767dac727a01 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpostauthtoolkit.rejected.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) > [rejected](./kibana-plugin-server.onpostauthtoolkit.rejected.md) + +## OnPostAuthToolkit.rejected property + +Fail the request with specified error. + +Signature: + +```typescript +rejected: (error: Error, options?: { + statusCode?: number; + }) => OnPostAuthResult; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md new file mode 100644 index 00000000000000..bc8c4da23b423d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md @@ -0,0 +1,12 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) + +## OnPreAuthHandler type + + +Signature: + +```typescript +export declare type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.md b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.md new file mode 100644 index 00000000000000..066a95f9fa7c75 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.md @@ -0,0 +1,22 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) + +## OnPreAuthToolkit interface + +A tool set defining an outcome of OnPreAuth interceptor for incoming request. + +Signature: + +```typescript +export interface OnPreAuthToolkit +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [next](./kibana-plugin-server.onpreauthtoolkit.next.md) | () => OnPreAuthResult | To pass request to the next handler | +| [redirected](./kibana-plugin-server.onpreauthtoolkit.redirected.md) | (url: string, options?: {`

` forward: boolean;`

` }) => OnPreAuthResult | To interrupt request handling and redirect to a configured url. If "options.forwarded" = true, request will be forwarded to another url right on the server. | +| [rejected](./kibana-plugin-server.onpreauthtoolkit.rejected.md) | (error: Error, options?: {`

` statusCode?: number;`

` }) => OnPreAuthResult | Fail the request with specified error. | + diff --git a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.next.md b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.next.md similarity index 52% rename from docs/development/core/server/kibana-plugin-server.onrequesttoolkit.next.md rename to docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.next.md index 976e3b1a2db875..86369f70ac1d97 100644 --- a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.next.md +++ b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.next.md @@ -1,13 +1,13 @@ -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [next](./kibana-plugin-server.onrequesttoolkit.next.md) +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) > [next](./kibana-plugin-server.onpreauthtoolkit.next.md) -## OnRequestToolkit.next property +## OnPreAuthToolkit.next property To pass request to the next handler Signature: ```typescript -next: () => OnRequestResult; +next: () => OnPreAuthResult; ``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.redirected.md b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.redirected.md new file mode 100644 index 00000000000000..65c0512b9367b2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.redirected.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) > [redirected](./kibana-plugin-server.onpreauthtoolkit.redirected.md) + +## OnPreAuthToolkit.redirected property + +To interrupt request handling and redirect to a configured url. If "options.forwarded" = true, request will be forwarded to another url right on the server. + +Signature: + +```typescript +redirected: (url: string, options?: { + forward: boolean; + }) => OnPreAuthResult; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.rejected.md b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.rejected.md similarity index 59% rename from docs/development/core/server/kibana-plugin-server.onrequesttoolkit.rejected.md rename to docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.rejected.md index 447d9b3fb9be5a..b267a03b6f9348 100644 --- a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.rejected.md +++ b/docs/development/core/server/kibana-plugin-server.onpreauthtoolkit.rejected.md @@ -1,8 +1,8 @@ -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md) +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) > [rejected](./kibana-plugin-server.onpreauthtoolkit.rejected.md) -## OnRequestToolkit.rejected property +## OnPreAuthToolkit.rejected property Fail the request with specified error. @@ -11,5 +11,5 @@ Fail the request with specified error. ```typescript rejected: (error: Error, options?: { statusCode?: number; - }) => OnRequestResult; + }) => OnPreAuthResult; ``` diff --git a/docs/development/core/server/kibana-plugin-server.onrequesthandler.md b/docs/development/core/server/kibana-plugin-server.onrequesthandler.md deleted file mode 100644 index 5d90e399db6762..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.onrequesthandler.md +++ /dev/null @@ -1,12 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md) - -## OnRequestHandler type - - -Signature: - -```typescript -export declare type OnRequestHandler = (req: KibanaRequest, t: OnRequestToolkit) => OnRequestResult | Promise; -``` diff --git a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.md b/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.md deleted file mode 100644 index e6a79a13dd4363..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) - -## OnRequestToolkit interface - -A tool set defining an outcome of OnRequest interceptor for incoming request. - -Signature: - -```typescript -export interface OnRequestToolkit -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [next](./kibana-plugin-server.onrequesttoolkit.next.md) | () => OnRequestResult | To pass request to the next handler | -| [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md) | (url: string) => OnRequestResult | To interrupt request handling and redirect to a configured url | -| [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md) | (error: Error, options?: {`

` statusCode?: number;`

` }) => OnRequestResult | Fail the request with specified error. | -| [setUrl](./kibana-plugin-server.onrequesttoolkit.seturl.md) | (newUrl: string | Url) => void | Change url for an incoming request. | - diff --git a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.redirected.md b/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.redirected.md deleted file mode 100644 index 311398845bd59a..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.redirected.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md) - -## OnRequestToolkit.redirected property - -To interrupt request handling and redirect to a configured url - -Signature: - -```typescript -redirected: (url: string) => OnRequestResult; -``` diff --git a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.seturl.md b/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.seturl.md deleted file mode 100644 index 0f20cbdb18d968..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.onrequesttoolkit.seturl.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [setUrl](./kibana-plugin-server.onrequesttoolkit.seturl.md) - -## OnRequestToolkit.setUrl property - -Change url for an incoming request. - -Signature: - -```typescript -setUrl: (newUrl: string | Url) => void; -``` diff --git a/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.http.md b/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.http.md index 0ca7fa2a882942..59446aa180c28b 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.http.md +++ b/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.http.md @@ -8,8 +8,9 @@ ```typescript http: { + registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; - registerOnRequest: HttpServiceSetup['registerOnRequest']; + registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; getBasePathFor: HttpServiceSetup['getBasePathFor']; setBasePathFor: HttpServiceSetup['setBasePathFor']; }; diff --git a/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.md b/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.md index 8878edb18230f7..a879cdbdfe33e8 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.md +++ b/docs/development/core/server/kibana-plugin-server.pluginsetupcontext.md @@ -17,5 +17,5 @@ export interface PluginSetupContext | Property | Type | Description | | --- | --- | --- | | [elasticsearch](./kibana-plugin-server.pluginsetupcontext.elasticsearch.md) | {`

` adminClient$: Observable<ClusterClient>;`

` dataClient$: Observable<ClusterClient>;`

` } | | -| [http](./kibana-plugin-server.pluginsetupcontext.http.md) | {`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnRequest: HttpServiceSetup['registerOnRequest'];`

` getBasePathFor: HttpServiceSetup['getBasePathFor'];`

` setBasePathFor: HttpServiceSetup['setBasePathFor'];`

` } | | +| [http](./kibana-plugin-server.pluginsetupcontext.http.md) | {`

` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`

` getBasePathFor: HttpServiceSetup['getBasePathFor'];`

` setBasePathFor: HttpServiceSetup['setBasePathFor'];`

` } | | diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ca7f63b01e5fb4..c20d6b266c0b8d 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -25,11 +25,11 @@ export type APICaller = (endpoint: string, clientParams: Record // Warning: (ae-forgotten-export) The symbol "AuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => Promise; +export type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; // @public export interface AuthToolkit { - authenticated: (credentials: any) => AuthResult; + authenticated: (state: object) => AuthResult; redirected: (url: string) => AuthResult; rejected: (error: Error, options?: { statusCode?: number; @@ -156,6 +156,8 @@ export class KibanaRequest { readonly query: Query; // (undocumented) unstable_getIncomingMessage(): import("http").IncomingMessage; + // (undocumented) + readonly url: Url; } // @public @@ -227,19 +229,34 @@ export interface LogRecord { timestamp: Date; } -// Warning: (ae-forgotten-export) The symbol "OnRequestResult" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; + +// @public +export interface OnPostAuthToolkit { + next: () => OnPostAuthResult; + redirected: (url: string) => OnPostAuthResult; + rejected: (error: Error, options?: { + statusCode?: number; + }) => OnPostAuthResult; +} + +// Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type OnRequestHandler = (req: KibanaRequest, t: OnRequestToolkit) => OnRequestResult | Promise; +export type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; // @public -export interface OnRequestToolkit { - next: () => OnRequestResult; - redirected: (url: string) => OnRequestResult; +export interface OnPreAuthToolkit { + next: () => OnPreAuthResult; + redirected: (url: string, options?: { + forward: boolean; + }) => OnPreAuthResult; rejected: (error: Error, options?: { statusCode?: number; - }) => OnRequestResult; - setUrl: (newUrl: string | Url) => void; + }) => OnPreAuthResult; } // @public @@ -282,8 +299,9 @@ export interface PluginSetupContext { }; // (undocumented) http: { + registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; - registerOnRequest: HttpServiceSetup['registerOnRequest']; + registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; getBasePathFor: HttpServiceSetup['getBasePathFor']; setBasePathFor: HttpServiceSetup['setBasePathFor']; }; From 2517c97f952d1e961f03c07565a3189e2026ba86 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 14:07:33 +0200 Subject: [PATCH 08/18] use full name, auth should not mutate request --- src/core/server/http/lifecycle/auth.ts | 2 +- src/core/server/http/lifecycle/on_post_auth.ts | 2 +- src/core/server/http/lifecycle/on_pre_auth.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/server/http/lifecycle/auth.ts b/src/core/server/http/lifecycle/auth.ts index 4ab1b8e39a0cb5..6310708fd040d4 100644 --- a/src/core/server/http/lifecycle/auth.ts +++ b/src/core/server/http/lifecycle/auth.ts @@ -95,7 +95,7 @@ const toolkit: AuthToolkit = { /** @public */ export type AuthenticationHandler = ( - request: Request, + request: Readonly, sessionStorage: SessionStorage, t: AuthToolkit ) => AuthResult | Promise; diff --git a/src/core/server/http/lifecycle/on_post_auth.ts b/src/core/server/http/lifecycle/on_post_auth.ts index e8d3ba33387475..92ff1c23dfdaf6 100644 --- a/src/core/server/http/lifecycle/on_post_auth.ts +++ b/src/core/server/http/lifecycle/on_post_auth.ts @@ -88,7 +88,7 @@ export interface OnPostAuthToolkit { /** @public */ export type OnPostAuthHandler = ( - req: KibanaRequest, + request: KibanaRequest, t: OnPostAuthToolkit ) => OnPostAuthResult | Promise; diff --git a/src/core/server/http/lifecycle/on_pre_auth.ts b/src/core/server/http/lifecycle/on_pre_auth.ts index 6902117ad82faf..3e2830d3ced63e 100644 --- a/src/core/server/http/lifecycle/on_pre_auth.ts +++ b/src/core/server/http/lifecycle/on_pre_auth.ts @@ -98,7 +98,7 @@ const toolkit: OnPreAuthToolkit = { /** @public */ export type OnPreAuthHandler = ( - req: KibanaRequest, + request: KibanaRequest, t: OnPreAuthToolkit ) => OnPreAuthResult | Promise; From 9599d328013b0ba734294959b90454eeeb9382f3 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 15:11:55 +0200 Subject: [PATCH 09/18] move basePath functionality under namespace --- src/core/server/http/http_server.test.ts | 38 ++++++-------- src/core/server/http/http_server.ts | 49 +++++++++---------- src/core/server/http/http_service.mock.ts | 6 ++- .../integration_tests/http_service.test.ts | 7 +-- src/core/server/index.ts | 3 +- src/core/server/plugins/plugin_context.ts | 3 +- .../server/http/setup_base_path_provider.js | 2 +- 7 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 0f1a85a336b048..aacd8f11c6929c 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -615,23 +615,19 @@ test('throws an error if starts without set up', async () => { ); }); -test('#getBasePathFor() returns base path associated with an incoming request', async () => { - const { - getBasePathFor, - setBasePathFor, - registerRouter, - server: innerServer, - registerOnPostAuth, - } = await server.setup(config); +test('#basePath.get() returns base path associated with an incoming request', async () => { + const { basePath, registerRouter, server: innerServer, registerOnPostAuth } = await server.setup( + config + ); const path = '/base-path'; registerOnPostAuth((req, t) => { - setBasePathFor(req, path); + basePath.set(req, path); return t.next(); }); const router = new Router('/'); - router.get({ path: '/', validate: false }, (req, res) => res.ok({ key: getBasePathFor(req) })); + router.get({ path: '/', validate: false }, (req, res) => res.ok({ key: basePath.get(req) })); registerRouter(router); await server.start(config); @@ -643,28 +639,24 @@ test('#getBasePathFor() returns base path associated with an incoming request', }); }); -test('#getBasePathFor() is based on server base path', async () => { +test('#basePath.get() is based on server base path', async () => { const configWithBasePath = { ...config, basePath: '/bar', }; - const { - getBasePathFor, - setBasePathFor, - registerRouter, - server: innerServer, - registerOnPostAuth, - } = await server.setup(configWithBasePath); + const { basePath, registerRouter, server: innerServer, registerOnPostAuth } = await server.setup( + configWithBasePath + ); const path = '/base-path'; registerOnPostAuth((req, t) => { - setBasePathFor(req, path); + basePath.set(req, path); return t.next(); }); const router = new Router('/'); router.get({ path: '/', validate: false }, async (req, res) => - res.ok({ key: getBasePathFor(req) }) + res.ok({ key: basePath.get(req) }) ); registerRouter(router); @@ -677,7 +669,7 @@ test('#getBasePathFor() is based on server base path', async () => { }); }); -test('#setBasePathFor() cannot be set twice for one request', async () => { +test('#basePath.set() cannot be set twice for one request', async () => { const incomingMessage = { url: '/', }; @@ -699,9 +691,9 @@ test('#setBasePathFor() cannot be set twice for one request', async () => { KibanaRequest: jest.fn(() => kibanaRequestFactory), })); - const { setBasePathFor } = await server.setup(config); + const { basePath } = await server.setup(config); - const setPath = () => setBasePathFor(kibanaRequestFactory.from(), '/path'); + const setPath = () => basePath.set(kibanaRequestFactory.from(), '/path'); setPath(); expect(setPath).toThrowErrorMatchingInlineSnapshot( diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 1c7f18c750ec05..eb2ce1cc698606 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -38,6 +38,9 @@ enum AuthStatus { unknown = 'unknown', } +const requestToKey = (request: KibanaRequest | Request) => + request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; + export interface HttpServerSetup { server: Server; options: ServerOptions; @@ -57,8 +60,10 @@ export interface HttpServerSetup { */ registerOnPreAuth: (requestHandler: OnPreAuthHandler) => void; registerOnPostAuth: (requestHandler: OnPostAuthHandler) => void; - getBasePathFor: (request: KibanaRequest | Request) => string; - setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void; + basePath: { + get: (request: KibanaRequest | Request) => string; + set: (request: KibanaRequest | Request, basePath: string) => void; + }; auth: { get: ( request: KibanaRequest | Request @@ -72,17 +77,10 @@ export interface HttpServerSetup { export class HttpServer { private server?: Server; - private registeredRouters = new Set(); private authRegistered = false; - private basePathCache = new WeakMap< - ReturnType, - string - >(); - - private authState = new WeakMap< - ReturnType, - unknown - >(); + private readonly registeredRouters = new Set(); + private readonly basePathCache = new WeakMap, string>(); + private readonly authState = new WeakMap, unknown>(); constructor(private readonly log: Logger) {} @@ -101,24 +99,22 @@ export class HttpServer { // passing hapi Request works for BWC. can be deleted once we remove legacy server. private getBasePathFor(config: HttpConfig, request: KibanaRequest | Request) { - const incomingMessage = - request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; + const key = requestToKey(request); - const requestScopePath = this.basePathCache.get(incomingMessage) || ''; + const requestScopePath = this.basePathCache.get(key) || ''; const serverBasePath = config.basePath || ''; return `${serverBasePath}${requestScopePath}`; } // should work only for KibanaRequest as soon as spaces migrate to NP private setBasePathFor(request: KibanaRequest | Request, basePath: string) { - const incomingMessage = - request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; - if (this.basePathCache.has(incomingMessage)) { + const key = requestToKey(request); + if (this.basePathCache.has(key)) { throw new Error( 'Request basePath was previously set. Setting multiple times is not supported.' ); } - this.basePathCache.set(incomingMessage, basePath); + this.basePathCache.set(key, basePath); } public setup(config: HttpConfig): HttpServerSetup { @@ -136,8 +132,10 @@ export class HttpServer { fn: AuthenticationHandler, cookieOptions: SessionStorageCookieOptions ) => this.registerAuth(fn, cookieOptions, config.basePath), - getBasePathFor: this.getBasePathFor.bind(this, config), - setBasePathFor: this.setBasePathFor.bind(this), + basePath: { + get: this.getBasePathFor.bind(this, config), + set: this.setBasePathFor.bind(this), + }, auth: { get: this.getAuthData.bind(this), isAuthenticated: this.isAuthenticated.bind(this), @@ -255,7 +253,7 @@ export class HttpServer { this.server.auth.scheme('login', () => ({ authenticate: adoptToHapiAuthFormat(fn, sessionStorage, (req, state) => { - this.authState.set(req.raw.req, state); + this.authState.set(requestToKey(req), state); }), })); this.server.auth.strategy('session', 'login'); @@ -267,11 +265,10 @@ export class HttpServer { this.server.auth.default('session'); } private getAuthData(request: KibanaRequest | Request) { - const incomingMessage = - request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; + const key = requestToKey(request); - const hasState = this.authState.has(incomingMessage); - const state = this.authState.get(incomingMessage); + const hasState = this.authState.has(key); + const state = this.authState.get(key); const status: AuthStatus = hasState ? AuthStatus.authenticated : this.authRegistered diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 6d49ac3010ef02..6c5267a3abc4b1 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -27,10 +27,12 @@ const createSetupContractMock = () => { registerAuth: jest.fn(), registerOnPostAuth: jest.fn(), registerRouter: jest.fn(), - getBasePathFor: jest.fn(), - setBasePathFor: jest.fn(), // we can mock some hapi server method when we need it server: {} as Server, + basePath: { + get: jest.fn(), + set: jest.fn(), + }, auth: { get: jest.fn(), isAuthenticated: jest.fn(), diff --git a/src/core/server/http/integration_tests/http_service.test.ts b/src/core/server/http/integration_tests/http_service.test.ts index 93fe20a80e1209..9d5068d3a874f3 100644 --- a/src/core/server/http/integration_tests/http_service.test.ts +++ b/src/core/server/http/integration_tests/http_service.test.ts @@ -18,6 +18,7 @@ */ import request from 'request'; import Boom from 'boom'; +import { Request } from 'hapi'; import { AuthenticationHandler } from '../../../../core/server'; import { Router } from '../router'; @@ -329,7 +330,7 @@ describe('http service', () => { }); }); - describe('#getBasePathFor()/#setBasePathFor()', () => { + describe('#basePath()', () => { let root: ReturnType; beforeEach(async () => { root = kbnTestServer.createRoot(); @@ -340,7 +341,7 @@ describe('http service', () => { const reqBasePath = '/requests-specific-base-path'; const { http } = await root.setup(); http.registerOnPreAuth((req, t) => { - http.setBasePathFor(req, reqBasePath); + http.basePath.set(req, reqBasePath); return t.next(); }); @@ -351,7 +352,7 @@ describe('http service', () => { kbnServer.server.route({ method: 'GET', path: legacyUrl, - handler: kbnServer.newPlatform.setup.core.http.getBasePathFor, + handler: (req: Request) => kbnServer.newPlatform.setup.core.http.basePath.get(req), }); await kbnTestServer.request.get(root, legacyUrl).expect(200, reqBasePath); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index e144c0f2568f8b..cee054cc9bc4c4 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -84,8 +84,7 @@ export interface CoreSetup { registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; - getBasePathFor: HttpServiceSetup['getBasePathFor']; - setBasePathFor: HttpServiceSetup['setBasePathFor']; + basePath: HttpServiceSetup['basePath']; }; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index b2a88bbd32760c..215f334bffab4d 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -120,8 +120,7 @@ export function createPluginSetupContext( registerOnPreAuth: deps.http.registerOnPreAuth, registerAuth: deps.http.registerAuth, registerOnPostAuth: deps.http.registerOnPostAuth, - getBasePathFor: deps.http.getBasePathFor, - setBasePathFor: deps.http.setBasePathFor, + basePath: deps.http.basePath, }, }; } diff --git a/src/legacy/server/http/setup_base_path_provider.js b/src/legacy/server/http/setup_base_path_provider.js index 8cf6cc1fde512d..10f79691870085 100644 --- a/src/legacy/server/http/setup_base_path_provider.js +++ b/src/legacy/server/http/setup_base_path_provider.js @@ -25,6 +25,6 @@ export function setupBasePathProvider(kbnServer) { kbnServer.server.decorate('request', 'getBasePath', function () { const request = this; - return kbnServer.newPlatform.setup.core.http.getBasePathFor(request); + return kbnServer.newPlatform.setup.core.http.basePath.get(request); }); } From 1799d3b0881b175ab2d36b312d1172806ab2a113 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 15:14:51 +0200 Subject: [PATCH 10/18] regenerate docs --- .../server/kibana-plugin-server.authenticationhandler.md | 2 +- .../core/server/kibana-plugin-server.coresetup.http.md | 3 +-- .../core/server/kibana-plugin-server.coresetup.md | 2 +- .../server/kibana-plugin-server.onpostauthhandler.md | 2 +- .../core/server/kibana-plugin-server.onpreauthhandler.md | 2 +- src/core/server/server.api.md | 9 ++++----- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md index 7ec9fd347baa4c..482ef44a6e4612 100644 --- a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md +++ b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; +export declare type AuthenticationHandler = (request: Readonly, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.http.md b/docs/development/core/server/kibana-plugin-server.coresetup.http.md index 8cb25af29e4baa..2cf1f867a3dc72 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.http.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.http.md @@ -11,7 +11,6 @@ http: { registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; - getBasePathFor: HttpServiceSetup['getBasePathFor']; - setBasePathFor: HttpServiceSetup['setBasePathFor']; + basePath: HttpServiceSetup['basePath']; }; ``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index 7b46817842defc..f94103ff635cdb 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -17,5 +17,5 @@ export interface CoreSetup | Property | Type | Description | | --- | --- | --- | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | {`

` adminClient$: Observable<ClusterClient>;`

` dataClient$: Observable<ClusterClient>;`

` } | | -| [http](./kibana-plugin-server.coresetup.http.md) | {`

` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`

` getBasePathFor: HttpServiceSetup['getBasePathFor'];`

` setBasePathFor: HttpServiceSetup['setBasePathFor'];`

` } | | +| [http](./kibana-plugin-server.coresetup.http.md) | {`

` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`

` basePath: HttpServiceSetup['basePath'];`

` } | | diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md index 3eca8c743169ad..83de25e3f3d6dd 100644 --- a/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md +++ b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; +export declare type OnPostAuthHandler = (request: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md index bc8c4da23b423d..606ed21dc6463b 100644 --- a/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md +++ b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; +export declare type OnPreAuthHandler = (request: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; ``` diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index c9678bd9b8e432..f257da20691af8 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -25,7 +25,7 @@ export type APICaller = (endpoint: string, clientParams: Record // Warning: (ae-forgotten-export) The symbol "AuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; +export type AuthenticationHandler = (request: Readonly, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; // @public export interface AuthToolkit { @@ -86,8 +86,7 @@ export interface CoreSetup { registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; - getBasePathFor: HttpServiceSetup['getBasePathFor']; - setBasePathFor: HttpServiceSetup['setBasePathFor']; + basePath: HttpServiceSetup['basePath']; }; } @@ -253,7 +252,7 @@ export interface LogRecord { // Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; +export type OnPostAuthHandler = (request: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; // @public export interface OnPostAuthToolkit { @@ -267,7 +266,7 @@ export interface OnPostAuthToolkit { // Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; +export type OnPreAuthHandler = (request: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; // @public export interface OnPreAuthToolkit { From f2a0aee1f5e0257fab9cbe5d7ef15fb8810043c4 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 15:32:18 +0200 Subject: [PATCH 11/18] Revert "move basePath functionality under namespace" This reverts commit 9599d328013b0ba734294959b90454eeeb9382f3. --- src/core/server/http/http_server.test.ts | 38 ++++++++------ src/core/server/http/http_server.ts | 49 ++++++++++--------- src/core/server/http/http_service.mock.ts | 6 +-- .../integration_tests/http_service.test.ts | 7 ++- src/core/server/index.ts | 3 +- src/core/server/plugins/plugin_context.ts | 3 +- .../server/http/setup_base_path_provider.js | 2 +- 7 files changed, 59 insertions(+), 49 deletions(-) diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index aacd8f11c6929c..0f1a85a336b048 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -615,19 +615,23 @@ test('throws an error if starts without set up', async () => { ); }); -test('#basePath.get() returns base path associated with an incoming request', async () => { - const { basePath, registerRouter, server: innerServer, registerOnPostAuth } = await server.setup( - config - ); +test('#getBasePathFor() returns base path associated with an incoming request', async () => { + const { + getBasePathFor, + setBasePathFor, + registerRouter, + server: innerServer, + registerOnPostAuth, + } = await server.setup(config); const path = '/base-path'; registerOnPostAuth((req, t) => { - basePath.set(req, path); + setBasePathFor(req, path); return t.next(); }); const router = new Router('/'); - router.get({ path: '/', validate: false }, (req, res) => res.ok({ key: basePath.get(req) })); + router.get({ path: '/', validate: false }, (req, res) => res.ok({ key: getBasePathFor(req) })); registerRouter(router); await server.start(config); @@ -639,24 +643,28 @@ test('#basePath.get() returns base path associated with an incoming request', as }); }); -test('#basePath.get() is based on server base path', async () => { +test('#getBasePathFor() is based on server base path', async () => { const configWithBasePath = { ...config, basePath: '/bar', }; - const { basePath, registerRouter, server: innerServer, registerOnPostAuth } = await server.setup( - configWithBasePath - ); + const { + getBasePathFor, + setBasePathFor, + registerRouter, + server: innerServer, + registerOnPostAuth, + } = await server.setup(configWithBasePath); const path = '/base-path'; registerOnPostAuth((req, t) => { - basePath.set(req, path); + setBasePathFor(req, path); return t.next(); }); const router = new Router('/'); router.get({ path: '/', validate: false }, async (req, res) => - res.ok({ key: basePath.get(req) }) + res.ok({ key: getBasePathFor(req) }) ); registerRouter(router); @@ -669,7 +677,7 @@ test('#basePath.get() is based on server base path', async () => { }); }); -test('#basePath.set() cannot be set twice for one request', async () => { +test('#setBasePathFor() cannot be set twice for one request', async () => { const incomingMessage = { url: '/', }; @@ -691,9 +699,9 @@ test('#basePath.set() cannot be set twice for one request', async () => { KibanaRequest: jest.fn(() => kibanaRequestFactory), })); - const { basePath } = await server.setup(config); + const { setBasePathFor } = await server.setup(config); - const setPath = () => basePath.set(kibanaRequestFactory.from(), '/path'); + const setPath = () => setBasePathFor(kibanaRequestFactory.from(), '/path'); setPath(); expect(setPath).toThrowErrorMatchingInlineSnapshot( diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index eb2ce1cc698606..1c7f18c750ec05 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -38,9 +38,6 @@ enum AuthStatus { unknown = 'unknown', } -const requestToKey = (request: KibanaRequest | Request) => - request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; - export interface HttpServerSetup { server: Server; options: ServerOptions; @@ -60,10 +57,8 @@ export interface HttpServerSetup { */ registerOnPreAuth: (requestHandler: OnPreAuthHandler) => void; registerOnPostAuth: (requestHandler: OnPostAuthHandler) => void; - basePath: { - get: (request: KibanaRequest | Request) => string; - set: (request: KibanaRequest | Request, basePath: string) => void; - }; + getBasePathFor: (request: KibanaRequest | Request) => string; + setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void; auth: { get: ( request: KibanaRequest | Request @@ -77,10 +72,17 @@ export interface HttpServerSetup { export class HttpServer { private server?: Server; + private registeredRouters = new Set(); private authRegistered = false; - private readonly registeredRouters = new Set(); - private readonly basePathCache = new WeakMap, string>(); - private readonly authState = new WeakMap, unknown>(); + private basePathCache = new WeakMap< + ReturnType, + string + >(); + + private authState = new WeakMap< + ReturnType, + unknown + >(); constructor(private readonly log: Logger) {} @@ -99,22 +101,24 @@ export class HttpServer { // passing hapi Request works for BWC. can be deleted once we remove legacy server. private getBasePathFor(config: HttpConfig, request: KibanaRequest | Request) { - const key = requestToKey(request); + const incomingMessage = + request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; - const requestScopePath = this.basePathCache.get(key) || ''; + const requestScopePath = this.basePathCache.get(incomingMessage) || ''; const serverBasePath = config.basePath || ''; return `${serverBasePath}${requestScopePath}`; } // should work only for KibanaRequest as soon as spaces migrate to NP private setBasePathFor(request: KibanaRequest | Request, basePath: string) { - const key = requestToKey(request); - if (this.basePathCache.has(key)) { + const incomingMessage = + request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; + if (this.basePathCache.has(incomingMessage)) { throw new Error( 'Request basePath was previously set. Setting multiple times is not supported.' ); } - this.basePathCache.set(key, basePath); + this.basePathCache.set(incomingMessage, basePath); } public setup(config: HttpConfig): HttpServerSetup { @@ -132,10 +136,8 @@ export class HttpServer { fn: AuthenticationHandler, cookieOptions: SessionStorageCookieOptions ) => this.registerAuth(fn, cookieOptions, config.basePath), - basePath: { - get: this.getBasePathFor.bind(this, config), - set: this.setBasePathFor.bind(this), - }, + getBasePathFor: this.getBasePathFor.bind(this, config), + setBasePathFor: this.setBasePathFor.bind(this), auth: { get: this.getAuthData.bind(this), isAuthenticated: this.isAuthenticated.bind(this), @@ -253,7 +255,7 @@ export class HttpServer { this.server.auth.scheme('login', () => ({ authenticate: adoptToHapiAuthFormat(fn, sessionStorage, (req, state) => { - this.authState.set(requestToKey(req), state); + this.authState.set(req.raw.req, state); }), })); this.server.auth.strategy('session', 'login'); @@ -265,10 +267,11 @@ export class HttpServer { this.server.auth.default('session'); } private getAuthData(request: KibanaRequest | Request) { - const key = requestToKey(request); + const incomingMessage = + request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; - const hasState = this.authState.has(key); - const state = this.authState.get(key); + const hasState = this.authState.has(incomingMessage); + const state = this.authState.get(incomingMessage); const status: AuthStatus = hasState ? AuthStatus.authenticated : this.authRegistered diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 6c5267a3abc4b1..6d49ac3010ef02 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -27,12 +27,10 @@ const createSetupContractMock = () => { registerAuth: jest.fn(), registerOnPostAuth: jest.fn(), registerRouter: jest.fn(), + getBasePathFor: jest.fn(), + setBasePathFor: jest.fn(), // we can mock some hapi server method when we need it server: {} as Server, - basePath: { - get: jest.fn(), - set: jest.fn(), - }, auth: { get: jest.fn(), isAuthenticated: jest.fn(), diff --git a/src/core/server/http/integration_tests/http_service.test.ts b/src/core/server/http/integration_tests/http_service.test.ts index 9d5068d3a874f3..93fe20a80e1209 100644 --- a/src/core/server/http/integration_tests/http_service.test.ts +++ b/src/core/server/http/integration_tests/http_service.test.ts @@ -18,7 +18,6 @@ */ import request from 'request'; import Boom from 'boom'; -import { Request } from 'hapi'; import { AuthenticationHandler } from '../../../../core/server'; import { Router } from '../router'; @@ -330,7 +329,7 @@ describe('http service', () => { }); }); - describe('#basePath()', () => { + describe('#getBasePathFor()/#setBasePathFor()', () => { let root: ReturnType; beforeEach(async () => { root = kbnTestServer.createRoot(); @@ -341,7 +340,7 @@ describe('http service', () => { const reqBasePath = '/requests-specific-base-path'; const { http } = await root.setup(); http.registerOnPreAuth((req, t) => { - http.basePath.set(req, reqBasePath); + http.setBasePathFor(req, reqBasePath); return t.next(); }); @@ -352,7 +351,7 @@ describe('http service', () => { kbnServer.server.route({ method: 'GET', path: legacyUrl, - handler: (req: Request) => kbnServer.newPlatform.setup.core.http.basePath.get(req), + handler: kbnServer.newPlatform.setup.core.http.getBasePathFor, }); await kbnTestServer.request.get(root, legacyUrl).expect(200, reqBasePath); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index cee054cc9bc4c4..e144c0f2568f8b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -84,7 +84,8 @@ export interface CoreSetup { registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; - basePath: HttpServiceSetup['basePath']; + getBasePathFor: HttpServiceSetup['getBasePathFor']; + setBasePathFor: HttpServiceSetup['setBasePathFor']; }; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 215f334bffab4d..b2a88bbd32760c 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -120,7 +120,8 @@ export function createPluginSetupContext( registerOnPreAuth: deps.http.registerOnPreAuth, registerAuth: deps.http.registerAuth, registerOnPostAuth: deps.http.registerOnPostAuth, - basePath: deps.http.basePath, + getBasePathFor: deps.http.getBasePathFor, + setBasePathFor: deps.http.setBasePathFor, }, }; } diff --git a/src/legacy/server/http/setup_base_path_provider.js b/src/legacy/server/http/setup_base_path_provider.js index 10f79691870085..8cf6cc1fde512d 100644 --- a/src/legacy/server/http/setup_base_path_provider.js +++ b/src/legacy/server/http/setup_base_path_provider.js @@ -25,6 +25,6 @@ export function setupBasePathProvider(kbnServer) { kbnServer.server.decorate('request', 'getBasePath', function () { const request = this; - return kbnServer.newPlatform.setup.core.http.basePath.get(request); + return kbnServer.newPlatform.setup.core.http.getBasePathFor(request); }); } From 8aad6f3f43d6fe39f61663aca43e8c0c83f0e10f Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 15:32:23 +0200 Subject: [PATCH 12/18] Revert "regenerate docs" This reverts commit 1799d3b0881b175ab2d36b312d1172806ab2a113. --- .../server/kibana-plugin-server.authenticationhandler.md | 2 +- .../core/server/kibana-plugin-server.coresetup.http.md | 3 ++- .../core/server/kibana-plugin-server.coresetup.md | 2 +- .../server/kibana-plugin-server.onpostauthhandler.md | 2 +- .../core/server/kibana-plugin-server.onpreauthhandler.md | 2 +- src/core/server/server.api.md | 9 +++++---- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md index 482ef44a6e4612..7ec9fd347baa4c 100644 --- a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md +++ b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type AuthenticationHandler = (request: Readonly, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; +export declare type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.http.md b/docs/development/core/server/kibana-plugin-server.coresetup.http.md index 2cf1f867a3dc72..8cb25af29e4baa 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.http.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.http.md @@ -11,6 +11,7 @@ http: { registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; - basePath: HttpServiceSetup['basePath']; + getBasePathFor: HttpServiceSetup['getBasePathFor']; + setBasePathFor: HttpServiceSetup['setBasePathFor']; }; ``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index f94103ff635cdb..7b46817842defc 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -17,5 +17,5 @@ export interface CoreSetup | Property | Type | Description | | --- | --- | --- | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | {`

` adminClient$: Observable<ClusterClient>;`

` dataClient$: Observable<ClusterClient>;`

` } | | -| [http](./kibana-plugin-server.coresetup.http.md) | {`

` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`

` basePath: HttpServiceSetup['basePath'];`

` } | | +| [http](./kibana-plugin-server.coresetup.http.md) | {`

` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`

` getBasePathFor: HttpServiceSetup['getBasePathFor'];`

` setBasePathFor: HttpServiceSetup['setBasePathFor'];`

` } | | diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md index 83de25e3f3d6dd..3eca8c743169ad 100644 --- a/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md +++ b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type OnPostAuthHandler = (request: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; +export declare type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md index 606ed21dc6463b..bc8c4da23b423d 100644 --- a/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md +++ b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type OnPreAuthHandler = (request: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; +export declare type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; ``` diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index f257da20691af8..c9678bd9b8e432 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -25,7 +25,7 @@ export type APICaller = (endpoint: string, clientParams: Record // Warning: (ae-forgotten-export) The symbol "AuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type AuthenticationHandler = (request: Readonly, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; +export type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; // @public export interface AuthToolkit { @@ -86,7 +86,8 @@ export interface CoreSetup { registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; - basePath: HttpServiceSetup['basePath']; + getBasePathFor: HttpServiceSetup['getBasePathFor']; + setBasePathFor: HttpServiceSetup['setBasePathFor']; }; } @@ -252,7 +253,7 @@ export interface LogRecord { // Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type OnPostAuthHandler = (request: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; +export type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; // @public export interface OnPostAuthToolkit { @@ -266,7 +267,7 @@ export interface OnPostAuthToolkit { // Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type OnPreAuthHandler = (request: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; +export type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; // @public export interface OnPreAuthToolkit { From 14667d3f38c465becc0b5143bf8b0a941e992a98 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 15:46:01 +0200 Subject: [PATCH 13/18] regenerate docs --- .../server/kibana-plugin-server.authenticationhandler.md | 2 +- .../core/server/kibana-plugin-server.onpostauthhandler.md | 2 +- .../core/server/kibana-plugin-server.onpreauthhandler.md | 2 +- src/core/server/server.api.md | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md index 7ec9fd347baa4c..482ef44a6e4612 100644 --- a/docs/development/core/server/kibana-plugin-server.authenticationhandler.md +++ b/docs/development/core/server/kibana-plugin-server.authenticationhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; +export declare type AuthenticationHandler = (request: Readonly, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md index 3eca8c743169ad..83de25e3f3d6dd 100644 --- a/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md +++ b/docs/development/core/server/kibana-plugin-server.onpostauthhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; +export declare type OnPostAuthHandler = (request: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md index bc8c4da23b423d..606ed21dc6463b 100644 --- a/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md +++ b/docs/development/core/server/kibana-plugin-server.onpreauthhandler.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; +export declare type OnPreAuthHandler = (request: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; ``` diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index c9678bd9b8e432..745fbdb7d9d99f 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -25,7 +25,7 @@ export type APICaller = (endpoint: string, clientParams: Record // Warning: (ae-forgotten-export) The symbol "AuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type AuthenticationHandler = (request: Request, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; +export type AuthenticationHandler = (request: Readonly, sessionStorage: SessionStorage, t: AuthToolkit) => AuthResult | Promise; // @public export interface AuthToolkit { @@ -253,7 +253,7 @@ export interface LogRecord { // Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type OnPostAuthHandler = (req: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; +export type OnPostAuthHandler = (request: KibanaRequest, t: OnPostAuthToolkit) => OnPostAuthResult | Promise; // @public export interface OnPostAuthToolkit { @@ -267,7 +267,7 @@ export interface OnPostAuthToolkit { // Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type OnPreAuthHandler = (req: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; +export type OnPreAuthHandler = (request: KibanaRequest, t: OnPreAuthToolkit) => OnPreAuthResult | Promise; // @public export interface OnPreAuthToolkit { From 39feb77534c6c342635df01ca28fc818be9ef16d Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 15:52:30 +0200 Subject: [PATCH 14/18] updated yarn.lock no idea why --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5ef2e44bc39b09..395f28efc2e17c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8471,9 +8471,9 @@ core-js@^2.5.3, core-js@^2.5.7: integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== core-js@^2.6.5: - version "2.6.8" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.8.tgz#dc3a1e633a04267944e0cb850d3880f340248139" - integrity sha512-RWlREFU74TEkdXzyl1bka66O3kYp8jeTXrvJZDzVVMH8AiHUSOFpL1yfhQJ+wHocAm1m+4971W1PPzfLuCv1vg== + version "2.6.9" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" + integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" From 97358912887b01616a926d30f3e26184b7edb64c Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 27 May 2019 16:39:06 +0200 Subject: [PATCH 15/18] extract AuthStateStorage to a separate entity --- src/core/server/http/auth_state_storage.ts | 51 ++++++++++++++++++++++ src/core/server/http/http_server.ts | 50 +++++---------------- 2 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 src/core/server/http/auth_state_storage.ts diff --git a/src/core/server/http/auth_state_storage.ts b/src/core/server/http/auth_state_storage.ts new file mode 100644 index 00000000000000..eafe755b79eea0 --- /dev/null +++ b/src/core/server/http/auth_state_storage.ts @@ -0,0 +1,51 @@ +/* + * 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 { Request } from 'hapi'; +import { KibanaRequest } from './router'; + +export enum AuthStatus { + authenticated = 'authenticated', + unauthenticated = 'unauthenticated', + unknown = 'unknown', +} + +const toKey = (request: KibanaRequest | Request) => + request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; + +export class AuthStateStorage { + private readonly storage = new WeakMap, unknown>(); + constructor(private readonly canBeAuthenticated: () => boolean) {} + public set = (request: KibanaRequest | Request, state: unknown) => { + this.storage.set(toKey(request), state); + }; + public get = (request: KibanaRequest | Request) => { + const key = toKey(request); + const state = this.storage.get(key); + const status: AuthStatus = this.storage.has(key) + ? AuthStatus.authenticated + : this.canBeAuthenticated() + ? AuthStatus.unauthenticated + : AuthStatus.unknown; + + return { status, state }; + }; + public isAuthenticated = (request: KibanaRequest | Request) => { + return this.get(request).status === AuthStatus.authenticated; + }; +} diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 1c7f18c750ec05..25fa77b16fd892 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -31,12 +31,7 @@ import { SessionStorageCookieOptions, createCookieSessionStorageFactory, } from './cookie_session_storage'; - -enum AuthStatus { - authenticated = 'authenticated', - unauthenticated = 'unauthenticated', - unknown = 'unknown', -} +import { AuthStateStorage } from './auth_state_storage'; export interface HttpServerSetup { server: Server; @@ -60,13 +55,8 @@ export interface HttpServerSetup { getBasePathFor: (request: KibanaRequest | Request) => string; setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void; auth: { - get: ( - request: KibanaRequest | Request - ) => { - status: AuthStatus; - state: unknown; - }; - isAuthenticated: (request: KibanaRequest | Request) => boolean; + get: AuthStateStorage['get']; + isAuthenticated: AuthStateStorage['isAuthenticated']; }; } @@ -79,12 +69,11 @@ export class HttpServer { string >(); - private authState = new WeakMap< - ReturnType, - unknown - >(); + private readonly authState: AuthStateStorage; - constructor(private readonly log: Logger) {} + constructor(private readonly log: Logger) { + this.authState = new AuthStateStorage(() => this.authRegistered); + } public isListening() { return this.server !== undefined && this.server.listener.listening; @@ -139,8 +128,8 @@ export class HttpServer { getBasePathFor: this.getBasePathFor.bind(this, config), setBasePathFor: this.setBasePathFor.bind(this), auth: { - get: this.getAuthData.bind(this), - isAuthenticated: this.isAuthenticated.bind(this), + get: this.authState.get, + isAuthenticated: this.authState.isAuthenticated, }, // Return server instance with the connection options so that we can properly // bridge core and the "legacy" Kibana internally. Once this bridge isn't @@ -254,9 +243,7 @@ export class HttpServer { ); this.server.auth.scheme('login', () => ({ - authenticate: adoptToHapiAuthFormat(fn, sessionStorage, (req, state) => { - this.authState.set(req.raw.req, state); - }), + authenticate: adoptToHapiAuthFormat(fn, sessionStorage, this.authState.set), })); this.server.auth.strategy('session', 'login'); @@ -266,21 +253,4 @@ export class HttpServer { // https://github.com/hapijs/hapi/blob/master/API.md#-serverauthdefaultoptions this.server.auth.default('session'); } - private getAuthData(request: KibanaRequest | Request) { - const incomingMessage = - request instanceof KibanaRequest ? request.unstable_getIncomingMessage() : request.raw.req; - - const hasState = this.authState.has(incomingMessage); - const state = this.authState.get(incomingMessage); - const status: AuthStatus = hasState - ? AuthStatus.authenticated - : this.authRegistered - ? AuthStatus.unauthenticated - : AuthStatus.unknown; - - return { status, state }; - } - private isAuthenticated(request: KibanaRequest | Request) { - return this.getAuthData(request).status === AuthStatus.authenticated; - } } From aa72f9194f8a4ca74e44241796a20f54a9fb4f81 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 29 May 2019 11:15:53 +0200 Subject: [PATCH 16/18] get rid of nested ifs --- src/core/server/http/lifecycle/auth.ts | 28 +++++++------ .../server/http/lifecycle/on_post_auth.ts | 27 ++++++------- src/core/server/http/lifecycle/on_pre_auth.ts | 39 +++++++++---------- 3 files changed, 43 insertions(+), 51 deletions(-) diff --git a/src/core/server/http/lifecycle/auth.ts b/src/core/server/http/lifecycle/auth.ts index 6310708fd040d4..ffe77e0120fe46 100644 --- a/src/core/server/http/lifecycle/auth.ts +++ b/src/core/server/http/lifecycle/auth.ts @@ -112,22 +112,20 @@ export function adoptToHapiAuthFormat( ): Promise { try { const result = await fn(req, sessionStorage.asScoped(req), toolkit); - if (authResult.isValid(result)) { - if (authResult.isAuthenticated(result)) { - onSuccess(req, result.state); - return h.authenticated({ credentials: result.state }); - } - if (authResult.isRedirected(result)) { - return h.redirect(result.url).takeover(); - } - if (authResult.isRejected(result)) { - const { error, statusCode } = result; - return Boom.boomify(error, { statusCode }); - } + if (!authResult.isValid(result)) { + throw new Error( + `Unexpected result from Authenticate. Expected AuthResult, but given: ${result}.` + ); } - throw new Error( - `Unexpected result from Authenticate. Expected AuthResult, but given: ${result}.` - ); + if (authResult.isAuthenticated(result)) { + onSuccess(req, result.state); + return h.authenticated({ credentials: result.state }); + } + if (authResult.isRedirected(result)) { + return h.redirect(result.url).takeover(); + } + const { error, statusCode } = result; + return Boom.boomify(error, { statusCode }); } catch (error) { return Boom.internal(error.message, { statusCode: 500 }); } diff --git a/src/core/server/http/lifecycle/on_post_auth.ts b/src/core/server/http/lifecycle/on_post_auth.ts index 92ff1c23dfdaf6..c0843a6bc764b6 100644 --- a/src/core/server/http/lifecycle/on_post_auth.ts +++ b/src/core/server/http/lifecycle/on_post_auth.ts @@ -110,22 +110,19 @@ export function adoptToHapiOnPostAuthFormat(fn: OnPostAuthHandler) { ): Promise { try { const result = await fn(KibanaRequest.from(request, undefined), toolkit); - if (postAuthResult.isValid(result)) { - if (postAuthResult.isNext(result)) { - return h.continue; - } - if (postAuthResult.isRedirected(result)) { - return h.redirect(result.url).takeover(); - } - if (postAuthResult.isRejected(result)) { - const { error, statusCode } = result; - return Boom.boomify(error, { statusCode }); - } + if (!postAuthResult.isValid(result)) { + throw new Error( + `Unexpected result from OnPostAuth. Expected OnPostAuthResult, but given: ${result}.` + ); } - - throw new Error( - `Unexpected result from OnPostAuth. Expected OnPostAuthResult, but given: ${result}.` - ); + if (postAuthResult.isNext(result)) { + return h.continue; + } + if (postAuthResult.isRedirected(result)) { + return h.redirect(result.url).takeover(); + } + const { error, statusCode } = result; + return Boom.boomify(error, { statusCode }); } catch (error) { return Boom.internal(error.message, { statusCode: 500 }); } diff --git a/src/core/server/http/lifecycle/on_pre_auth.ts b/src/core/server/http/lifecycle/on_pre_auth.ts index 3e2830d3ced63e..317dd2f621f74e 100644 --- a/src/core/server/http/lifecycle/on_pre_auth.ts +++ b/src/core/server/http/lifecycle/on_pre_auth.ts @@ -116,31 +116,28 @@ export function adoptToHapiOnPreAuthFormat(fn: OnPreAuthHandler) { try { const result = await fn(KibanaRequest.from(request, undefined), toolkit); - if (preAuthResult.isValid(result)) { - if (preAuthResult.isNext(result)) { - return h.continue; - } - - if (preAuthResult.isRedirected(result)) { - const { url, forward } = result; - if (forward) { - request.setUrl(url); - // We should update raw request as well since it can be proxied to the old platform - request.raw.req.url = url; - return h.continue; - } - return h.redirect(url).takeover(); - } + if (!preAuthResult.isValid(result)) { + throw new Error( + `Unexpected result from OnPreAuth. Expected OnPreAuthResult, but given: ${result}.` + ); + } + if (preAuthResult.isNext(result)) { + return h.continue; + } - if (preAuthResult.isRejected(result)) { - const { error, statusCode } = result; - return Boom.boomify(error, { statusCode }); + if (preAuthResult.isRedirected(result)) { + const { url, forward } = result; + if (forward) { + request.setUrl(url); + // We should update raw request as well since it can be proxied to the old platform + request.raw.req.url = url; + return h.continue; } + return h.redirect(url).takeover(); } - throw new Error( - `Unexpected result from OnPreAuth. Expected OnPreAuthResult, but given: ${result}.` - ); + const { error, statusCode } = result; + return Boom.boomify(error, { statusCode }); } catch (error) { return Boom.internal(error.message, { statusCode: 500 }); } From ea1edee0c3a6d0ffd7aacfa2fbecce43bf96f4da Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 29 May 2019 11:17:02 +0200 Subject: [PATCH 17/18] describe what is the difference between hooks --- src/core/server/http/http_server.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 25fa77b16fd892..a36d5ac090598a 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -39,19 +39,30 @@ export interface HttpServerSetup { registerRouter: (router: Router) => void; /** * Define custom authentication and/or authorization mechanism for incoming requests. - * Applied to all resources by default. Only one AuthenticationHandler can be registered. + * A handler should return a state to associate with the incoming request. + * The state can be retrieved later via http.auth.get(..) + * Only one AuthenticationHandler can be registered. */ registerAuth: ( - authenticationHandler: AuthenticationHandler, + handler: AuthenticationHandler, cookieOptions: SessionStorageCookieOptions ) => Promise; /** - * Define custom logic to perform for incoming requests. - * Applied to all resources by default. - * Can register any number of OnRequestHandlers, which are called in sequence (from the first registered to the last) + * Define custom logic to perform for incoming requests. Runs the handler before Auth + * hook performs a check that user has access to requested resources, so it's the only + * place when you can forward a request to another URL right on the server. + * Can register any number of registerOnPostAuth, which are called in sequence + * (from the first registered to the last). */ - registerOnPreAuth: (requestHandler: OnPreAuthHandler) => void; - registerOnPostAuth: (requestHandler: OnPostAuthHandler) => void; + registerOnPreAuth: (handler: OnPreAuthHandler) => void; + /** + * Define custom logic to perform for incoming requests. Runs the handler after Auth hook + * did make sure a user has access to the requested resource. + * The auth state is available at stage via http.auth.get(..) + * Can register any number of registerOnPreAuth, which are called in sequence + * (from the first registered to the last). + */ + registerOnPostAuth: (handler: OnPostAuthHandler) => void; getBasePathFor: (request: KibanaRequest | Request) => string; setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void; auth: { From b8d604148bb0af7a2c8120d4b0f13f258ffa4217 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 29 May 2019 11:22:12 +0200 Subject: [PATCH 18/18] re-wording --- src/core/server/http/http_server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index a36d5ac090598a..db2233b59a2e87 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -38,7 +38,7 @@ export interface HttpServerSetup { options: ServerOptions; registerRouter: (router: Router) => void; /** - * Define custom authentication and/or authorization mechanism for incoming requests. + * To define custom authentication and/or authorization mechanism for incoming requests. * A handler should return a state to associate with the incoming request. * The state can be retrieved later via http.auth.get(..) * Only one AuthenticationHandler can be registered. @@ -48,7 +48,7 @@ export interface HttpServerSetup { cookieOptions: SessionStorageCookieOptions ) => Promise; /** - * Define custom logic to perform for incoming requests. Runs the handler before Auth + * To define custom logic to perform for incoming requests. Runs the handler before Auth * hook performs a check that user has access to requested resources, so it's the only * place when you can forward a request to another URL right on the server. * Can register any number of registerOnPostAuth, which are called in sequence @@ -56,7 +56,7 @@ export interface HttpServerSetup { */ registerOnPreAuth: (handler: OnPreAuthHandler) => void; /** - * Define custom logic to perform for incoming requests. Runs the handler after Auth hook + * To define custom logic to perform for incoming requests. Runs the handler after Auth hook * did make sure a user has access to the requested resource. * The auth state is available at stage via http.auth.get(..) * Can register any number of registerOnPreAuth, which are called in sequence