From b681a1861b7c7e24e7a7ed469bf5ae90b2c6b2d1 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 9 Mar 2020 15:27:05 -0400 Subject: [PATCH] server code NP migration --- x-pack/legacy/plugins/painless_lab/index.ts | 9 +- .../plugins/painless_lab/public/register.tsx | 18 ++-- .../painless_lab/server/lib/check_license.ts | 46 ----------- .../server/lib/license_pre_routing_factory.ts | 24 ------ .../server/register_execute_route.ts | 31 ------- .../server/register_license_checker.ts | 21 ----- .../plugins/painless_lab/common/constants.ts | 19 +++++ x-pack/plugins/painless_lab/kibana.json | 15 ++++ x-pack/plugins/painless_lab/server/index.ts | 11 +++ .../plugins/painless_lab/server/lib/index.ts | 7 ++ .../painless_lab/server/lib/is_es_error.ts | 13 +++ x-pack/plugins/painless_lab/server/plugin.ts | 47 +++++++++++ .../painless_lab/server/routes/api/execute.ts | 47 +++++++++++ .../painless_lab/server/routes/api/index.ts | 7 ++ .../painless_lab/server/services/index.ts | 7 ++ .../painless_lab/server/services/license.ts | 82 +++++++++++++++++++ x-pack/plugins/painless_lab/server/types.ts | 19 +++++ 17 files changed, 284 insertions(+), 139 deletions(-) delete mode 100644 x-pack/legacy/plugins/painless_lab/server/lib/check_license.ts delete mode 100644 x-pack/legacy/plugins/painless_lab/server/lib/license_pre_routing_factory.ts delete mode 100644 x-pack/legacy/plugins/painless_lab/server/register_execute_route.ts delete mode 100644 x-pack/legacy/plugins/painless_lab/server/register_license_checker.ts create mode 100644 x-pack/plugins/painless_lab/common/constants.ts create mode 100644 x-pack/plugins/painless_lab/kibana.json create mode 100644 x-pack/plugins/painless_lab/server/index.ts create mode 100644 x-pack/plugins/painless_lab/server/lib/index.ts create mode 100644 x-pack/plugins/painless_lab/server/lib/is_es_error.ts create mode 100644 x-pack/plugins/painless_lab/server/plugin.ts create mode 100644 x-pack/plugins/painless_lab/server/routes/api/execute.ts create mode 100644 x-pack/plugins/painless_lab/server/routes/api/index.ts create mode 100644 x-pack/plugins/painless_lab/server/services/index.ts create mode 100644 x-pack/plugins/painless_lab/server/services/license.ts create mode 100644 x-pack/plugins/painless_lab/server/types.ts diff --git a/x-pack/legacy/plugins/painless_lab/index.ts b/x-pack/legacy/plugins/painless_lab/index.ts index 5372bbf37cf2e..a753fce92ec8e 100644 --- a/x-pack/legacy/plugins/painless_lab/index.ts +++ b/x-pack/legacy/plugins/painless_lab/index.ts @@ -6,10 +6,6 @@ import { resolve } from 'path'; import { PLUGIN_ID } from './common/constants'; -import { registerLicenseChecker } from './server/register_license_checker'; -import { registerExecuteRoute } from './server/register_execute_route'; -import { Legacy } from '../../../../kibana'; - export const painlessLab = (kibana: any) => new kibana.Plugin({ id: PLUGIN_ID, @@ -25,8 +21,5 @@ export const painlessLab = (kibana: any) => styleSheetPaths: resolve(__dirname, 'public/index.scss'), devTools: [resolve(__dirname, 'public/register')], }, - init: (server: Legacy.Server) => { - registerLicenseChecker(server); - registerExecuteRoute(server); - }, + init: () => {}, }); diff --git a/x-pack/legacy/plugins/painless_lab/public/register.tsx b/x-pack/legacy/plugins/painless_lab/public/register.tsx index 1f5446bd07c27..467f1b445d3d2 100644 --- a/x-pack/legacy/plugins/painless_lab/public/register.tsx +++ b/x-pack/legacy/plugins/painless_lab/public/register.tsx @@ -56,16 +56,16 @@ npSetup.plugins.devTools.register({ async mount(context, { element }) { registerPainless(); - const licenseCheck = { - showPage: xpackInfo.get('features.painlessLab.enableLink'), - message: xpackInfo.get('features.painlessLab.message'), - }; + // const licenseCheck = { + // showPage: xpackInfo.get('features.painlessLab.enableLink'), + // message: xpackInfo.get('features.painlessLab.message'), + // }; - if (!licenseCheck.showPage) { - npStart.core.notifications.toasts.addDanger(licenseCheck.message); - window.location.hash = '/dev_tools'; - return () => {}; - } + // if (!licenseCheck.showPage) { + // npStart.core.notifications.toasts.addDanger(licenseCheck.message); + // window.location.hash = '/dev_tools'; + // return () => {}; + // } const { renderApp } = await import('./render_app'); return renderApp(element, npStart.core); diff --git a/x-pack/legacy/plugins/painless_lab/server/lib/check_license.ts b/x-pack/legacy/plugins/painless_lab/server/lib/check_license.ts deleted file mode 100644 index 5cbed532ca56a..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/lib/check_license.ts +++ /dev/null @@ -1,46 +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 { i18n } from '@kbn/i18n'; - -export function checkLicense(xpackLicenseInfo: any) { - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable the Watcher UI - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - enableLink: false, - enableAPIRoute: false, - message: i18n.translate('xpack.painlessLab.unavailableLicenseInformationMessage', { - defaultMessage: - 'You cannot use the Painless Lab because license information is not available at this time.', - }), - }; - } - - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - - // License is not valid - if (!isLicenseActive) { - return { - enableLink: false, - enableAPIRoute: false, - message: i18n.translate('xpack.painlessLab.licenseHasExpiredMessage', { - defaultMessage: - 'You cannot use the Painless Lab because your {licenseType} license has expired.', - values: { - licenseType, - }, - }), - }; - } - - // License is valid and active - return { - enableLink: true, - enableAPIRoute: true, - }; -} diff --git a/x-pack/legacy/plugins/painless_lab/server/lib/license_pre_routing_factory.ts b/x-pack/legacy/plugins/painless_lab/server/lib/license_pre_routing_factory.ts deleted file mode 100644 index 387a263114a6e..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/lib/license_pre_routing_factory.ts +++ /dev/null @@ -1,24 +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 Boom from 'boom'; -import { PLUGIN_ID } from '../../common/constants'; -import { ServerFacade } from '../../../index_management'; - -export const licensePreRoutingFactory = (server: ServerFacade) => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN_ID).getLicenseCheckResults(); - if (!licenseCheckResults.enableAPIRoute) { - throw Boom.forbidden(licenseCheckResults.message); - } - - return null; - } - - return licensePreRouting; -}; diff --git a/x-pack/legacy/plugins/painless_lab/server/register_execute_route.ts b/x-pack/legacy/plugins/painless_lab/server/register_execute_route.ts deleted file mode 100644 index e4ffad9c21d60..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/register_execute_route.ts +++ /dev/null @@ -1,31 +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 { ServerRoute } from 'hapi'; -import { licensePreRoutingFactory } from './lib/license_pre_routing_factory'; -import { Legacy } from '../../../../../kibana'; -import { API_ROUTE_EXECUTE } from '../common/constants'; - -export function registerExecuteRoute(server: any) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: API_ROUTE_EXECUTE, - method: 'POST', - handler: (request: Legacy.Request) => { - const cluster = server.plugins.elasticsearch.getCluster('data'); - return cluster - .callWithRequest(request, 'scriptsPainlessExecute', { - body: request.payload, - }) - .catch((e: any) => { - return e.body; - }); - }, - config: { - pre: [licensePreRouting], - }, - } as ServerRoute); -} diff --git a/x-pack/legacy/plugins/painless_lab/server/register_license_checker.ts b/x-pack/legacy/plugins/painless_lab/server/register_license_checker.ts deleted file mode 100644 index 1ec5b33c4d9f9..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/register_license_checker.ts +++ /dev/null @@ -1,21 +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. - */ -// @ts-ignore -import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; -import { checkLicense } from './lib/check_license'; -import { PLUGIN_ID } from '../common/constants'; - -export function registerLicenseChecker(server: any) { - const xpackMainPlugin = server.plugins.xpack_main; - const plugin = server.plugins[PLUGIN_ID]; - - mirrorPluginStatus(xpackMainPlugin, plugin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN_ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/plugins/painless_lab/common/constants.ts b/x-pack/plugins/painless_lab/common/constants.ts new file mode 100644 index 0000000000000..2483c2aa7f99d --- /dev/null +++ b/x-pack/plugins/painless_lab/common/constants.ts @@ -0,0 +1,19 @@ +/* + * 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 { LicenseType } from '../../licensing/common/types'; + +const basicLicense: LicenseType = 'basic'; + +export const PLUGIN = { + id: 'painlessLab', + minimumLicenseType: basicLicense, + getI18nName: (i18n: any): string => + i18n.translate('xpack.painlessLab.appTitle', { + defaultMessage: 'Painless Lab', + }), +}; + +export const API_BASE_PATH = '/api/painless_lab'; diff --git a/x-pack/plugins/painless_lab/kibana.json b/x-pack/plugins/painless_lab/kibana.json new file mode 100644 index 0000000000000..291d336869150 --- /dev/null +++ b/x-pack/plugins/painless_lab/kibana.json @@ -0,0 +1,15 @@ +{ + "id": "painlessLab", + "version": "8.0.0", + "kibanaVersion": "kibana", + "requiredPlugins": [ + "devTools", + "licensing" + ], + "configPath": [ + "xpack", + "painless_lab" + ], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/painless_lab/server/index.ts b/x-pack/plugins/painless_lab/server/index.ts new file mode 100644 index 0000000000000..96ea9a163deca --- /dev/null +++ b/x-pack/plugins/painless_lab/server/index.ts @@ -0,0 +1,11 @@ +/* + * 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 { PluginInitializerContext } from 'kibana/server'; +import { PainlessLabServerPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => { + return new PainlessLabServerPlugin(ctx); +}; diff --git a/x-pack/plugins/painless_lab/server/lib/index.ts b/x-pack/plugins/painless_lab/server/lib/index.ts new file mode 100644 index 0000000000000..a9a3c61472d8c --- /dev/null +++ b/x-pack/plugins/painless_lab/server/lib/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { isEsError } from './is_es_error'; diff --git a/x-pack/plugins/painless_lab/server/lib/is_es_error.ts b/x-pack/plugins/painless_lab/server/lib/is_es_error.ts new file mode 100644 index 0000000000000..4137293cf39c0 --- /dev/null +++ b/x-pack/plugins/painless_lab/server/lib/is_es_error.ts @@ -0,0 +1,13 @@ +/* + * 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 * as legacyElasticsearch from 'elasticsearch'; + +const esErrorsParent = legacyElasticsearch.errors._Abstract; + +export function isEsError(err: Error) { + return err instanceof esErrorsParent; +} diff --git a/x-pack/plugins/painless_lab/server/plugin.ts b/x-pack/plugins/painless_lab/server/plugin.ts new file mode 100644 index 0000000000000..74629a0b035ed --- /dev/null +++ b/x-pack/plugins/painless_lab/server/plugin.ts @@ -0,0 +1,47 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; + +import { PLUGIN } from '../common/constants'; +import { License } from './services'; +import { Dependencies } from './types'; +import { registerExecuteRoute } from './routes/api'; + +export class PainlessLabServerPlugin implements Plugin { + private readonly license: License; + private readonly logger: Logger; + + constructor({ logger }: PluginInitializerContext) { + this.logger = logger.get(); + this.license = new License(); + } + + async setup({ http }: CoreSetup, { licensing }: Dependencies) { + const router = http.createRouter(); + + this.license.setup( + { + pluginId: PLUGIN.id, + minimumLicenseType: PLUGIN.minimumLicenseType, + defaultErrorMessage: i18n.translate('xpack.painlessLab.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + }, + { + licensing, + logger: this.logger, + } + ); + + registerExecuteRoute({ router, license: this.license }); + } + + start() {} + + stop() {} +} diff --git a/x-pack/plugins/painless_lab/server/routes/api/execute.ts b/x-pack/plugins/painless_lab/server/routes/api/execute.ts new file mode 100644 index 0000000000000..8cd1e023f72f9 --- /dev/null +++ b/x-pack/plugins/painless_lab/server/routes/api/execute.ts @@ -0,0 +1,47 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../types'; +import { API_BASE_PATH } from '../../../common/constants'; +import { isEsError } from '../../lib'; + +const bodySchema = schema.object({ + script: schema.object({ + source: schema.string(), + }), +}); + +export function registerExecuteRoute({ router, license }: RouteDependencies) { + router.post( + { + path: `${API_BASE_PATH}/execute`, + validate: { + body: bodySchema, + }, + }, + license.guardApiRoute(async (ctx, req, res) => { + const body = req.body as typeof bodySchema.type; + + try { + const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + + const response = await callAsCurrentUser('scriptsPainlessExecute', { + body, + }); + + return res.ok({ + body: response, + }); + } catch (e) { + if (isEsError(e)) { + return res.customError({ statusCode: e.statusCode, body: e }); + } + return res.internalError({ body: e }); + } + }) + ); +} diff --git a/x-pack/plugins/painless_lab/server/routes/api/index.ts b/x-pack/plugins/painless_lab/server/routes/api/index.ts new file mode 100644 index 0000000000000..62f05971d59cc --- /dev/null +++ b/x-pack/plugins/painless_lab/server/routes/api/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { registerExecuteRoute } from './execute'; diff --git a/x-pack/plugins/painless_lab/server/services/index.ts b/x-pack/plugins/painless_lab/server/services/index.ts new file mode 100644 index 0000000000000..b7a45e59549eb --- /dev/null +++ b/x-pack/plugins/painless_lab/server/services/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { License } from './license'; diff --git a/x-pack/plugins/painless_lab/server/services/license.ts b/x-pack/plugins/painless_lab/server/services/license.ts new file mode 100644 index 0000000000000..c490aa7b57ed9 --- /dev/null +++ b/x-pack/plugins/painless_lab/server/services/license.ts @@ -0,0 +1,82 @@ +/* + * 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 { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'kibana/server'; + +import { LicensingPluginSetup } from '../../../licensing/server'; +import { LicenseType, LICENSE_CHECK_STATE } from '../../../licensing/common/types'; + +export interface LicenseStatus { + isValid: boolean; + message?: string; +} + +interface SetupSettings { + pluginId: string; + minimumLicenseType: LicenseType; + defaultErrorMessage: string; +} + +export class License { + private licenseStatus: LicenseStatus = { + isValid: false, + message: 'Invalid License', + }; + + setup( + { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, + { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } + ) { + licensing.license$.subscribe(license => { + const { state, message } = license.check(pluginId, minimumLicenseType); + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + + if (hasRequiredLicense) { + this.licenseStatus = { isValid: true }; + } else { + this.licenseStatus = { + isValid: false, + message: message || defaultErrorMessage, + }; + if (message) { + logger.info(message); + } + } + }); + } + + guardApiRoute(handler: RequestHandler) { + const license = this; + + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseStatus = license.getStatus(); + + if (!licenseStatus.isValid) { + return response.customError({ + body: { + message: licenseStatus.message || '', + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; + } + + getStatus() { + return this.licenseStatus; + } +} diff --git a/x-pack/plugins/painless_lab/server/types.ts b/x-pack/plugins/painless_lab/server/types.ts new file mode 100644 index 0000000000000..dba0dda0d5cbd --- /dev/null +++ b/x-pack/plugins/painless_lab/server/types.ts @@ -0,0 +1,19 @@ +/* + * 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 { ScopedClusterClient, IRouter } from 'src/core/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { License } from './services'; + +export interface RouteDependencies { + router: IRouter; + license: License; +} + +export interface Dependencies { + licensing: LicensingPluginSetup; +} + +export type CallAsCurrentUser = ScopedClusterClient['callAsCurrentUser'];