From 6eb8572d8251bda946cd1f0a308f6d2ee44020db Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 27 Nov 2019 10:28:39 +0100 Subject: [PATCH 01/19] add onPreResponse interceptor --- src/core/server/http/http_server.ts | 49 ++---- .../http/integration_tests/lifecycle.test.ts | 154 ++++++++++++++++++ .../server/http/lifecycle/on_pre_response.ts | 145 +++++++++++++++++ src/core/server/http/types.ts | 13 ++ 4 files changed, 327 insertions(+), 34 deletions(-) create mode 100644 src/core/server/http/lifecycle/on_pre_response.ts diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index da97ab535516c..598c99e5627ec 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Request, Server } from 'hapi'; +import { Server } from 'hapi'; import { Logger, LoggerFactory } from '../logging'; import { HttpConfig } from './http_config'; @@ -25,8 +25,9 @@ import { createServer, getListenerOptions, getServerOptions } from './http_tools import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth'; import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth'; import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth'; +import { adoptToHapiOnPreResponseFormat, OnPreResponseHandler } from './lifecycle/on_pre_response'; -import { ResponseHeaders, IRouter } from './router'; +import { IRouter } from './router'; import { SessionStorageCookieOptions, createCookieSessionStorageFactory, @@ -49,6 +50,7 @@ export interface HttpServerSetup { registerAuth: HttpServiceSetup['registerAuth']; registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; + registerOnPreResponse: HttpServiceSetup['registerOnPreResponse']; isTlsEnabled: HttpServiceSetup['isTlsEnabled']; auth: { get: GetAuthState; @@ -101,6 +103,7 @@ export class HttpServer { registerRouter: this.registerRouter.bind(this), registerOnPreAuth: this.registerOnPreAuth.bind(this), registerOnPostAuth: this.registerOnPostAuth.bind(this), + registerOnPreResponse: this.registerOnPreResponse.bind(this), createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => this.createCookieSessionStorageFactory(cookieOptions, config.basePath), registerAuth: this.registerAuth.bind(this), @@ -198,6 +201,14 @@ export class HttpServer { this.server.ext('onRequest', adoptToHapiOnPreAuthFormat(fn, this.log)); } + private registerOnPreResponse(fn: OnPreResponseHandler) { + if (this.server === undefined) { + throw new Error('Server is not created yet'); + } + + this.server.ext('onPreResponse', adoptToHapiOnPreResponseFormat(fn, this.log)); + } + private async createCookieSessionStorageFactory( cookieOptions: SessionStorageCookieOptions, basePath?: string @@ -255,39 +266,9 @@ export class HttpServer { // https://github.com/hapijs/hapi/blob/master/API.md#-serverauthdefaultoptions this.server.auth.default('session'); - this.server.ext('onPreResponse', (request, t) => { + this.registerOnPreResponse((request, responseInfo, t) => { const authResponseHeaders = this.authResponseHeaders.get(request); - this.extendResponseWithHeaders(request, authResponseHeaders); - return t.continue; - }); - } - - private extendResponseWithHeaders(request: Request, headers?: ResponseHeaders) { - const response = request.response; - if (!headers || !response) return; - - if (response instanceof Error) { - this.findHeadersIntersection(response.output.headers, headers); - // hapi wraps all error response in Boom object internally - response.output.headers = { - ...response.output.headers, - ...(headers as any), // hapi types don't specify string[] as valid value - }; - } else { - for (const [headerName, headerValue] of Object.entries(headers)) { - this.findHeadersIntersection(response.headers, headers); - response.header(headerName, headerValue as any); // hapi types don't specify string[] as valid value - } - } - } - - // NOTE: responseHeaders contains not a full list of response headers, but only explicitly set on a response object. - // any headers added by hapi internally, like `content-type`, `content-length`, etc. do not present here. - private findHeadersIntersection(responseHeaders: ResponseHeaders, headers: ResponseHeaders) { - Object.keys(headers).forEach(headerName => { - if (responseHeaders[headerName] !== undefined) { - this.log.warn(`Server rewrites a response header [${headerName}].`); - } + return t.next({ headers: authResponseHeaders }); }); } } diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 4592a646b7f04..ad33096e56208 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -913,3 +913,157 @@ describe('Auth', () => { .expect(200, { customField: 'undefined' }); }); }); + +describe('OnPreResponse', () => { + it('supports registering response inceptors', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' })); + + const callingOrder: string[] = []; + registerOnPreResponse((req, res, t) => { + callingOrder.push('first'); + return t.next(); + }); + + registerOnPreResponse((req, res, t) => { + callingOrder.push('second'); + return t.next(); + }); + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect(200, 'ok'); + + expect(callingOrder).toEqual(['first', 'second']); + }); + + it('supports additional headers attachments', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok()); + + registerOnPreResponse((req, res, t) => + t.next({ + headers: { + 'x-kibana-header': 'value', + }, + }) + ); + await server.start(); + + const result = await supertest(innerServer.listener) + .get('/') + .expect(200); + + expect(result.header['x-kibana-header']).toBe('value'); + }); + + it('logs a warning if interceptor rewrites response header', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => + res.ok({ + headers: { 'x-kibana-header': 'value' }, + }) + ); + registerOnPreResponse((req, res, t) => + t.next({ + headers: { 'x-kibana-header': 'value' }, + }) + ); + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect(200); + + expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "Server rewrites a response header [x-kibana-header].", + ], + ] + `); + }); + + it("doesn't expose error details if interceptor throws", async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok(undefined)); + registerOnPreResponse((req, res, t) => { + throw new Error('reason'); + }); + await server.start(); + + const result = await supertest(innerServer.listener) + .get('/') + .expect(500); + + expect(result.body.message).toBe('An internal server error occurred.'); + expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + [Error: reason], + ], + ] + `); + }); + + it('returns internal error if interceptor returns unexpected result', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok()); + registerOnPreResponse((req, res, t) => ({} as any)); + await server.start(); + + const result = await supertest(innerServer.listener) + .get('/') + .expect(500); + + expect(result.body.message).toBe('An internal server error occurred.'); + expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + [Error: Unexpected result from OnPreResponse. Expected OnPreResponseResult, but given: [object Object].], + ], + ] + `); + }); + + it('cannot change response statusCode', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + registerOnPreResponse((req, res, t) => { + res.statusCode = 500; + return t.next(); + }); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' })); + + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect(200); + }); +}); diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts new file mode 100644 index 0000000000000..1a2eeaa912c8d --- /dev/null +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -0,0 +1,145 @@ +/* + * 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 { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi'; +import Boom from 'boom'; +import { Logger } from '../../logging'; + +import { HapiResponseAdapter, KibanaRequest, ResponseHeaders } from '../router'; +enum ResultType { + next = 'next', +} + +interface Next { + type: ResultType.next; + headers?: ResponseHeaders; +} + +type OnPreResponseResult = Next; + +/** + * Additional data to extend a response. + * @public + */ +export interface ResponseExtensions { + headers?: ResponseHeaders; +} + +const preResponseResult = { + next(responseExtensions?: ResponseExtensions): OnPreResponseResult { + return { type: ResultType.next, headers: responseExtensions?.headers }; + }, + isNext(result: OnPreResponseResult): result is Next { + return result && result.type === ResultType.next; + }, +}; + +/** + * A tool set defining an outcome of OnPreAuth interceptor for incoming request. + * @public + */ +export interface OnPreResponseToolkit { + /** To pass request to the next handler */ + next: (responseExtensions?: ResponseExtensions) => OnPreResponseResult; +} + +const toolkit: OnPreResponseToolkit = { + next: preResponseResult.next, +}; + +interface ResponseInfo { + statusCode: number; // body? header? +} +/** + * See {@link OnPreAuthToolkit}. + * @public + */ +export type OnPreResponseHandler = ( + request: KibanaRequest, + response: ResponseInfo, + toolkit: OnPreResponseToolkit +) => OnPreResponseResult | 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 adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Logger) { + return async function interceptPreResponse( + request: Request, + responseToolkit: HapiResponseToolkit + ): Promise { + const response = request.response; + + try { + if (response) { + const statusCode: number = isBoom(response) + ? response.output.statusCode + : response.statusCode; + + const result = await fn(KibanaRequest.from(request), { statusCode }, toolkit); + if (!preResponseResult.isNext(result)) { + throw new Error( + `Unexpected result from OnPreResponse. Expected OnPreResponseResult, but given: ${result}.` + ); + } + if (result.headers) { + if (isBoom(response)) { + findHeadersIntersection(response.output.headers, result.headers, log); + // hapi wraps all error response in Boom object internally + response.output.headers = { + ...response.output.headers, + ...(result.headers as any), // hapi types don't specify string[] as valid value + }; + } else { + for (const [headerName, headerValue] of Object.entries(result.headers)) { + findHeadersIntersection(response.headers, result.headers, log); + response.header(headerName, headerValue as any); // hapi types don't specify string[] as valid value + } + } + } + } + } catch (error) { + log.error(error); + const hapiResponseAdapter = new HapiResponseAdapter(responseToolkit); + return hapiResponseAdapter.toInternalError(); + } + return responseToolkit.continue; + }; +} + +function isBoom(response: any): response is Boom { + return response instanceof Boom; +} + +// NOTE: responseHeaders contains not a full list of response headers, but only explicitly set on a response object. +// any headers added by hapi internally, like `content-type`, `content-length`, etc. do not present here. +function findHeadersIntersection( + responseHeaders: ResponseHeaders, + headers: ResponseHeaders, + log: Logger +) { + Object.keys(headers).forEach(headerName => { + if (responseHeaders[headerName] !== undefined) { + log.warn(`Server rewrites a response header [${headerName}].`); + } + }); +} diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 2c3dfedd1d181..ee617fc9408c1 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -24,6 +24,7 @@ import { SessionStorageFactory } from './session_storage'; import { AuthenticationHandler } from './lifecycle/auth'; import { OnPreAuthHandler } from './lifecycle/on_pre_auth'; import { OnPostAuthHandler } from './lifecycle/on_post_auth'; +import { OnPreResponseHandler } from './lifecycle/on_pre_response'; import { IBasePath } from './base_path_service'; import { PluginOpaqueId, RequestHandlerContext } from '..'; @@ -163,6 +164,18 @@ export interface HttpServiceSetup { */ registerOnPostAuth: (handler: OnPostAuthHandler) => void; + /** + * To define custom logic to perform for the server response. + * + * @remarks + * Doesn't provide the whole response object. + * Allows to extend response with custom headers. + * See {@link OnPreResponseHandler}. + * + * @param handler {@link OnPreResponseHandler} - function to call. + */ + registerOnPreResponse: (handler: OnPreResponseHandler) => void; + /** * Access or manipulate the Kibana base path * See {@link IBasePath}. From c861c8bdb1e496f7d01b73ee6b1b777ac14a6e53 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 27 Nov 2019 10:30:25 +0100 Subject: [PATCH 02/19] use onPreResponse interceptor to add license sign --- x-pack/plugins/licensing/public/plugin.ts | 2 +- x-pack/plugins/licensing/server/plugin.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index 79ad6f289b67e..6db6975272ffa 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -91,7 +91,7 @@ export class LicensingPlugin implements Plugin { this.removeInterceptor = core.http.intercept({ response: async httpResponse => { if (httpResponse.response) { - const signatureHeader = httpResponse.response.headers.get('kbn-xpack-sig'); + const signatureHeader = httpResponse.response.headers.get('kbn-license-sig'); if (this.prevSignature !== signatureHeader) { if (!httpResponse.request!.url.includes(this.infoEndpoint)) { signatureUpdated$.next(); diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index d3dc84c05e25c..29f9b1936ca28 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -94,7 +94,26 @@ export class LicensingPlugin implements Plugin { const { refresh, license$ } = this.createLicensePoller(dataClient, config.pollingFrequency); core.http.registerRouteHandlerContext('licensing', createRouteHandlerContext(license$)); + registerRoutes(core.http.createRouter()); + core.http.registerOnPreResponse(async (req, res, t) => { + // If we're returning an error response, refresh license info from + // Elasticsearch in case the error is due to a change in license information + // in Elasticsearch. + if (res.statusCode >= 400) { + // await info.refreshNow(); + // fetching should be a blocking call + } + const license = await license$.pipe(take(1)).toPromise(); + if (license.isAvailable) { + return t.next({ + headers: { + 'kbn-license-sig': license.signature, + }, + }); + } + return t.next(); + }); return { refresh, From 58a31d27d7d82db2eb285e21342713e8fbf08a46 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 27 Nov 2019 15:24:41 +0100 Subject: [PATCH 03/19] expose registerPreResponse to plugins --- src/core/server/http/http_service.mock.ts | 1 + src/core/server/http/index.ts | 1 + src/core/server/index.ts | 2 ++ src/core/server/legacy/legacy_service.ts | 1 + src/core/server/mocks.ts | 1 + src/core/server/plugins/plugin_context.ts | 1 + 6 files changed, 7 insertions(+) diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index e9a2571382edc..61499fbf339d9 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -61,6 +61,7 @@ const createSetupContractMock = () => { registerAuth: jest.fn(), registerOnPostAuth: jest.fn(), registerRouteHandlerContext: jest.fn(), + registerOnPreResponse: jest.fn(), createRouter: jest.fn(), basePath: createBasePathMock(), auth: { diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 2fa67750f6406..16d2247792326 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -59,6 +59,7 @@ export { AuthResultType, } from './lifecycle/auth'; export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth'; +export { OnPreResponseHandler, OnPreResponseToolkit } from './lifecycle/on_pre_response'; export { SessionStorageFactory, SessionStorage } from './session_storage'; export { SessionStorageCookieOptions } from './cookie_session_storage'; export * from './types'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 31dec2c9b96ff..f4aa0f4bbc42b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -101,6 +101,8 @@ export { OnPreAuthToolkit, OnPostAuthHandler, OnPostAuthToolkit, + OnPreResponseHandler, + OnPreResponseToolkit, RedirectResponseOptions, RequestHandler, RequestHandlerContextContainer, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 99963ad9ce3e8..eb4399a64ea49 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -246,6 +246,7 @@ export class LegacyService implements CoreService { registerOnPreAuth: setupDeps.core.http.registerOnPreAuth, registerAuth: setupDeps.core.http.registerAuth, registerOnPostAuth: setupDeps.core.http.registerOnPostAuth, + registerOnPreResponse: setupDeps.core.http.registerOnPreResponse, basePath: setupDeps.core.http.basePath, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index b51d5302e3274..d452ac12b837b 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -73,6 +73,7 @@ function createCoreSetupMock() { registerOnPreAuth: httpService.registerOnPreAuth, registerAuth: httpService.registerAuth, registerOnPostAuth: httpService.registerOnPostAuth, + registerOnPreResponse: httpService.registerOnPreResponse, basePath: httpService.basePath, isTlsEnabled: httpService.isTlsEnabled, createRouter: jest.fn(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 9885a572ad8c0..00f185d4671c4 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -120,6 +120,7 @@ export function createPluginSetupContext( registerOnPreAuth: deps.http.registerOnPreAuth, registerAuth: deps.http.registerAuth, registerOnPostAuth: deps.http.registerOnPostAuth, + registerOnPreResponse: deps.http.registerOnPreResponse, basePath: deps.http.basePath, isTlsEnabled: deps.http.isTlsEnabled, }, From d536a92712f1f3bd932fff3c34c21be1fe42329e Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 27 Nov 2019 15:28:35 +0100 Subject: [PATCH 04/19] refresh for license update get the most fresh license --- .../licensing/common/license_update.test.ts | 75 +++++++++++++------ .../licensing/common/license_update.ts | 23 ++++-- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/licensing/common/license_update.test.ts b/x-pack/plugins/licensing/common/license_update.test.ts index 345085d3e3a8f..68660eaf2d713 100644 --- a/x-pack/plugins/licensing/common/license_update.test.ts +++ b/x-pack/plugins/licensing/common/license_update.test.ts @@ -12,7 +12,7 @@ import { createLicenseUpdate } from './license_update'; import { licenseMock } from './license.mock'; const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - +const stop$ = new Subject(); describe('licensing update', () => { it('loads updates when triggered', async () => { const types: LicenseType[] = ['basic', 'gold']; @@ -24,16 +24,16 @@ describe('licensing update', () => { Promise.resolve(licenseMock.create({ license: { type: types.shift() } })) ); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); expect(fetcher).toHaveBeenCalledTimes(0); trigger$.next(); - const first = await update$.pipe(take(1)).toPromise(); + const first = await license$.pipe(take(1)).toPromise(); expect(first.type).toBe('basic'); trigger$.next(); - const [, second] = await update$.pipe(take(2), toArray()).toPromise(); + const [, second] = await license$.pipe(take(2), toArray()).toPromise(); expect(second.type).toBe('gold'); }); @@ -43,9 +43,9 @@ describe('licensing update', () => { const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(fetchedLicense); - const { update$ } = createLicenseUpdate(trigger$, fetcher, initialLicense); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher, initialLicense); trigger$.next(); - const [first, second] = await update$.pipe(take(2), toArray()).toPromise(); + const [first, second] = await license$.pipe(take(2), toArray()).toPromise(); expect(first.type).toBe('platinum'); expect(second.type).toBe('gold'); @@ -64,17 +64,17 @@ describe('licensing update', () => { ) ); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); trigger$.next(); - const [first] = await update$.pipe(take(1), toArray()).toPromise(); + const [first] = await license$.pipe(take(1), toArray()).toPromise(); expect(first.type).toBe('basic'); trigger$.next(); trigger$.next(); - const [, second] = await update$.pipe(take(2), toArray()).toPromise(); + const [, second] = await license$.pipe(take(2), toArray()).toPromise(); expect(second.type).toBe('gold'); expect(fetcher).toHaveBeenCalledTimes(3); @@ -85,11 +85,11 @@ describe('licensing update', () => { const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); - update$.subscribe(() => {}); - update$.subscribe(() => {}); - update$.subscribe(() => {}); + license$.subscribe(() => {}); + license$.subscribe(() => {}); + license$.subscribe(() => {}); trigger$.next(); expect(fetcher).toHaveBeenCalledTimes(1); @@ -110,9 +110,9 @@ describe('licensing update', () => { }) ); const trigger$ = new Subject(); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); const values: ILicense[] = []; - update$.subscribe(license => values.push(license)); + license$.subscribe(license => values.push(license)); trigger$.next(); trigger$.next(); @@ -124,29 +124,58 @@ describe('licensing update', () => { await expect(values[0].type).toBe('gold'); }); - it('completes update$ stream when trigger is completed', () => { + it('completes license$ stream when stop$ is triggered', () => { const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); let completed = false; - update$.subscribe({ complete: () => (completed = true) }); + license$.subscribe({ complete: () => (completed = true) }); - trigger$.complete(); + stop$.next(); expect(completed).toBe(true); }); - it('stops fetching when fetch subscription unsubscribed', () => { + it('stops fetching when stop$ is triggered', () => { const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); - const { update$, fetchSubscription } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); const values: ILicense[] = []; - update$.subscribe(license => values.push(license)); + license$.subscribe(license => values.push(license)); - fetchSubscription.unsubscribe(); + stop$.next(); trigger$.next(); expect(fetcher).toHaveBeenCalledTimes(0); }); + + it('refreshManually guarantees license fetching', async () => { + const trigger$ = new Subject(); + const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); + const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + + const fetcher = jest + .fn() + .mockImplementationOnce(async () => { + await delay(100); + return firstLicense; + }) + .mockImplementationOnce(async () => { + await delay(100); + return secondLicense; + }); + + const { license$, refreshManually } = createLicenseUpdate(trigger$, stop$, fetcher); + let fromObservable; + license$.subscribe(license => (fromObservable = license)); + + const licenseResult = await refreshManually(); + expect(licenseResult.uid).toBe('first'); + expect(licenseResult).toBe(fromObservable); + + const secondResult = await refreshManually(); + expect(secondResult.uid).toBe('second'); + expect(secondResult).toBe(fromObservable); + }); }); diff --git a/x-pack/plugins/licensing/common/license_update.ts b/x-pack/plugins/licensing/common/license_update.ts index 254ea680460ee..0197ca5396ad1 100644 --- a/x-pack/plugins/licensing/common/license_update.ts +++ b/x-pack/plugins/licensing/common/license_update.ts @@ -3,36 +3,45 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ConnectableObservable, Observable, from, merge } from 'rxjs'; +import { ConnectableObservable, Observable, Subject, from, merge } from 'rxjs'; -import { filter, map, pairwise, switchMap, publishReplay } from 'rxjs/operators'; +import { filter, map, pairwise, switchMap, publishReplay, takeUntil } from 'rxjs/operators'; import { hasLicenseInfoChanged } from './has_license_info_changed'; import { ILicense } from './types'; export function createLicenseUpdate( trigger$: Observable, + stop$: Observable, fetcher: () => Promise, initialValues?: ILicense ) { - const fetched$ = trigger$.pipe( - switchMap(fetcher), + const triggerRefresh$ = trigger$.pipe(switchMap(fetcher)); + const manuallyFetched$ = new Subject(); + + const fetched$ = merge(triggerRefresh$, manuallyFetched$).pipe( + takeUntil(stop$), publishReplay(1) // have to cast manually as pipe operator cannot return ConnectableObservable // https://github.com/ReactiveX/rxjs/issues/2972 ) as ConnectableObservable; const fetchSubscription = fetched$.connect(); + stop$.subscribe({ complete: () => fetchSubscription.unsubscribe() }); const initialValues$ = initialValues ? from([undefined, initialValues]) : from([undefined]); - const update$: Observable = merge(initialValues$, fetched$).pipe( + const license$: Observable = merge(initialValues$, fetched$).pipe( pairwise(), filter(([previous, next]) => hasLicenseInfoChanged(previous, next!)), map(([, next]) => next!) ); return { - update$, - fetchSubscription, + license$, + async refreshManually() { + const license = await fetcher(); + manuallyFetched$.next(license); + return license; + }, }; } From c194e3723a806c03291f379394853fa866d8a685 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 27 Nov 2019 15:29:24 +0100 Subject: [PATCH 05/19] license plugin injects own header for signature: 'kbn-license-sig' --- .../plugins/licensing/public/plugin.test.ts | 52 ++++++++++--------- x-pack/plugins/licensing/public/plugin.ts | 27 +++------- .../server/on_pre_response_handler.ts | 30 +++++++++++ .../plugins/licensing/server/plugin.test.ts | 52 ++++++++----------- x-pack/plugins/licensing/server/plugin.ts | 43 ++++----------- 5 files changed, 96 insertions(+), 108 deletions(-) create mode 100644 x-pack/plugins/licensing/server/on_pre_response_handler.ts diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index 60dfd1f6cb260..abdf923ec8ec9 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -14,6 +14,8 @@ import { licenseMock } from '../common/license.mock'; import { coreMock } from '../../../../src/core/public/mocks'; import { HttpInterceptor } from 'src/core/public'; +const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); + describe('licensing plugin', () => { let plugin: LicensingPlugin; @@ -28,15 +30,30 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const fetchedLicense = licenseMock.create({ license: { uid: 'fetched' } }); - coreSetup.http.get.mockResolvedValue(fetchedLicense); + const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); + const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + coreSetup.http.get + .mockImplementationOnce(async () => { + await delay(100); + return firstLicense; + }) + .mockImplementationOnce(async () => { + await delay(100); + return secondLicense; + }); const { license$, refresh } = await plugin.setup(coreSetup); - refresh(); - const license = await license$.pipe(take(1)).toPromise(); + let fromObservable; + license$.subscribe(license => (fromObservable = license)); + + const licenseResult = await refresh(); + expect(licenseResult.uid).toBe('first'); + expect(licenseResult).toBe(fromObservable); - expect(license.uid).toBe('fetched'); + const secondResult = await refresh(); + expect(secondResult.uid).toBe('second'); + expect(secondResult).toBe(fromObservable); }); it('data re-fetch call marked as a system api', async () => { @@ -49,7 +66,7 @@ describe('licensing plugin', () => { const { refresh } = await plugin.setup(coreSetup); - refresh(); + await refresh(); expect(coreSetup.http.get.mock.calls[0][1]).toMatchObject({ headers: { @@ -119,7 +136,7 @@ describe('licensing plugin', () => { const { license$, refresh } = await plugin.setup(coreSetup); - refresh(); + await refresh(); const license = await license$.pipe(take(1)).toPromise(); expect(license.uid).toBe('fresh'); @@ -143,7 +160,7 @@ describe('licensing plugin', () => { coreSetup.http.get.mockRejectedValue(new Error('reason')); const { license$, refresh } = await plugin.setup(coreSetup); - refresh(); + await refresh(); const license = await license$.pipe(take(1)).toPromise(); @@ -161,7 +178,7 @@ describe('licensing plugin', () => { const { license$, refresh } = await plugin.setup(coreSetup); expect(sessionStorage.removeItem).toHaveBeenCalledTimes(0); - refresh(); + await refresh(); await license$.pipe(take(1)).toPromise(); expect(sessionStorage.removeItem).toHaveBeenCalledTimes(1); @@ -201,7 +218,7 @@ describe('licensing plugin', () => { response: { headers: { get(name: string) { - if (name === 'kbn-xpack-sig') { + if (name === 'kbn-license-sig') { return 'signature-1'; } throw new Error('unexpected header'); @@ -249,7 +266,7 @@ describe('licensing plugin', () => { response: { headers: { get(name: string) { - if (name === 'kbn-xpack-sig') { + if (name === 'kbn-license-sig') { return 'signature-1'; } throw new Error('unexpected header'); @@ -283,19 +300,6 @@ describe('licensing plugin', () => { expect(completed).toBe(true); }); - it('refresh does not trigger data re-fetch', async () => { - const sessionStorage = coreMock.createStorage(); - plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); - const coreSetup = coreMock.createSetup(); - const { refresh } = await plugin.setup(coreSetup); - - await plugin.stop(); - - refresh(); - - expect(coreSetup.http.get).toHaveBeenCalledTimes(0); - }); - it('removes http interceptor', async () => { const sessionStorage = coreMock.createStorage(); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index 6db6975272ffa..7141571876d53 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Subject, Subscription, merge } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; @@ -30,7 +29,6 @@ export class LicensingPlugin implements Plugin { * A function to execute once the plugin's HTTP interceptor needs to stop listening. */ private removeInterceptor?: () => void; - private licenseFetchSubscription?: Subscription; private storageSubscription?: Subscription; private readonly infoEndpoint = '/api/licensing/info'; @@ -65,19 +63,16 @@ export class LicensingPlugin implements Plugin { } public setup(core: CoreSetup) { - const manualRefresh$ = new Subject(); const signatureUpdated$ = new Subject(); - const refresh$ = merge(signatureUpdated$, manualRefresh$).pipe(takeUntil(this.stop$)); - const savedLicense = this.getSaved(); - const { update$, fetchSubscription } = createLicenseUpdate( - refresh$, + const { license$, refreshManually } = createLicenseUpdate( + signatureUpdated$, + this.stop$, () => this.fetchLicense(core), - savedLicense + this.getSaved() ); - this.licenseFetchSubscription = fetchSubscription; - this.storageSubscription = update$.subscribe(license => { + this.storageSubscription = license$.subscribe(license => { if (license.isAvailable) { this.prevSignature = license.signature; this.save(license); @@ -103,10 +98,8 @@ export class LicensingPlugin implements Plugin { }); return { - refresh: () => { - manualRefresh$.next(); - }, - license$: update$, + refresh: refreshManually, + license$, }; } @@ -119,10 +112,6 @@ export class LicensingPlugin implements Plugin { if (this.removeInterceptor !== undefined) { this.removeInterceptor(); } - if (this.licenseFetchSubscription !== undefined) { - this.licenseFetchSubscription.unsubscribe(); - this.licenseFetchSubscription = undefined; - } if (this.storageSubscription !== undefined) { this.storageSubscription.unsubscribe(); this.storageSubscription = undefined; diff --git a/x-pack/plugins/licensing/server/on_pre_response_handler.ts b/x-pack/plugins/licensing/server/on_pre_response_handler.ts new file mode 100644 index 0000000000000..c8befceb4fe32 --- /dev/null +++ b/x-pack/plugins/licensing/server/on_pre_response_handler.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { OnPreResponseHandler } from '../../../../src/core/server'; +import { ILicense } from '../common/types'; + +export function createOnPreResponseHandler( + refresh: () => Promise, + license$: Observable +): OnPreResponseHandler { + return async (req, res, t) => { + // If we're returning an error response, refresh license info from + // Elasticsearch in case the error is due to a change in license information + // in Elasticsearch. + // https://github.com/elastic/x-pack-kibana/pull/2876 + if (res.statusCode >= 400) { + await refresh(); + } + const license = await license$.pipe(take(1)).toPromise(); + return t.next({ + headers: { + 'kbn-license-sig': license.signature, + }, + }); + }; +} diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts index 2af3637a2aaf0..ffde569f988b7 100644 --- a/x-pack/plugins/licensing/server/plugin.test.ts +++ b/x-pack/plugins/licensing/server/plugin.test.ts @@ -225,11 +225,7 @@ describe('licensing plugin', () => { let plugin: LicensingPlugin; beforeEach(() => { - plugin = new LicensingPlugin( - coreMock.createPluginInitializerContext({ - pollingFrequency, - }) - ); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext()); }); afterEach(async () => { @@ -251,6 +247,26 @@ describe('licensing plugin', () => { `); }); }); + + describe('registers on pre-response interceptor', () => { + let plugin: LicensingPlugin; + + beforeEach(() => { + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext()); + }); + + afterEach(async () => { + await plugin.stop(); + }); + + it('once', async () => { + const coreSetup = coreMock.createSetup(); + + await plugin.setup(coreSetup); + + expect(coreSetup.http.registerOnPreResponse).toHaveBeenCalledTimes(1); + }); + }); }); describe('#stop', () => { @@ -269,31 +285,5 @@ describe('licensing plugin', () => { await plugin.stop(); expect(completed).toBe(true); }); - - it('refresh does not trigger data re-fetch', async () => { - const plugin = new LicensingPlugin( - coreMock.createPluginInitializerContext({ - pollingFrequency, - }) - ); - - const dataClient = elasticsearchServiceMock.createClusterClient(); - dataClient.callAsInternalUser.mockResolvedValue({ - license: buildRawLicense(), - features: {}, - }); - - const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); - - const { refresh } = await plugin.setup(coreSetup); - - dataClient.callAsInternalUser.mockClear(); - - await plugin.stop(); - refresh(); - - expect(dataClient.callAsInternalUser).toHaveBeenCalledTimes(0); - }); }); }); diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index 29f9b1936ca28..2c828745b3d73 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Observable, Subject, Subscription, merge, timer } from 'rxjs'; -import { take, takeUntil } from 'rxjs/operators'; +import { Observable, Subject, Subscription, timer } from 'rxjs'; +import { take } from 'rxjs/operators'; import moment from 'moment'; import { createHash } from 'crypto'; import stringify from 'json-stable-stringify'; @@ -28,6 +28,7 @@ import { registerRoutes } from './routes'; import { LicenseConfigType } from './licensing_config'; import { createRouteHandlerContext } from './licensing_route_handler_context'; +import { createOnPreResponseHandler } from './on_pre_response_handler'; function normalizeServerLicense(license: RawLicense): PublicLicense { return { @@ -78,7 +79,6 @@ export class LicensingPlugin implements Plugin { private stop$ = new Subject(); private readonly logger: Logger; private readonly config$: Observable; - private licenseFetchSubscription?: Subscription; private loggingSubscription?: Subscription; constructor(private readonly context: PluginInitializerContext) { @@ -96,24 +96,7 @@ export class LicensingPlugin implements Plugin { core.http.registerRouteHandlerContext('licensing', createRouteHandlerContext(license$)); registerRoutes(core.http.createRouter()); - core.http.registerOnPreResponse(async (req, res, t) => { - // If we're returning an error response, refresh license info from - // Elasticsearch in case the error is due to a change in license information - // in Elasticsearch. - if (res.statusCode >= 400) { - // await info.refreshNow(); - // fetching should be a blocking call - } - const license = await license$.pipe(take(1)).toPromise(); - if (license.isAvailable) { - return t.next({ - headers: { - 'kbn-license-sig': license.signature, - }, - }); - } - return t.next(); - }); + core.http.registerOnPreResponse(createOnPreResponseHandler(refresh, license$)); return { refresh, @@ -122,16 +105,13 @@ export class LicensingPlugin implements Plugin { } private createLicensePoller(clusterClient: IClusterClient, pollingFrequency: number) { - const manualRefresh$ = new Subject(); const intervalRefresh$ = timer(0, pollingFrequency); - const refresh$ = merge(intervalRefresh$, manualRefresh$).pipe(takeUntil(this.stop$)); - const { update$, fetchSubscription } = createLicenseUpdate(refresh$, () => + const { license$, refreshManually } = createLicenseUpdate(intervalRefresh$, this.stop$, () => this.fetchLicense(clusterClient) ); - this.licenseFetchSubscription = fetchSubscription; - this.loggingSubscription = update$.subscribe(license => + this.loggingSubscription = license$.subscribe(license => this.logger.debug( 'Imported license information from Elasticsearch:' + [ @@ -143,11 +123,11 @@ export class LicensingPlugin implements Plugin { ); return { - refresh: () => { + refresh: async () => { this.logger.debug('Requesting Elasticsearch licensing API'); - manualRefresh$.next(); + return await refreshManually(); }, - license$: update$, + license$, }; } @@ -198,11 +178,6 @@ export class LicensingPlugin implements Plugin { this.stop$.next(); this.stop$.complete(); - if (this.licenseFetchSubscription !== undefined) { - this.licenseFetchSubscription.unsubscribe(); - this.licenseFetchSubscription = undefined; - } - if (this.loggingSubscription !== undefined) { this.loggingSubscription.unsubscribe(); this.loggingSubscription = undefined; From 674062eeea3afd3c8c1b074a7b781145d3da57ec Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 27 Nov 2019 15:29:54 +0100 Subject: [PATCH 06/19] add integration tests for license type and license header --- .../api_integration/apis/licensing/header.ts | 21 +++++++++++++++++++ .../api_integration/apis/licensing/index.ts | 1 + .../api_integration/apis/licensing/info.ts | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 x-pack/test/api_integration/apis/licensing/header.ts diff --git a/x-pack/test/api_integration/apis/licensing/header.ts b/x-pack/test/api_integration/apis/licensing/header.ts new file mode 100644 index 0000000000000..977c3854ccf30 --- /dev/null +++ b/x-pack/test/api_integration/apis/licensing/header.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('Header', () => { + it("Injects 'kbn-license-sig' header to the all responses", async () => { + const response = await supertest.get('/'); + + expect(response.header).property('kbn-license-sig'); + expect(response.header['kbn-license-sig']).to.not.be.empty(); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/licensing/index.ts b/x-pack/test/api_integration/apis/licensing/index.ts index f14d5102f6f4e..112bc582f79dd 100644 --- a/x-pack/test/api_integration/apis/licensing/index.ts +++ b/x-pack/test/api_integration/apis/licensing/index.ts @@ -9,5 +9,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function licensingIntegrationTests({ loadTestFile }: FtrProviderContext) { describe('Licensing', () => { loadTestFile(require.resolve('./info')); + loadTestFile(require.resolve('./header')); }); } diff --git a/x-pack/test/api_integration/apis/licensing/info.ts b/x-pack/test/api_integration/apis/licensing/info.ts index 0b48080616fb9..48158f0d893b7 100644 --- a/x-pack/test/api_integration/apis/licensing/info.ts +++ b/x-pack/test/api_integration/apis/licensing/info.ts @@ -19,6 +19,11 @@ export default function({ getService }: FtrProviderContext) { expect(response.body).property('license'); expect(response.body).property('signature'); }); + it('returns a correct license type', async () => { + const response = await supertest.get('/api/licensing/info').expect(200); + + expect(response.body.license.type).to.be('trial'); + }); }); }); } From 58c92b6ffab4b9307879cd632e06676e8cfdd19f Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Fri, 29 Nov 2019 16:21:04 +0100 Subject: [PATCH 07/19] switch config to duration --- .../plugins/licensing/server/licensing_config.ts | 3 +-- x-pack/plugins/licensing/server/plugin.test.ts | 13 +++++++++---- x-pack/plugins/licensing/server/plugin.ts | 15 ++++++++++----- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/licensing/server/licensing_config.ts b/x-pack/plugins/licensing/server/licensing_config.ts index 7be19398828e9..6cb3e8d9ef3a1 100644 --- a/x-pack/plugins/licensing/server/licensing_config.ts +++ b/x-pack/plugins/licensing/server/licensing_config.ts @@ -6,10 +6,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; -const SECOND = 1000; export const config = { schema: schema.object({ - pollingFrequency: schema.number({ defaultValue: 30 * SECOND }), + pollingFrequency: schema.duration({ defaultValue: '30s' }), }), }; diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts index ffde569f988b7..62b6ec6a106b7 100644 --- a/x-pack/plugins/licensing/server/plugin.test.ts +++ b/x-pack/plugins/licensing/server/plugin.test.ts @@ -6,6 +6,7 @@ import { BehaviorSubject } from 'rxjs'; import { take, toArray } from 'rxjs/operators'; +import moment from 'moment'; import { LicenseType } from '../common/types'; import { ElasticsearchError, RawLicense } from './types'; import { LicensingPlugin } from './plugin'; @@ -24,7 +25,7 @@ function buildRawLicense(options: Partial = {}): RawLicense { }; return Object.assign(defaultRawLicense, options); } -const pollingFrequency = 100; +const pollingFrequency = moment.duration(100); const flushPromises = (ms = 50) => new Promise(res => setTimeout(res, ms)); @@ -199,7 +200,7 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin( coreMock.createPluginInitializerContext({ // disable polling mechanism - pollingFrequency: 50000, + pollingFrequency: moment.duration(50000), }) ); const dataClient = elasticsearchServiceMock.createClusterClient(); @@ -225,7 +226,11 @@ describe('licensing plugin', () => { let plugin: LicensingPlugin; beforeEach(() => { - plugin = new LicensingPlugin(coreMock.createPluginInitializerContext()); + plugin = new LicensingPlugin( + coreMock.createPluginInitializerContext({ + pollingFrequency, + }) + ); }); afterEach(async () => { @@ -252,7 +257,7 @@ describe('licensing plugin', () => { let plugin: LicensingPlugin; beforeEach(() => { - plugin = new LicensingPlugin(coreMock.createPluginInitializerContext()); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext({ pollingFrequency })); }); afterEach(async () => { diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index 2c828745b3d73..64f7cc56948f2 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -6,7 +6,7 @@ import { Observable, Subject, Subscription, timer } from 'rxjs'; import { take } from 'rxjs/operators'; -import moment from 'moment'; +import moment, { Duration } from 'moment'; import { createHash } from 'crypto'; import stringify from 'json-stable-stringify'; @@ -104,8 +104,8 @@ export class LicensingPlugin implements Plugin { }; } - private createLicensePoller(clusterClient: IClusterClient, pollingFrequency: number) { - const intervalRefresh$ = timer(0, pollingFrequency); + private createLicensePoller(clusterClient: IClusterClient, pollingFrequency: Duration) { + const intervalRefresh$ = timer(0, pollingFrequency.asMilliseconds()); const { license$, refreshManually } = createLicenseUpdate(intervalRefresh$, this.stop$, () => this.fetchLicense(clusterClient) @@ -138,8 +138,13 @@ export class LicensingPlugin implements Plugin { path: '/_xpack', }); - const normalizedLicense = normalizeServerLicense(response.license); - const normalizedFeatures = normalizeFeatures(response.features); + const normalizedLicense = response.license + ? normalizeServerLicense(response.license) + : undefined; + const normalizedFeatures = response.features + ? normalizeFeatures(response.features) + : undefined; + const signature = sign({ license: normalizedLicense, features: normalizedFeatures, From 61d8a726837583d1c24afdf666777e21d14a6d65 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Fri, 29 Nov 2019 16:22:24 +0100 Subject: [PATCH 08/19] don't run interceptor on anon paths. add tests --- src/core/public/http/http_service.mock.ts | 7 ++- src/core/public/mocks.ts | 6 +-- src/core/server/http/http_service.mock.ts | 6 +++ .../plugins/licensing/public/plugin.test.ts | 34 ++++++++++++ x-pack/plugins/licensing/public/plugin.ts | 2 + .../server/on_pre_response_handler.test.ts | 54 +++++++++++++++++++ 6 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/licensing/server/on_pre_response_handler.test.ts diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index 52f188c7b20a0..5c25e6193c4f0 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -21,10 +21,10 @@ import { HttpService } from './http_service'; import { HttpSetup } from './types'; import { BehaviorSubject } from 'rxjs'; import { BasePath } from './base_path_service'; -import { AnonymousPaths } from './anonymous_paths'; type ServiceSetupMockType = jest.Mocked & { basePath: BasePath; + anonymousPaths: jest.Mocked; }; const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ @@ -37,7 +37,10 @@ const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ delete: jest.fn(), options: jest.fn(), basePath: new BasePath(basePath), - anonymousPaths: new AnonymousPaths(new BasePath(basePath)), + anonymousPaths: { + register: jest.fn(), + isAnonymous: jest.fn(), + }, addLoadingCount: jest.fn(), getLoadingCount$: jest.fn().mockReturnValue(new BehaviorSubject(0)), stop: jest.fn(), diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 695f0454f8b65..644df259b8e24 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -18,7 +18,7 @@ */ import { applicationServiceMock } from './application/application_service.mock'; import { chromeServiceMock } from './chrome/chrome_service.mock'; -import { CoreContext, CoreSetup, CoreStart, PluginInitializerContext, NotificationsSetup } from '.'; +import { CoreContext, PluginInitializerContext } from '.'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -42,7 +42,7 @@ export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; function createCoreSetupMock({ basePath = '' } = {}) { - const mock: MockedKeys & { notifications: MockedKeys } = { + const mock = { application: applicationServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), @@ -58,7 +58,7 @@ function createCoreSetupMock({ basePath = '' } = {}) { } function createCoreStartMock({ basePath = '' } = {}) { - const mock: MockedKeys & { notifications: MockedKeys } = { + const mock = { application: applicationServiceMock.createStartContract(), chrome: chromeServiceMock.createStartContract(), docLinks: docLinksServiceMock.createStartContract(), diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index a74b6df7a01af..1e8b339f6c25e 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -25,6 +25,7 @@ import { AuthToolkit } from './lifecycle/auth'; import { sessionStorageMock } from './cookie_session_storage.mocks'; import { IRouter } from './router'; import { OnPostAuthToolkit } from './lifecycle/on_post_auth'; +import { OnPreResponseToolkit } from './lifecycle/on_pre_response'; type ServiceSetupMockType = jest.Mocked & { basePath: jest.Mocked; @@ -104,12 +105,17 @@ const createAuthToolkitMock = (): jest.Mocked => ({ authenticated: jest.fn(), }); +const createOnPreResponseToolkitMock = (): jest.Mocked => ({ + next: jest.fn(), +}); + export const httpServiceMock = { create: createHttpServiceMock, createBasePath: createBasePathMock, createSetupContract: createSetupContractMock, createOnPreAuthToolkit: createOnPreAuthToolkitMock, createOnPostAuthToolkit: createOnPostAuthToolkitMock, + createOnPreResponseToolkit: createOnPreResponseToolkitMock, createAuthToolkit: createAuthToolkitMock, createRouter: createRouterMock, }; diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index abdf923ec8ec9..f5576f3fdadd5 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -243,6 +243,40 @@ describe('licensing plugin', () => { expect(coreSetup.http.get).toHaveBeenCalledTimes(1); }); + it('http interceptor does not trigger license re-fetch for anonymous pages', async () => { + const sessionStorage = coreMock.createStorage(); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + + const coreSetup = coreMock.createSetup(); + coreSetup.http.anonymousPaths.isAnonymous.mockReturnValue(true); + + let registeredInterceptor: HttpInterceptor; + coreSetup.http.intercept.mockImplementation((interceptor: HttpInterceptor) => { + registeredInterceptor = interceptor; + return () => undefined; + }); + + await plugin.setup(coreSetup); + const httpResponse = { + response: { + headers: { + get(name: string) { + if (name === 'kbn-license-sig') { + return 'signature-1'; + } + throw new Error('unexpected header'); + }, + }, + }, + request: { + url: 'http://10.10.10.10:5601/api/hello', + }, + }; + await registeredInterceptor!.response!(httpResponse as any, null as any); + + expect(coreSetup.http.get).toHaveBeenCalledTimes(0); + }); + it('http interceptor does not trigger re-fetch if requested x-pack/info endpoint', async () => { const sessionStorage = coreMock.createStorage(); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index 7141571876d53..12d298f011a93 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -85,6 +85,8 @@ export class LicensingPlugin implements Plugin { this.removeInterceptor = core.http.intercept({ response: async httpResponse => { + // we don't track license as anon users do not have one. + if (core.http.anonymousPaths.isAnonymous(window.location.pathname)) return httpResponse; if (httpResponse.response) { const signatureHeader = httpResponse.response.headers.get('kbn-license-sig'); if (this.prevSignature !== signatureHeader) { diff --git a/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts new file mode 100644 index 0000000000000..4251e72accc9f --- /dev/null +++ b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { BehaviorSubject } from 'rxjs'; +import { createOnPreResponseHandler } from './on_pre_response_handler'; +import { httpServiceMock, httpServerMock } from '../../../../src/core/server/mocks'; +import { licenseMock } from '../common/license.mock'; + +describe('createOnPreResponseHandler', () => { + it('sets license.signature header immediately for non-error responses', async () => { + const refresh = jest.fn(); + const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const toolkit = httpServiceMock.createOnPreResponseToolkit(); + + const interceptor = createOnPreResponseHandler(refresh, license$); + await interceptor(httpServerMock.createKibanaRequest(), { statusCode: 200 }, toolkit); + + expect(refresh).toHaveBeenCalledTimes(0); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-license-sig': 'foo', + }, + }); + }); + it('sets license.signature header after refresh for non-error responses', async () => { + const updatedLicense = licenseMock.create({ signature: 'bar' }); + const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const refresh = jest.fn().mockImplementation( + () => + new Promise(resolve => { + setTimeout(() => { + license$.next(updatedLicense); + resolve(); + }, 50); + }) + ); + + const toolkit = httpServiceMock.createOnPreResponseToolkit(); + + const interceptor = createOnPreResponseHandler(refresh, license$); + await interceptor(httpServerMock.createKibanaRequest(), { statusCode: 400 }, toolkit); + + expect(refresh).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-license-sig': 'bar', + }, + }); + }); +}); From 2166b3dbecd30440563c300e9e4fdbe8ea1fb147 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Fri, 29 Nov 2019 16:29:53 +0100 Subject: [PATCH 09/19] add functional tests for licensing plugin --- x-pack/scripts/functional_tests.js | 1 + x-pack/test/licensing_plugin/apis/changes.ts | 134 ++++++++++++++++++ .../apis}/header.ts | 2 +- .../apis}/index.ts | 9 +- .../apis}/info.ts | 5 +- x-pack/test/licensing_plugin/config.ts | 54 +++++++ x-pack/test/licensing_plugin/services.ts | 20 +++ 7 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 x-pack/test/licensing_plugin/apis/changes.ts rename x-pack/test/{api_integration/apis/licensing => licensing_plugin/apis}/header.ts (91%) rename x-pack/test/{api_integration/apis/licensing => licensing_plugin/apis}/index.ts (58%) rename x-pack/test/{api_integration/apis/licensing => licensing_plugin/apis}/info.ts (88%) create mode 100644 x-pack/test/licensing_plugin/config.ts create mode 100644 x-pack/test/licensing_plugin/services.ts diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 2ac8fff6ef8ab..e50b19462fff6 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -34,4 +34,5 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/ui_capabilities/security_only/config'), require.resolve('../test/ui_capabilities/spaces_only/config'), require.resolve('../test/upgrade_assistant_integration/config'), + require.resolve('../test/licensing_plugin/config'), ]); diff --git a/x-pack/test/licensing_plugin/apis/changes.ts b/x-pack/test/licensing_plugin/apis/changes.ts new file mode 100644 index 0000000000000..3e67880ac1b4d --- /dev/null +++ b/x-pack/test/licensing_plugin/apis/changes.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../services'; +import { SecurityService } from '../../common/services'; +import { PublicLicenseJSON } from '../../../plugins/licensing/server'; + +const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const supertest = getService('supertest'); + const esSupertestWithoutAuth = getService('esSupertestWithoutAuth'); + const security: SecurityService = getService('security'); + const PageObjects = getPageObjects(['common', 'security']); + + const scenario = { + async setup() { + await security.role.create('license_manager-role', { + elasticsearch: { + cluster: ['all'], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }); + + await security.user.create('license_manager_user', { + password: 'license_manager_user-password', + roles: ['license_manager-role'], + full_name: 'license_manager user', + }); + + // ensure we're logged out so we can login as the appropriate users + await PageObjects.security.logout(); + await PageObjects.security.login('license_manager_user', 'license_manager_user-password'); + }, + + async teardown() { + await security.role.delete('license_manager-role'); + }, + + async startBasic() { + const response = await esSupertestWithoutAuth + .post('/_license/start_basic?acknowledge=true') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.basic_was_started).to.be(true); + }, + + async startTrial() { + const response = await esSupertestWithoutAuth + .post('/_license/start_trial?acknowledge=true') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.trial_was_started).to.be(true); + }, + + async deleteLicense() { + const response = await esSupertestWithoutAuth + .delete('/_license') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.acknowledged).to.be(true); + }, + + async getLicense(): Promise { + // > --xpack.licensing.pollingFrequency set in test config + // to wait for Kibana server to re-fetch the license from Elasticsearch + await delay(1000); + + const { body } = await supertest.get('/api/licensing/info').expect(200); + return body; + }, + }; + + describe('changes in license types', () => { + after(async () => { + await scenario.startBasic(); + }); + + it('provides changes in license types', async () => { + await scenario.setup(); + const initialLicense = await scenario.getLicense(); + expect(initialLicense.license?.type).to.be('basic'); + // security enabled explicitly in test config + expect(initialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + const refetchedLicense = await scenario.getLicense(); + expect(refetchedLicense.license?.type).to.be('basic'); + expect(refetchedLicense.signature).to.be(initialLicense.signature); + + // server allows to request trial only once. + // other attempts will throw 403 + await scenario.startTrial(); + const trialLicense = await scenario.getLicense(); + expect(trialLicense.license?.type).to.be('trial'); + expect(trialLicense.signature).to.not.be(initialLicense.signature); + expect(trialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + await scenario.startBasic(); + const basicLicense = await scenario.getLicense(); + expect(basicLicense.license?.type).to.be('basic'); + expect(basicLicense.signature).not.to.be(initialLicense.signature); + expect(trialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + await scenario.deleteLicense(); + const inactiveLicense = await scenario.getLicense(); + expect(inactiveLicense.signature).to.not.be(initialLicense.signature); + expect(inactiveLicense).to.not.have.property('license'); + expect(inactiveLicense.features?.security).to.eql({ + isAvailable: false, + isEnabled: true, + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/licensing/header.ts b/x-pack/test/licensing_plugin/apis/header.ts similarity index 91% rename from x-pack/test/api_integration/apis/licensing/header.ts rename to x-pack/test/licensing_plugin/apis/header.ts index 977c3854ccf30..8d95054feaaf2 100644 --- a/x-pack/test/api_integration/apis/licensing/header.ts +++ b/x-pack/test/licensing_plugin/apis/header.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../services'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/api_integration/apis/licensing/index.ts b/x-pack/test/licensing_plugin/apis/index.ts similarity index 58% rename from x-pack/test/api_integration/apis/licensing/index.ts rename to x-pack/test/licensing_plugin/apis/index.ts index 112bc582f79dd..12d7cdc6a2629 100644 --- a/x-pack/test/api_integration/apis/licensing/index.ts +++ b/x-pack/test/licensing_plugin/apis/index.ts @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../services'; -export default function licensingIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('Licensing', () => { +export default function({ loadTestFile }: FtrProviderContext) { + describe('Licensing plugin', function() { loadTestFile(require.resolve('./info')); loadTestFile(require.resolve('./header')); + + // MUST BE LAST! CHANGES LICENSE TYPE! + loadTestFile(require.resolve('./changes')); }); } diff --git a/x-pack/test/api_integration/apis/licensing/info.ts b/x-pack/test/licensing_plugin/apis/info.ts similarity index 88% rename from x-pack/test/api_integration/apis/licensing/info.ts rename to x-pack/test/licensing_plugin/apis/info.ts index 48158f0d893b7..7ec009d85cd09 100644 --- a/x-pack/test/api_integration/apis/licensing/info.ts +++ b/x-pack/test/licensing_plugin/apis/info.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../services'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -19,10 +19,11 @@ export default function({ getService }: FtrProviderContext) { expect(response.body).property('license'); expect(response.body).property('signature'); }); + it('returns a correct license type', async () => { const response = await supertest.get('/api/licensing/info').expect(200); - expect(response.body.license.type).to.be('trial'); + expect(response.body.license.type).to.be('basic'); }); }); }); diff --git a/x-pack/test/licensing_plugin/config.ts b/x-pack/test/licensing_plugin/config.ts new file mode 100644 index 0000000000000..810dd3edc76b9 --- /dev/null +++ b/x-pack/test/licensing_plugin/config.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services, pageObjects } from './services'; + +const license = 'basic'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const functionalTestsConfig = await readConfigFile(require.resolve('../functional/config.js')); + + const servers = { + ...functionalTestsConfig.get('servers'), + elasticsearch: { + ...functionalTestsConfig.get('servers.elasticsearch'), + }, + kibana: { + ...functionalTestsConfig.get('servers.kibana'), + }, + }; + + return { + testFiles: [require.resolve('./apis')], + servers, + services, + pageObjects, + junit: { + reportName: 'License plugin API Integration Tests', + }, + + esTestCluster: { + ...functionalTestsConfig.get('esTestCluster'), + license, + serverArgs: [ + ...functionalTestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.enabled=true', + ], + }, + + kbnTestServer: { + ...functionalTestsConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalTestsConfig.get('kbnTestServer.serverArgs'), + '--xpack.licensing.pollingFrequency=300', + ], + }, + + apps: { + ...functionalTestsConfig.get('apps'), + }, + }; +} diff --git a/x-pack/test/licensing_plugin/services.ts b/x-pack/test/licensing_plugin/services.ts new file mode 100644 index 0000000000000..7ded6df80cf55 --- /dev/null +++ b/x-pack/test/licensing_plugin/services.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { services as functionalTestServices } from '../functional/services'; +import { services as kibanaApiIntegrationServices } from '../api_integration/services'; +import { pageObjects } from '../functional/page_objects'; + +export const services = { + ...functionalTestServices, + supertest: kibanaApiIntegrationServices.supertest, + esSupertestWithoutAuth: kibanaApiIntegrationServices.esSupertestWithoutAuth, +}; + +export { pageObjects }; + +export type FtrProviderContext = GenericFtrProviderContext; From 268cc4555c1a2ecd2ebc8e08ec21cb082a971621 Mon Sep 17 00:00:00 2001 From: restrry Date: Fri, 29 Nov 2019 17:29:03 +0100 Subject: [PATCH 10/19] regen docs --- .../kibana-plugin-server.httpservicesetup.md | 1 + ....httpservicesetup.registeronpreresponse.md | 18 +++++++++++++++ .../core/server/kibana-plugin-server.md | 4 ++++ ...bana-plugin-server.onpreresponsehandler.md | 13 +++++++++++ ...bana-plugin-server.onpreresponsetoolkit.md | 20 ++++++++++++++++ ...plugin-server.onpreresponsetoolkit.next.md | 13 +++++++++++ ...lugin-server.responseextensions.headers.md | 11 +++++++++ ...kibana-plugin-server.responseextensions.md | 20 ++++++++++++++++ .../kibana-plugin-server.responseinfo.md | 20 ++++++++++++++++ ...a-plugin-server.responseinfo.statuscode.md | 11 +++++++++ src/core/server/http/index.ts | 7 +++++- .../server/http/lifecycle/on_pre_response.ts | 15 +++++++++--- src/core/server/index.ts | 2 ++ src/core/server/server.api.md | 23 +++++++++++++++++++ 14 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md create mode 100644 docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md create mode 100644 docs/development/core/server/kibana-plugin-server.responseextensions.headers.md create mode 100644 docs/development/core/server/kibana-plugin-server.responseextensions.md create mode 100644 docs/development/core/server/kibana-plugin-server.responseinfo.md create mode 100644 docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index dba0ad8c8560c..25eebf1c06d01 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -23,6 +23,7 @@ export interface HttpServiceSetup | [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | | [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | | [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. | +| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | | [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | ## Example diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md new file mode 100644 index 0000000000000..d88a0c979a4e1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) + +## HttpServiceSetup.registerOnPreResponse property + +To define custom logic to perform for the server response. + +Signature: + +```typescript +registerOnPreResponse: (handler: OnPreResponseHandler) => void; +``` + +## Remarks + +Doesn't provide the whole response object. Allows to extend response with custom headers. See [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md). + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 17c5136fdc318..3a98e4b0f9987 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -74,6 +74,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | | [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | | [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | +| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | | [PackageInfo](./kibana-plugin-server.packageinfo.md) | | | [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. | @@ -82,6 +83,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | | [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | | [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | +| [ResponseExtensions](./kibana-plugin-server.responseextensions.md) | Additional data to extend a response. | +| [ResponseInfo](./kibana-plugin-server.responseinfo.md) | Response status code. | | [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | | [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | @@ -168,6 +171,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | | [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | | [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | +| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | | [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The 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.onpreresponsehandler.md b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md new file mode 100644 index 0000000000000..ca80946fc1470 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) + +## OnPreResponseHandler type + +See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). + +Signature: + +```typescript +export declare type OnPreResponseHandler = (request: KibanaRequest, response: ResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md new file mode 100644 index 0000000000000..0c433202a73f5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) + +## OnPreResponseToolkit interface + +A tool set defining an outcome of OnPreAuth interceptor for incoming request. + +Signature: + +```typescript +export interface OnPreResponseToolkit +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) | (responseExtensions?: ResponseExtensions) => OnPreResponseResult | To pass request to the next handler | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md new file mode 100644 index 0000000000000..bf66577822e5e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) > [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) + +## OnPreResponseToolkit.next property + +To pass request to the next handler + +Signature: + +```typescript +next: (responseExtensions?: ResponseExtensions) => OnPreResponseResult; +``` diff --git a/docs/development/core/server/kibana-plugin-server.responseextensions.headers.md b/docs/development/core/server/kibana-plugin-server.responseextensions.headers.md new file mode 100644 index 0000000000000..4975f8b65b66a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.responseextensions.headers.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseExtensions](./kibana-plugin-server.responseextensions.md) > [headers](./kibana-plugin-server.responseextensions.headers.md) + +## ResponseExtensions.headers property + +Signature: + +```typescript +headers?: ResponseHeaders; +``` diff --git a/docs/development/core/server/kibana-plugin-server.responseextensions.md b/docs/development/core/server/kibana-plugin-server.responseextensions.md new file mode 100644 index 0000000000000..1988bb1ebec02 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.responseextensions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseExtensions](./kibana-plugin-server.responseextensions.md) + +## ResponseExtensions interface + +Additional data to extend a response. + +Signature: + +```typescript +export interface ResponseExtensions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [headers](./kibana-plugin-server.responseextensions.headers.md) | ResponseHeaders | | + diff --git a/docs/development/core/server/kibana-plugin-server.responseinfo.md b/docs/development/core/server/kibana-plugin-server.responseinfo.md new file mode 100644 index 0000000000000..995834e331aab --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.responseinfo.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseInfo](./kibana-plugin-server.responseinfo.md) + +## ResponseInfo interface + +Response status code. + +Signature: + +```typescript +export interface ResponseInfo +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [statusCode](./kibana-plugin-server.responseinfo.statuscode.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md b/docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md new file mode 100644 index 0000000000000..7ed8e7283f24a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseInfo](./kibana-plugin-server.responseinfo.md) > [statusCode](./kibana-plugin-server.responseinfo.statuscode.md) + +## ResponseInfo.statusCode property + +Signature: + +```typescript +statusCode: number; +``` diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index cdc6ec6ce94fe..00791ccc2e657 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -64,7 +64,12 @@ export { AuthResultType, } from './lifecycle/auth'; export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth'; -export { OnPreResponseHandler, OnPreResponseToolkit } from './lifecycle/on_pre_response'; +export { + OnPreResponseHandler, + OnPreResponseToolkit, + ResponseInfo, + ResponseExtensions, +} from './lifecycle/on_pre_response'; export { SessionStorageFactory, SessionStorage } from './session_storage'; export { SessionStorageCookieOptions, diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts index 1a2eeaa912c8d..d20e3c468131f 100644 --- a/src/core/server/http/lifecycle/on_pre_response.ts +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -22,6 +22,7 @@ import Boom from 'boom'; import { Logger } from '../../logging'; import { HapiResponseAdapter, KibanaRequest, ResponseHeaders } from '../router'; + enum ResultType { next = 'next', } @@ -31,6 +32,9 @@ interface Next { headers?: ResponseHeaders; } +/** + * @internal + */ type OnPreResponseResult = Next; /** @@ -41,6 +45,14 @@ export interface ResponseExtensions { headers?: ResponseHeaders; } +/** + * Response status code. + * @public + */ +export interface ResponseInfo { + statusCode: number; +} + const preResponseResult = { next(responseExtensions?: ResponseExtensions): OnPreResponseResult { return { type: ResultType.next, headers: responseExtensions?.headers }; @@ -63,9 +75,6 @@ const toolkit: OnPreResponseToolkit = { next: preResponseResult.next, }; -interface ResponseInfo { - statusCode: number; // body? header? -} /** * See {@link OnPreAuthToolkit}. * @public diff --git a/src/core/server/index.ts b/src/core/server/index.ts index c27b9cbd7ace7..7bb2737a46350 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -105,6 +105,8 @@ export { OnPostAuthToolkit, OnPreResponseHandler, OnPreResponseToolkit, + ResponseInfo, + ResponseExtensions, RedirectResponseOptions, RequestHandler, RequestHandlerContextContainer, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 25ca8ade77aca..e0bce94a87efe 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -666,6 +666,7 @@ export interface HttpServiceSetup { registerAuth: (handler: AuthenticationHandler) => void; registerOnPostAuth: (handler: OnPostAuthHandler) => void; registerOnPreAuth: (handler: OnPreAuthHandler) => void; + registerOnPreResponse: (handler: OnPreResponseHandler) => void; registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; } @@ -945,6 +946,16 @@ export interface OnPreAuthToolkit { rewriteUrl: (url: string) => OnPreAuthResult; } +// Warning: (ae-forgotten-export) The symbol "OnPreResponseResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPreResponseHandler = (request: KibanaRequest, response: ResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; + +// @public +export interface OnPreResponseToolkit { + next: (responseExtensions?: ResponseExtensions) => OnPreResponseResult; +} + // @public (undocumented) export interface PackageInfo { // (undocumented) @@ -1086,6 +1097,12 @@ export type ResponseError = string | Error | { // @public export type ResponseErrorAttributes = Record; +// @public +export interface ResponseExtensions { + // (undocumented) + headers?: ResponseHeaders; +} + // @public export type ResponseHeaders = { [header in KnownHeaders]?: string | string[]; @@ -1093,6 +1110,12 @@ export type ResponseHeaders = { [header: string]: string | string[]; }; +// @public +export interface ResponseInfo { + // (undocumented) + statusCode: number; +} + // @public export interface RouteConfig

| Type, Method extends RouteMethod> { options?: RouteConfigOptions; From a3a890a2d23fc8b1bdb6223d6d1ed20c2b15aeb5 Mon Sep 17 00:00:00 2001 From: restrry Date: Fri, 29 Nov 2019 17:34:47 +0100 Subject: [PATCH 11/19] fix test in security due to updated mocks; --- x-pack/plugins/security/public/session/session_timeout.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/public/session/session_timeout.test.tsx b/x-pack/plugins/security/public/session/session_timeout.test.tsx index eb947ab95c43b..eca3e7d6727df 100644 --- a/x-pack/plugins/security/public/session/session_timeout.test.tsx +++ b/x-pack/plugins/security/public/session/session_timeout.test.tsx @@ -124,7 +124,7 @@ describe('Session Timeout', () => { }); test(`starts and does not initialize on an anonymous path`, async () => { - http.anonymousPaths.register(window.location.pathname); + http.anonymousPaths.isAnonymous.mockReturnValue(true); await sessionTimeout.start(); // eslint-disable-next-line dot-notation expect(sessionTimeout['channel']).toBeUndefined(); From 6f0397d1769a114816cad97e404468a77ec16e97 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 2 Dec 2019 09:04:47 +0100 Subject: [PATCH 12/19] update snapshots accoring to new mock implementation --- .../query_string_input.test.tsx.snap | 66 +++++-------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap index 61aac70b4a7ec..46a431190efe4 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap @@ -236,14 +236,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -798,14 +793,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -1348,14 +1338,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -1907,14 +1892,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -2457,14 +2437,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -3016,14 +2991,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", From 59928785ab5f270b2d96212cf019374a8bd09e83 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 3 Dec 2019 14:36:00 +0100 Subject: [PATCH 13/19] migrate license expired banner to NP --- x-pack/legacy/plugins/xpack_main/index.js | 3 - .../__tests__/check_xpack_info_change.js | 102 ----------------- .../public/hacks/check_xpack_info_change.js | 107 ------------------ .../licensing/public/expired_banner.tsx | 48 ++++++++ .../licensing/public/plugin.test.mocks.ts | 10 ++ .../plugins/licensing/public/plugin.test.ts | 56 +++++++++ x-pack/plugins/licensing/public/plugin.ts | 38 +++++-- .../translations/translations/ja-JP.json | 10 +- .../translations/translations/zh-CN.json | 10 +- x-pack/test/licensing_plugin/apis/changes.ts | 3 + 10 files changed, 156 insertions(+), 231 deletions(-) delete mode 100644 x-pack/legacy/plugins/xpack_main/public/hacks/__tests__/check_xpack_info_change.js delete mode 100644 x-pack/legacy/plugins/xpack_main/public/hacks/check_xpack_info_change.js create mode 100644 x-pack/plugins/licensing/public/expired_banner.tsx create mode 100644 x-pack/plugins/licensing/public/plugin.test.mocks.ts diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js index de8928061d141..6828833c3f982 100644 --- a/x-pack/legacy/plugins/xpack_main/index.js +++ b/x-pack/legacy/plugins/xpack_main/index.js @@ -47,9 +47,6 @@ export const xpackMain = (kibana) => { }, uiExports: { - hacks: [ - 'plugins/xpack_main/hacks/check_xpack_info_change', - ], replaceInjectedVars, injectDefaultVars(server) { const config = server.config(); diff --git a/x-pack/legacy/plugins/xpack_main/public/hacks/__tests__/check_xpack_info_change.js b/x-pack/legacy/plugins/xpack_main/public/hacks/__tests__/check_xpack_info_change.js deleted file mode 100644 index 8f3ae33017b39..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/public/hacks/__tests__/check_xpack_info_change.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import ngMock from 'ng_mock'; -import sinon from 'sinon'; -import { banners } from 'ui/notify'; - -const XPACK_INFO_SIG_KEY = 'xpackMain.infoSignature'; -const XPACK_INFO_KEY = 'xpackMain.info'; - -describe('CheckXPackInfoChange Factory', () => { - const sandbox = sinon.createSandbox(); - - let mockSessionStorage; - beforeEach(ngMock.module('kibana', ($provide) => { - mockSessionStorage = sinon.stub({ - setItem() {}, - getItem() {}, - removeItem() {} - }); - - mockSessionStorage.getItem.withArgs(XPACK_INFO_SIG_KEY).returns('foo'); - - $provide.service('$window', () => ({ - sessionStorage: mockSessionStorage, - location: { pathname: '' } - })); - })); - - let $http; - let $httpBackend; - let $timeout; - beforeEach(ngMock.inject(($injector) => { - $http = $injector.get('$http'); - $httpBackend = $injector.get('$httpBackend'); - $timeout = $injector.get('$timeout'); - - // We set 'kbn-system-api' to not trigger other unrelated toast notifications - // like the one related to the session expiration. - $http.defaults.headers.common['kbn-system-api'] = 'x'; - - sandbox.stub(banners, 'add'); - })); - - afterEach(function () { - $httpBackend.verifyNoOutstandingRequest(); - $timeout.verifyNoPendingTasks(); - - sandbox.restore(); - }); - - it('does not show "license expired" banner if license is not expired.', () => { - const license = { license: { isActive: true, type: 'x-license' } }; - mockSessionStorage.getItem.withArgs(XPACK_INFO_KEY).returns(JSON.stringify(license)); - - $httpBackend - .when('POST', '/api/test') - .respond('ok', { 'kbn-xpack-sig': 'foo' }); - - $httpBackend - .when('GET', '/api/xpack/v1/info') - .respond(license, { 'kbn-xpack-sig': 'foo' }); - - $http.post('/api/test'); - $httpBackend.flush(); - $timeout.flush(); - - sinon.assert.notCalled(banners.add); - }); - - it('shows "license expired" banner if license is expired only once.', async () => { - const license = { license: { isActive: false, type: 'diamond' } }; - mockSessionStorage.getItem.withArgs(XPACK_INFO_KEY).returns(JSON.stringify(license)); - - $httpBackend - .when('POST', '/api/test') - .respond('ok', { 'kbn-xpack-sig': 'bar' }); - - $httpBackend - .when('GET', '/api/xpack/v1/info') - .respond(license, { 'kbn-xpack-sig': 'bar' }); - - $http.post('/api/test'); - $httpBackend.flush(); - $timeout.flush(); - - sinon.assert.calledOnce(banners.add); - - // If license didn't change banner shouldn't be displayed. - banners.add.resetHistory(); - mockSessionStorage.getItem.withArgs(XPACK_INFO_SIG_KEY).returns('bar'); - - $http.post('/api/test'); - $httpBackend.flush(); - $timeout.flush(); - - sinon.assert.notCalled(banners.add); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/public/hacks/check_xpack_info_change.js b/x-pack/legacy/plugins/xpack_main/public/hacks/check_xpack_info_change.js deleted file mode 100644 index 587dd8cc11f55..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/public/hacks/check_xpack_info_change.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { identity } from 'lodash'; -import { EuiCallOut } from '@elastic/eui'; -import { uiModules } from 'ui/modules'; -import chrome from 'ui/chrome'; -import { banners } from 'ui/notify'; -import { DebounceProvider } from 'ui/directives/debounce'; -import { Path } from 'plugins/xpack_main/services/path'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { xpackInfoSignature } from 'plugins/xpack_main/services/xpack_info_signature'; -import { FormattedMessage } from '@kbn/i18n/react'; - -const module = uiModules.get('xpack_main', []); - -module.factory('checkXPackInfoChange', ($q, Private, $injector) => { - const debounce = Private(DebounceProvider); - const isUnauthenticated = Path.isUnauthenticated(); - let isLicenseExpirationBannerShown = false; - - const notifyIfLicenseIsExpired = debounce(() => { - const license = xpackInfo.get('license'); - if (license.isActive) { - return; - } - - const uploadLicensePath = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management/upload_license`; - - if (!isLicenseExpirationBannerShown) { - isLicenseExpirationBannerShown = true; - banners.add({ - component: ( - } - > - - - - ) - }} - /> - - ), - }); - } - }); - - /** - * Intercept each network response to look for the kbn-xpack-sig header. - * When that header is detected, compare its value with the value cached - * in the browser storage. When the value is new, call `xpackInfo.refresh()` - * so that it will pull down the latest x-pack info - * - * @param {object} response - the angular $http response object - * @param {function} handleResponse - callback, expects to receive the response - * @return - */ - function interceptor(response, handleResponse) { - if (isUnauthenticated) { - return handleResponse(response); - } - - const currentSignature = response.headers('kbn-xpack-sig'); - const cachedSignature = xpackInfoSignature.get(); - - if (currentSignature && cachedSignature !== currentSignature) { - // Signature from the server differ from the signature of our - // cached info, so we need to refresh it. - // Intentionally swallowing this error - // because nothing catches it and it's an ugly console error. - xpackInfo.refresh($injector).then( - () => notifyIfLicenseIsExpired(), - () => {} - ); - } - - return handleResponse(response); - } - - return { - response: (response) => interceptor(response, identity), - responseError: (response) => interceptor(response, $q.reject) - }; -}); - -module.config(($httpProvider) => { - $httpProvider.interceptors.push('checkXPackInfoChange'); -}); diff --git a/x-pack/plugins/licensing/public/expired_banner.tsx b/x-pack/plugins/licensing/public/expired_banner.tsx new file mode 100644 index 0000000000000..9c42d3ff6997d --- /dev/null +++ b/x-pack/plugins/licensing/public/expired_banner.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; + +interface Props { + type: string; + uploadUrl: string; +} + +const ExpiredBanner: React.FunctionComponent = props => ( + + } + > + + + + ), + }} + /> + +); + +export const mountExpiredBanner = (props: Props) => + toMountPoint(); diff --git a/x-pack/plugins/licensing/public/plugin.test.mocks.ts b/x-pack/plugins/licensing/public/plugin.test.mocks.ts new file mode 100644 index 0000000000000..6635a7950c0e8 --- /dev/null +++ b/x-pack/plugins/licensing/public/plugin.test.mocks.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mountExpiredBannerMock = jest.fn(); +jest.doMock('./expired_banner', () => ({ + mountExpiredBanner: mountExpiredBannerMock, +})); diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index f5576f3fdadd5..c356f7f5df184 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -5,6 +5,7 @@ */ import { take } from 'rxjs/operators'; +import { mountExpiredBannerMock } from './plugin.test.mocks'; import { LicenseType } from '../common/types'; import { LicensingPlugin, licensingSessionStorageKey } from './plugin'; @@ -20,6 +21,7 @@ describe('licensing plugin', () => { let plugin: LicensingPlugin; afterEach(async () => { + jest.clearAllMocks(); await plugin.stop(); }); @@ -186,6 +188,7 @@ describe('licensing plugin', () => { }); }); }); + describe('interceptor', () => { it('register http interceptor checking signature header', async () => { const sessionStorage = coreMock.createStorage(); @@ -320,6 +323,59 @@ describe('licensing plugin', () => { expect(updated).toBe(false); }); }); + + describe('expired banner', () => { + it('does not show "license expired" banner if license is not expired.', async () => { + const sessionStorage = coreMock.createStorage(); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + + const coreSetup = coreMock.createSetup(); + coreSetup.http.get.mockResolvedValueOnce( + licenseMock.create({ license: { status: 'active', type: 'gold' } }) + ); + + const { refresh } = await plugin.setup(coreSetup); + + const coreStart = coreMock.createStart(); + await plugin.start(coreStart); + + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(0); + }); + + it('shows "license expired" banner if license is expired only once.', async () => { + const sessionStorage = coreMock.createStorage(); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + + const coreSetup = coreMock.createSetup(); + const activeLicense = licenseMock.create({ license: { status: 'active', type: 'gold' } }); + const expiredLicense = licenseMock.create({ license: { status: 'expired', type: 'gold' } }); + coreSetup.http.get + .mockResolvedValueOnce(activeLicense) + .mockResolvedValueOnce(expiredLicense) + .mockResolvedValueOnce(activeLicense) + .mockResolvedValueOnce(expiredLicense); + + const { refresh } = await plugin.setup(coreSetup); + + const coreStart = coreMock.createStart(); + await plugin.start(coreStart); + + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(0); + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); + expect(mountExpiredBannerMock).toHaveBeenCalledWith({ + type: 'gold', + uploadUrl: '/app/kibana#/management/elasticsearch/license_management/upload_license', + }); + }); + }); + describe('#stop', () => { it('stops polling', async () => { const sessionStorage = coreMock.createStorage(); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index 12d298f011a93..c0dc0f21b90be 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -3,14 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { Subject, Subscription } from 'rxjs'; -import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { ILicense, LicensingPluginSetup } from '../common/types'; import { createLicenseUpdate } from '../common/license_update'; import { License } from '../common/license'; +import { mountExpiredBanner } from './expired_banner'; export const licensingSessionStorageKey = 'xpack.licensing'; @@ -29,9 +29,11 @@ export class LicensingPlugin implements Plugin { * A function to execute once the plugin's HTTP interceptor needs to stop listening. */ private removeInterceptor?: () => void; - private storageSubscription?: Subscription; + private internalSubscription?: Subscription; + private isLicenseExpirationBannerShown? = false; private readonly infoEndpoint = '/api/licensing/info'; + private coreStart?: CoreStart; private prevSignature?: string; constructor( @@ -72,7 +74,7 @@ export class LicensingPlugin implements Plugin { this.getSaved() ); - this.storageSubscription = license$.subscribe(license => { + this.internalSubscription = license$.subscribe(license => { if (license.isAvailable) { this.prevSignature = license.signature; this.save(license); @@ -81,6 +83,11 @@ export class LicensingPlugin implements Plugin { // Prevent reusing stale license if the fetch operation fails this.removeSaved(); } + + if (license.status === 'expired' && !this.isLicenseExpirationBannerShown && this.coreStart) { + this.isLicenseExpirationBannerShown = true; + this.showExpiredBanner(license); + } }); this.removeInterceptor = core.http.intercept({ @@ -105,7 +112,9 @@ export class LicensingPlugin implements Plugin { }; } - public async start() {} + public async start(core: CoreStart) { + this.coreStart = core; + } public stop() { this.stop$.next(); @@ -114,9 +123,9 @@ export class LicensingPlugin implements Plugin { if (this.removeInterceptor !== undefined) { this.removeInterceptor(); } - if (this.storageSubscription !== undefined) { - this.storageSubscription.unsubscribe(); - this.storageSubscription = undefined; + if (this.internalSubscription !== undefined) { + this.internalSubscription.unsubscribe(); + this.internalSubscription = undefined; } } @@ -127,7 +136,6 @@ export class LicensingPlugin implements Plugin { 'kbn-system-api': 'true', }, }); - return new License({ license: response.license, features: response.features, @@ -137,4 +145,16 @@ export class LicensingPlugin implements Plugin { return new License({ error: error.message, signature: '' }); } }; + + private showExpiredBanner(license: ILicense) { + const uploadUrl = this.coreStart!.http.basePath.prepend( + '/app/kibana#/management/elasticsearch/license_management/upload_license' + ); + this.coreStart!.overlays.banners.add( + mountExpiredBanner({ + type: license.type!, + uploadUrl, + }) + ); + } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3b7f3ded77290..cf3f9ffd4115c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6394,9 +6394,6 @@ "xpack.logstash.upgradeFailureActions.goBackButtonLabel": "戻る", "xpack.logstash.upstreamPipelineArgumentMustContainAnIdPropertyErrorMessage": "upstreamPipeline 引数には id プロパティを含める必要があります", "xpack.logstash.workersTooltip": "パイプラインのフィルターとアウトプットステージを同時に実行するワーカーの数です。イベントが詰まってしまう場合や CPU が飽和状態ではない場合は、マシンの処理能力をより有効に活用するため、この数字を上げてみてください。\n\nデフォルト値:ホストの CPU コア数です", - "xpack.main.welcomeBanner.licenseIsExpiredDescription": "管理者または {updateYourLicenseLinkText} に直接お問い合わせください。", - "xpack.main.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", - "xpack.main.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです", "xpack.maps.addLayerPanel.changeDataSourceButtonLabel": "データソースを変更", "xpack.maps.addLayerPanel.chooseDataSourceTitle": "データソースの選択", "xpack.maps.appDescription": "マップアプリケーション", @@ -12750,6 +12747,9 @@ "xpack.lens.xyVisualization.xyLabel": "XY", "xpack.licensing.check.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません。", "xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", - "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。" + "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription": "管理者または {updateYourLicenseLinkText} に直接お問い合わせください。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", + "xpack.licensing.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 017aa7d91d74b..6d57627bcf3bb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6396,9 +6396,6 @@ "xpack.logstash.upgradeFailureActions.goBackButtonLabel": "返回", "xpack.logstash.upstreamPipelineArgumentMustContainAnIdPropertyErrorMessage": "upstreamPipeline 参数必须包含 id 属性", "xpack.logstash.workersTooltip": "并行执行管道的筛选和输出阶段的工作线程数目。如果您发现事件出现积压或 CPU 未饱和,请考虑增大此数值,以更好地利用机器处理能力。\n\n默认值:主机的 CPU 核心数", - "xpack.main.welcomeBanner.licenseIsExpiredDescription": "联系您的管理员或直接{updateYourLicenseLinkText}。", - "xpack.main.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "更新您的许可", - "xpack.main.welcomeBanner.licenseIsExpiredTitle": "您的{licenseType}许可已过期", "xpack.maps.addLayerPanel.changeDataSourceButtonLabel": "更改数据源", "xpack.maps.addLayerPanel.chooseDataSourceTitle": "选择数据源", "xpack.maps.appDescription": "地图应用程序", @@ -12839,6 +12836,9 @@ "xpack.lens.xyVisualization.xyLabel": "XY", "xpack.licensing.check.errorExpiredMessage": "您不能使用 {pluginName},因为您的{licenseType}许可证已过期。", "xpack.licensing.check.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", - "xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。" + "xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription": "联系您的管理员或直接{updateYourLicenseLinkText}。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "更新您的许可", + "xpack.licensing.welcomeBanner.licenseIsExpiredTitle": "您的{licenseType}许可已过期" } -} \ No newline at end of file +} diff --git a/x-pack/test/licensing_plugin/apis/changes.ts b/x-pack/test/licensing_plugin/apis/changes.ts index 3e67880ac1b4d..1c220b3abb507 100644 --- a/x-pack/test/licensing_plugin/apis/changes.ts +++ b/x-pack/test/licensing_plugin/apis/changes.ts @@ -15,6 +15,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esSupertestWithoutAuth = getService('esSupertestWithoutAuth'); const security: SecurityService = getService('security'); const PageObjects = getPageObjects(['common', 'security']); + const testSubjects = getService('testSubjects'); const scenario = { async setup() { @@ -129,6 +130,8 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { isAvailable: false, isEnabled: true, }); + // banner shown only when license expired not just deleted + await testSubjects.missingOrFail('licenseExpiredBanner'); }); }); } From 4b457084327ab956eeea9fb06312349503d268c7 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 4 Dec 2019 11:58:05 +0100 Subject: [PATCH 14/19] add readme for the licensing plugin --- src/core/MIGRATION.md | 1 + x-pack/plugins/licensing/README.md | 94 ++++++++++++++++++++++++ x-pack/plugins/licensing/common/types.ts | 2 +- 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/licensing/README.md diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 7c1489a345e55..60abf9e07a740 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1198,6 +1198,7 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client | | `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client | +| `xpackMainPlugin.info.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | | _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-server.coresetup.md)_ diff --git a/x-pack/plugins/licensing/README.md b/x-pack/plugins/licensing/README.md new file mode 100644 index 0000000000000..fe0c82790b820 --- /dev/null +++ b/x-pack/plugins/licensing/README.md @@ -0,0 +1,94 @@ +# Licensing plugin + +Retrieves license data from Elasticsearch and becomes a source of license data for all Kibana plugins on server-side and client-side. + +## API: +### Server-side + The licensing plugin retrieves license data from **Elasticsearch** at regular configurable intervals. +- `license$: Observable` Provides a steam of license data [ILicense](./common/types.ts). Plugin emits new value whenever it detects changes in license info. If the plugin cannot retrieve a license from **Elasticsearch**, it will emit `an empty license` object. +- `refresh: () => Promise` allows a plugin to enforce license retrieval. + +### Client-side + The licensing plugin retrieves license data from **licensing Kibana plugin** and does not communicate with Elasticsearch directly. +- `license$: Observable` Provides a steam of license data [ILicense](./common/types.ts). Plugin emits new value whenever it detects changes in license info. If the plugin cannot retrieve a license from **Kibana**, it will emit `an empty license` object. +- `refresh: () => Promise` allows a plugin to enforce license retrieval. + +## Migration path +The new platform licensing plugin became stateless now. It means that instead of storing all your data from `checkLicense` within the plugin, you should react on license data change on both the client and server sides. + +### Before +```ts +// my_plugin/server/plugin.ts +function checkLicense(xpackLicenseInfo: XPackInfo){ + if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { + return { + isAvailable: false, + showLinks: true, + } + } + if (!xpackLicenseInfo.feature('name').isEnabled()) { + return { + isAvailable: false, + showLinks: false, + } + } + const hasRequiredLicense = xPackInfo.license.isOneOf([ + 'gold', + 'platinum', + 'trial', + ]); + return { + isAvailable: hasRequiredLicense, + showLinks: hasRequiredLicense, + } +} +xpackMainPlugin.info.feature(pluginId).registerLicenseCheckResultsGenerator(checkLicense); + +// my_plugin/client/plugin.ts +chrome.navLinks.update('myPlugin', { + hidden: !xpackInfo.get('features.myPlugin.showLinks', false) +}); +``` + +### After +```ts +// kibana.json +"requiredPlugins": ["licensing"], + +// my_plugin/server/plugin.ts +import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing' + +interface SetupDeps { + licensing: LicensingPluginSetup; +} + +class MyPlugin { + setup(core: CoreSetup, deps: SetupDeps) { + deps.licensing.license$.subscribe(license => { + const { state, message } = license.check('myPlugin', 'gold') + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + if(hasRequiredLicense && license.getFeature('name').isAvailable){ + // enable some server side logic + } else { + log(message); + // disable some server side logic + } + }) + } +} + +// my_plugin/client/plugin.ts +class MyPlugin { + setup(core: CoreSetup, deps: SetupDeps) { + deps.licensing.license$.subscribe(license => { + const { state, message } = license.check('myPlugin', 'gold') + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const showLinks = hasRequiredLicense && license.getFeature('name').isAvailable; + + chrome.navLinks.update('myPlugin', { + hidden: !showLinks + }); + }) + } +} +``` diff --git a/x-pack/plugins/licensing/common/types.ts b/x-pack/plugins/licensing/common/types.ts index c8edd8fd0cca8..c5d838d23d8c3 100644 --- a/x-pack/plugins/licensing/common/types.ts +++ b/x-pack/plugins/licensing/common/types.ts @@ -183,5 +183,5 @@ export interface LicensingPluginSetup { /** * Triggers licensing information re-fetch. */ - refresh(): void; + refresh(): Promise; } From 6b9c7bf2345fa1ca273c4990c3ae819c4b72bf10 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 4 Dec 2019 14:07:27 +0100 Subject: [PATCH 15/19] remove outdated import. licensing has separate functional tests --- x-pack/test/api_integration/apis/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index ca339e9f407f2..86ef445899039 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -27,6 +27,5 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./siem')); loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); - loadTestFile(require.resolve('./licensing')); }); } From b9fbe71b160a7ce583fb04a5f95681cbe9ab98ac Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 4 Dec 2019 15:15:17 +0100 Subject: [PATCH 16/19] add tag for test to run on CI --- x-pack/test/licensing_plugin/apis/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/licensing_plugin/apis/index.ts b/x-pack/test/licensing_plugin/apis/index.ts index 12d7cdc6a2629..fbc0449dcd8fc 100644 --- a/x-pack/test/licensing_plugin/apis/index.ts +++ b/x-pack/test/licensing_plugin/apis/index.ts @@ -8,6 +8,7 @@ import { FtrProviderContext } from '../services'; export default function({ loadTestFile }: FtrProviderContext) { describe('Licensing plugin', function() { + this.tags('ciGroup2'); loadTestFile(require.resolve('./info')); loadTestFile(require.resolve('./header')); From 7eec72a6966396cd5fbc2ba53b892053380e5d90 Mon Sep 17 00:00:00 2001 From: restrry Date: Fri, 6 Dec 2019 17:58:56 +0100 Subject: [PATCH 17/19] regen docs --- ....httpservicesetup.registeronpreresponse.md | 36 +- .../core/server/kibana-plugin-server.md | 404 +++++++++--------- ...bana-plugin-server.onpreresponsehandler.md | 26 +- ...bana-plugin-server.onpreresponsetoolkit.md | 40 +- ...plugin-server.onpreresponsetoolkit.next.md | 26 +- ...lugin-server.responseextensions.headers.md | 11 - ...kibana-plugin-server.responseextensions.md | 20 - .../kibana-plugin-server.responseinfo.md | 20 - ...a-plugin-server.responseinfo.statuscode.md | 11 - src/core/server/server.api.md | 53 --- 10 files changed, 265 insertions(+), 382 deletions(-) delete mode 100644 docs/development/core/server/kibana-plugin-server.responseextensions.headers.md delete mode 100644 docs/development/core/server/kibana-plugin-server.responseextensions.md delete mode 100644 docs/development/core/server/kibana-plugin-server.responseinfo.md delete mode 100644 docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md index 25248066cbc25..9f0eaae8830e1 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md @@ -1,18 +1,18 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) - -## HttpServiceSetup.registerOnPreResponse property - -To define custom logic to perform for the server response. - -Signature: - -```typescript -registerOnPreResponse: (handler: OnPreResponseHandler) => void; -``` - -## Remarks - -Doesn't provide the whole response object. Supports extending response with custom headers. See [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md). - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) + +## HttpServiceSetup.registerOnPreResponse property + +To define custom logic to perform for the server response. + +Signature: + +```typescript +registerOnPreResponse: (handler: OnPreResponseHandler) => void; +``` + +## Remarks + +Doesn't provide the whole response object. Supports extending response with custom headers. See [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md). + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index ea45b8bf1f66b..fceabd1237665 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -1,203 +1,201 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) - -## kibana-plugin-server package - -The Kibana Core APIs for server-side plugins. - -A plugin requires a `kibana.json` file at it's root directory that follows [the manfiest schema](./kibana-plugin-server.pluginmanifest.md) to define static plugin information required to load the plugin. - -A plugin's `server/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-server.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-server.plugin.md). - -The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-server.coresetup.md) or [CoreStart](./kibana-plugin-server.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. - -## Classes - -| Class | Description | -| --- | --- | -| [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | -| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | -| [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | -| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | -| [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | -| [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | -| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | -| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | - -## Enumerations - -| Enumeration | Description | -| --- | --- | -| [AuthResultType](./kibana-plugin-server.authresulttype.md) | | -| [AuthStatus](./kibana-plugin-server.authstatus.md) | Status indicating an outcome of the authentication. | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [APICaller](./kibana-plugin-server.apicaller.md) | | -| [AssistanceAPIResponse](./kibana-plugin-server.assistanceapiresponse.md) | | -| [AssistantAPIClientParams](./kibana-plugin-server.assistantapiclientparams.md) | | -| [Authenticated](./kibana-plugin-server.authenticated.md) | | -| [AuthResultParams](./kibana-plugin-server.authresultparams.md) | Result of an incoming request authentication. | -| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | -| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | -| [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | -| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | -| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). | -| [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | -| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | -| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | -| [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | -| [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | -| [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | -| [DeprecationInfo](./kibana-plugin-server.deprecationinfo.md) | | -| [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | -| [ElasticsearchError](./kibana-plugin-server.elasticsearcherror.md) | | -| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | -| [EnvironmentMode](./kibana-plugin-server.environmentmode.md) | | -| [ErrorHttpResponseOptions](./kibana-plugin-server.errorhttpresponseoptions.md) | HTTP response parameters | -| [FakeRequest](./kibana-plugin-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | -| [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) | HTTP response parameters | -| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | -| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | -| [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | -| [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | -| [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | -| [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | -| [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | -| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | -| [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | -| [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | -| [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | -| [LegacyServiceStartDeps](./kibana-plugin-server.legacyservicestartdeps.md) | | -| [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 | -| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | -| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | -| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. | -| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. | -| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | -| [PackageInfo](./kibana-plugin-server.packageinfo.md) | | -| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | -| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. | -| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | -| [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | -| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | -| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | -| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | -| [ResponseExtensions](./kibana-plugin-server.responseextensions.md) | Additional data to extend a response. | -| [ResponseInfo](./kibana-plugin-server.responseinfo.md) | Response status code. | -| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | -| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | -| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | -| [RouteSchemas](./kibana-plugin-server.routeschemas.md) | RouteSchemas contains the schemas for validating the different parts of a request. | -| [SavedObject](./kibana-plugin-server.savedobject.md) | | -| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | -| [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | -| [SavedObjectsBaseOptions](./kibana-plugin-server.savedobjectsbaseoptions.md) | | -| [SavedObjectsBulkCreateObject](./kibana-plugin-server.savedobjectsbulkcreateobject.md) | | -| [SavedObjectsBulkGetObject](./kibana-plugin-server.savedobjectsbulkgetobject.md) | | -| [SavedObjectsBulkResponse](./kibana-plugin-server.savedobjectsbulkresponse.md) | | -| [SavedObjectsBulkUpdateObject](./kibana-plugin-server.savedobjectsbulkupdateobject.md) | | -| [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) | | -| [SavedObjectsBulkUpdateResponse](./kibana-plugin-server.savedobjectsbulkupdateresponse.md) | | -| [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | -| [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | -| [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | -| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | | -| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | -| [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | -| [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | -| [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | | -| [SavedObjectsFindResponse](./kibana-plugin-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | -| [SavedObjectsImportConflictError](./kibana-plugin-server.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | -| [SavedObjectsImportError](./kibana-plugin-server.savedobjectsimporterror.md) | Represents a failure to import. | -| [SavedObjectsImportMissingReferencesError](./kibana-plugin-server.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | -| [SavedObjectsImportOptions](./kibana-plugin-server.savedobjectsimportoptions.md) | Options to control the import operation. | -| [SavedObjectsImportResponse](./kibana-plugin-server.savedobjectsimportresponse.md) | The response describing the result of an import. | -| [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | -| [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | -| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | -| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | | -| [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | | -| [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | -| [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | -| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | -| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | -| [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | -| [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | -| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | -| [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | -| [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | -| [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | -| [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | -| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | -| [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | - -## Variables - -| Variable | Description | -| --- | --- | -| [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | -| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-server.authtoolkit.md). | -| [AuthHeaders](./kibana-plugin-server.authheaders.md) | Auth Headers map | -| [AuthResult](./kibana-plugin-server.authresult.md) | | -| [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | -| [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | -| [ConfigPath](./kibana-plugin-server.configpath.md) | | -| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | -| [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | -| [GetAuthState](./kibana-plugin-server.getauthstate.md) | Get authentication state for a request. Returned by auth interceptor. | -| [HandlerContextType](./kibana-plugin-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md) to represent the type of the context. | -| [HandlerFunction](./kibana-plugin-server.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | -| [HandlerParameters](./kibana-plugin-server.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-server.handlercontexttype.md). | -| [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | -| [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | -| [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | -| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | -| [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | -| [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | -| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | -| [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | -| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | -| [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | -| [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | -| [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | -| [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-server.migration_assistance_index_action.md) | | -| [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-server.migration_deprecation_level.md) | | -| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | -| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | -| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | -| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | -| [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. | -| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The 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. | -| [PluginOpaqueId](./kibana-plugin-server.pluginopaqueid.md) | | -| [RecursiveReadonly](./kibana-plugin-server.recursivereadonly.md) | | -| [RedirectResponseOptions](./kibana-plugin-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | -| [RequestHandler](./kibana-plugin-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) functions. | -| [RequestHandlerContextContainer](./kibana-plugin-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | -| [RequestHandlerContextProvider](./kibana-plugin-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | -| [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | -| [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | -| [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | -| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | -| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | -| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | -| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | -| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | -| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | -| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | -| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | -| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) + +## kibana-plugin-server package + +The Kibana Core APIs for server-side plugins. + +A plugin requires a `kibana.json` file at it's root directory that follows [the manfiest schema](./kibana-plugin-server.pluginmanifest.md) to define static plugin information required to load the plugin. + +A plugin's `server/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-server.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-server.plugin.md). + +The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-server.coresetup.md) or [CoreStart](./kibana-plugin-server.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. + +## Classes + +| Class | Description | +| --- | --- | +| [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | +| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | +| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | +| [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | +| [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | +| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | +| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | + +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [AuthResultType](./kibana-plugin-server.authresulttype.md) | | +| [AuthStatus](./kibana-plugin-server.authstatus.md) | Status indicating an outcome of the authentication. | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [APICaller](./kibana-plugin-server.apicaller.md) | | +| [AssistanceAPIResponse](./kibana-plugin-server.assistanceapiresponse.md) | | +| [AssistantAPIClientParams](./kibana-plugin-server.assistantapiclientparams.md) | | +| [Authenticated](./kibana-plugin-server.authenticated.md) | | +| [AuthResultParams](./kibana-plugin-server.authresultparams.md) | Result of an incoming request authentication. | +| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | +| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | +| [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | +| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | +| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). | +| [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | +| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | +| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | +| [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | +| [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | +| [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | +| [DeprecationInfo](./kibana-plugin-server.deprecationinfo.md) | | +| [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | +| [ElasticsearchError](./kibana-plugin-server.elasticsearcherror.md) | | +| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | +| [EnvironmentMode](./kibana-plugin-server.environmentmode.md) | | +| [ErrorHttpResponseOptions](./kibana-plugin-server.errorhttpresponseoptions.md) | HTTP response parameters | +| [FakeRequest](./kibana-plugin-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | +| [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) | HTTP response parameters | +| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | +| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | +| [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | +| [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | +| [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | +| [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | +| [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | +| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | +| [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | +| [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | +| [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | +| [LegacyServiceStartDeps](./kibana-plugin-server.legacyservicestartdeps.md) | | +| [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 | +| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | +| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | +| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. | +| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. | +| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | +| [PackageInfo](./kibana-plugin-server.packageinfo.md) | | +| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | +| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. | +| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | +| [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | +| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | +| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | +| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | +| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | +| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | +| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | +| [RouteSchemas](./kibana-plugin-server.routeschemas.md) | RouteSchemas contains the schemas for validating the different parts of a request. | +| [SavedObject](./kibana-plugin-server.savedobject.md) | | +| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | +| [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | +| [SavedObjectsBaseOptions](./kibana-plugin-server.savedobjectsbaseoptions.md) | | +| [SavedObjectsBulkCreateObject](./kibana-plugin-server.savedobjectsbulkcreateobject.md) | | +| [SavedObjectsBulkGetObject](./kibana-plugin-server.savedobjectsbulkgetobject.md) | | +| [SavedObjectsBulkResponse](./kibana-plugin-server.savedobjectsbulkresponse.md) | | +| [SavedObjectsBulkUpdateObject](./kibana-plugin-server.savedobjectsbulkupdateobject.md) | | +| [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) | | +| [SavedObjectsBulkUpdateResponse](./kibana-plugin-server.savedobjectsbulkupdateresponse.md) | | +| [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | +| [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | +| [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | +| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | | +| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | +| [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | +| [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | +| [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | | +| [SavedObjectsFindResponse](./kibana-plugin-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | +| [SavedObjectsImportConflictError](./kibana-plugin-server.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | +| [SavedObjectsImportError](./kibana-plugin-server.savedobjectsimporterror.md) | Represents a failure to import. | +| [SavedObjectsImportMissingReferencesError](./kibana-plugin-server.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | +| [SavedObjectsImportOptions](./kibana-plugin-server.savedobjectsimportoptions.md) | Options to control the import operation. | +| [SavedObjectsImportResponse](./kibana-plugin-server.savedobjectsimportresponse.md) | The response describing the result of an import. | +| [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | +| [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | +| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | +| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | | +| [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | | +| [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | +| [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | +| [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | +| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | +| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | +| [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | +| [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | +| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | +| [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | +| [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | +| [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | +| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | +| [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | + +## Variables + +| Variable | Description | +| --- | --- | +| [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | +| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-server.authtoolkit.md). | +| [AuthHeaders](./kibana-plugin-server.authheaders.md) | Auth Headers map | +| [AuthResult](./kibana-plugin-server.authresult.md) | | +| [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [ConfigPath](./kibana-plugin-server.configpath.md) | | +| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | +| [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | +| [GetAuthState](./kibana-plugin-server.getauthstate.md) | Get authentication state for a request. Returned by auth interceptor. | +| [HandlerContextType](./kibana-plugin-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md) to represent the type of the context. | +| [HandlerFunction](./kibana-plugin-server.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | +| [HandlerParameters](./kibana-plugin-server.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-server.handlercontexttype.md). | +| [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | +| [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | +| [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | +| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | +| [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | +| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | +| [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | +| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | +| [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | +| [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | +| [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | +| [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-server.migration_assistance_index_action.md) | | +| [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-server.migration_deprecation_level.md) | | +| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | +| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | +| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | +| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | +| [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. | +| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The 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. | +| [PluginOpaqueId](./kibana-plugin-server.pluginopaqueid.md) | | +| [RecursiveReadonly](./kibana-plugin-server.recursivereadonly.md) | | +| [RedirectResponseOptions](./kibana-plugin-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | +| [RequestHandler](./kibana-plugin-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) functions. | +| [RequestHandlerContextContainer](./kibana-plugin-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | +| [RequestHandlerContextProvider](./kibana-plugin-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | +| [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | +| [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | +| [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | +| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | +| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | +| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | +| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | +| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | +| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | +| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | +| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md index 9390686280a78..082de0a9b4aeb 100644 --- a/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md @@ -1,13 +1,13 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) - -## OnPreResponseHandler type - -See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). - -Signature: - -```typescript -export declare type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) + +## OnPreResponseHandler type + +See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). + +Signature: + +```typescript +export declare type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md index 9355731817409..5525f5bf60284 100644 --- a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md @@ -1,20 +1,20 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) - -## OnPreResponseToolkit interface - -A tool set defining an outcome of OnPreAuth interceptor for incoming request. - -Signature: - -```typescript -export interface OnPreResponseToolkit -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) | (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult | To pass request to the next handler | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) + +## OnPreResponseToolkit interface + +A tool set defining an outcome of OnPreAuth interceptor for incoming request. + +Signature: + +```typescript +export interface OnPreResponseToolkit +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) | (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult | To pass request to the next handler | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md index cb4d67646a604..bfb5827b16b2f 100644 --- a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md @@ -1,13 +1,13 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) > [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) - -## OnPreResponseToolkit.next property - -To pass request to the next handler - -Signature: - -```typescript -next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) > [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) + +## OnPreResponseToolkit.next property + +To pass request to the next handler + +Signature: + +```typescript +next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; +``` diff --git a/docs/development/core/server/kibana-plugin-server.responseextensions.headers.md b/docs/development/core/server/kibana-plugin-server.responseextensions.headers.md deleted file mode 100644 index 4975f8b65b66a..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.responseextensions.headers.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseExtensions](./kibana-plugin-server.responseextensions.md) > [headers](./kibana-plugin-server.responseextensions.headers.md) - -## ResponseExtensions.headers property - -Signature: - -```typescript -headers?: ResponseHeaders; -``` diff --git a/docs/development/core/server/kibana-plugin-server.responseextensions.md b/docs/development/core/server/kibana-plugin-server.responseextensions.md deleted file mode 100644 index 1988bb1ebec02..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.responseextensions.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseExtensions](./kibana-plugin-server.responseextensions.md) - -## ResponseExtensions interface - -Additional data to extend a response. - -Signature: - -```typescript -export interface ResponseExtensions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [headers](./kibana-plugin-server.responseextensions.headers.md) | ResponseHeaders | | - diff --git a/docs/development/core/server/kibana-plugin-server.responseinfo.md b/docs/development/core/server/kibana-plugin-server.responseinfo.md deleted file mode 100644 index 995834e331aab..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.responseinfo.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseInfo](./kibana-plugin-server.responseinfo.md) - -## ResponseInfo interface - -Response status code. - -Signature: - -```typescript -export interface ResponseInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [statusCode](./kibana-plugin-server.responseinfo.statuscode.md) | number | | - diff --git a/docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md b/docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md deleted file mode 100644 index 7ed8e7283f24a..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.responseinfo.statuscode.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ResponseInfo](./kibana-plugin-server.responseinfo.md) > [statusCode](./kibana-plugin-server.responseinfo.statuscode.md) - -## ResponseInfo.statusCode property - -Signature: - -```typescript -statusCode: number; -``` diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index cb0d77d233d84..c855e04e420f7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -108,10 +108,7 @@ import { PingParams } from 'elasticsearch'; import { PutScriptParams } from 'elasticsearch'; import { PutTemplateParams } from 'elasticsearch'; import { Readable } from 'stream'; -<<<<<<< HEAD -======= import { RecursiveReadonly as RecursiveReadonly_2 } from 'kibana/public'; ->>>>>>> master import { ReindexParams } from 'elasticsearch'; import { ReindexRethrottleParams } from 'elasticsearch'; import { RenderSearchTemplateParams } from 'elasticsearch'; @@ -980,16 +977,6 @@ export interface OnPreAuthToolkit { rewriteUrl: (url: string) => OnPreAuthResult; } -<<<<<<< HEAD -// Warning: (ae-forgotten-export) The symbol "OnPreResponseResult" needs to be exported by the entry point index.d.ts -// -// @public -export type OnPreResponseHandler = (request: KibanaRequest, response: ResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; - -// @public -export interface OnPreResponseToolkit { - next: (responseExtensions?: ResponseExtensions) => OnPreResponseResult; -======= // @public export interface OnPreResponseExtensions { headers?: ResponseHeaders; @@ -1009,7 +996,6 @@ export interface OnPreResponseInfo { // @public export interface OnPreResponseToolkit { next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; ->>>>>>> master } // @public (undocumented) @@ -1054,12 +1040,9 @@ export type PluginInitializer { // (undocumented) config: { -<<<<<<< HEAD -======= legacy: { globalConfig$: Observable; }; ->>>>>>> master create: () => Observable; createIfExists: () => Observable; }; @@ -1160,15 +1143,6 @@ export type ResponseError = string | Error | { export type ResponseErrorAttributes = Record; // @public -<<<<<<< HEAD -export interface ResponseExtensions { - // (undocumented) - headers?: ResponseHeaders; -} - -// @public -======= ->>>>>>> master export type ResponseHeaders = { [header in KnownHeaders]?: string | string[]; } & { @@ -1176,15 +1150,6 @@ export type ResponseHeaders = { }; // @public -<<<<<<< HEAD -export interface ResponseInfo { - // (undocumented) - statusCode: number; -} - -// @public -======= ->>>>>>> master export interface RouteConfig

| Type, Method extends RouteMethod> { options?: RouteConfigOptions; path: string; @@ -1656,25 +1621,15 @@ export interface SavedObjectsRawDoc { // @public (undocumented) export class SavedObjectsRepository { -<<<<<<< HEAD - // Warning: (ae-forgotten-export) The symbol "SavedObjectsRepositoryOptions" needs to be exported by the entry point index.d.ts - // - // @internal - constructor(options: SavedObjectsRepositoryOptions); -======= ->>>>>>> master bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; -<<<<<<< HEAD -======= // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "LegacyConfig" needs to be exported by the entry point index.d.ts // // @internal static createRepository(migrator: KibanaMigrator, schema: SavedObjectsSchema, config: LegacyConfig, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): any; ->>>>>>> master delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; // (undocumented) @@ -1713,11 +1668,6 @@ export class SavedObjectsSchema { constructor(schemaDefinition?: SavedObjectsSchemaDefinition); // (undocumented) getConvertToAliasScript(type: string): string | undefined; -<<<<<<< HEAD - // Warning: (ae-forgotten-export) The symbol "LegacyConfig" needs to be exported by the entry point index.d.ts - // -======= ->>>>>>> master // (undocumented) getIndexForType(config: LegacyConfig, type: string): string | undefined; // (undocumented) @@ -1835,9 +1785,6 @@ export const validBodyOutput: readonly ["data", "stream"]; // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts // src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts -<<<<<<< HEAD -======= // src/core/server/plugins/types.ts:228:15 - (ae-forgotten-export) The symbol "SharedGlobalConfig" needs to be exported by the entry point index.d.ts ->>>>>>> master ``` From 404582d4f6fd3d08ba20e93e8f3fa4e692ff7336 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Sat, 7 Dec 2019 11:40:26 +0100 Subject: [PATCH 18/19] Update x-pack/plugins/licensing/README.md Co-Authored-By: Josh Dover --- x-pack/plugins/licensing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/licensing/README.md b/x-pack/plugins/licensing/README.md index fe0c82790b820..339abb77ced88 100644 --- a/x-pack/plugins/licensing/README.md +++ b/x-pack/plugins/licensing/README.md @@ -67,7 +67,7 @@ class MyPlugin { deps.licensing.license$.subscribe(license => { const { state, message } = license.check('myPlugin', 'gold') const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; - if(hasRequiredLicense && license.getFeature('name').isAvailable){ + if (hasRequiredLicense && license.getFeature('name').isAvailable) { // enable some server side logic } else { log(message); From 51680549b80575b7f15c7c18c3b74b39cf6c6a2b Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 9 Dec 2019 09:22:33 +0100 Subject: [PATCH 19/19] update tests --- x-pack/test/licensing_plugin/apis/changes.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/test/licensing_plugin/apis/changes.ts b/x-pack/test/licensing_plugin/apis/changes.ts index 1c220b3abb507..cbff783a0633c 100644 --- a/x-pack/test/licensing_plugin/apis/changes.ts +++ b/x-pack/test/licensing_plugin/apis/changes.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; import { FtrProviderContext } from '../services'; -import { SecurityService } from '../../common/services'; import { PublicLicenseJSON } from '../../../plugins/licensing/server'; const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); @@ -13,7 +12,7 @@ const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); export default function({ getService, getPageObjects }: FtrProviderContext) { const supertest = getService('supertest'); const esSupertestWithoutAuth = getService('esSupertestWithoutAuth'); - const security: SecurityService = getService('security'); + const security = getService('security'); const PageObjects = getPageObjects(['common', 'security']); const testSubjects = getService('testSubjects');