From 004dd70a3b6a13840d473671e0c2f3dd6c5ef6fe Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Fri, 8 May 2020 13:33:34 -0700 Subject: [PATCH 01/31] WIP: Move routes to new API, license and other checks inbound --- .../legacy/plugins/reporting/server/core.ts | 7 +- .../reporting/server/lib/enqueue_job.ts | 5 +- .../plugins/reporting/server/lib/get_user.ts | 8 +- .../reporting/server/lib/jobs_query.ts | 22 +- .../legacy/plugins/reporting/server/plugin.ts | 3 + .../server/routes/generate_from_jobparams.ts | 92 ++--- .../routes/generate_from_savedobject.ts | 51 ++- .../generate_from_savedobject_immediate.ts | 65 ++-- .../reporting/server/routes/generation.ts | 55 +-- .../plugins/reporting/server/routes/index.ts | 10 +- .../plugins/reporting/server/routes/jobs.ts | 335 +++++++++--------- .../routes/lib/authorized_user_pre_routing.ts | 26 +- .../server/routes/lib/get_document_payload.ts | 6 +- .../server/routes/lib/job_response_handler.ts | 56 ++- .../routes/lib/make_request_facade.test.ts | 23 +- .../server/routes/lib/make_request_facade.ts | 33 +- .../reporting/server/routes/types.d.ts | 8 +- .../plugins/reporting/server/types.d.ts | 8 +- x-pack/legacy/plugins/reporting/types.d.ts | 25 +- 19 files changed, 414 insertions(+), 424 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index 0b243f13adb80..e5e27966255d6 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -13,6 +13,8 @@ import { SavedObjectsClient, SavedObjectsServiceStart, UiSettingsServiceStart, + IRouter, + IBasePath, } from 'src/core/server'; // @ts-ignore no module definition import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; @@ -67,9 +69,10 @@ export class ReportingCore { // to re-compute the license check results for this plugin xpackMainPlugin.info.feature(PLUGIN_ID).registerLicenseCheckResultsGenerator(checkLicense); }); + } - // legacy routes - registerRoutes(this, __LEGACY, plugins, this.logger); + public setupRoutes(plugins: ReportingSetupDeps, router: IRouter, basePath: IBasePath['get']) { + registerRoutes(this, plugins, router, basePath, this.logger); } public pluginSetup(reportingSetupDeps: ReportingInternalSetup) { diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 3e87337dc4355..b26033badf328 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import { ConditionalHeaders, EnqueueJobFn, @@ -36,7 +35,7 @@ export function enqueueJobFactory(reporting: ReportingCore, parentLogger: Logger return async function enqueueJob( exportTypeId: string, jobParams: JobParamsType, - user: string, + username: string, headers: ConditionalHeaders['headers'], request: RequestFacade ): Promise { @@ -54,7 +53,7 @@ export function enqueueJobFactory(reporting: ReportingCore, parentLogger: Logger const options = { timeout: queueTimeout, - created_by: get(user, 'username', false), + created_by: username, browser_type: browserType, max_attempts: maxAttempts, }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/get_user.ts b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts index 5e73fe77ecb79..f307148533fe2 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/get_user.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts @@ -4,16 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { KibanaRequest } from '../../../../../../src/core/server'; import { Logger } from '../../types'; import { ReportingSetupDeps } from '../types'; export function getUserFactory(security: ReportingSetupDeps['security'], logger: Logger) { - /* - * Legacy.Request because this is called from routing middleware - */ - return async (request: Legacy.Request) => { - return security?.authc.getCurrentUser(KibanaRequest.from(request)) ?? null; + return (request: KibanaRequest) => { + return security?.authc.getCurrentUser(request) ?? null; }; } diff --git a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts index 0affc111c1368..58de35f2763a6 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts @@ -47,10 +47,6 @@ export function jobsQueryFactory( const index = config.get('index'); const { callAsInternalUser } = elasticsearch.adminClient; - function getUsername(user: any) { - return get(user, 'username', false); - } - function execQuery(queryType: string, body: QueryBody) { const defaultBody: Record = { search: { @@ -82,9 +78,13 @@ export function jobsQueryFactory( } return { - list(jobTypes: string[], user: any, page = 0, size = defaultSize, jobIds: string[] | null) { - const username = getUsername(user); - + list( + jobTypes: string[], + username: string, + page = 0, + size = defaultSize, + jobIds: string[] | null + ) { const body: QueryBody = { size, from: size * page, @@ -108,9 +108,7 @@ export function jobsQueryFactory( return getHits(execQuery('search', body)); }, - count(jobTypes: string[], user: any) { - const username = getUsername(user); - + count(jobTypes: string[], username: string) { const body: QueryBody = { query: { constant_score: { @@ -129,11 +127,9 @@ export function jobsQueryFactory( }); }, - get(user: any, id: string, opts: GetOpts = {}): Promise | void> { + get(username: string, id: string, opts: GetOpts = {}): Promise | void> { if (!id) return Promise.resolve(); - const username = getUsername(user); - const body: QueryBody = { query: { constant_score: { diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index e0fa99106a93e..fbe456442c840 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -27,12 +27,15 @@ export class ReportingPlugin public async setup(core: CoreSetup, plugins: ReportingSetupDeps) { const { config } = this; const { elasticsearch, __LEGACY } = plugins; + const router = core.http.createRouter(); + const basePath = core.http.basePath.get; const browserDriverFactory = await createBrowserDriverFactory(config, this.logger); // required for validations :( runValidations(config, elasticsearch, browserDriverFactory, this.logger); const { xpack_main: xpackMainLegacy, reporting: reportingLegacy } = __LEGACY.plugins; this.reportingCore.legacySetup(xpackMainLegacy, reportingLegacy, __LEGACY, plugins); + this.reportingCore.setupRoutes(plugins, router, basePath); // Register a function with server to manage the collection of usage stats registerReportingUsageCollector(this.reportingCore, plugins); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 6b4f5dbd9203a..f12b4d323ae76 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -5,70 +5,48 @@ */ import boom from 'boom'; -import Joi from 'joi'; -import { Legacy } from 'kibana'; import rison from 'rison-node'; +import { IRouter, IBasePath } from 'src/core/server'; +import { schema } from '@kbn/config-schema'; import { API_BASE_URL } from '../../common/constants'; -import { Logger, ReportingResponseToolkit, ServerFacade } from '../../types'; +import { Logger } from '../../types'; import { ReportingCore, ReportingSetupDeps } from '../types'; import { makeRequestFacade } from './lib/make_request_facade'; -import { - GetRouteConfigFactoryFn, - getRouteConfigFactoryReportingPre, - RouteConfigFactory, -} from './lib/route_config_factories'; import { HandlerErrorFunction, HandlerFunction } from './types'; const BASE_GENERATE = `${API_BASE_URL}/generate`; export function registerGenerateFromJobParams( reporting: ReportingCore, - server: ServerFacade, plugins: ReportingSetupDeps, + router: IRouter, + basePath: IBasePath['get'], handler: HandlerFunction, handleError: HandlerErrorFunction, logger: Logger ) { - const config = reporting.getConfig(); - const getRouteConfig = () => { - const getOriginalRouteConfig: GetRouteConfigFactoryFn = getRouteConfigFactoryReportingPre( - config, - plugins, - logger - ); - const routeConfigFactory: RouteConfigFactory = getOriginalRouteConfig( - ({ params: { exportType } }) => exportType - ); - - return { - ...routeConfigFactory, + // generate report + router.post( + { + path: `${BASE_GENERATE}/{exportType}`, validate: { - params: Joi.object({ - exportType: Joi.string().required(), - }).required(), - payload: Joi.object({ - jobParams: Joi.string() - .optional() - .default(null), - }).allow(null), // allow optional payload - query: Joi.object({ - jobParams: Joi.string().default(null), - }).default(), + params: schema.object({ + exportType: schema.string({ minLength: 2 }), + }), + body: schema.object({ + jobParams: schema.string(), + }), + query: schema.object({ + jobParams: schema.string(), + }), }, - }; - }; - - // generate report - server.route({ - path: `${BASE_GENERATE}/{exportType}`, - method: 'POST', - options: getRouteConfig(), - handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { - const request = makeRequestFacade(legacyRequest); + }, + router.handleLegacyErrors(async (context, req, res) => { + const request = makeRequestFacade(context, req, basePath); let jobParamsRison: string | null; - if (request.payload) { - const { jobParams: jobParamsPayload } = request.payload as { jobParams: string }; + if (request.body) { + const { jobParams: jobParamsPayload } = request.body as { jobParams: string }; jobParamsRison = jobParamsPayload; } else { const { jobParams: queryJobParams } = request.query as { jobParams: string }; @@ -83,7 +61,7 @@ export function registerGenerateFromJobParams( throw boom.badRequest('A jobParams RISON string is required'); } - const { exportType } = request.params; + const { exportType } = request.params as { exportType: string }; let jobParams; let response; try { @@ -95,22 +73,22 @@ export function registerGenerateFromJobParams( throw boom.badRequest(`invalid rison: ${jobParamsRison}`); } try { - response = await handler(exportType, jobParams, legacyRequest, h); + response = await handler(exportType, jobParams, request, res); } catch (err) { throw handleError(exportType, err); } return response; - }, - }); + }) + ); // Get route to generation endpoint: show error about GET method to user - server.route({ - path: `${BASE_GENERATE}/{p*}`, - method: 'GET', - handler: () => { - const err = boom.methodNotAllowed('GET is not allowed'); - err.output.headers.allow = 'POST'; - return err; + router.get( + { + path: `${BASE_GENERATE}/{p*}`, + validate: false, }, - }); + router.handleLegacyErrors(() => { + throw boom.methodNotAllowed('GET is not allowed'); + }) + ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 830953d532243..9f08b6ccd2bf1 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; +import { schema } from '@kbn/config-schema'; import { get } from 'lodash'; +import { IRouter, IBasePath } from 'src/core/server'; import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../common/constants'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; -import { Logger, ReportingResponseToolkit, ServerFacade } from '../../types'; +import { Logger } from '../../types'; import { ReportingCore, ReportingSetupDeps } from '../types'; import { makeRequestFacade } from './lib/make_request_facade'; -import { getRouteOptionsCsv } from './lib/route_config_factories'; import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types'; /* @@ -25,21 +25,33 @@ import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types */ export function registerGenerateCsvFromSavedObject( reporting: ReportingCore, - server: ServerFacade, plugins: ReportingSetupDeps, + router: IRouter, + basePath: IBasePath['get'], handleRoute: HandlerFunction, handleRouteError: HandlerErrorFunction, logger: Logger ) { - const config = reporting.getConfig(); - const routeOptions = getRouteOptionsCsv(config, plugins, logger); - - server.route({ - path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, - method: 'POST', - options: routeOptions, - handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { - const requestFacade = makeRequestFacade(legacyRequest); + router.post( + { + path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, + validate: { + params: schema.object({ + savedObjectType: schema.string({ minLength: 2 }), + savedObjectId: schema.string({ minLength: 2 }), + }), + body: schema.object({ + state: schema.object({}), + timerange: schema.object({ + timezone: schema.string({ defaultValue: 'UTC' }), + min: schema.duration(), + max: schema.duration(), + }), + }), + }, + }, + router.handleLegacyErrors(async (context, req, res) => { + const requestFacade = makeRequestFacade(context, req, basePath); /* * 1. Build `jobParams` object: job data that execution will need to reference in various parts of the lifecycle @@ -49,7 +61,7 @@ export function registerGenerateCsvFromSavedObject( let result: QueuedJobPayload; try { const jobParams = getJobParamsFromRequest(requestFacade, { isImmediate: false }); - result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, legacyRequest, h); // pass the original request because the handler will make the request facade on its own + result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, requestFacade, res); } catch (err) { throw handleRouteError(CSV_FROM_SAVEDOBJECT_JOB_TYPE, err); } @@ -60,7 +72,12 @@ export function registerGenerateCsvFromSavedObject( ); } - return result; - }, - }); + return res.ok({ + body: result, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 519e49f56c377..1feff024ca042 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -4,21 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; +import { IRouter, IBasePath } from 'src/core/server'; +import { schema } from '@kbn/config-schema'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; import { createJobFactory, executeJobFactory } from '../../export_types/csv_from_savedobject'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types'; -import { - JobDocOutput, - Logger, - ReportingResponseToolkit, - ResponseFacade, - ServerFacade, -} from '../../types'; +import { JobDocOutput, Logger } from '../../types'; import { ReportingCore, ReportingSetupDeps } from '../types'; import { makeRequestFacade } from './lib/make_request_facade'; -import { getRouteOptionsCsv } from './lib/route_config_factories'; /* * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: @@ -31,24 +25,36 @@ import { getRouteOptionsCsv } from './lib/route_config_factories'; */ export function registerGenerateCsvFromSavedObjectImmediate( reporting: ReportingCore, - server: ServerFacade, plugins: ReportingSetupDeps, + router: IRouter, + basePath: IBasePath['get'], parentLogger: Logger ) { - const config = reporting.getConfig(); - const routeOptions = getRouteOptionsCsv(config, plugins, parentLogger); - /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: * - re-use the createJob function to build up es query config * - re-use the executeJob function to run the scan and scroll queries and capture the entire CSV in a result object. */ - server.route({ - path: `${API_BASE_GENERATE_V1}/immediate/csv/saved-object/{savedObjectType}:{savedObjectId}`, - method: 'POST', - options: routeOptions, - handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { - const request = makeRequestFacade(legacyRequest); + router.post( + { + path: `${API_BASE_GENERATE_V1}/immediate/csv/saved-object/{savedObjectType}:{savedObjectId}`, + validate: { + params: schema.object({ + savedObjectType: schema.string({ minLength: 5 }), + savedObjectId: schema.string({ minLength: 5 }), + }), + body: schema.object({ + state: schema.object({}), + timerange: schema.object({ + timezone: schema.string({ defaultValue: 'UTC' }), + min: schema.duration(), + max: schema.duration(), + }), + }), + }, + }, + router.handleLegacyErrors(async (context, req, res) => { + const request = makeRequestFacade(context, req, basePath); const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(request, { isImmediate: true }); const createJobFn = createJobFactory(reporting, logger); @@ -75,17 +81,14 @@ export function registerGenerateCsvFromSavedObjectImmediate( if (jobOutputContent === null) { logger.warn('CSV Job Execution created empty content result'); } - const response = h - .response(jobOutputContent ? jobOutputContent : undefined) - .type(jobOutputContentType); - // Set header for buffer download, not streaming - const { isBoom } = response as ResponseFacade; - if (isBoom == null) { - response.header('accept-ranges', 'none'); - } - - return response; - }, - }); + return res.ok({ + body: jobOutputContent || '', + headers: { + 'content-type': jobOutputContentType, + 'accept-ranges': 'none', + }, + }); + }) + ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 1c6129313db4b..1d291b6ce18e1 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -6,21 +6,22 @@ import boom from 'boom'; import { errors as elasticsearchErrors } from 'elasticsearch'; -import { Legacy } from 'kibana'; +import { IRouter, IBasePath, kibanaResponseFactory } from 'src/core/server'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { API_BASE_URL } from '../../common/constants'; -import { Logger, ReportingResponseToolkit, ServerFacade } from '../../types'; +import { Logger, RequestFacade } from '../../types'; import { ReportingCore, ReportingSetupDeps } from '../types'; import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; -import { makeRequestFacade } from './lib/make_request_facade'; const esErrors = elasticsearchErrors as Record; export function registerJobGenerationRoutes( reporting: ReportingCore, - server: ServerFacade, plugins: ReportingSetupDeps, + router: IRouter, + basePath: IBasePath['get'], logger: Logger ) { const config = reporting.getConfig(); @@ -33,45 +34,57 @@ export function registerJobGenerationRoutes( async function handler( exportTypeId: string, jobParams: object, - legacyRequest: Legacy.Request, - h: ReportingResponseToolkit + r: RequestFacade, + h: typeof kibanaResponseFactory ) { - const request = makeRequestFacade(legacyRequest); - const user = request.pre.user; - const headers = request.headers; + const getUser = authorizedUserPreRoutingFactory(config, plugins, logger); + const { username } = getUser(r.getRawRequest()); + const { headers } = r; const enqueueJob = await reporting.getEnqueueJob(); - const job = await enqueueJob(exportTypeId, jobParams, user, headers, request); + const job = await enqueueJob(exportTypeId, jobParams, username, headers, r); // return the queue's job information const jobJson = job.toJSON(); - return h - .response({ + return h.ok({ + headers: { + 'content-type': 'application/json', + }, + body: { path: `${downloadBaseUrl}/${jobJson.id}`, job: jobJson, - }) - .type('application/json'); + }, + }); } function handleError(exportTypeId: string, err: Error) { if (err instanceof esErrors['401']) { - return boom.unauthorized(`Sorry, you aren't authenticated`); + throw boom.unauthorized(`Sorry, you aren't authenticated`); } if (err instanceof esErrors['403']) { - return boom.forbidden(`Sorry, you are not authorized to create ${exportTypeId} reports`); + throw boom.forbidden(`Sorry, you are not authorized to create ${exportTypeId} reports`); } if (err instanceof esErrors['404']) { - return boom.boomify(err, { statusCode: 404 }); + throw boom.boomify(err, { statusCode: 404 }); } - return err; + throw err; } - registerGenerateFromJobParams(reporting, server, plugins, handler, handleError, logger); + registerGenerateFromJobParams(reporting, plugins, router, basePath, handler, handleError, logger); // Register beta panel-action download-related API's if (config.get('csv', 'enablePanelActionDownload')) { - registerGenerateCsvFromSavedObject(reporting, server, plugins, handler, handleError, logger); - registerGenerateCsvFromSavedObjectImmediate(reporting, server, plugins, logger); + registerGenerateCsvFromSavedObject( + reporting, + plugins, + router, + basePath, + handler, + handleError, + logger + ); + + registerGenerateCsvFromSavedObjectImmediate(reporting, plugins, router, basePath, logger); } } diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index 610ab4907d369..b25cf02293b2b 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -4,17 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, ServerFacade } from '../../types'; +import { IRouter, IBasePath } from 'src/core/server'; +import { Logger } from '../../types'; import { ReportingCore, ReportingSetupDeps } from '../types'; import { registerJobGenerationRoutes } from './generation'; import { registerJobInfoRoutes } from './jobs'; export function registerRoutes( reporting: ReportingCore, - server: ServerFacade, plugins: ReportingSetupDeps, + router: IRouter, + basePath: IBasePath['get'], logger: Logger ) { - registerJobGenerationRoutes(reporting, server, plugins, logger); - registerJobInfoRoutes(reporting, server, plugins, logger); + registerJobGenerationRoutes(reporting, plugins, router, basePath, logger); + registerJobInfoRoutes(reporting, plugins, router, basePath, logger); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index f6f98b2377db6..7a9725635e437 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -5,17 +5,10 @@ */ import Boom from 'boom'; -import { ResponseObject } from 'hapi'; -import { Legacy } from 'kibana'; +import { IRouter, IBasePath } from 'src/core/server'; +import { schema } from '@kbn/config-schema'; import { API_BASE_URL } from '../../common/constants'; -import { - JobDocOutput, - JobSource, - ListQuery, - Logger, - ReportingResponseToolkit, - ServerFacade, -} from '../../types'; +import { ListQuery, Logger } from '../../types'; import { jobsQueryFactory } from '../lib/jobs_query'; import { ReportingCore, ReportingSetupDeps } from '../types'; import { @@ -23,201 +16,209 @@ import { downloadJobResponseHandlerFactory, } from './lib/job_response_handler'; import { makeRequestFacade } from './lib/make_request_facade'; -import { - getRouteConfigFactoryDeletePre, - getRouteConfigFactoryDownloadPre, - getRouteConfigFactoryManagementPre, -} from './lib/route_config_factories'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -function isResponse(response: Boom | ResponseObject): response is ResponseObject { - return !(response as Boom).isBoom; -} - export function registerJobInfoRoutes( reporting: ReportingCore, - server: ServerFacade, plugins: ReportingSetupDeps, + router: IRouter, + basePath: IBasePath['get'], logger: Logger ) { const config = reporting.getConfig(); + const getUser = authorizedUserPreRoutingFactory(config, plugins, logger); const { elasticsearch } = plugins; const jobsQuery = jobsQueryFactory(config, elasticsearch); - const getRouteConfig = getRouteConfigFactoryManagementPre(config, plugins, logger); // list jobs in the queue, paginated - server.route({ - path: `${MAIN_ENTRY}/list`, - method: 'GET', - options: getRouteConfig(), - handler: (legacyRequest: Legacy.Request) => { - const request = makeRequestFacade(legacyRequest); + router.get( + { + path: `${MAIN_ENTRY}/list`, + validate: {}, + }, + router.handleLegacyErrors(async (context, req, res) => { + const request = makeRequestFacade(context, req, basePath); + const { username } = getUser(request.getRawRequest()); const { page: queryPage, size: querySize, ids: queryIds } = request.query as ListQuery; const page = parseInt(queryPage, 10) || 0; const size = Math.min(100, parseInt(querySize, 10) || 10); const jobIds = queryIds ? queryIds.split(',') : null; - const results = jobsQuery.list( - request.pre.management.jobTypes, - request.pre.user, - page, - size, - jobIds - ); - return results; - }, - }); + // @todo: fill in jobtypes from license checks + const results = await jobsQuery.list([], username, page, size, jobIds); + + return res.ok({ + body: results, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); // return the count of all jobs in the queue - server.route({ - path: `${MAIN_ENTRY}/count`, - method: 'GET', - options: getRouteConfig(), - handler: (legacyRequest: Legacy.Request) => { - const request = makeRequestFacade(legacyRequest); - const results = jobsQuery.count(request.pre.management.jobTypes, request.pre.user); - return results; + router.get( + { + path: `${MAIN_ENTRY}/count`, + validate: {}, }, - }); + router.handleLegacyErrors(async (context, req, res) => { + const request = makeRequestFacade(context, req, basePath); + const { username } = getUser(request.getRawRequest()); + + // @todo: fill in jobtypes from license checks + const results = await jobsQuery.count([], username); + + return res.ok({ + body: { count: results }, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); // return the raw output from a job - server.route({ - path: `${MAIN_ENTRY}/output/{docId}`, - method: 'GET', - options: getRouteConfig(), - handler: (legacyRequest: Legacy.Request) => { - const request = makeRequestFacade(legacyRequest); - const { docId } = request.params; - - return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then( - (result): JobDocOutput => { - if (!result) { - throw Boom.notFound(); - } - const { - _source: { jobtype: jobType, output: jobOutput }, - } = result; - - if (!request.pre.management.jobTypes.includes(jobType)) { - throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); - } - - return jobOutput; - } - ); + router.get( + { + path: `${MAIN_ENTRY}/output/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 2 }), + }), + }, }, - }); + router.handleLegacyErrors(async (context, req, res) => { + const request = makeRequestFacade(context, req, basePath); + const { username } = getUser(request.getRawRequest()); + const { docId } = req.params; + + const result = await jobsQuery.get(username, docId, { includeContent: true }); + + if (!result) { + throw Boom.notFound(); + } + + const { + _source: { jobtype: jobType, output: jobOutput }, + } = result; + + if (!['@todo'].includes(jobType)) { + throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); + } + + return res.ok({ + body: jobOutput, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); // return some info about the job - server.route({ - path: `${MAIN_ENTRY}/info/{docId}`, - method: 'GET', - options: getRouteConfig(), - handler: (legacyRequest: Legacy.Request) => { - const request = makeRequestFacade(legacyRequest); - const { docId } = request.params; - - return jobsQuery.get(request.pre.user, docId).then((result): JobSource['_source'] => { - if (!result) { - throw Boom.notFound(); - } - - const { _source: job } = result; - const { jobtype: jobType, payload: jobPayload } = job; - if (!request.pre.management.jobTypes.includes(jobType)) { - throw Boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); - } - - return { + router.get( + { + path: `${MAIN_ENTRY}/info/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 2 }), + }), + }, + }, + router.handleLegacyErrors(async (context, req, res) => { + const request = makeRequestFacade(context, req, basePath); + const { username } = getUser(request.getRawRequest()); + const { docId } = req.params; + + const result = await jobsQuery.get(username, docId); + + if (!result) { + throw Boom.notFound(); + } + + const { _source: job } = result; + const { jobtype: jobType, payload: jobPayload } = job; + + if (!['@todo'].includes(jobType)) { + throw Boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); + } + + return res.ok({ + body: { ...job, payload: { ...jobPayload, headers: undefined, }, - }; + }, + headers: { + 'content-type': 'application/json', + }, }); - }, - }); + }) + ); // trigger a download of the output from a job const exportTypesRegistry = reporting.getExportTypesRegistry(); - const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(config, plugins, logger); - const downloadResponseHandler = downloadJobResponseHandlerFactory(config, elasticsearch, exportTypesRegistry); // prettier-ignore - server.route({ - path: `${MAIN_ENTRY}/download/{docId}`, - method: 'GET', - options: getRouteConfigDownload(), - handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { - const request = makeRequestFacade(legacyRequest); - const { docId } = request.params; - - let response = await downloadResponseHandler( - request.pre.management.jobTypes, - request.pre.user, - h, - { docId } - ); - - if (isResponse(response)) { - const { statusCode } = response; - - if (statusCode !== 200) { - if (statusCode === 500) { - logger.error(`Report ${docId} has failed: ${JSON.stringify(response.source)}`); - } else { - logger.debug( - `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify( - response.source - )}]` - ); - } - } - - response = response.header('accept-ranges', 'none'); - } - - return response; + const downloadResponseHandler = downloadJobResponseHandlerFactory( + config, + elasticsearch, + exportTypesRegistry + ); + + router.get( + { + path: `${MAIN_ENTRY}/download/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 3 }), + }), + }, }, - }); + router.handleLegacyErrors(async (context, req, res) => { + const { username } = getUser(req); + const { docId } = req.params; + + // @TODD: JobTypes checks + const response = await downloadResponseHandler([], username, { docId }); + + return res.ok({ + body: response.content, + headers: { + ...response.headers, + 'content-type': response.contentType, + }, + }); + }) + ); // allow a report to be deleted - const getRouteConfigDelete = getRouteConfigFactoryDeletePre(config, plugins, logger); const deleteResponseHandler = deleteJobResponseHandlerFactory(config, elasticsearch); - server.route({ - path: `${MAIN_ENTRY}/delete/{docId}`, - method: 'DELETE', - options: getRouteConfigDelete(), - handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { - const request = makeRequestFacade(legacyRequest); - const { docId } = request.params; - - let response = await deleteResponseHandler( - request.pre.management.jobTypes, - request.pre.user, - h, - { docId } - ); - - if (isResponse(response)) { - const { statusCode } = response; - - if (statusCode !== 200) { - if (statusCode === 500) { - logger.error(`Report ${docId} has failed: ${JSON.stringify(response.source)}`); - } else { - logger.debug( - `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify( - response.source - )}]` - ); - } - } - - response = response.header('accept-ranges', 'none'); - } - - return response; + router.delete( + { + path: `${MAIN_ENTRY}/delete/{docId}`, + validate: { + params: schema.object({ + docId: schema.string({ minLength: 3 }), + }), + }, }, - }); + router.handleLegacyErrors(async (context, req, res) => { + const { username } = getUser(req); + const { docId } = req.params; + + // @TODD Jobtypes here. + const response = await deleteResponseHandler([], username, { docId }); + + return res.ok({ + body: response, + headers: { + 'content-type': 'application/json', + }, + }); + }) + ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 1ca28ca62a7f2..ef57f149560fa 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -5,17 +5,16 @@ */ import Boom from 'boom'; -import { Legacy } from 'kibana'; +import { KibanaRequest } from 'src/core/server'; import { AuthenticatedUser } from '../../../../../../plugins/security/server'; import { ReportingConfig } from '../../../server'; import { Logger } from '../../../types'; import { getUserFactory } from '../../lib/get_user'; import { ReportingSetupDeps } from '../../types'; - const superuserRole = 'superuser'; export type PreRoutingFunction = ( - request: Legacy.Request + request: KibanaRequest ) => Promise | AuthenticatedUser | null>; export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( @@ -24,30 +23,17 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting logger: Logger ) { const getUser = getUserFactory(plugins.security, logger); - const { info: xpackInfo } = plugins.__LEGACY.plugins.xpack_main; - - return async function authorizedUserPreRouting(request: Legacy.Request) { - if (!xpackInfo || !xpackInfo.isAvailable()) { - logger.warn('Unable to authorize user before xpack info is available.', [ - 'authorizedUserPreRouting', - ]); - return Boom.notFound(); - } - - const security = xpackInfo.feature('security'); - if (!security.isEnabled() || !security.isAvailable()) { - return null; - } - const user = await getUser(request); + return function authorizedUserPreRouting(request: KibanaRequest) { + const user = getUser(request); if (!user) { - return Boom.unauthorized(`Sorry, you aren't authenticated`); + throw Boom.unauthorized(`Sorry, you aren't authenticated`); } const authorizedRoles = [superuserRole, ...(config.get('roles', 'allow') as string[])]; if (!user.roles.find(role => authorizedRoles.includes(role))) { - return Boom.forbidden(`Sorry, you don't have access to Reporting`); + throw Boom.forbidden(`Sorry, you don't have access to Reporting`); } return user; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts index c243d0b4266ea..a18b90925deef 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -11,10 +11,6 @@ import { CSV_JOB_TYPE } from '../../../common/constants'; import { ExportTypeDefinition, ExportTypesRegistry, JobDocOutput, JobSource } from '../../../types'; import { statuses } from '../../lib/esqueue/constants/statuses'; -interface ICustomHeaders { - [x: string]: any; -} - type ExportTypeType = ExportTypeDefinition; interface ErrorFromPayload { @@ -36,7 +32,7 @@ const getTitle = (exportType: ExportTypeType, title?: string): string => `${title || DEFAULT_TITLE}.${exportType.jobContentExtension}`; const getReportingHeaders = (output: JobDocOutput, exportType: ExportTypeType) => { - const metaDataHeaders: ICustomHeaders = {}; + const metaDataHeaders: Record = {}; if (exportType.jobType === CSV_JOB_TYPE) { const csvContainsFormulas = _.get(output, 'csv_contains_formulas', false); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts index e7e7c866db96a..ba9400f6c139e 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts @@ -5,7 +5,6 @@ */ import Boom from 'boom'; -import { ResponseToolkit } from 'hapi'; import { ElasticsearchServiceSetup } from 'kibana/server'; import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; import { ExportTypesRegistry } from '../../../types'; @@ -29,44 +28,32 @@ export function downloadJobResponseHandlerFactory( const jobsQuery = jobsQueryFactory(config, elasticsearch); const getDocumentPayload = getDocumentPayloadFactory(exportTypesRegistry); - return function jobResponseHandler( + return async function jobResponseHandler( validJobTypes: string[], - user: any, - h: ResponseToolkit, + username: string, params: JobResponseHandlerParams, opts: JobResponseHandlerOpts = {} ) { const { docId } = params; - // TODO: async/await - return jobsQuery.get(user, docId, { includeContent: !opts.excludeContent }).then(doc => { - if (!doc) return Boom.notFound(); - const { jobtype: jobType } = doc._source; - if (!validJobTypes.includes(jobType)) { - return Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); - } + const doc = await jobsQuery.get(username, docId, { includeContent: !opts.excludeContent }); + if (!doc) throw Boom.notFound(); - const output = getDocumentPayload(doc); + const { jobtype: jobType } = doc._source; - if (!WHITELISTED_JOB_CONTENT_TYPES.includes(output.contentType)) { - return Boom.badImplementation( - `Unsupported content-type of ${output.contentType} specified by job output` - ); - } + if (!validJobTypes.includes(jobType)) { + throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); + } - const response = h - .response(output.content) - .type(output.contentType) - .code(output.statusCode); + const output = getDocumentPayload(doc); - if (output.headers) { - Object.keys(output.headers).forEach(key => { - response.header(key, output.headers[key]); - }); - } + if (!WHITELISTED_JOB_CONTENT_TYPES.includes(output.contentType)) { + throw Boom.badImplementation( + `Unsupported content-type of ${output.contentType} specified by job output` + ); + } - return response; // Hapi - }); + return output; }; } @@ -78,25 +65,24 @@ export function deleteJobResponseHandlerFactory( return async function deleteJobResponseHander( validJobTypes: string[], - user: any, - h: ResponseToolkit, + username: string, params: JobResponseHandlerParams ) { const { docId } = params; - const doc = await jobsQuery.get(user, docId, { includeContent: false }); - if (!doc) return Boom.notFound(); + const doc = await jobsQuery.get(username, docId, { includeContent: false }); + if (!doc) throw Boom.notFound(); const { jobtype: jobType } = doc._source; if (!validJobTypes.includes(jobType)) { - return Boom.unauthorized(`Sorry, you are not authorized to delete ${jobType} reports`); + throw Boom.unauthorized(`Sorry, you are not authorized to delete ${jobType} reports`); } try { const docIndex = doc._index; await jobsQuery.delete(docIndex, docId); - return h.response({ deleted: true }); + return { deleted: true }; } catch (error) { - return Boom.boomify(error, { statusCode: error.statusCode }); + throw Boom.boomify(error, { statusCode: error.statusCode }); } }; } diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts index 8cdb7b4c018d7..ef6759cffc557 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts @@ -4,13 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { makeRequestFacade } from './make_request_facade'; describe('makeRequestFacade', () => { test('creates a default object', () => { - const legacyRequest = ({ - getBasePath: () => 'basebase', + const getBasePath = () => 'basebase'; + const context = ({ + getSavedObjectsClient: () => {}, + } as unknown) as RequestHandlerContext; + const request = ({ params: { param1: 123, }, @@ -20,9 +23,9 @@ describe('makeRequestFacade', () => { headers: { user: 123, }, - } as unknown) as Legacy.Request; + } as unknown) as KibanaRequest; - expect(makeRequestFacade(legacyRequest)).toMatchInlineSnapshot(` + expect(makeRequestFacade(context, request, getBasePath)).toMatchInlineSnapshot(` Object { "getBasePath": [Function], "getRawRequest": [Function], @@ -44,7 +47,11 @@ describe('makeRequestFacade', () => { }); test('getRawRequest', () => { - const legacyRequest = ({ + const getBasePath = () => 'basebase'; + const context = ({ + getSavedObjectsClient: () => {}, + } as unknown) as RequestHandlerContext; + const request = ({ getBasePath: () => 'basebase', params: { param1: 123, @@ -55,8 +62,8 @@ describe('makeRequestFacade', () => { headers: { user: 123, }, - } as unknown) as Legacy.Request; + } as unknown) as KibanaRequest; - expect(makeRequestFacade(legacyRequest).getRawRequest()).toBe(legacyRequest); + expect(makeRequestFacade(context, request, getBasePath).getRawRequest()).toBe(request); }); }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts index fb8a2dbbff17b..dbf96a97693d0 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts @@ -4,28 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestQuery } from 'hapi'; -import { Legacy } from 'kibana'; -import { - RequestFacade, - ReportingRequestPayload, - ReportingRequestPre, - ReportingRequestQuery, -} from '../../../types'; +import { KibanaRequest, IBasePath, RequestHandlerContext } from 'src/core/server'; +import { RequestFacade, ReportingRequestPayload, ReportingRequestQuery } from '../../../types'; -export function makeRequestFacade(request: Legacy.Request): RequestFacade { - // This condition is for unit tests - const getSavedObjectsClient = request.getSavedObjectsClient - ? request.getSavedObjectsClient.bind(request) - : request.getSavedObjectsClient; +export function makeRequestFacade( + context: RequestHandlerContext, + request: KibanaRequest, + basePath: IBasePath['get'] +): RequestFacade { return { - getSavedObjectsClient, - headers: request.headers, - params: request.params, - payload: (request.payload as object) as ReportingRequestPayload, - query: ((request.query as RequestQuery) as object) as ReportingRequestQuery, - pre: (request.pre as Record) as ReportingRequestPre, - getBasePath: request.getBasePath, + getSavedObjectsClient: () => context.core.savedObjects.client, + headers: request.headers as Record, + params: request.params as Record, + body: (request.body as object) as ReportingRequestPayload, + query: (request.query as object) as ReportingRequestQuery, + getBasePath: () => basePath(request), route: request.route, getRawRequest: () => request, }; diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts index 28862a765d666..b2584470d72de 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; -import { JobDocPayload, ReportingResponseToolkit } from '../../types'; +import { kibanaResponseFactory } from 'src/core/server'; +import { JobDocPayload, RequestFacade } from '../../types'; export type HandlerFunction = ( exportType: string, jobParams: object, - request: Legacy.Request, - h: ReportingResponseToolkit + request: RequestFacade, + h: typeof kibanaResponseFactory ) => any; export type HandlerErrorFunction = (exportType: string, err: Error) => any; diff --git a/x-pack/legacy/plugins/reporting/server/types.d.ts b/x-pack/legacy/plugins/reporting/server/types.d.ts index fb77eae4e7eea..9d204f53bcc9a 100644 --- a/x-pack/legacy/plugins/reporting/server/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/types.d.ts @@ -5,10 +5,10 @@ */ import { Legacy } from 'kibana'; -import { ElasticsearchServiceSetup } from 'src/core/server'; +import { ElasticsearchServiceSetup, KibanaRequest } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; -import { SecurityPluginSetup } from '../../../../plugins/security/server'; +import { SecurityPluginSetup, AuthenticatedUser } from '../../../../plugins/security/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { ReportingPluginSpecOptions } from '../types'; import { ReportingConfigType } from './core'; @@ -43,3 +43,7 @@ export { ReportingConfig, ReportingConfigType, ReportingCore } from './core'; export type CaptureConfig = ReportingConfigType['capture']; export type ScrollConfig = ReportingConfigType['csv']['scroll']; + +export interface KibanaRequestWithUser extends KibanaRequest { + user: AuthenticatedUser; +} diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 2e7da6663ab03..253298a948ee6 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -4,6 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + BasePath, + SavedObjectsClient, + KibanaRequest, + SavedObjectsClientContract, + // @TODO: Move server deps to /server + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from 'src/core/server'; import { EventEmitter } from 'events'; import { ResponseObject } from 'hapi'; import { Legacy } from 'kibana'; @@ -57,7 +65,7 @@ export type EnqueueJobFn = ( exportTypeId: string, jobParams: JobParamsType, user: string, - headers: Record, + headers: RequestFacade['headers'], request: RequestFacade ) => Promise; @@ -72,15 +80,14 @@ export interface ReportingRequestPre { } export interface RequestFacade { - getBasePath: Legacy.Request['getBasePath']; - getSavedObjectsClient: Legacy.Request['getSavedObjectsClient']; - headers: Legacy.Request['headers']; - params: Legacy.Request['params']; - payload: JobParamPostPayload | GenerateExportTypePayload; + getBasePath: BasePath['get']; + getSavedObjectsClient: () => SavedObjectsClientContract; + headers: Record; + params: KibanaRequest['params']; + body: JobParamPostPayload | GenerateExportTypePayload; query: ReportingRequestQuery; - route: Legacy.Request['route']; - pre: ReportingRequestPre; - getRawRequest: () => Legacy.Request; + route: KibanaRequest['route']; + getRawRequest: () => KibanaRequest; } export type ResponseFacade = ResponseObject & { From 500b933ba996cdb5f0601f89422aea3a40eeaf47 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Tue, 12 May 2020 16:28:41 -0700 Subject: [PATCH 02/31] Move license checks over to np licensing observable --- .../legacy/plugins/reporting/server/core.ts | 32 +++-- .../legacy/plugins/reporting/server/legacy.ts | 8 ++ .../reporting/server/lib/check_license.ts | 44 +++--- .../legacy/plugins/reporting/server/plugin.ts | 18 ++- .../reporting/server/routes/generation.ts | 7 + .../plugins/reporting/server/routes/jobs.ts | 41 ++++-- .../routes/lib/route_config_factories.ts | 129 ------------------ .../plugins/reporting/server/types.d.ts | 11 ++ x-pack/plugins/reporting/kibana.json | 3 +- 9 files changed, 106 insertions(+), 187 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index e5e27966255d6..ca63369e29946 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -18,12 +18,12 @@ import { } from 'src/core/server'; // @ts-ignore no module definition import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; +import { ILicense } from '../../../../plugins/licensing/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; -import { PLUGIN_ID } from '../common/constants'; import { EnqueueJobFn, ESQueueInstance, ReportingPluginSpecOptions, ServerFacade } from '../types'; import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory'; import { ReportingConfig, ReportingConfigType } from './config'; -import { checkLicenseFactory, getExportTypesRegistry, LevelLogger } from './lib'; +import { LevelLogger, getExportTypesRegistry, checkLicenseFactory } from './lib'; import { registerRoutes } from './routes'; import { ReportingSetupDeps } from './types'; import { @@ -34,7 +34,9 @@ import { interface ReportingInternalSetup { browserDriverFactory: HeadlessChromiumDriverFactory; elasticsearch: ElasticsearchServiceSetup; + license$: Rx.Observable; } + interface ReportingInternalStart { enqueueJob: EnqueueJobFn; esqueue: ESQueueInstance; @@ -45,7 +47,7 @@ interface ReportingInternalStart { export { ReportingConfig, ReportingConfigType }; export class ReportingCore { - private pluginSetupDeps?: ReportingInternalSetup; + pluginSetupDeps?: ReportingInternalSetup; private pluginStartDeps?: ReportingInternalStart; private readonly pluginSetup$ = new Rx.ReplaySubject(); private readonly pluginStart$ = new Rx.ReplaySubject(); @@ -56,19 +58,10 @@ export class ReportingCore { legacySetup( xpackMainPlugin: XPackMainPlugin, reporting: ReportingPluginSpecOptions, - __LEGACY: ServerFacade, - plugins: ReportingSetupDeps + __LEGACY: ServerFacade ) { // legacy plugin status mirrorPluginStatus(xpackMainPlugin, reporting); - - // legacy license check - const checkLicense = checkLicenseFactory(this.exportTypesRegistry); - (xpackMainPlugin as any).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); - }); } public setupRoutes(plugins: ReportingSetupDeps, router: IRouter, basePath: IBasePath['get']) { @@ -102,9 +95,22 @@ export class ReportingCore { return (await this.getPluginStartDeps()).enqueueJob; } + public async getLicenseInfo() { + const exportTypeRegistry = this.getExportTypesRegistry(); + const $license = await this.getLicenseObservable(); + const getLicenseInfo = await checkLicenseFactory(exportTypeRegistry, $license); + + return getLicenseInfo(); + } + public getConfig(): ReportingConfig { return this.config; } + + public async getLicenseObservable(): Promise> { + return (await this.getPluginSetupDeps()).license$; + } + public async getScreenshotsObservable(): Promise { const { browserDriverFactory } = await this.getPluginSetupDeps(); return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory); diff --git a/x-pack/legacy/plugins/reporting/server/legacy.ts b/x-pack/legacy/plugins/reporting/server/legacy.ts index d044dc866ed0e..b2319acbc9205 100644 --- a/x-pack/legacy/plugins/reporting/server/legacy.ts +++ b/x-pack/legacy/plugins/reporting/server/legacy.ts @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as Rx from 'rxjs'; import { Legacy } from 'kibana'; import { take } from 'rxjs/operators'; import { PluginInitializerContext } from 'src/core/server'; +import { ILicense, LicensingPluginSetup } from '../../../../plugins/licensing/server'; import { PluginsSetup } from '../../../../plugins/reporting/server'; import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { ReportingPluginSpecOptions } from '../types'; @@ -41,11 +43,17 @@ export const legacyInit = async ( server.newPlatform.coreContext as PluginInitializerContext, buildConfig(coreSetup, server, reportingConfig) ); + await pluginInstance.setup(coreSetup, { elasticsearch: coreSetup.elasticsearch, security: server.newPlatform.setup.plugins.security as SecurityPluginSetup, usageCollection: server.newPlatform.setup.plugins.usageCollection, __LEGACY, + licensing: { + license$: (server.newPlatform.setup.plugins.licensing as any).$license as Rx.Observable< + ILicense + >, + } as LicensingPluginSetup, }); // Schedule to call the "start" hook only after start dependencies are ready diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts index 02e1196f1d00d..9d3baa42adb3a 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts @@ -4,22 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { XPackInfo } from '../../../xpack_main/server/lib/xpack_info'; -import { XPackInfoLicense } from '../../../xpack_main/server/lib/xpack_info_license'; +import * as Rx from 'rxjs'; +import { ILicense } from '../../../../../plugins/licensing/server'; +import { LicenseCheckResult } from '../types'; import { ExportTypesRegistry, ExportTypeDefinition } from '../../types'; -interface LicenseCheckResult { - showLinks: boolean; - enableLinks: boolean; - message?: string; -} - const messages = { getUnavailable: () => { return 'You cannot use Reporting because license information is not available at this time.'; }, - getExpired: (license: XPackInfoLicense) => { - return `You cannot use Reporting because your ${license.getType()} license has expired.`; + getExpired: (license: ILicense) => { + return `You cannot use Reporting because your ${license.type} license has expired.`; }, }; @@ -28,8 +23,8 @@ const makeManagementFeature = ( ) => { return { id: 'management', - checkLicense: (license: XPackInfoLicense | null) => { - if (!license) { + checkLicense: (license: ILicense) => { + if (!license || !license.type) { return { showLinks: true, enableLinks: false, @@ -37,7 +32,7 @@ const makeManagementFeature = ( }; } - if (!license.isActive()) { + if (!license.isActive) { return { showLinks: true, enableLinks: false, @@ -46,7 +41,7 @@ const makeManagementFeature = ( } const validJobTypes = exportTypes - .filter(exportType => license.isOneOf(exportType.validLicenses)) + .filter(exportType => license.type && exportType.validLicenses.includes(license.type)) .map(exportType => exportType.jobType); return { @@ -63,8 +58,8 @@ const makeExportTypeFeature = ( ) => { return { id: exportType.id, - checkLicense: (license: XPackInfoLicense | null) => { - if (!license) { + checkLicense: (license: ILicense | null) => { + if (!license || !license.type) { return { showLinks: true, enableLinks: false, @@ -72,17 +67,15 @@ const makeExportTypeFeature = ( }; } - if (!license.isOneOf(exportType.validLicenses)) { + if (!exportType.validLicenses.includes(license.type)) { return { showLinks: false, enableLinks: false, - message: `Your ${license.getType()} license does not support ${ - exportType.name - } Reporting. Please upgrade your license.`, + message: `Your ${license.type} license does not support ${exportType.name} Reporting. Please upgrade your license.`, }; } - if (!license.isActive()) { + if (!license.isActive) { return { showLinks: true, enableLinks: false, @@ -98,9 +91,12 @@ const makeExportTypeFeature = ( }; }; -export function checkLicenseFactory(exportTypesRegistry: ExportTypesRegistry) { - return function checkLicense(xpackInfo: XPackInfo) { - const license = xpackInfo === null || !xpackInfo.isAvailable() ? null : xpackInfo.license; +export function checkLicenseFactory( + exportTypesRegistry: ExportTypesRegistry, + $license: Rx.Observable +) { + return async function checkLicense() { + const license = await $license.toPromise(); const exportTypes = Array.from(exportTypesRegistry.getAll()); const reportingFeatures = [ ...exportTypes.map(makeExportTypeFeature), diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index fbe456442c840..749c3e007665b 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -26,22 +26,28 @@ export class ReportingPlugin public async setup(core: CoreSetup, plugins: ReportingSetupDeps) { const { config } = this; - const { elasticsearch, __LEGACY } = plugins; + const { + elasticsearch, + __LEGACY, + licensing: { license$ }, + } = plugins; const router = core.http.createRouter(); const basePath = core.http.basePath.get; + const { xpack_main: xpackMainLegacy, reporting: reportingLegacy } = __LEGACY.plugins; + + this.reportingCore.legacySetup(xpackMainLegacy, reportingLegacy, __LEGACY); const browserDriverFactory = await createBrowserDriverFactory(config, this.logger); // required for validations :( runValidations(config, elasticsearch, browserDriverFactory, this.logger); - const { xpack_main: xpackMainLegacy, reporting: reportingLegacy } = __LEGACY.plugins; - this.reportingCore.legacySetup(xpackMainLegacy, reportingLegacy, __LEGACY, plugins); - this.reportingCore.setupRoutes(plugins, router, basePath); - // Register a function with server to manage the collection of usage stats registerReportingUsageCollector(this.reportingCore, plugins); // regsister setup internals - this.reportingCore.pluginSetup({ browserDriverFactory, elasticsearch }); + this.reportingCore.pluginSetup({ browserDriverFactory, elasticsearch, license$ }); + + // Setup routing + this.reportingCore.setupRoutes(plugins, router, basePath); return {}; } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 1d291b6ce18e1..47610b95a4ec7 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -37,6 +37,13 @@ export function registerJobGenerationRoutes( r: RequestFacade, h: typeof kibanaResponseFactory ) { + const licenseInfo = await reporting.getLicenseInfo(); + const licenseResults = licenseInfo[exportTypeId]; + + if (!licenseResults.enableLinks) { + throw boom.forbidden(licenseResults.message); + } + const getUser = authorizedUserPreRoutingFactory(config, plugins, logger); const { username } = getUser(r.getRawRequest()); const { headers } = r; diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 7a9725635e437..1ba1955ec6a87 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -20,7 +20,7 @@ import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routi const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -export function registerJobInfoRoutes( +export async function registerJobInfoRoutes( reporting: ReportingCore, plugins: ReportingSetupDeps, router: IRouter, @@ -36,18 +36,19 @@ export function registerJobInfoRoutes( router.get( { path: `${MAIN_ENTRY}/list`, - validate: {}, + validate: false, }, router.handleLegacyErrors(async (context, req, res) => { const request = makeRequestFacade(context, req, basePath); + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); const { username } = getUser(request.getRawRequest()); const { page: queryPage, size: querySize, ids: queryIds } = request.query as ListQuery; const page = parseInt(queryPage, 10) || 0; const size = Math.min(100, parseInt(querySize, 10) || 10); const jobIds = queryIds ? queryIds.split(',') : null; - - // @todo: fill in jobtypes from license checks - const results = await jobsQuery.list([], username, page, size, jobIds); + const results = await jobsQuery.list(jobTypes, username, page, size, jobIds); return res.ok({ body: results, @@ -62,14 +63,16 @@ export function registerJobInfoRoutes( router.get( { path: `${MAIN_ENTRY}/count`, - validate: {}, + validate: false, }, router.handleLegacyErrors(async (context, req, res) => { const request = makeRequestFacade(context, req, basePath); const { username } = getUser(request.getRawRequest()); + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); - // @todo: fill in jobtypes from license checks - const results = await jobsQuery.count([], username); + const results = await jobsQuery.count(jobTypes, username); return res.ok({ body: { count: results }, @@ -94,6 +97,9 @@ export function registerJobInfoRoutes( const request = makeRequestFacade(context, req, basePath); const { username } = getUser(request.getRawRequest()); const { docId } = req.params; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); const result = await jobsQuery.get(username, docId, { includeContent: true }); @@ -105,7 +111,7 @@ export function registerJobInfoRoutes( _source: { jobtype: jobType, output: jobOutput }, } = result; - if (!['@todo'].includes(jobType)) { + if (!jobTypes.includes(jobType)) { throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); } @@ -132,6 +138,9 @@ export function registerJobInfoRoutes( const request = makeRequestFacade(context, req, basePath); const { username } = getUser(request.getRawRequest()); const { docId } = req.params; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); const result = await jobsQuery.get(username, docId); @@ -142,7 +151,7 @@ export function registerJobInfoRoutes( const { _source: job } = result; const { jobtype: jobType, payload: jobPayload } = job; - if (!['@todo'].includes(jobType)) { + if (!jobTypes.includes(jobType)) { throw Boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); } @@ -181,9 +190,11 @@ export function registerJobInfoRoutes( router.handleLegacyErrors(async (context, req, res) => { const { username } = getUser(req); const { docId } = req.params; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); - // @TODD: JobTypes checks - const response = await downloadResponseHandler([], username, { docId }); + const response = await downloadResponseHandler(jobTypes, username, { docId }); return res.ok({ body: response.content, @@ -209,9 +220,11 @@ export function registerJobInfoRoutes( router.handleLegacyErrors(async (context, req, res) => { const { username } = getUser(req); const { docId } = req.params; + const { + management: { jobTypes = [] }, + } = await reporting.getLicenseInfo(); - // @TODD Jobtypes here. - const response = await deleteResponseHandler([], username, { docId }); + const response = await deleteResponseHandler(jobTypes, username, { docId }); return res.ok({ body: response, diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts deleted file mode 100644 index 06f7efaa9dcbb..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts +++ /dev/null @@ -1,129 +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 Joi from 'joi'; -import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { Logger } from '../../../types'; -import { ReportingConfig, ReportingSetupDeps } from '../../types'; -import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; -import { - GetReportingFeatureIdFn, - reportingFeaturePreRoutingFactory, -} from './reporting_feature_pre_routing'; - -const API_TAG = 'api'; - -export interface RouteConfigFactory { - tags?: string[]; - pre: any[]; - response?: { - ranges: boolean; - }; -} - -export type GetRouteConfigFactoryFn = ( - getFeatureId?: GetReportingFeatureIdFn -) => RouteConfigFactory; - -export function getRouteConfigFactoryReportingPre( - config: ReportingConfig, - plugins: ReportingSetupDeps, - logger: Logger -): GetRouteConfigFactoryFn { - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(config, plugins, logger); - const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(config, plugins, logger); - - return (getFeatureId?: GetReportingFeatureIdFn): RouteConfigFactory => { - const preRouting: any[] = [{ method: authorizedUserPreRouting, assign: 'user' }]; - if (getFeatureId) { - preRouting.push(reportingFeaturePreRouting(getFeatureId)); - } - - return { - tags: [API_TAG], - pre: preRouting, - }; - }; -} - -export function getRouteOptionsCsv( - config: ReportingConfig, - plugins: ReportingSetupDeps, - logger: Logger -) { - const getRouteConfig = getRouteConfigFactoryReportingPre(config, plugins, logger); - return { - ...getRouteConfig(() => CSV_FROM_SAVEDOBJECT_JOB_TYPE), - validate: { - params: Joi.object({ - savedObjectType: Joi.string().required(), - savedObjectId: Joi.string().required(), - }).required(), - payload: Joi.object({ - state: Joi.object().default({}), - timerange: Joi.object({ - timezone: Joi.string().default('UTC'), - min: Joi.date().required(), - max: Joi.date().required(), - }).optional(), - }), - }, - }; -} - -export function getRouteConfigFactoryManagementPre( - config: ReportingConfig, - plugins: ReportingSetupDeps, - logger: Logger -): GetRouteConfigFactoryFn { - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(config, plugins, logger); - const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(config, plugins, logger); - const managementPreRouting = reportingFeaturePreRouting(() => 'management'); - - return (): RouteConfigFactory => { - return { - pre: [ - { method: authorizedUserPreRouting, assign: 'user' }, - { method: managementPreRouting, assign: 'management' }, - ], - }; - }; -} - -// NOTE: We're disabling range request for downloading the PDF. There's a bug in Firefox's PDF.js viewer -// (https://github.com/mozilla/pdf.js/issues/8958) where they're using a range request to retrieve the -// TOC at the end of the PDF, but it's sending multiple cookies and causing our auth to fail with a 401. -// Additionally, the range-request doesn't alleviate any performance issues on the server as the entire -// download is loaded into memory. -export function getRouteConfigFactoryDownloadPre( - config: ReportingConfig, - plugins: ReportingSetupDeps, - logger: Logger -): GetRouteConfigFactoryFn { - const getManagementRouteConfig = getRouteConfigFactoryManagementPre(config, plugins, logger); - return (): RouteConfigFactory => ({ - ...getManagementRouteConfig(), - tags: [API_TAG, 'download'], - response: { - ranges: false, - }, - }); -} - -export function getRouteConfigFactoryDeletePre( - config: ReportingConfig, - plugins: ReportingSetupDeps, - logger: Logger -): GetRouteConfigFactoryFn { - const getManagementRouteConfig = getRouteConfigFactoryManagementPre(config, plugins, logger); - return (): RouteConfigFactory => ({ - ...getManagementRouteConfig(), - tags: [API_TAG, 'delete'], - response: { - ranges: false, - }, - }); -} diff --git a/x-pack/legacy/plugins/reporting/server/types.d.ts b/x-pack/legacy/plugins/reporting/server/types.d.ts index 9d204f53bcc9a..a3977d14be795 100644 --- a/x-pack/legacy/plugins/reporting/server/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/types.d.ts @@ -7,6 +7,7 @@ import { Legacy } from 'kibana'; import { ElasticsearchServiceSetup, KibanaRequest } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; import { SecurityPluginSetup, AuthenticatedUser } from '../../../../plugins/security/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; @@ -17,6 +18,7 @@ export interface ReportingSetupDeps { elasticsearch: ElasticsearchServiceSetup; security: SecurityPluginSetup; usageCollection?: UsageCollectionSetup; + licensing: LicensingPluginSetup; __LEGACY: LegacySetup; } @@ -47,3 +49,12 @@ export type ScrollConfig = ReportingConfigType['csv']['scroll']; export interface KibanaRequestWithUser extends KibanaRequest { user: AuthenticatedUser; } + +export interface LicenseCheckResult { + showLinks: boolean; + enableLinks: boolean; + message?: string; + jobTypes?: string[]; +} + +export type checkLicense = () => Promise>; diff --git a/x-pack/plugins/reporting/kibana.json b/x-pack/plugins/reporting/kibana.json index d068711b87c9d..e44bd92c42391 100644 --- a/x-pack/plugins/reporting/kibana.json +++ b/x-pack/plugins/reporting/kibana.json @@ -13,7 +13,8 @@ "uiActions", "embeddable", "share", - "kibanaLegacy" + "kibanaLegacy", + "licensing" ], "server": true, "ui": true From 4feb02f3f7e10c144155af4a76ba0e9c6e25d797 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 14 May 2020 15:26:09 -0700 Subject: [PATCH 03/31] Fix license checks + remove older modules --- .../legacy/plugins/reporting/server/core.ts | 16 +++------ .../legacy/plugins/reporting/server/legacy.ts | 9 ++--- .../reporting/server/lib/check_license.ts | 30 +++++++--------- .../plugins/reporting/server/lib/index.ts | 2 +- .../server/lib/iso_string_validate.ts | 16 +++++++++ .../server/routes/generate_from_jobparams.ts | 12 ++++--- .../routes/generate_from_savedobject.ts | 9 +++-- .../generate_from_savedobject_immediate.ts | 11 ++++-- .../reporting/server/routes/generation.ts | 2 +- .../plugins/reporting/server/routes/jobs.ts | 18 +++++----- .../lib/reporting_feature_pre_routing.ts | 35 ------------------- 11 files changed, 70 insertions(+), 90 deletions(-) create mode 100644 x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts delete mode 100644 x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index ca63369e29946..350e6a04f0100 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -23,7 +23,7 @@ import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { EnqueueJobFn, ESQueueInstance, ReportingPluginSpecOptions, ServerFacade } from '../types'; import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory'; import { ReportingConfig, ReportingConfigType } from './config'; -import { LevelLogger, getExportTypesRegistry, checkLicenseFactory } from './lib'; +import { LevelLogger, getExportTypesRegistry, checkLicense } from './lib'; import { registerRoutes } from './routes'; import { ReportingSetupDeps } from './types'; import { @@ -48,6 +48,7 @@ export { ReportingConfig, ReportingConfigType }; export class ReportingCore { pluginSetupDeps?: ReportingInternalSetup; + private license?: ILicense; private pluginStartDeps?: ReportingInternalStart; private readonly pluginSetup$ = new Rx.ReplaySubject(); private readonly pluginStart$ = new Rx.ReplaySubject(); @@ -70,6 +71,7 @@ export class ReportingCore { public pluginSetup(reportingSetupDeps: ReportingInternalSetup) { this.pluginSetup$.next(reportingSetupDeps); + reportingSetupDeps.license$.subscribe(license => (this.license = license)); } public pluginStart(reportingStartDeps: ReportingInternalStart) { @@ -95,22 +97,14 @@ export class ReportingCore { return (await this.getPluginStartDeps()).enqueueJob; } - public async getLicenseInfo() { - const exportTypeRegistry = this.getExportTypesRegistry(); - const $license = await this.getLicenseObservable(); - const getLicenseInfo = await checkLicenseFactory(exportTypeRegistry, $license); - - return getLicenseInfo(); + public getLicenseInfo() { + return checkLicense(this.getExportTypesRegistry(), this.license); } public getConfig(): ReportingConfig { return this.config; } - public async getLicenseObservable(): Promise> { - return (await this.getPluginSetupDeps()).license$; - } - public async getScreenshotsObservable(): Promise { const { browserDriverFactory } = await this.getPluginSetupDeps(); return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory); diff --git a/x-pack/legacy/plugins/reporting/server/legacy.ts b/x-pack/legacy/plugins/reporting/server/legacy.ts index b2319acbc9205..dd2bd886ae8bd 100644 --- a/x-pack/legacy/plugins/reporting/server/legacy.ts +++ b/x-pack/legacy/plugins/reporting/server/legacy.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as Rx from 'rxjs'; import { Legacy } from 'kibana'; import { take } from 'rxjs/operators'; import { PluginInitializerContext } from 'src/core/server'; -import { ILicense, LicensingPluginSetup } from '../../../../plugins/licensing/server'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; import { PluginsSetup } from '../../../../plugins/reporting/server'; import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { ReportingPluginSpecOptions } from '../types'; @@ -48,12 +47,8 @@ export const legacyInit = async ( elasticsearch: coreSetup.elasticsearch, security: server.newPlatform.setup.plugins.security as SecurityPluginSetup, usageCollection: server.newPlatform.setup.plugins.usageCollection, + licensing: server.newPlatform.setup.plugins.licensing as LicensingPluginSetup, __LEGACY, - licensing: { - license$: (server.newPlatform.setup.plugins.licensing as any).$license as Rx.Observable< - ILicense - >, - } as LicensingPluginSetup, }); // Schedule to call the "start" hook only after start dependencies are ready diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts index 9d3baa42adb3a..2a5365f7f9453 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as Rx from 'rxjs'; import { ILicense } from '../../../../../plugins/licensing/server'; import { LicenseCheckResult } from '../types'; import { ExportTypesRegistry, ExportTypeDefinition } from '../../types'; @@ -23,7 +22,7 @@ const makeManagementFeature = ( ) => { return { id: 'management', - checkLicense: (license: ILicense) => { + checkLicense: (license: ILicense | undefined) => { if (!license || !license.type) { return { showLinks: true, @@ -58,7 +57,7 @@ const makeExportTypeFeature = ( ) => { return { id: exportType.id, - checkLicense: (license: ILicense | null) => { + checkLicense: (license: ILicense | undefined) => { if (!license || !license.type) { return { showLinks: true, @@ -91,21 +90,18 @@ const makeExportTypeFeature = ( }; }; -export function checkLicenseFactory( +export function checkLicense( exportTypesRegistry: ExportTypesRegistry, - $license: Rx.Observable + license: ILicense | undefined ) { - return async function checkLicense() { - const license = await $license.toPromise(); - const exportTypes = Array.from(exportTypesRegistry.getAll()); - const reportingFeatures = [ - ...exportTypes.map(makeExportTypeFeature), - makeManagementFeature(exportTypes), - ]; + const exportTypes = Array.from(exportTypesRegistry.getAll()); + const reportingFeatures = [ + ...exportTypes.map(makeExportTypeFeature), + makeManagementFeature(exportTypes), + ]; - return reportingFeatures.reduce((result, feature) => { - result[feature.id] = feature.checkLicense(license); - return result; - }, {} as Record); - }; + return reportingFeatures.reduce((result, feature) => { + result[feature.id] = feature.checkLicense(license); + return result; + }, {} as Record); } diff --git a/x-pack/legacy/plugins/reporting/server/lib/index.ts b/x-pack/legacy/plugins/reporting/server/lib/index.ts index 2a8fa45b6fcef..0e9c49b170887 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/index.ts @@ -5,7 +5,7 @@ */ export { LevelLogger } from './level_logger'; -export { checkLicenseFactory } from './check_license'; +export { checkLicense } from './check_license'; export { createQueueFactory } from './create_queue'; export { cryptoFactory } from './crypto'; export { enqueueJobFactory } from './enqueue_job'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts b/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts new file mode 100644 index 0000000000000..05e685b2ec76f --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts @@ -0,0 +1,16 @@ +/* + * 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 isoStringValidate = (input: string): string | undefined => { + const message = `value must be a valid ISO format`; + if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(input)) { + return message; + } + + if (new Date(input).toISOString() !== input) { + return message; + } +}; diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index f12b4d323ae76..2f708e15f6b68 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -33,11 +33,15 @@ export function registerGenerateFromJobParams( params: schema.object({ exportType: schema.string({ minLength: 2 }), }), - body: schema.object({ - jobParams: schema.string(), - }), + body: schema.maybe( + schema.object({ + jobParams: schema.maybe(schema.string()), + }) + ), query: schema.object({ - jobParams: schema.string(), + jobParams: schema.string({ + defaultValue: '', + }), }), }, }, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 9f08b6ccd2bf1..a7d4762318e7e 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -11,6 +11,7 @@ import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../commo import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; import { Logger } from '../../types'; import { ReportingCore, ReportingSetupDeps } from '../types'; +import { isoStringValidate } from '../lib/iso_string_validate'; import { makeRequestFacade } from './lib/make_request_facade'; import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types'; @@ -44,8 +45,12 @@ export function registerGenerateCsvFromSavedObject( state: schema.object({}), timerange: schema.object({ timezone: schema.string({ defaultValue: 'UTC' }), - min: schema.duration(), - max: schema.duration(), + min: schema.string({ + validate: isoStringValidate, + }), + max: schema.string({ + validate: isoStringValidate, + }), }), }), }, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 1feff024ca042..ea9c2234f29e0 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -12,6 +12,7 @@ import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types'; import { JobDocOutput, Logger } from '../../types'; import { ReportingCore, ReportingSetupDeps } from '../types'; +import { isoStringValidate } from '../lib/iso_string_validate'; import { makeRequestFacade } from './lib/make_request_facade'; /* @@ -44,11 +45,15 @@ export function registerGenerateCsvFromSavedObjectImmediate( savedObjectId: schema.string({ minLength: 5 }), }), body: schema.object({ - state: schema.object({}), + state: schema.object({}, { unknowns: 'allow' }), timerange: schema.object({ timezone: schema.string({ defaultValue: 'UTC' }), - min: schema.duration(), - max: schema.duration(), + min: schema.string({ + validate: isoStringValidate, + }), + max: schema.string({ + validate: isoStringValidate, + }), }), }), }, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 47610b95a4ec7..06deec1c43138 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -37,7 +37,7 @@ export function registerJobGenerationRoutes( r: RequestFacade, h: typeof kibanaResponseFactory ) { - const licenseInfo = await reporting.getLicenseInfo(); + const licenseInfo = reporting.getLicenseInfo(); const licenseResults = licenseInfo[exportTypeId]; if (!licenseResults.enableLinks) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 1ba1955ec6a87..690b669aafe81 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -42,7 +42,7 @@ export async function registerJobInfoRoutes( const request = makeRequestFacade(context, req, basePath); const { management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); + } = reporting.getLicenseInfo(); const { username } = getUser(request.getRawRequest()); const { page: queryPage, size: querySize, ids: queryIds } = request.query as ListQuery; const page = parseInt(queryPage, 10) || 0; @@ -70,14 +70,14 @@ export async function registerJobInfoRoutes( const { username } = getUser(request.getRawRequest()); const { management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); + } = reporting.getLicenseInfo(); - const results = await jobsQuery.count(jobTypes, username); + const count = await jobsQuery.count(jobTypes, username); return res.ok({ - body: { count: results }, + body: count, headers: { - 'content-type': 'application/json', + 'content-type': 'text/plain', }, }); }) @@ -99,7 +99,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params; const { management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); + } = reporting.getLicenseInfo(); const result = await jobsQuery.get(username, docId, { includeContent: true }); @@ -140,7 +140,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params; const { management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); + } = reporting.getLicenseInfo(); const result = await jobsQuery.get(username, docId); @@ -192,7 +192,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params; const { management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); + } = reporting.getLicenseInfo(); const response = await downloadResponseHandler(jobTypes, username, { docId }); @@ -222,7 +222,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params; const { management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); + } = reporting.getLicenseInfo(); const response = await deleteResponseHandler(jobTypes, username, { docId }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts deleted file mode 100644 index 8a79566aafae2..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts +++ /dev/null @@ -1,35 +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 { Legacy } from 'kibana'; -import { Logger } from '../../../types'; -import { ReportingConfig, ReportingSetupDeps } from '../../types'; - -export type GetReportingFeatureIdFn = (request: Legacy.Request) => string; - -export const reportingFeaturePreRoutingFactory = function reportingFeaturePreRoutingFn( - config: ReportingConfig, - plugins: ReportingSetupDeps, - logger: Logger -) { - const xpackMainPlugin = plugins.__LEGACY.plugins.xpack_main; - const pluginId = 'reporting'; - - // License checking and enable/disable logic - return function reportingFeaturePreRouting(getReportingFeatureId: GetReportingFeatureIdFn) { - return function licensePreRouting(request: Legacy.Request) { - const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults(); - const reportingFeatureId = getReportingFeatureId(request) as string; - const reportingFeature = licenseCheckResults[reportingFeatureId]; - if (!reportingFeature.showLinks || !reportingFeature.enableLinks) { - throw Boom.forbidden(reportingFeature.message); - } else { - return reportingFeature; - } - }; - }; -}; From 2b18fe2b205541a1535efc30476ad4b767044b5a Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Tue, 19 May 2020 09:54:12 -0700 Subject: [PATCH 04/31] Fixing check_license tests, move to TS/Jest --- .../server/lib/__tests__/check_license.js | 147 ------------- .../server/lib/check_license.test.ts | 193 ++++++++++++++++++ .../reporting/server/lib/check_license.ts | 4 +- 3 files changed, 195 insertions(+), 149 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/server/lib/__tests__/check_license.js create mode 100644 x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts diff --git a/x-pack/legacy/plugins/reporting/server/lib/__tests__/check_license.js b/x-pack/legacy/plugins/reporting/server/lib/__tests__/check_license.js deleted file mode 100644 index 394f88c37087c..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/lib/__tests__/check_license.js +++ /dev/null @@ -1,147 +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 expect from '@kbn/expect'; -import { set } from 'lodash'; -import { checkLicenseFactory } from '../check_license'; - -describe('check_license', function() { - let mockLicenseInfo; - let checkLicense; - - beforeEach(() => { - mockLicenseInfo = {}; - checkLicense = checkLicenseFactory({ - getAll: () => [ - { - id: 'test', - name: 'Test Export Type', - jobType: 'testJobType', - }, - ], - }); - }); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set management.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).management.showLinks).to.be(true); - }); - - it('should set test.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).test.showLinks).to.be(true); - }); - - it('should set management.enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).management.enableLinks).to.be(false); - }); - - it('should set test.enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).test.enableLinks).to.be(false); - }); - - it('should set management.jobTypes to undefined', () => { - expect(checkLicense(mockLicenseInfo).management.jobTypes).to.be(undefined); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); - }); - - describe('& license is > basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set management.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).management.showLinks).to.be(true); - }); - - it('should set test.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).test.showLinks).to.be(true); - }); - - it('should set management.enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).management.enableLinks).to.be(true); - }); - - it('should set test.enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).test.enableLinks).to.be(true); - }); - - it('should set management.jobTypes to contain testJobType', () => { - expect(checkLicense(mockLicenseInfo).management.jobTypes).to.contain('testJobType'); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set management.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).management.showLinks).to.be(true); - }); - - it('should set test.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).test.showLinks).to.be(true); - }); - - it('should set management.enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).management.enableLinks).to.be(false); - }); - - it('should set test.enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).test.enableLinks).to.be(false); - }); - - it('should set management.jobTypes to undefined', () => { - expect(checkLicense(mockLicenseInfo).management.jobTypes).to.be(undefined); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => false)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set management.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).management.showLinks).to.be(false); - }); - - it('should set test.showLinks to false', () => { - expect(checkLicense(mockLicenseInfo).test.showLinks).to.be(false); - }); - - it('should set management.jobTypes to an empty array', () => { - expect(checkLicense(mockLicenseInfo).management.jobTypes).to.be.an(Array); - expect(checkLicense(mockLicenseInfo).management.jobTypes).to.have.length(0); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set management.showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).management.showLinks).to.be(true); - }); - - it('should set test.showLinks to false', () => { - expect(checkLicense(mockLicenseInfo).test.showLinks).to.be(false); - }); - - it('should set management.jobTypes to undefined', () => { - expect(checkLicense(mockLicenseInfo).management.jobTypes).to.be(undefined); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts b/x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts new file mode 100644 index 0000000000000..3dd18ff5ac5dd --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts @@ -0,0 +1,193 @@ +/* + * 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 { set } from 'lodash'; +import { checkLicense } from './check_license'; +import { ILicense } from '../../../../../plugins/licensing/server'; +import { ExportTypesRegistry } from '../../types'; + +describe('check_license', function() { + let exportTypesRegistry: ExportTypesRegistry; + let license: ILicense; + + beforeEach(() => { + exportTypesRegistry = ({ + getAll: () => [], + } as unknown) as ExportTypesRegistry; + }); + + describe('license information is not ready', () => { + beforeEach(() => { + exportTypesRegistry = ({ + getAll: () => [{ id: 'csv' }], + } as unknown) as ExportTypesRegistry; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, undefined).management.showLinks).toEqual(true); + }); + + it('should set csv.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, undefined).csv.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, undefined).management.enableLinks).toEqual(false); + }); + + it('should set csv.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, undefined).csv.enableLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, undefined).management.jobTypes).toEqual(undefined); + }); + }); + + describe('license information is not available', () => { + beforeEach(() => { + license = { + type: undefined, + } as ILicense; + exportTypesRegistry = ({ + getAll: () => [{ id: 'csv' }], + } as unknown) as ExportTypesRegistry; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should set csv.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).csv.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).management.enableLinks).toEqual(false); + }); + + it('should set csv.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).csv.enableLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual(undefined); + }); + }); + + describe('license information is available', () => { + beforeEach(() => { + license = {} as ILicense; + }); + + describe('& license is > basic', () => { + beforeEach(() => { + license.type = 'gold'; + exportTypesRegistry = ({ + getAll: () => [{ id: 'pdf', validLicenses: ['gold'], jobType: 'printable_pdf' }], + } as unknown) as ExportTypesRegistry; + }); + + describe('& license is active', () => { + beforeEach(() => (license.isActive = true)); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should setpdf.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.enableLinks).toEqual(true); + }); + + it('should setpdf.enableLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.enableLinks).toEqual(true); + }); + + it('should set management.jobTypes to contain testJobType', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toContain( + 'printable_pdf' + ); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => { + license.isActive = false; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should set pdf.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(true); + }); + + it('should set management.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).management.enableLinks).toEqual(false); + }); + + it('should set pdf.enableLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.enableLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual(undefined); + }); + }); + }); + + describe('& license is basic', () => { + beforeEach(() => { + license.type = 'basic'; + exportTypesRegistry = ({ + getAll: () => [{ id: 'pdf', validLicenses: ['gold'], jobType: 'printable_pdf' }], + } as unknown) as ExportTypesRegistry; + }); + + describe('& license is active', () => { + beforeEach(() => { + license.isActive = true; + }); + + it('should set management.showLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(false); + }); + + it('should set test.showLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(false); + }); + + it('should set management.jobTypes to an empty array', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual([]); + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toHaveLength(0); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => { + license.isActive = false; + }); + + it('should set management.showLinks to true', () => { + expect(checkLicense(exportTypesRegistry, license).management.showLinks).toEqual(true); + }); + + it('should set test.showLinks to false', () => { + expect(checkLicense(exportTypesRegistry, license).pdf.showLinks).toEqual(false); + }); + + it('should set management.jobTypes to undefined', () => { + expect(checkLicense(exportTypesRegistry, license).management.jobTypes).toEqual(undefined); + }); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts index 2a5365f7f9453..86c70d3f39f7b 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts @@ -22,7 +22,7 @@ const makeManagementFeature = ( ) => { return { id: 'management', - checkLicense: (license: ILicense | undefined) => { + checkLicense: (license?: ILicense) => { if (!license || !license.type) { return { showLinks: true, @@ -57,7 +57,7 @@ const makeExportTypeFeature = ( ) => { return { id: exportType.id, - checkLicense: (license: ILicense | undefined) => { + checkLicense: (license?: ILicense) => { if (!license || !license.type) { return { showLinks: true, From 7fab2c69fc5f65aa3c63c54dd576825f14972458 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Wed, 20 May 2020 07:04:18 -0700 Subject: [PATCH 05/31] Fix licensing setup for mocks --- .../reporting/test_helpers/create_mock_reportingplugin.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts index 274f7344c7261..9c89962d83bfc 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts @@ -12,6 +12,7 @@ jest.mock('../server/lib/create_queue'); jest.mock('../server/lib/enqueue_job'); jest.mock('../server/lib/validate'); +import * as Rx from 'rxjs'; import { EventEmitter } from 'events'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { coreMock } from 'src/core/server/mocks'; @@ -24,6 +25,9 @@ const createMockSetupDeps = (setupMock?: any): ReportingSetupDeps => { security: setupMock.security, usageCollection: {} as any, __LEGACY: { plugins: { xpack_main: { status: new EventEmitter() } } } as any, + licensing: { + license$: Rx.of({ isAvailable: true, isActive: true, type: 'basic' }), + } as any, }; }; From d58fc1406ef24e975d7e9cf83917ca93dac78678 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 21 May 2020 15:50:11 -0700 Subject: [PATCH 06/31] Move job.test.ts over to np --- .../plugins/reporting/common/constants.ts | 1 + .../plugins/reporting/server/lib/get_user.ts | 3 +- .../reporting/server/routes/jobs.test.ts | 575 +++++++++--------- .../plugins/reporting/server/routes/jobs.ts | 6 +- .../routes/lib/authorized_user_pre_routing.ts | 5 +- .../server/routes/lib/get_document_payload.ts | 8 +- .../test_helpers/create_mock_server.ts | 31 +- 7 files changed, 342 insertions(+), 287 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/common/constants.ts b/x-pack/legacy/plugins/reporting/common/constants.ts index f30a7cc87f318..48483c79d1af2 100644 --- a/x-pack/legacy/plugins/reporting/common/constants.ts +++ b/x-pack/legacy/plugins/reporting/common/constants.ts @@ -27,6 +27,7 @@ export const WHITELISTED_JOB_CONTENT_TYPES = [ 'application/pdf', CONTENT_TYPE_CSV, 'image/png', + 'text/plain', ]; // See: diff --git a/x-pack/legacy/plugins/reporting/server/lib/get_user.ts b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts index b25fc37f92768..b9f986349fce7 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/get_user.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts @@ -6,9 +6,8 @@ import { KibanaRequest } from '../../../../../../src/core/server'; import { ReportingSetupDeps } from '../types'; -import { LevelLogger as Logger } from './level_logger'; -export function getUserFactory(security: ReportingSetupDeps['security'], logger: Logger) { +export function getUserFactory(security: ReportingSetupDeps['security']) { return (request: KibanaRequest) => { return security?.authc.getCurrentUser(request) ?? null; }; diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index 4f597bcee858e..5d4cb33465c63 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -4,327 +4,356 @@ * you may not use this file except in compliance with the Elastic License. */ -import Hapi from 'hapi'; -import { ReportingConfig, ReportingCore } from '../'; -import { LevelLogger } from '../lib'; -import { createMockReportingCore } from '../../test_helpers'; +import supertest from 'supertest'; +import { UnwrapPromise } from '@kbn/utility-types'; +import { registerJobInfoRoutes } from './jobs'; +import { createMockReportingCore, createMockServer } from '../../test_helpers'; +import { ReportingCore } from '..'; import { ExportTypesRegistry } from '../lib/export_types_registry'; import { ExportTypeDefinition, ReportingSetupDeps } from '../types'; -import { registerJobInfoRoutes } from './jobs'; - -jest.mock('./lib/authorized_user_pre_routing', () => ({ - authorizedUserPreRoutingFactory: () => () => ({}), -})); -jest.mock('./lib/reporting_feature_pre_routing', () => ({ - reportingFeaturePreRoutingFactory: () => () => () => ({ - jobTypes: ['unencodedJobType', 'base64EncodedJobType'], - }), -})); - -let mockServer: any; -let exportTypesRegistry: ExportTypesRegistry; -let mockReportingPlugin: ReportingCore; -let mockReportingConfig: ReportingConfig; -const mockLogger = ({ - error: jest.fn(), - debug: jest.fn(), -} as unknown) as LevelLogger; - -beforeEach(async () => { - mockServer = new Hapi.Server({ debug: false, port: 8080, routes: { log: { collect: true } } }); - exportTypesRegistry = new ExportTypesRegistry(); - exportTypesRegistry.register({ - id: 'unencoded', - jobType: 'unencodedJobType', - jobContentExtension: 'csv', - } as ExportTypeDefinition); - exportTypesRegistry.register({ - id: 'base64Encoded', - jobType: 'base64EncodedJobType', - jobContentEncoding: 'base64', - jobContentExtension: 'pdf', - } as ExportTypeDefinition); - - mockReportingConfig = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; - mockReportingPlugin = await createMockReportingCore(mockReportingConfig); - mockReportingPlugin.getExportTypesRegistry = () => exportTypesRegistry; -}); - -const mockPlugins = ({ - elasticsearch: { - adminClient: { callAsInternalUser: jest.fn() }, - }, - security: null, -} as unknown) as ReportingSetupDeps; - -const getHits = (...sources: any) => { - return { - hits: { - hits: sources.map((source: object) => ({ _source: source })), +import { LevelLogger } from '../lib'; +import { IRouter } from 'src/core/server'; + +type setupServerReturn = UnwrapPromise>; + +describe('GET /api/reporting/jobs/download', () => { + let server: setupServerReturn['server']; + let httpSetup: setupServerReturn['httpSetup']; + let exportTypesRegistry: ExportTypesRegistry; + let core: ReportingCore; + let basePath: () => string; + let router: IRouter; + + const config = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; + const mockLogger = ({ + error: jest.fn(), + debug: jest.fn(), + } as unknown) as jest.Mocked; + + const mockPlugins = ({ + elasticsearch: { + adminClient: { callAsInternalUser: jest.fn() }, }, - }; -}; - -const getErrorsFromRequest = (request: any) => - request.logs.filter((log: any) => log.tags.includes('error')).map((log: any) => log.error); - -test(`returns 404 if job not found`, async () => { - // @ts-ignore - mockPlugins.elasticsearch.adminClient = { - callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), - }; - - registerJobInfoRoutes(mockReportingPlugin, mockServer, mockPlugins, mockLogger); - - const request = { - method: 'GET', - url: '/api/reporting/jobs/download/1', - }; - - const response = await mockServer.inject(request); - const { statusCode } = response; - expect(statusCode).toBe(404); -}); - -test(`returns 401 if not valid job type`, async () => { - // @ts-ignore - mockPlugins.elasticsearch.adminClient = { - callAsInternalUser: jest - .fn() - .mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))), - }; - - registerJobInfoRoutes(mockReportingPlugin, mockServer, mockPlugins, mockLogger); + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + } as unknown) as ReportingSetupDeps; - const request = { - method: 'GET', - url: '/api/reporting/jobs/download/1', + const getHits = (...sources: any) => { + return { + hits: { + hits: sources.map((source: object) => ({ _source: source })), + }, + }; }; - const { statusCode } = await mockServer.inject(request); - expect(statusCode).toBe(401); -}); - -describe(`when job is incomplete`, () => { - const getIncompleteResponse = async () => { + beforeEach(async () => { + basePath = () => '/'; + core = await createMockReportingCore(config); // @ts-ignore - mockPlugins.elasticsearch.adminClient = { - callAsInternalUser: jest - .fn() - .mockReturnValue( - Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' })) - ), - }; - - registerJobInfoRoutes(mockReportingPlugin, mockServer, mockPlugins, mockLogger); - - const request = { - method: 'GET', - url: '/api/reporting/jobs/download/1', + core.license = { + isActive: true, + isAvailable: true, + type: 'gold', }; - - return await mockServer.inject(request); - }; - - test(`sets statusCode to 503`, async () => { - const { statusCode } = await getIncompleteResponse(); - expect(statusCode).toBe(503); - }); - - test(`uses status as payload`, async () => { - const { payload } = await getIncompleteResponse(); - expect(payload).toBe('pending'); - }); - - test(`sets content-type header to application/json; charset=utf-8`, async () => { - const { headers } = await getIncompleteResponse(); - expect(headers['content-type']).toBe('application/json; charset=utf-8'); + exportTypesRegistry = new ExportTypesRegistry(); + exportTypesRegistry.register({ + id: 'unencoded', + jobType: 'unencodedJobType', + jobContentExtension: 'csv', + validLicenses: ['basic', 'gold'], + } as ExportTypeDefinition); + exportTypesRegistry.register({ + id: 'base64Encoded', + jobType: 'base64EncodedJobType', + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + validLicenses: ['basic', 'gold'], + } as ExportTypeDefinition); + core.getExportTypesRegistry = () => exportTypesRegistry; + ({ server, httpSetup } = await createMockServer()); }); - test(`sets retry-after header to 30`, async () => { - const { headers } = await getIncompleteResponse(); - expect(headers['retry-after']).toBe(30); + afterEach(async () => { + mockLogger.debug.mockReset(); + mockLogger.error.mockReset(); + await server.stop(); }); -}); -describe(`when job is failed`, () => { - const getFailedResponse = async () => { - const hits = getHits({ - jobtype: 'unencodedJobType', - status: 'failed', - output: { content: 'job failure message' }, - }); + it('fails on malformed download IDs', async () => { // @ts-ignore mockPlugins.elasticsearch.adminClient = { - callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), }; - - registerJobInfoRoutes(mockReportingPlugin, mockServer, mockPlugins, mockLogger); - - const request = { - method: 'GET', - url: '/api/reporting/jobs/download/1', - }; - - return await mockServer.inject(request); - }; - - test(`sets status code to 500`, async () => { - const { statusCode } = await getFailedResponse(); - expect(statusCode).toBe(500); - }); - - test(`sets content-type header to application/json; charset=utf-8`, async () => { - const { headers } = await getFailedResponse(); - expect(headers['content-type']).toBe('application/json; charset=utf-8'); - }); - - test(`sets the payload.reason to the job content`, async () => { - const { payload } = await getFailedResponse(); - expect(JSON.parse(payload).reason).toBe('job failure message'); + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/1') + .expect(400) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot( + '"[request params.docId]: value has length [1] but it must have a minimum length of [3]."' + ) + ); }); -}); -describe(`when job is completed`, () => { - const getCompletedResponse = async ({ - jobType = 'unencodedJobType', - outputContent = 'job output content', - outputContentType = 'application/pdf', - title = '', - } = {}) => { - const hits = getHits({ - jobtype: jobType, - status: 'completed', - output: { content: outputContent, content_type: outputContentType }, - payload: { - title, - }, - }); + it('fails on unauthenticated users', async () => { // @ts-ignore - mockPlugins.elasticsearch.adminClient = { - callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), - }; - - registerJobInfoRoutes(mockReportingPlugin, mockServer, mockPlugins, mockLogger); + const unauthenticatedPlugin = ({ + ...mockPlugins, + security: { + authc: { + getCurrentUser: () => undefined, + }, + }, + } as unknown) as ReportingSetupDeps; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, unauthenticatedPlugin, router, basePath, mockLogger); - const request = { - method: 'GET', - url: '/api/reporting/jobs/download/1', - }; + await server.start(); - return await mockServer.inject(request); - }; + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dope') + .expect(401) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot(`"Sorry, you aren't authenticated"`) + ); + }); - test(`sets statusCode to 200`, async () => { - const { statusCode, request } = await getCompletedResponse(); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); - expect(statusCode).toBe(200); + it('fails when security is not there', async () => { + // @ts-ignore + const noSecurityPlugins = ({ + ...mockPlugins, + security: null, + } as unknown) as ReportingSetupDeps; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, noSecurityPlugins, router, basePath, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dope') + .expect(401) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot(`"Sorry, you aren't authenticated"`) + ); }); - test(`doesn't encode output content for not-specified jobTypes`, async () => { - const { payload, request } = await getCompletedResponse({ - jobType: 'unencodedJobType', - outputContent: 'test', - }); + it('fails on users without the appropriate role', async () => { + // @ts-ignore + const peasantUser = ({ + ...mockPlugins, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['peasant'], + username: 'Tom Riddle', + }), + }, + }, + } as unknown) as ReportingSetupDeps; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, peasantUser, router, basePath, mockLogger); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); + await server.start(); - expect(payload).toBe('test'); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dope') + .expect(403) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot(`"Sorry, you don't have access to Reporting"`) + ); }); - test(`base64 encodes output content for configured jobTypes`, async () => { - const { payload, request } = await getCompletedResponse({ - jobType: 'base64EncodedJobType', - outputContent: 'test', - }); + it('returns 404 if job not found', async () => { + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); + await server.start(); - expect(payload).toBe(Buffer.from('test', 'base64').toString()); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/poo') + .expect(404); }); - test(`specifies text/csv; charset=utf-8 contentType header from the job output`, async () => { - const { headers, request } = await getCompletedResponse({ outputContentType: 'text/csv' }); + it('returns a 401 if not a valid job type', async () => { + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest + .fn() + .mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); + await server.start(); - expect(headers['content-type']).toBe('text/csv; charset=utf-8'); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/poo') + .expect(401); }); - test(`specifies default filename in content-disposition header if no title`, async () => { - const { headers, request } = await getCompletedResponse({}); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); - expect(headers['content-disposition']).toBe('inline; filename="report.csv"'); + it('when a job is incomplete', async () => { + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest + .fn() + .mockReturnValue( + Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' })) + ), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(503) + .expect('Content-Type', 'text/plain; charset=utf-8') + .expect('Retry-After', '30') + .then(({ text }) => expect(text).toEqual('pending')); }); - test(`specifies payload title in content-disposition header`, async () => { - const { headers, request } = await getCompletedResponse({ title: 'something' }); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); - expect(headers['content-disposition']).toBe('inline; filename="something.csv"'); + it('when a job fails', async () => { + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue( + Promise.resolve( + getHits({ + jobtype: 'unencodedJobType', + status: 'failed', + output: { content: 'job failure message' }, + }) + ) + ), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(500) + .expect('Content-Type', 'application/json; charset=utf-8') + .then(({ body }) => + expect(body.message).toEqual('Reporting generation failed: job failure message') + ); }); - test(`specifies jobContentExtension in content-disposition header`, async () => { - const { headers, request } = await getCompletedResponse({ jobType: 'base64EncodedJobType' }); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); - expect(headers['content-disposition']).toBe('inline; filename="report.pdf"'); - }); + describe('successful downloads', () => { + const getCompleteHits = async ({ + jobType = 'unencodedJobType', + outputContent = 'job output content', + outputContentType = 'text/plain', + title = '', + } = {}) => { + return getHits({ + jobtype: jobType, + status: 'completed', + output: { content: outputContent, content_type: outputContentType }, + payload: { + title, + }, + }); + }; - test(`specifies application/pdf contentType header from the job output`, async () => { - const { headers, request } = await getCompletedResponse({ - outputContentType: 'application/pdf', + it('when a known job-type is complete', async () => { + const hits = getCompleteHits(); + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(200) + .expect('Content-Type', 'text/plain; charset=utf-8') + .expect('content-disposition', 'inline; filename="report.csv"'); }); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toEqual([]); - expect(headers['content-type']).toBe('application/pdf'); - }); - describe(`when non-whitelisted contentType specified in job output`, () => { - test(`sets statusCode to 500`, async () => { - const { statusCode, request } = await getCompletedResponse({ - outputContentType: 'application/html', + it(`doesn't encode output-content for non-specified job-types`, async () => { + const hits = getCompleteHits({ + jobType: 'unencodedJobType', + outputContent: 'test', }); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toMatchInlineSnapshot(` - Array [ - [Error: Unsupported content-type of application/html specified by job output], - [Error: Unsupported content-type of application/html specified by job output], - ] - `); - expect(statusCode).toBe(500); + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(200) + .expect('Content-Type', 'text/plain; charset=utf-8') + .then(({ text }) => expect(text).toEqual('test')); }); - test(`doesn't include job output content in payload`, async () => { - const { payload, request } = await getCompletedResponse({ - outputContentType: 'application/html', + it(`base64 encodes output content for configured jobTypes`, async () => { + const hits = getCompleteHits({ + jobType: 'base64EncodedJobType', + outputContent: 'test', + outputContentType: 'application/pdf', }); - expect(payload).toMatchInlineSnapshot( - `"{\\"statusCode\\":500,\\"error\\":\\"Internal Server Error\\",\\"message\\":\\"An internal server error occurred\\"}"` - ); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toMatchInlineSnapshot(` - Array [ - [Error: Unsupported content-type of application/html specified by job output], - [Error: Unsupported content-type of application/html specified by job output], - ] - `); + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(200) + .expect('Content-Type', 'application/pdf') + .expect('content-disposition', 'inline; filename="report.pdf"') + .then(({ body }) => expect(Buffer.from(body).toString('base64')).toEqual('test')); }); - test(`logs error message about invalid content type`, async () => { - const { request } = await getCompletedResponse({ outputContentType: 'application/html' }); - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toMatchInlineSnapshot(` - Array [ - [Error: Unsupported content-type of application/html specified by job output], - [Error: Unsupported content-type of application/html specified by job output], - ] - `); + it('refuses to return unknown content-types', async () => { + const hits = getCompleteHits({ + jobType: 'unencodedJobType', + outputContent: 'alert("all your base mine now");', + outputContentType: 'application/html', + }); + // @ts-ignore + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + router = httpSetup.createRouter(''); + registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + + await server.start(); + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dank') + .expect(500) + .then(({ body }) => { + expect(body).toEqual({ + error: 'Internal Server Error', + message: 'An internal server error occurred', + statusCode: 500, + }); + }); }); }); }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 270ba789526b6..e3ce3ab5dc8e9 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -202,8 +202,10 @@ export async function registerJobInfoRoutes( const response = await downloadResponseHandler(jobTypes, username, { docId }); - return res.ok({ - body: response.content, + return res.custom({ + body: + typeof response.content === 'string' ? Buffer.from(response.content) : response.content, + statusCode: response.statusCode, headers: { ...response.headers, 'content-type': response.contentType, diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 40e4ed1d48b0f..2f96541464ec3 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -23,7 +23,7 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting plugins: ReportingSetupDeps, logger: Logger ) { - const getUser = getUserFactory(plugins.security, logger); + const getUser = getUserFactory(plugins.security); return function authorizedUserPreRouting(request: KibanaRequest) { const user = getUser(request); @@ -31,8 +31,9 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting if (!user) { throw Boom.unauthorized(`Sorry, you aren't authenticated`); } + const allowedRoles = config.get('roles', 'allow') || []; + const authorizedRoles = [superuserRole, ...allowedRoles]; - const authorizedRoles = [superuserRole, ...(config.get('roles', 'allow') as string[])]; if (!user.roles.find(role => authorizedRoles.includes(role))) { throw Boom.forbidden(`Sorry, you don't have access to Reporting`); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts index 3e0fa158b6f08..e16f5278c8cc7 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -16,7 +16,6 @@ type ExportTypeType = ExportTypeDefinition; interface ErrorFromPayload { message: string; - reason: string | null; } // A camelCase version of JobDocOutput @@ -72,12 +71,13 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist }; } + // @TODO: These should be semantic HTTP codes as 500/503's indicate + // error then these are really operating properly. function getFailure(output: JobDocOutput): Payload { return { statusCode: 500, content: { - message: 'Reporting generation failed', - reason: output.content, + message: `Reporting generation failed: ${output.content}`, }, contentType: 'application/json', headers: {}, @@ -88,7 +88,7 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist return { statusCode: 503, content: status, - contentType: 'application/json', + contentType: 'text/plain', headers: { 'retry-after': 30 }, }; } diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts index 819636b714631..01b9f6cbd9cd6 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts @@ -4,9 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ServerFacade } from '../server/types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createHttpServer, createCoreContext } from 'src/core/server/http/test_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { coreMock } from 'src/core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ContextService } from 'src/core/server/context/context_service'; -export const createMockServer = (): ServerFacade => { - const mockServer = {}; - return mockServer as any; +const coreId = Symbol('reporting'); + +export const createMockServer = async () => { + const coreContext = createCoreContext({ coreId }); + const contextService = new ContextService(coreContext); + + const server = createHttpServer(coreContext); + const httpSetup = await server.setup({ + context: contextService.setup({ pluginDependencies: new Map() }), + }); + const handlerContext = coreMock.createRequestHandlerContext(); + + httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { + return handlerContext; + }); + + return { + server, + httpSetup, + handlerContext, + }; }; From ed1ac8cbff30a66232e20f2b5fc5a97c5a2572d2 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Tue, 26 May 2020 15:49:43 -0700 Subject: [PATCH 07/31] WIP: move user checks to higher-order func --- .../plugins/reporting/server/lib/get_user.ts | 4 +- .../server/lib/iso_string_validate.ts | 6 +-- .../server/routes/generate_from_jobparams.ts | 42 ++++++++++------ .../reporting/server/routes/generation.ts | 42 ++++++++-------- .../plugins/reporting/server/routes/jobs.ts | 50 +++++++++---------- .../routes/lib/authorized_user_pre_routing.ts | 39 +++++++++------ .../reporting/server/routes/types.d.ts | 7 ++- 7 files changed, 106 insertions(+), 84 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/lib/get_user.ts b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts index b9f986349fce7..164ffc5742d04 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/get_user.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SecurityPluginSetup } from '../../../../../plugins/security/server'; import { KibanaRequest } from '../../../../../../src/core/server'; -import { ReportingSetupDeps } from '../types'; -export function getUserFactory(security: ReportingSetupDeps['security']) { +export function getUserFactory(security?: SecurityPluginSetup) { return (request: KibanaRequest) => { return security?.authc.getCurrentUser(request) ?? null; }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts b/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts index 05e685b2ec76f..6dd8f81bffa90 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.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. */ +const isoValidationMessage = `value must be a valid ISO format`; export const isoStringValidate = (input: string): string | undefined => { - const message = `value must be a valid ISO format`; if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(input)) { - return message; + return isoValidationMessage; } if (new Date(input).toISOString() !== input) { - return message; + return isoValidationMessage; } }; diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 1a1740c31bf6e..f7ce1bdd38294 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; import rison from 'rison-node'; import { IRouter, IBasePath } from 'src/core/server'; import { schema } from '@kbn/config-schema'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { HandlerErrorFunction, HandlerFunction } from './types'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; -import { LevelLogger as Logger } from '../lib'; import { ReportingSetupDeps } from '../types'; import { makeRequestFacade } from './lib/make_request_facade'; @@ -23,10 +22,11 @@ export function registerGenerateFromJobParams( router: IRouter, basePath: IBasePath['get'], handler: HandlerFunction, - handleError: HandlerErrorFunction, - logger: Logger + handleError: HandlerErrorFunction ) { - // generate report + const config = reporting.getConfig(); + const userHandler = authorizedUserPreRoutingFactory(config, plugins); + router.post( { path: `${BASE_GENERATE}/{exportType}`, @@ -46,7 +46,8 @@ export function registerGenerateFromJobParams( }), }, }, - router.handleLegacyErrors(async (context, req, res) => { + userHandler(async (user, context, req, res) => { + const { username } = user; const request = makeRequestFacade(context, req, basePath); let jobParamsRison: string | null; @@ -63,26 +64,35 @@ export function registerGenerateFromJobParams( } if (!jobParamsRison) { - throw boom.badRequest('A jobParams RISON string is required'); + return res.customError({ + statusCode: 400, + body: 'A jobParams RISON string is required', + }); } const { exportType } = request.params as { exportType: string }; let jobParams; - let response; + try { jobParams = rison.decode(jobParamsRison) as object | null; if (!jobParams) { - throw new Error('missing jobParams!'); + return res.customError({ + statusCode: 400, + body: 'Missing jobParams!', + }); } } catch (err) { - throw boom.badRequest(`invalid rison: ${jobParamsRison}`); + return res.customError({ + statusCode: 400, + body: `invalid rison: ${jobParamsRison}`, + }); } + try { - response = await handler(exportType, jobParams, request, res); + return await handler(username, exportType, jobParams, request, res); } catch (err) { - throw handleError(exportType, err); + return handleError(exportType, err, res); } - return response; }) ); @@ -92,8 +102,8 @@ export function registerGenerateFromJobParams( path: `${BASE_GENERATE}/{p*}`, validate: false, }, - router.handleLegacyErrors(() => { - throw boom.methodNotAllowed('GET is not allowed'); - }) + (context, req, res) => { + return res.customError({ statusCode: 405, body: 'GET is not allowed' }); + } ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index dd6fa3af963f5..a56e44afca5ec 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -4,17 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; import { errors as elasticsearchErrors } from 'elasticsearch'; import { IRouter, IBasePath, kibanaResponseFactory } from 'src/core/server'; -import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; import { LevelLogger as Logger } from '../lib'; -import { ReportingSetupDeps, RequestFacade } from '../types'; +import { ReportingSetupDeps } from '../types'; import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; +import { HandlerFunction } from './types'; const esErrors = elasticsearchErrors as Record; @@ -32,23 +31,15 @@ export function registerJobGenerationRoutes( /* * Generates enqueued job details to use in responses */ - async function handler( - exportTypeId: string, - jobParams: object, - r: RequestFacade, - h: typeof kibanaResponseFactory - ) { + const handler: HandlerFunction = async (username, exportTypeId, jobParams, r, h) => { const licenseInfo = reporting.getLicenseInfo(); const licenseResults = licenseInfo[exportTypeId]; if (!licenseResults.enableLinks) { - throw boom.forbidden(licenseResults.message); + return h.forbidden({ body: licenseResults.message }); } - const getUser = authorizedUserPreRoutingFactory(config, plugins, logger); - const { username } = getUser(r.getRawRequest()); const { headers } = r; - const enqueueJob = await reporting.getEnqueueJob(); const job = await enqueueJob(exportTypeId, jobParams, username, headers, r); @@ -64,22 +55,33 @@ export function registerJobGenerationRoutes( job: jobJson, }, }); - } + }; - function handleError(exportTypeId: string, err: Error) { + function handleError(exportTypeId: string, err: Error, res: typeof kibanaResponseFactory) { if (err instanceof esErrors['401']) { - throw boom.unauthorized(`Sorry, you aren't authenticated`); + return res.unauthorized({ + body: `Sorry, you aren't authenticated`, + }); } + if (err instanceof esErrors['403']) { - throw boom.forbidden(`Sorry, you are not authorized to create ${exportTypeId} reports`); + return res.forbidden({ + body: `Sorry, you are not authorized to create ${exportTypeId} reports`, + }); } + if (err instanceof esErrors['404']) { - throw boom.boomify(err, { statusCode: 404 }); + return res.notFound({ + body: err.message, + }); } - throw err; + + return res.badRequest({ + body: err.message, + }); } - registerGenerateFromJobParams(reporting, plugins, router, basePath, handler, handleError, logger); + registerGenerateFromJobParams(reporting, plugins, router, basePath, handler, handleError); // Register beta panel-action download-related API's if (config.get('csv', 'enablePanelActionDownload')) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index e3ce3ab5dc8e9..5056b4f99e011 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -9,14 +9,12 @@ import { IRouter, IBasePath } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; -import { LevelLogger as Logger } from '../lib'; import { jobsQueryFactory } from '../lib/jobs_query'; import { ReportingSetupDeps } from '../types'; import { deleteJobResponseHandlerFactory, downloadJobResponseHandlerFactory, } from './lib/job_response_handler'; -import { makeRequestFacade } from './lib/make_request_facade'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; interface ListQuery { @@ -29,12 +27,10 @@ const MAIN_ENTRY = `${API_BASE_URL}/jobs`; export async function registerJobInfoRoutes( reporting: ReportingCore, plugins: ReportingSetupDeps, - router: IRouter, - basePath: IBasePath['get'], - logger: Logger + router: IRouter ) { const config = reporting.getConfig(); - const getUser = authorizedUserPreRoutingFactory(config, plugins, logger); + const userHandler = authorizedUserPreRoutingFactory(config, plugins); const { elasticsearch } = plugins; const jobsQuery = jobsQueryFactory(config, elasticsearch); @@ -44,13 +40,16 @@ export async function registerJobInfoRoutes( path: `${MAIN_ENTRY}/list`, validate: false, }, - router.handleLegacyErrors(async (context, req, res) => { - const request = makeRequestFacade(context, req, basePath); + userHandler(async (user, context, req, res) => { const { management: { jobTypes = [] }, } = reporting.getLicenseInfo(); - const { username } = getUser(request.getRawRequest()); - const { page: queryPage, size: querySize, ids: queryIds } = request.query as ListQuery; + const { username } = user; + const { + page: queryPage = '0', + size: querySize = '10', + ids: queryIds = null, + } = req.query as ListQuery; const page = parseInt(queryPage, 10) || 0; const size = Math.min(100, parseInt(querySize, 10) || 10); const jobIds = queryIds ? queryIds.split(',') : null; @@ -71,9 +70,8 @@ export async function registerJobInfoRoutes( path: `${MAIN_ENTRY}/count`, validate: false, }, - router.handleLegacyErrors(async (context, req, res) => { - const request = makeRequestFacade(context, req, basePath); - const { username } = getUser(request.getRawRequest()); + userHandler(async (user, context, req, res) => { + const { username } = user; const { management: { jobTypes = [] }, } = reporting.getLicenseInfo(); @@ -99,10 +97,9 @@ export async function registerJobInfoRoutes( }), }, }, - router.handleLegacyErrors(async (context, req, res) => { - const request = makeRequestFacade(context, req, basePath); - const { username } = getUser(request.getRawRequest()); - const { docId } = req.params; + userHandler(async (user, context, req, res) => { + const { username } = user; + const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = reporting.getLicenseInfo(); @@ -140,10 +137,9 @@ export async function registerJobInfoRoutes( }), }, }, - router.handleLegacyErrors(async (context, req, res) => { - const request = makeRequestFacade(context, req, basePath); - const { username } = getUser(request.getRawRequest()); - const { docId } = req.params; + userHandler(async (user, context, req, res) => { + const { username } = user; + const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = reporting.getLicenseInfo(); @@ -193,9 +189,9 @@ export async function registerJobInfoRoutes( }), }, }, - router.handleLegacyErrors(async (context, req, res) => { - const { username } = getUser(req); - const { docId } = req.params; + userHandler(async (user, context, req, res) => { + const { username } = user; + const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = reporting.getLicenseInfo(); @@ -225,9 +221,9 @@ export async function registerJobInfoRoutes( }), }, }, - router.handleLegacyErrors(async (context, req, res) => { - const { username } = getUser(req); - const { docId } = req.params; + userHandler(async (user, context, req, res) => { + const { username } = user; + const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = reporting.getLicenseInfo(); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 2f96541464ec3..fd14498446db2 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -5,10 +5,9 @@ */ import Boom from 'boom'; -import { KibanaRequest } from 'src/core/server'; +import { KibanaRequest, RequestHandler, RouteMethod } from 'src/core/server'; import { AuthenticatedUser } from '../../../../../../plugins/security/server'; import { ReportingConfig } from '../../../server'; -import { LevelLogger as Logger } from '../../../server/lib'; import { getUserFactory } from '../../lib/get_user'; import { ReportingSetupDeps } from '../../types'; @@ -18,26 +17,36 @@ export type PreRoutingFunction = ( request: KibanaRequest ) => Promise | AuthenticatedUser | null>; +export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer R + ? (user: AuthenticatedUser, ...a: U) => R + : never; + export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( config: ReportingConfig, - plugins: ReportingSetupDeps, - logger: Logger + plugins: ReportingSetupDeps ) { const getUser = getUserFactory(plugins.security); - return function authorizedUserPreRouting(request: KibanaRequest) { - const user = getUser(request); + return (handler: RequestHandlerUser): RequestHandler => { + return (context, req, res) => { + const user = getUser(req); + + if (!user) { + return res.unauthorized({ + body: `Sorry, you aren't authenticated`, + }); + } - if (!user) { - throw Boom.unauthorized(`Sorry, you aren't authenticated`); - } - const allowedRoles = config.get('roles', 'allow') || []; - const authorizedRoles = [superuserRole, ...allowedRoles]; + const allowedRoles = config.get('roles', 'allow') || []; + const authorizedRoles = [superuserRole, ...allowedRoles]; - if (!user.roles.find(role => authorizedRoles.includes(role))) { - throw Boom.forbidden(`Sorry, you don't have access to Reporting`); - } + if (!user.roles.find((role) => authorizedRoles.includes(role))) { + return res.forbidden({ + body: `Sorry, you don't have access to Reporting`, + }); + } - return user; + return handler(user, context, req, res); + }; }; }; diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts index 11f56a8255dac..e787152137433 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts @@ -8,13 +8,18 @@ import { kibanaResponseFactory } from 'src/core/server'; import { JobDocPayload, RequestFacade } from '../types'; export type HandlerFunction = ( + username: string, exportType: string, jobParams: object, request: RequestFacade, h: typeof kibanaResponseFactory ) => any; -export type HandlerErrorFunction = (exportType: string, err: Error) => any; +export type HandlerErrorFunction = ( + exportType: string, + err: Error, + res: typeof kibanaResponseFactory +) => any; export interface QueuedJobPayload { error?: boolean; From 756551f7156fb5a858cae596f6de5776962dd8bf Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Tue, 26 May 2020 16:18:18 -0700 Subject: [PATCH 08/31] Move more handler logic over to Response factory vs Boom --- .../routes/generate_from_savedobject.ts | 21 +++++--- .../generate_from_savedobject_immediate.ts | 4 +- .../plugins/reporting/server/routes/index.ts | 2 +- .../plugins/reporting/server/routes/jobs.ts | 23 ++------- .../server/routes/lib/job_response_handler.ts | 50 ++++++++++++++----- 5 files changed, 59 insertions(+), 41 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 67bd6aaf9bbe3..afd0c82b174a9 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -15,6 +15,7 @@ import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject import { LevelLogger as Logger } from '../lib'; import { ReportingSetupDeps } from '../types'; import { makeRequestFacade } from './lib/make_request_facade'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; /* * This function registers API Endpoints for queuing Reporting jobs. The API inputs are: @@ -34,6 +35,7 @@ export function registerGenerateCsvFromSavedObject( handleRouteError: HandlerErrorFunction, logger: Logger ) { + const userHandler = authorizedUserPreRoutingFactory(reporting.getConfig(), plugins); router.post( { path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, @@ -56,8 +58,9 @@ export function registerGenerateCsvFromSavedObject( }), }, }, - router.handleLegacyErrors(async (context, req, res) => { + userHandler(async (user, context, req, res) => { const requestFacade = makeRequestFacade(context, req, basePath); + const { username } = user; /* * 1. Build `jobParams` object: job data that execution will need to reference in various parts of the lifecycle @@ -67,15 +70,21 @@ export function registerGenerateCsvFromSavedObject( let result: QueuedJobPayload; try { const jobParams = getJobParamsFromRequest(requestFacade, { isImmediate: false }); - result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, requestFacade, res); + result = await handleRoute( + username, + CSV_FROM_SAVEDOBJECT_JOB_TYPE, + jobParams, + requestFacade, + res + ); } catch (err) { - throw handleRouteError(CSV_FROM_SAVEDOBJECT_JOB_TYPE, err); + return handleRouteError(CSV_FROM_SAVEDOBJECT_JOB_TYPE, err, res); } if (get(result, 'source.job') == null) { - throw new Error( - `The Export handler is expected to return a result with job info! ${result}` - ); + return res.badRequest({ + body: `The Export handler is expected to return a result with job info! ${result}`, + }); } return res.ok({ diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 7efc564cb1c7c..a1821d9d0b223 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -15,6 +15,7 @@ import { isoStringValidate } from '../lib/iso_string_validate'; import { makeRequestFacade } from './lib/make_request_facade'; import { LevelLogger as Logger } from '../lib'; import { JobDocOutput, ReportingSetupDeps } from '../types'; +import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; /* * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: @@ -32,6 +33,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( basePath: IBasePath['get'], parentLogger: Logger ) { + const userHandler = authorizedUserPreRoutingFactory(reporting.getConfig(), plugins); /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: * - re-use the createJob function to build up es query config @@ -59,7 +61,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( }), }, }, - router.handleLegacyErrors(async (context, req, res) => { + userHandler(async (user, context, req, res) => { const request = makeRequestFacade(context, req, basePath); const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(request, { isImmediate: true }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index 131d9029fabe0..edc09fbe0b1b5 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -19,5 +19,5 @@ export function registerRoutes( logger: Logger ) { registerJobGenerationRoutes(reporting, plugins, router, basePath, logger); - registerJobInfoRoutes(reporting, plugins, router, basePath, logger); + registerJobInfoRoutes(reporting, plugins, router); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 5056b4f99e011..15d0216d1ff7c 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { IRouter, IBasePath } from 'src/core/server'; +import { IRouter } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; @@ -196,17 +196,7 @@ export async function registerJobInfoRoutes( management: { jobTypes = [] }, } = reporting.getLicenseInfo(); - const response = await downloadResponseHandler(jobTypes, username, { docId }); - - return res.custom({ - body: - typeof response.content === 'string' ? Buffer.from(response.content) : response.content, - statusCode: response.statusCode, - headers: { - ...response.headers, - 'content-type': response.contentType, - }, - }); + return downloadResponseHandler(res, jobTypes, username, { docId }); }) ); @@ -228,14 +218,7 @@ export async function registerJobInfoRoutes( management: { jobTypes = [] }, } = reporting.getLicenseInfo(); - const response = await deleteResponseHandler(jobTypes, username, { docId }); - - return res.ok({ - body: response, - headers: { - 'content-type': 'application/json', - }, - }); + return deleteResponseHandler(res, jobTypes, username, { docId }); }) ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts index c0e142ea0f419..5bab0870cb17b 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { ElasticsearchServiceSetup } from 'kibana/server'; +import { ElasticsearchServiceSetup, kibanaResponseFactory } from 'kibana/server'; import { ReportingConfig } from '../../'; import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; import { ExportTypesRegistry } from '../../lib/export_types_registry'; @@ -29,6 +29,7 @@ export function downloadJobResponseHandlerFactory( const getDocumentPayload = getDocumentPayloadFactory(exportTypesRegistry); return async function jobResponseHandler( + res: typeof kibanaResponseFactory, validJobTypes: string[], username: string, params: JobResponseHandlerParams, @@ -37,23 +38,34 @@ export function downloadJobResponseHandlerFactory( const { docId } = params; const doc = await jobsQuery.get(username, docId, { includeContent: !opts.excludeContent }); - if (!doc) throw Boom.notFound(); + if (!doc) { + return res.notFound(); + } const { jobtype: jobType } = doc._source; if (!validJobTypes.includes(jobType)) { - throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); + return res.unauthorized({ + body: `Sorry, you are not authorized to download ${jobType} reports`, + }); } - const output = getDocumentPayload(doc); + const response = getDocumentPayload(doc); - if (!WHITELISTED_JOB_CONTENT_TYPES.includes(output.contentType)) { - throw Boom.badImplementation( - `Unsupported content-type of ${output.contentType} specified by job output` - ); + if (!WHITELISTED_JOB_CONTENT_TYPES.includes(response.contentType)) { + return res.badRequest({ + body: `Unsupported content-type of ${response.contentType} specified by job output`, + }); } - return output; + return res.custom({ + body: typeof response.content === 'string' ? Buffer.from(response.content) : response.content, + statusCode: response.statusCode, + headers: { + ...response.headers, + 'content-type': response.contentType, + }, + }); }; } @@ -64,25 +76,37 @@ export function deleteJobResponseHandlerFactory( const jobsQuery = jobsQueryFactory(config, elasticsearch); return async function deleteJobResponseHander( + res: typeof kibanaResponseFactory, validJobTypes: string[], username: string, params: JobResponseHandlerParams ) { const { docId } = params; const doc = await jobsQuery.get(username, docId, { includeContent: false }); - if (!doc) throw Boom.notFound(); + + if (!doc) { + return res.notFound(); + } const { jobtype: jobType } = doc._source; + if (!validJobTypes.includes(jobType)) { - throw Boom.unauthorized(`Sorry, you are not authorized to delete ${jobType} reports`); + return res.unauthorized({ + body: `Sorry, you are not authorized to delete ${jobType} reports`, + }); } try { const docIndex = doc._index; await jobsQuery.delete(docIndex, docId); - return { deleted: true }; + return res.ok({ + body: { deleted: true }, + }); } catch (error) { - throw Boom.boomify(error, { statusCode: error.statusCode }); + return res.customError({ + statusCode: error.statusCode, + body: error.message, + }); } }; } From 7e6efa2928dce76e1af6c2bc2897ddee116748c8 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Wed, 27 May 2020 11:00:17 -0700 Subject: [PATCH 09/31] Major refactor to consolidate types, remove facades, and udpate helpers --- .../export_types/csv/server/create_job.ts | 21 +-- .../csv/server/execute_job.test.ts | 161 +++++++++++++----- .../export_types/csv/server/execute_job.ts | 7 +- .../server/create_job/create_job.ts | 23 +-- .../server/execute_job.ts | 31 ++-- .../server/lib/generate_csv.ts | 14 +- .../server/lib/generate_csv_search.ts | 20 ++- .../server/lib/get_job_params_from_request.ts | 4 +- .../png/server/create_job/index.ts | 20 +-- .../png/server/execute_job/index.test.ts | 12 +- .../png/server/execute_job/index.ts | 6 +- .../printable_pdf/server/create_job/index.ts | 18 +- .../server/execute_job/index.test.ts | 13 +- .../printable_pdf/server/execute_job/index.ts | 6 +- .../legacy/plugins/reporting/server/core.ts | 28 +-- .../reporting/server/lib/check_license.ts | 4 +- .../reporting/server/lib/create_queue.ts | 8 +- .../reporting/server/lib/create_worker.ts | 15 +- .../reporting/server/lib/enqueue_job.ts | 25 ++- .../legacy/plugins/reporting/server/plugin.ts | 24 ++- .../server/routes/generate_from_jobparams.ts | 23 +-- .../routes/generate_from_savedobject.ts | 21 +-- .../generate_from_savedobject_immediate.ts | 31 ++-- .../reporting/server/routes/generation.ts | 34 ++-- .../plugins/reporting/server/routes/index.ts | 17 +- .../reporting/server/routes/jobs.test.ts | 119 ++++++------- .../plugins/reporting/server/routes/jobs.ts | 10 +- .../routes/lib/authorized_user_pre_routing.ts | 17 +- .../server/routes/lib/job_response_handler.ts | 1 - .../routes/lib/make_request_facade.test.ts | 69 -------- .../server/routes/lib/make_request_facade.ts | 29 ---- .../reporting/server/routes/types.d.ts | 11 +- .../legacy/plugins/reporting/server/types.ts | 23 +-- .../create_mock_reportingplugin.ts | 4 +- 34 files changed, 403 insertions(+), 466 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts delete mode 100644 x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts index 8320cd05aa2e7..3e258cd8567a8 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts @@ -4,30 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { ReportingCore } from '../../../server'; import { cryptoFactory } from '../../../server/lib'; -import { - ConditionalHeaders, - CreateJobFactory, - ESQueueCreateJobFn, - RequestFacade, -} from '../../../server/types'; +import { CreateJobFactory, ESQueueCreateJobFn } from '../../../server/types'; import { JobParamsDiscoverCsv } from '../types'; +import { ReportingInternalSetup } from '../../../server/core'; export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore) { +>> = function createJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); return async function createJob( jobParams: JobParamsDiscoverCsv, - headers: ConditionalHeaders['headers'], - request: RequestFacade + context: RequestHandlerContext, + request: KibanaRequest ) { - const serializedEncryptedHeaders = await crypto.encrypt(headers); + const serializedEncryptedHeaders = await crypto.encrypt(request.headers); - const savedObjectsClient = request.getSavedObjectsClient(); + const savedObjectsClient = context.core.savedObjects.client; const indexPatternSavedObject = await savedObjectsClient.get( 'index-pattern', jobParams.indexPatternId! @@ -36,7 +33,7 @@ export const createJobFactory: CreateJobFactory new Promise((resolve) => setTimeout(() => resolve(), ms)); @@ -114,7 +115,9 @@ describe('CSV Execute Job', function () { describe('basic Elasticsearch call behavior', function () { it('should decrypt encrypted headers and pass to callAsCurrentUser', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); await executeJob( 'job456', getJobDocPayload({ @@ -134,7 +137,9 @@ describe('CSV Execute Job', function () { testBody: true, }; - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const job = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -161,7 +166,9 @@ describe('CSV Execute Job', function () { _scroll_id: scrollId, }); callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); await executeJob( 'job456', getJobDocPayload({ @@ -179,7 +186,9 @@ describe('CSV Execute Job', function () { }); it('should not execute scroll if there are no hits from the search', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); await executeJob( 'job456', getJobDocPayload({ @@ -213,7 +222,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); await executeJob( 'job456', getJobDocPayload({ @@ -252,7 +263,9 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); await executeJob( 'job456', getJobDocPayload({ @@ -284,7 +297,9 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -311,7 +326,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -336,7 +353,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -362,7 +381,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -388,7 +409,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -414,7 +437,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -441,7 +466,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -462,7 +489,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -485,7 +514,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -506,7 +537,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -522,7 +555,9 @@ describe('CSV Execute Job', function () { describe('Elasticsearch call errors', function () { it('should reject Promise if search call errors out', async function () { callAsCurrentUserStub.rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -541,7 +576,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); callAsCurrentUserStub.onSecondCall().rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -562,7 +599,9 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -583,7 +622,9 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -611,7 +652,9 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -639,7 +682,9 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -675,7 +720,9 @@ describe('CSV Execute Job', function () { }); it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); executeJob( 'job345', getJobDocPayload({ @@ -694,7 +741,9 @@ describe('CSV Execute Job', function () { }); it(`shouldn't call clearScroll if it never got a scrollId`, async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); executeJob( 'job345', getJobDocPayload({ @@ -712,7 +761,9 @@ describe('CSV Execute Job', function () { }); it('should call clearScroll if it got a scrollId', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); executeJob( 'job345', getJobDocPayload({ @@ -734,7 +785,9 @@ describe('CSV Execute Job', function () { describe('csv content', function () { it('should write column headers to output, even if there are no results', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -746,7 +799,9 @@ describe('CSV Execute Job', function () { it('should use custom uiSettings csv:separator for header', async function () { mockUiSettingsClient.get.withArgs('csv:separator').returns(';'); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -758,7 +813,9 @@ describe('CSV Execute Job', function () { it('should escape column headers if uiSettings csv:quoteValues is true', async function () { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -770,7 +827,9 @@ describe('CSV Execute Job', function () { it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(false); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -781,7 +840,9 @@ describe('CSV Execute Job', function () { }); it('should write column headers to output, when there are results', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ one: '1', two: '2' }], @@ -801,7 +862,9 @@ describe('CSV Execute Job', function () { }); it('should use comma separated values of non-nested fields from _source', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -822,7 +885,9 @@ describe('CSV Execute Job', function () { }); it('should concatenate the hits from multiple responses', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -850,7 +915,9 @@ describe('CSV Execute Job', function () { }); it('should use field formatters to format fields', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -892,7 +959,9 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(1); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -922,7 +991,9 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -959,7 +1030,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -998,7 +1071,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1035,7 +1110,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1061,7 +1138,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1087,7 +1166,9 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const executeJob = await executeJobFactory(mockReportingPlugin, { + logger: mockLogger, + } as ReportingInternalSetup); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index 7d95c45d5d233..1e450ac99c115 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -9,19 +9,20 @@ import Hapi from 'hapi'; import { IUiSettingsClient, KibanaRequest } from '../../../../../../../src/core/server'; import { CSV_BOM_CHARS, CSV_JOB_TYPE } from '../../../common/constants'; import { ReportingCore } from '../../../server'; -import { cryptoFactory, LevelLogger } from '../../../server/lib'; +import { cryptoFactory } from '../../../server/lib'; import { getFieldFormats } from '../../../server/services'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory } from '../../../server/types'; import { JobDocPayloadDiscoverCsv } from '../types'; import { fieldFormatMapFactory } from './lib/field_format_map'; import { createGenerateCsv } from './lib/generate_csv'; +import { ReportingInternalSetup } from '../../../server/core'; export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { +>> = async function executeJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = parentLogger.clone([CSV_JOB_TYPE, 'execute-job']); + const logger = deps.logger.clone([CSV_JOB_TYPE, 'execute-job']); const serverBasePath = config.kbnConfig.get('server', 'basePath'); return async function executeJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts index 36572db7a54db..9bf28d99320b1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts @@ -6,10 +6,12 @@ import { notFound, notImplemented } from 'boom'; import { get } from 'lodash'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { ReportingInternalSetup } from '../../../../server/core'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; -import { cryptoFactory, LevelLogger } from '../../../../server/lib'; -import { CreateJobFactory, RequestFacade, TimeRangeParams } from '../../../../server/types'; +import { cryptoFactory } from '../../../../server/lib'; +import { CreateJobFactory, TimeRangeParams } from '../../../../server/types'; import { JobDocPayloadPanelCsv, JobParamsPanelCsv, @@ -23,8 +25,9 @@ import { createJobSearch } from './create_job_search'; export type ImmediateCreateJobFn = ( jobParams: JobParamsType, - headers: Record, - req: RequestFacade + headers: KibanaRequest['headers'], + context: RequestHandlerContext, + req: KibanaRequest ) => Promise<{ type: string | null; title: string; @@ -39,22 +42,22 @@ interface VisData { export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { +>> = function createJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); + const logger = deps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); return async function createJob( jobParams: JobParamsPanelCsv, - headers: any, - req: RequestFacade + headers: KibanaRequest['headers'], + context: RequestHandlerContext, + req: KibanaRequest ): Promise { const { savedObjectType, savedObjectId } = jobParams; const serializedEncryptedHeaders = await crypto.encrypt(headers); - const client = req.getSavedObjectsClient(); const { panel, title, visType }: VisData = await Promise.resolve() - .then(() => client.get(savedObjectType, savedObjectId)) + .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId)) .then(async (savedObject: SavedObject) => { const { attributes, references } = savedObject; const { diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index 5761a98ed160c..09222aab9ccab 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -5,18 +5,15 @@ */ import { i18n } from '@kbn/i18n'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; import { ReportingCore } from '../../../server'; -import { cryptoFactory, LevelLogger } from '../../../server/lib'; -import { - ExecuteJobFactory, - JobDocOutput, - JobDocPayload, - RequestFacade, -} from '../../../server/types'; +import { cryptoFactory } from '../../../server/lib'; +import { ExecuteJobFactory, JobDocOutput, JobDocPayload } from '../../../server/types'; import { CsvResultFromSearch } from '../../csv/types'; import { FakeRequest, JobDocPayloadPanelCsv, JobParamsPanelCsv, SearchPanel } from '../types'; import { createGenerateCsv } from './lib'; +import { ReportingInternalSetup } from '../../../server/core'; /* * ImmediateExecuteFn receives the job doc payload because the payload was @@ -25,21 +22,23 @@ import { createGenerateCsv } from './lib'; export type ImmediateExecuteFn = ( jobId: null, job: JobDocPayload, - request: RequestFacade + context: RequestHandlerContext, + req: KibanaRequest ) => Promise; export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { +>> = async function executeJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); - const generateCsv = createGenerateCsv(reporting, parentLogger); + const logger = deps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); + const generateCsv = createGenerateCsv(reporting, deps); return async function executeJob( jobId: string | null, job: JobDocPayloadPanelCsv, - realRequest?: RequestFacade + context, + req ): Promise { // There will not be a jobID for "immediate" generation. // jobID is only for "queued" jobs @@ -58,10 +57,11 @@ export const executeJobFactory: ExecuteJobFactory; @@ -103,6 +103,7 @@ export const executeJobFactory: ExecuteJobFactory { }; export async function generateCsvSearch( - req: RequestFacade, reporting: ReportingCore, - logger: LevelLogger, + deps: ReportingInternalSetup, + context: RequestHandlerContext, + req: KibanaRequest, searchPanel: SearchPanel, jobParams: JobParamsDiscoverCsv ): Promise { - const savedObjectsClient = req.getSavedObjectsClient(); + const savedObjectsClient = context.core.savedObjects.client; const { indexPatternSavedObjectId, timerange } = searchPanel; const savedSearchObjectAttr = searchPanel.attributes; const { indexPatternSavedObject } = await getDataSource( @@ -145,7 +149,7 @@ export async function generateCsvSearch( const config = reporting.getConfig(); const elasticsearch = await reporting.getElasticsearchService(); - const { callAsCurrentUser } = elasticsearch.dataClient.asScoped(req.getRawRequest()); + const { callAsCurrentUser } = elasticsearch.dataClient.asScoped(req); const callCluster = (...params: [string, object]) => callAsCurrentUser(...params); const uiSettings = await getUiSettings(uiConfig); @@ -166,7 +170,7 @@ export async function generateCsvSearch( }, }; - const generateCsv = createGenerateCsv(logger); + const generateCsv = createGenerateCsv(deps.logger); return { type: 'CSV from Saved Search', diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts index 7a224094dbcdd..1e3303cbd1a9a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestFacade } from '../../../../server/types'; +import { KibanaRequest } from 'src/core/server'; import { JobParamsPanelCsv, JobParamsPostPayloadPanelCsv } from '../../types'; export function getJobParamsFromRequest( - request: RequestFacade, + request: KibanaRequest, opts: { isImmediate: boolean } ): JobParamsPanelCsv { const { savedObjectType, savedObjectId } = request.params as { diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts index b19513de29eee..815158cddb0f6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts @@ -5,28 +5,22 @@ */ import { validateUrls } from '../../../../common/validate_urls'; -import { ReportingCore } from '../../../../server'; import { cryptoFactory } from '../../../../server/lib'; -import { - ConditionalHeaders, - CreateJobFactory, - ESQueueCreateJobFn, - RequestFacade, -} from '../../../../server/types'; +import { CreateJobFactory, ESQueueCreateJobFn } from '../../../../server/types'; import { JobParamsPNG } from '../../types'; export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore) { +>> = function createJobFactoryFn(reporting, deps) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); return async function createJob( - { objectType, title, relativeUrl, browserTimezone, layout }: JobParamsPNG, - headers: ConditionalHeaders['headers'], - request: RequestFacade + { objectType, title, relativeUrl, browserTimezone, layout }, + context, + req ) { - const serializedEncryptedHeaders = await crypto.encrypt(headers); + const serializedEncryptedHeaders = await crypto.encrypt(req.headers); validateUrls([relativeUrl]); @@ -37,7 +31,7 @@ export const createJobFactory: CreateJobFactory ({ generatePngObservableFactory: jest.fn() })); @@ -28,7 +29,10 @@ const mockLoggerFactory = { warn: jest.fn(), })), }; -const getMockLogger = () => new LevelLogger(mockLoggerFactory); +const getMockDeps = () => + ({ + logger: new LevelLogger(mockLoggerFactory), + } as ReportingInternalSetup); const mockEncryptionKey = 'abcabcsecuresecret'; const encryptHeaders = async (headers: Record) => { @@ -77,7 +81,7 @@ test(`passes browserTimezone to generatePng`, async () => { const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const executeJob = await executeJobFactory(mockReporting, getMockDeps()); const browserTimezone = 'UTC'; await executeJob( 'pngJobId', @@ -121,7 +125,7 @@ test(`passes browserTimezone to generatePng`, async () => { }); test(`returns content_type of application/png`, async () => { - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const executeJob = await executeJobFactory(mockReporting, getMockDeps()); const encryptedHeaders = await encryptHeaders({}); const generatePngObservable = await generatePngObservableFactory(mockReporting); @@ -140,7 +144,7 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => { const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ base64: testContent })); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const executeJob = await executeJobFactory(mockReporting, getMockDeps()); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pngJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index 0d0a9e748682a..465b72de29609 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -7,9 +7,9 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; +import { ReportingInternalSetup } from '../../../../server/core'; import { PNG_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; -import { LevelLogger } from '../../../../server/lib'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../server/types'; import { decryptJobHeaders, @@ -24,11 +24,11 @@ type QueuedPngExecutorFactory = ExecuteJobFactory> = function createJobFactoryFn(reporting: ReportingCore) { +>> = function createJobFactoryFn(reporting, deps) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); return async function createJobFn( { title, relativeUrls, browserTimezone, layout, objectType }: JobParamsPDF, - headers: ConditionalHeaders['headers'], - request: RequestFacade + context, + req ) { - const serializedEncryptedHeaders = await crypto.encrypt(headers); + const serializedEncryptedHeaders = await crypto.encrypt(req.headers); validateUrls(relativeUrls); return { - basePath: request.getBasePath(), + basePath: deps.basePath(req), browserTimezone, forceNow: new Date().toISOString(), headers: serializedEncryptedHeaders, diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts index b081521fef8dd..5fdd36f07ed43 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts @@ -14,6 +14,7 @@ import { createMockReportingCore } from '../../../../test_helpers'; import { JobDocPayloadPDF } from '../../types'; import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { executeJobFactory } from './index'; +import { ReportingInternalSetup } from '../../../../server/core'; let mockReporting: ReportingCore; @@ -28,7 +29,10 @@ const mockLoggerFactory = { warn: jest.fn(), })), }; -const getMockLogger = () => new LevelLogger(mockLoggerFactory); +const getMockDeps = () => + ({ + logger: new LevelLogger(mockLoggerFactory), + } as ReportingInternalSetup); const mockEncryptionKey = 'testencryptionkey'; const encryptHeaders = async (headers: Record) => { @@ -75,7 +79,7 @@ test(`passes browserTimezone to generatePdf`, async () => { const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const executeJob = await executeJobFactory(mockReporting, getMockDeps()); const browserTimezone = 'UTC'; await executeJob( 'pdfJobId', @@ -123,8 +127,7 @@ test(`passes browserTimezone to generatePdf`, async () => { }); test(`returns content_type of application/pdf`, async () => { - const logger = getMockLogger(); - const executeJob = await executeJobFactory(mockReporting, logger); + const executeJob = await executeJobFactory(mockReporting, getMockDeps()); const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = await generatePdfObservableFactory(mockReporting); @@ -143,7 +146,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const executeJob = await executeJobFactory(mockReporting, getMockDeps()); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pdfJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index b0b2d02305b9b..b2d23f14f5834 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -7,9 +7,9 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; +import { ReportingInternalSetup } from '../../../../server/core'; import { PDF_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; -import { LevelLogger } from '../../../../server/lib'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../server/types'; import { decryptJobHeaders, @@ -25,12 +25,12 @@ type QueuedPdfExecutorFactory = ExecuteJobFactory; + basePath: BasePath['get']; + router: IRouter; + security: SecurityPluginSetup; + logger: LevelLogger; } interface ReportingInternalStart { @@ -51,7 +55,7 @@ export class ReportingCore { private readonly pluginStart$ = new Rx.ReplaySubject(); private exportTypesRegistry = getExportTypesRegistry(); - constructor(private logger: LevelLogger, private config: ReportingConfig) {} + constructor(private config: ReportingConfig) {} legacySetup( xpackMainPlugin: XPackMainPlugin, @@ -62,13 +66,14 @@ export class ReportingCore { mirrorPluginStatus(xpackMainPlugin, reporting); } - public setupRoutes(plugins: ReportingSetupDeps, router: IRouter, basePath: IBasePath['get']) { - registerRoutes(this, plugins, router, basePath, this.logger); + public async setupRoutes() { + const dep = await this.getPluginSetupDeps(); + registerRoutes(this, dep); } public pluginSetup(reportingSetupDeps: ReportingInternalSetup) { this.pluginSetup$.next(reportingSetupDeps); - reportingSetupDeps.license$.subscribe(license => (this.license = license)); + reportingSetupDeps.license$.subscribe((license) => (this.license = license)); } public pluginStart(reportingStartDeps: ReportingInternalStart) { @@ -107,16 +112,17 @@ export class ReportingCore { return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory); } - /* - * Outside dependencies - */ - private async getPluginSetupDeps() { + public async getPluginSetupDeps() { if (this.pluginSetupDeps) { return this.pluginSetupDeps; } return await this.pluginSetup$.pipe(first()).toPromise(); } + /* + * Outside dependencies + */ + private async getPluginStartDeps() { if (this.pluginStartDeps) { return this.pluginStartDeps; diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts index 4cb39a4a1c7b0..1b4eeaa0bae3e 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts @@ -47,8 +47,8 @@ const makeManagementFeature = ( } const validJobTypes = exportTypes - .filter(exportType => exportType.validLicenses.includes(license.type || '')) - .map(exportType => exportType.jobType); + .filter((exportType) => exportType.validLicenses.includes(license.type || '')) + .map((exportType) => exportType.jobType); return { showLinks: validJobTypes.length > 0, diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 2cac4bd654487..eac362d95b467 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReportingCore } from '../core'; +import { ReportingCore, ReportingInternalSetup } from '../core'; import { JobDocOutput, JobSource } from '../types'; import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed import { createWorkerFactory } from './create_worker'; import { Job } from './enqueue_job'; // @ts-ignore import { Esqueue } from './esqueue'; -import { LevelLogger } from './level_logger'; interface ESQueueWorker { on: (event: string, handler: any) => void; @@ -39,9 +38,10 @@ type GenericWorkerFn = ( export async function createQueueFactory( reporting: ReportingCore, - logger: LevelLogger + deps: ReportingInternalSetup ): Promise { const config = reporting.getConfig(); + const { logger } = deps; const queueIndexInterval = config.get('queue', 'indexInterval'); const queueTimeout = config.get('queue', 'timeout'); const queueIndex = config.get('index'); @@ -60,7 +60,7 @@ export async function createQueueFactory( if (isPollingEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed - const createWorker = createWorkerFactory(reporting, logger); + const createWorker = createWorkerFactory(reporting, deps); await createWorker(queue); } else { logger.info( diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index 57bd61aee7195..6c7bf6bb93907 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -7,13 +7,16 @@ import { CancellationToken } from '../../../../../plugins/reporting/common'; import { PLUGIN_ID } from '../../common/constants'; import { ReportingCore } from '../../server'; -import { LevelLogger } from '../../server/lib'; import { ESQueueWorkerExecuteFn, ExportTypeDefinition, JobSource } from '../../server/types'; import { ESQueueInstance } from './create_queue'; // @ts-ignore untyped dependency import { events as esqueueEvents } from './esqueue'; +import { ReportingInternalSetup } from '../core'; -export function createWorkerFactory(reporting: ReportingCore, logger: LevelLogger) { +export function createWorkerFactory( + reporting: ReportingCore, + deps: ReportingInternalSetup +) { const config = reporting.getConfig(); const queueConfig = config.get('queue'); const kibanaName = config.kbnConfig.get('server', 'name'); @@ -27,7 +30,7 @@ export function createWorkerFactory(reporting: ReportingCore, log for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< ExportTypeDefinition> >) { - const jobExecutor = await exportType.executeJobFactory(reporting, logger); // FIXME: does not "need" to be async + const jobExecutor = await exportType.executeJobFactory(reporting, deps); // FIXME: does not "need" to be async jobExecutors.set(exportType.jobType, jobExecutor); } @@ -63,13 +66,13 @@ export function createWorkerFactory(reporting: ReportingCore, log const worker = queue.registerWorker(PLUGIN_ID, workerFn, workerOptions); worker.on(esqueueEvents.EVENT_WORKER_COMPLETE, (res: any) => { - logger.debug(`Worker completed: (${res.job.id})`); + deps.logger.debug(`Worker completed: (${res.job.id})`); }); worker.on(esqueueEvents.EVENT_WORKER_JOB_EXECUTION_ERROR, (res: any) => { - logger.debug(`Worker error: (${res.job.id})`); + deps.logger.debug(`Worker error: (${res.job.id})`); }); worker.on(esqueueEvents.EVENT_WORKER_JOB_TIMEOUT, (res: any) => { - logger.debug(`Job timeout exceeded: (${res.job.id})`); + deps.logger.debug(`Job timeout exceeded: (${res.job.id})`); }); }; } diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 0fc08f42f8275..26ce8951497c6 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -5,11 +5,11 @@ */ import { EventEmitter } from 'events'; -import { ConditionalHeaders, ESQueueCreateJobFn, RequestFacade } from '../../server/types'; -import { ReportingCore } from '../core'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { ESQueueCreateJobFn } from '../../server/types'; +import { ReportingCore, ReportingInternalSetup } from '../core'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; -import { LevelLogger } from './level_logger'; interface ConfirmedJob { id: string; @@ -28,28 +28,27 @@ export type Job = EventEmitter & { export type EnqueueJobFn = ( exportTypeId: string, jobParams: JobParamsType, - user: string, - headers: Record, - request: RequestFacade + username: string, + context: RequestHandlerContext, + request: KibanaRequest ) => Promise; export function enqueueJobFactory( reporting: ReportingCore, - parentLogger: LevelLogger + deps: ReportingInternalSetup ): EnqueueJobFn { const config = reporting.getConfig(); const queueTimeout = config.get('queue', 'timeout'); const browserType = config.get('capture', 'browser', 'type'); const maxAttempts = config.get('capture', 'maxAttempts'); - - const logger = parentLogger.clone(['queue-job']); + const logger = deps.logger.clone(['queue-job']); return async function enqueueJob( exportTypeId: string, jobParams: JobParamsType, username: string, - headers: ConditionalHeaders['headers'], - request: RequestFacade + context: RequestHandlerContext, + request: KibanaRequest ): Promise { type CreateJobFn = ESQueueCreateJobFn; @@ -60,8 +59,8 @@ export function enqueueJobFactory( throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); } - const createJob = exportType.createJobFactory(reporting, logger) as CreateJobFn; - const payload = await createJob(jobParams, headers, request); + const createJob = exportType.createJobFactory(reporting, deps) as CreateJobFn; + const payload = await createJob(jobParams, context, request); const options = { timeout: queueTimeout, diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index ea14f44ec3a7e..0d76d014cd845 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -22,7 +22,7 @@ export class ReportingPlugin constructor(context: PluginInitializerContext, config: ReportingConfig) { this.config = config; this.logger = new LevelLogger(context.logger.get('reporting')); - this.reportingCore = new ReportingCore(this.logger, this.config); + this.reportingCore = new ReportingCore(this.config); } public async setup(core: CoreSetup, plugins: ReportingSetupDeps) { @@ -31,10 +31,12 @@ export class ReportingPlugin elasticsearch, __LEGACY, licensing: { license$ }, + security, } = plugins; const router = core.http.createRouter(); const basePath = core.http.basePath.get; const { xpack_main: xpackMainLegacy, reporting: reportingLegacy } = __LEGACY.plugins; + const { logger } = this; this.reportingCore.legacySetup(xpackMainLegacy, reportingLegacy, __LEGACY); @@ -45,19 +47,29 @@ export class ReportingPlugin registerReportingUsageCollector(this.reportingCore, plugins); // regsister setup internals - this.reportingCore.pluginSetup({ browserDriverFactory, elasticsearch, license$ }); + this.reportingCore.pluginSetup({ + browserDriverFactory, + elasticsearch, + license$, + basePath, + router, + security, + logger, + }); // Setup routing - this.reportingCore.setupRoutes(plugins, router, basePath); + this.reportingCore.setupRoutes(); return {}; } public async start(core: CoreStart, plugins: ReportingStartDeps) { - const { reportingCore, logger } = this; + const { reportingCore } = this; + + const deps = await reportingCore.getPluginSetupDeps(); + const esqueue = await createQueueFactory(reportingCore, deps); - const esqueue = await createQueueFactory(reportingCore, logger); - const enqueueJob = enqueueJobFactory(reportingCore, logger); + const enqueueJob = enqueueJobFactory(reportingCore, deps); this.reportingCore.pluginStart({ savedObjects: core.savedObjects, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index f7ce1bdd38294..832278285d49f 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -5,27 +5,23 @@ */ import rison from 'rison-node'; -import { IRouter, IBasePath } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { HandlerErrorFunction, HandlerFunction } from './types'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; -import { ReportingSetupDeps } from '../types'; -import { makeRequestFacade } from './lib/make_request_facade'; +import { ReportingInternalSetup } from '../core'; const BASE_GENERATE = `${API_BASE_URL}/generate`; export function registerGenerateFromJobParams( reporting: ReportingCore, - plugins: ReportingSetupDeps, - router: IRouter, - basePath: IBasePath['get'], + deps: ReportingInternalSetup, handler: HandlerFunction, handleError: HandlerErrorFunction ) { - const config = reporting.getConfig(); - const userHandler = authorizedUserPreRoutingFactory(config, plugins); + const userHandler = authorizedUserPreRoutingFactory(reporting, deps); + const { router } = deps; router.post( { @@ -48,14 +44,13 @@ export function registerGenerateFromJobParams( }, userHandler(async (user, context, req, res) => { const { username } = user; - const request = makeRequestFacade(context, req, basePath); let jobParamsRison: string | null; - if (request.body) { - const { jobParams: jobParamsPayload } = request.body as { jobParams: string }; + if (req.body) { + const { jobParams: jobParamsPayload } = req.body as { jobParams: string }; jobParamsRison = jobParamsPayload; } else { - const { jobParams: queryJobParams } = request.query as { jobParams: string }; + const { jobParams: queryJobParams } = req.query as { jobParams: string }; if (queryJobParams) { jobParamsRison = queryJobParams; } else { @@ -70,7 +65,7 @@ export function registerGenerateFromJobParams( }); } - const { exportType } = request.params as { exportType: string }; + const { exportType } = req.params as { exportType: string }; let jobParams; try { @@ -89,7 +84,7 @@ export function registerGenerateFromJobParams( } try { - return await handler(username, exportType, jobParams, request, res); + return await handler(username, exportType, jobParams, context, req, res); } catch (err) { return handleError(exportType, err, res); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index afd0c82b174a9..9d1c5f81ae357 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -6,16 +6,13 @@ import { schema } from '@kbn/config-schema'; import { get } from 'lodash'; -import { IRouter, IBasePath } from 'src/core/server'; import { isoStringValidate } from '../lib/iso_string_validate'; import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types'; import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../common/constants'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; -import { LevelLogger as Logger } from '../lib'; -import { ReportingSetupDeps } from '../types'; -import { makeRequestFacade } from './lib/make_request_facade'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; +import { ReportingInternalSetup } from '../core'; /* * This function registers API Endpoints for queuing Reporting jobs. The API inputs are: @@ -28,14 +25,12 @@ import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routi */ export function registerGenerateCsvFromSavedObject( reporting: ReportingCore, - plugins: ReportingSetupDeps, - router: IRouter, - basePath: IBasePath['get'], + deps: ReportingInternalSetup, handleRoute: HandlerFunction, - handleRouteError: HandlerErrorFunction, - logger: Logger + handleRouteError: HandlerErrorFunction ) { - const userHandler = authorizedUserPreRoutingFactory(reporting.getConfig(), plugins); + const userHandler = authorizedUserPreRoutingFactory(reporting, deps); + const { router } = deps; router.post( { path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, @@ -59,7 +54,6 @@ export function registerGenerateCsvFromSavedObject( }, }, userHandler(async (user, context, req, res) => { - const requestFacade = makeRequestFacade(context, req, basePath); const { username } = user; /* @@ -69,12 +63,13 @@ export function registerGenerateCsvFromSavedObject( */ let result: QueuedJobPayload; try { - const jobParams = getJobParamsFromRequest(requestFacade, { isImmediate: false }); + const jobParams = getJobParamsFromRequest(req, { isImmediate: false }); result = await handleRoute( username, CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, - requestFacade, + context, + req, res ); } catch (err) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index a1821d9d0b223..a88683ea3e34c 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, IBasePath } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; @@ -12,10 +11,9 @@ import { createJobFactory, executeJobFactory } from '../../export_types/csv_from import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types'; import { isoStringValidate } from '../lib/iso_string_validate'; -import { makeRequestFacade } from './lib/make_request_facade'; -import { LevelLogger as Logger } from '../lib'; -import { JobDocOutput, ReportingSetupDeps } from '../types'; +import { JobDocOutput } from '../types'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; +import { ReportingInternalSetup } from '../core'; /* * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: @@ -28,12 +26,11 @@ import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routi */ export function registerGenerateCsvFromSavedObjectImmediate( reporting: ReportingCore, - plugins: ReportingSetupDeps, - router: IRouter, - basePath: IBasePath['get'], - parentLogger: Logger + deps: ReportingInternalSetup ) { - const userHandler = authorizedUserPreRoutingFactory(reporting.getConfig(), plugins); + const userHandler = authorizedUserPreRoutingFactory(reporting, deps); + const { router } = deps; + /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: * - re-use the createJob function to build up es query config @@ -62,21 +59,21 @@ export function registerGenerateCsvFromSavedObjectImmediate( }, }, userHandler(async (user, context, req, res) => { - const request = makeRequestFacade(context, req, basePath); - const logger = parentLogger.clone(['savedobject-csv']); - const jobParams = getJobParamsFromRequest(request, { isImmediate: true }); - const createJobFn = createJobFactory(reporting, logger); - const executeJobFn = await executeJobFactory(reporting, logger); // FIXME: does not "need" to be async + const logger = deps.logger.clone(['savedobject-csv']); + const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); + const createJobFn = createJobFactory(reporting, deps); + const executeJobFn = await executeJobFactory(reporting, deps); // FIXME: does not "need" to be async const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( jobParams, - request.headers, - request + req.headers, + context, + req ); const { content_type: jobOutputContentType, content: jobOutputContent, size: jobOutputSize, - }: JobDocOutput = await executeJobFn(null, jobDocPayload, request); + }: JobDocOutput = await executeJobFn(null, jobDocPayload, context, req); logger.info(`Job output size: ${jobOutputSize} bytes`); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index a56e44afca5ec..a3c075ab4fde1 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -5,24 +5,20 @@ */ import { errors as elasticsearchErrors } from 'elasticsearch'; -import { IRouter, IBasePath, kibanaResponseFactory } from 'src/core/server'; +import { kibanaResponseFactory } from 'src/core/server'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; -import { LevelLogger as Logger } from '../lib'; -import { ReportingSetupDeps } from '../types'; import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; import { HandlerFunction } from './types'; +import { ReportingInternalSetup } from '../core'; const esErrors = elasticsearchErrors as Record; export function registerJobGenerationRoutes( reporting: ReportingCore, - plugins: ReportingSetupDeps, - router: IRouter, - basePath: IBasePath['get'], - logger: Logger + deps: ReportingInternalSetup ) { const config = reporting.getConfig(); const downloadBaseUrl = @@ -31,22 +27,21 @@ export function registerJobGenerationRoutes( /* * Generates enqueued job details to use in responses */ - const handler: HandlerFunction = async (username, exportTypeId, jobParams, r, h) => { + const handler: HandlerFunction = async (username, exportTypeId, jobParams, context, req, res) => { const licenseInfo = reporting.getLicenseInfo(); const licenseResults = licenseInfo[exportTypeId]; if (!licenseResults.enableLinks) { - return h.forbidden({ body: licenseResults.message }); + return res.forbidden({ body: licenseResults.message }); } - const { headers } = r; const enqueueJob = await reporting.getEnqueueJob(); - const job = await enqueueJob(exportTypeId, jobParams, username, headers, r); + const job = await enqueueJob(exportTypeId, jobParams, username, context, req); // return the queue's job information const jobJson = job.toJSON(); - return h.ok({ + return res.ok({ headers: { 'content-type': 'application/json', }, @@ -81,20 +76,11 @@ export function registerJobGenerationRoutes( }); } - registerGenerateFromJobParams(reporting, plugins, router, basePath, handler, handleError); + registerGenerateFromJobParams(reporting, deps, handler, handleError); // Register beta panel-action download-related API's if (config.get('csv', 'enablePanelActionDownload')) { - registerGenerateCsvFromSavedObject( - reporting, - plugins, - router, - basePath, - handler, - handleError, - logger - ); - - registerGenerateCsvFromSavedObjectImmediate(reporting, plugins, router, basePath, logger); + registerGenerateCsvFromSavedObject(reporting, deps, handler, handleError); + registerGenerateCsvFromSavedObjectImmediate(reporting, deps); } } diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index edc09fbe0b1b5..9241b3cd3b16b 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -4,20 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, IBasePath } from 'src/core/server'; -import { LevelLogger as Logger } from '../lib'; -import { ReportingSetupDeps } from '../types'; import { registerJobGenerationRoutes } from './generation'; import { registerJobInfoRoutes } from './jobs'; -import { ReportingCore } from '..'; +import { ReportingCore, ReportingInternalSetup } from '../core'; -export function registerRoutes( - reporting: ReportingCore, - plugins: ReportingSetupDeps, - router: IRouter, - basePath: IBasePath['get'], - logger: Logger -) { - registerJobGenerationRoutes(reporting, plugins, router, basePath, logger); - registerJobInfoRoutes(reporting, plugins, router); +export function registerRoutes(reporting: ReportingCore, deps: ReportingInternalSetup) { + registerJobGenerationRoutes(reporting, deps); + registerJobInfoRoutes(reporting, deps); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index 5d4cb33465c63..fe4da0cae21ae 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -6,23 +6,24 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setupServer } from 'src/core/server/saved_objects/routes/integration_tests/test_utils'; import { registerJobInfoRoutes } from './jobs'; -import { createMockReportingCore, createMockServer } from '../../test_helpers'; +import { createMockReportingCore } from '../../test_helpers'; import { ReportingCore } from '..'; import { ExportTypesRegistry } from '../lib/export_types_registry'; -import { ExportTypeDefinition, ReportingSetupDeps } from '../types'; +import { ExportTypeDefinition } from '../types'; import { LevelLogger } from '../lib'; -import { IRouter } from 'src/core/server'; +import { ReportingInternalSetup } from '../core'; -type setupServerReturn = UnwrapPromise>; +type setupServerReturn = UnwrapPromise>; describe('GET /api/reporting/jobs/download', () => { + let mockDeps: ReportingInternalSetup; let server: setupServerReturn['server']; let httpSetup: setupServerReturn['httpSetup']; let exportTypesRegistry: ExportTypesRegistry; let core: ReportingCore; - let basePath: () => string; - let router: IRouter; const config = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; const mockLogger = ({ @@ -30,21 +31,6 @@ describe('GET /api/reporting/jobs/download', () => { debug: jest.fn(), } as unknown) as jest.Mocked; - const mockPlugins = ({ - elasticsearch: { - adminClient: { callAsInternalUser: jest.fn() }, - }, - security: { - authc: { - getCurrentUser: () => ({ - id: '123', - roles: ['superuser'], - username: 'Tom Riddle', - }), - }, - }, - } as unknown) as ReportingSetupDeps; - const getHits = (...sources: any) => { return { hits: { @@ -77,7 +63,22 @@ describe('GET /api/reporting/jobs/download', () => { validLicenses: ['basic', 'gold'], } as ExportTypeDefinition); core.getExportTypesRegistry = () => exportTypesRegistry; - ({ server, httpSetup } = await createMockServer()); + ({ server, httpSetup } = await setupServer()); + mockDeps = ({ + elasticsearch: { + adminClient: { callAsInternalUser: jest.fn() }, + }, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + router: httpSetup.createRouter(''), + } as unknown) as ReportingInternalSetup; }); afterEach(async () => { @@ -88,11 +89,10 @@ describe('GET /api/reporting/jobs/download', () => { it('fails on malformed download IDs', async () => { // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); @@ -109,15 +109,14 @@ describe('GET /api/reporting/jobs/download', () => { it('fails on unauthenticated users', async () => { // @ts-ignore const unauthenticatedPlugin = ({ - ...mockPlugins, + ...mockDeps, security: { authc: { getCurrentUser: () => undefined, }, }, - } as unknown) as ReportingSetupDeps; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, unauthenticatedPlugin, router, basePath, mockLogger); + } as unknown) as ReportingInternalSetup; + registerJobInfoRoutes(core, unauthenticatedPlugin); await server.start(); @@ -132,11 +131,10 @@ describe('GET /api/reporting/jobs/download', () => { it('fails when security is not there', async () => { // @ts-ignore const noSecurityPlugins = ({ - ...mockPlugins, + ...mockDeps, security: null, - } as unknown) as ReportingSetupDeps; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, noSecurityPlugins, router, basePath, mockLogger); + } as unknown) as ReportingInternalSetup; + registerJobInfoRoutes(core, noSecurityPlugins); await server.start(); @@ -151,7 +149,7 @@ describe('GET /api/reporting/jobs/download', () => { it('fails on users without the appropriate role', async () => { // @ts-ignore const peasantUser = ({ - ...mockPlugins, + ...mockDeps, security: { authc: { getCurrentUser: () => ({ @@ -161,9 +159,8 @@ describe('GET /api/reporting/jobs/download', () => { }), }, }, - } as unknown) as ReportingSetupDeps; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, peasantUser, router, basePath, mockLogger); + } as unknown) as ReportingInternalSetup; + registerJobInfoRoutes(core, peasantUser); await server.start(); @@ -177,47 +174,40 @@ describe('GET /api/reporting/jobs/download', () => { it('returns 404 if job not found', async () => { // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); - await supertest(httpSetup.server.listener) - .get('/api/reporting/jobs/download/poo') - .expect(404); + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(404); }); it('returns a 401 if not a valid job type', async () => { // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest .fn() .mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); - await supertest(httpSetup.server.listener) - .get('/api/reporting/jobs/download/poo') - .expect(401); + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(401); }); it('when a job is incomplete', async () => { // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest .fn() .mockReturnValue( Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' })) ), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); await supertest(httpSetup.server.listener) @@ -230,7 +220,7 @@ describe('GET /api/reporting/jobs/download', () => { it('when a job fails', async () => { // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue( Promise.resolve( getHits({ @@ -241,8 +231,7 @@ describe('GET /api/reporting/jobs/download', () => { ) ), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); await supertest(httpSetup.server.listener) @@ -274,11 +263,10 @@ describe('GET /api/reporting/jobs/download', () => { it('when a known job-type is complete', async () => { const hits = getCompleteHits(); // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); await supertest(httpSetup.server.listener) @@ -294,11 +282,10 @@ describe('GET /api/reporting/jobs/download', () => { outputContent: 'test', }); // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); await supertest(httpSetup.server.listener) @@ -315,11 +302,10 @@ describe('GET /api/reporting/jobs/download', () => { outputContentType: 'application/pdf', }); // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); await supertest(httpSetup.server.listener) @@ -337,11 +323,10 @@ describe('GET /api/reporting/jobs/download', () => { outputContentType: 'application/html', }); // @ts-ignore - mockPlugins.elasticsearch.adminClient = { + mockDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - router = httpSetup.createRouter(''); - registerJobInfoRoutes(core, mockPlugins, router, basePath, mockLogger); + registerJobInfoRoutes(core, mockDeps); await server.start(); await supertest(httpSetup.server.listener) diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 15d0216d1ff7c..97b633cacc1e1 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -5,17 +5,16 @@ */ import Boom from 'boom'; -import { IRouter } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; import { jobsQueryFactory } from '../lib/jobs_query'; -import { ReportingSetupDeps } from '../types'; import { deleteJobResponseHandlerFactory, downloadJobResponseHandlerFactory, } from './lib/job_response_handler'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; +import { ReportingInternalSetup } from '../core'; interface ListQuery { page: string; @@ -26,12 +25,11 @@ const MAIN_ENTRY = `${API_BASE_URL}/jobs`; export async function registerJobInfoRoutes( reporting: ReportingCore, - plugins: ReportingSetupDeps, - router: IRouter + deps: ReportingInternalSetup ) { const config = reporting.getConfig(); - const userHandler = authorizedUserPreRoutingFactory(config, plugins); - const { elasticsearch } = plugins; + const userHandler = authorizedUserPreRoutingFactory(reporting, deps); + const { elasticsearch, router } = deps; const jobsQuery = jobsQueryFactory(config, elasticsearch); // list jobs in the queue, paginated diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index fd14498446db2..a71b0ff4f7c94 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -4,28 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; -import { KibanaRequest, RequestHandler, RouteMethod } from 'src/core/server'; +import { RequestHandler, RouteMethod } from 'src/core/server'; import { AuthenticatedUser } from '../../../../../../plugins/security/server'; -import { ReportingConfig } from '../../../server'; import { getUserFactory } from '../../lib/get_user'; -import { ReportingSetupDeps } from '../../types'; +import { ReportingInternalSetup, ReportingCore } from '../../core'; const superuserRole = 'superuser'; -export type PreRoutingFunction = ( - request: KibanaRequest -) => Promise | AuthenticatedUser | null>; - export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer R ? (user: AuthenticatedUser, ...a: U) => R : never; export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( - config: ReportingConfig, - plugins: ReportingSetupDeps + core: ReportingCore, + deps: ReportingInternalSetup ) { - const getUser = getUserFactory(plugins.security); + const config = core.getConfig(); + const getUser = getUserFactory(deps.security); return (handler: RequestHandlerUser): RequestHandler => { return (context, req, res) => { diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts index 5bab0870cb17b..5e94302a2c149 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; import { ElasticsearchServiceSetup, kibanaResponseFactory } from 'kibana/server'; import { ReportingConfig } from '../../'; import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts deleted file mode 100644 index ef6759cffc557..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts +++ /dev/null @@ -1,69 +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 { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { makeRequestFacade } from './make_request_facade'; - -describe('makeRequestFacade', () => { - test('creates a default object', () => { - const getBasePath = () => 'basebase'; - const context = ({ - getSavedObjectsClient: () => {}, - } as unknown) as RequestHandlerContext; - const request = ({ - params: { - param1: 123, - }, - payload: { - payload1: 123, - }, - headers: { - user: 123, - }, - } as unknown) as KibanaRequest; - - expect(makeRequestFacade(context, request, getBasePath)).toMatchInlineSnapshot(` - Object { - "getBasePath": [Function], - "getRawRequest": [Function], - "getSavedObjectsClient": undefined, - "headers": Object { - "user": 123, - }, - "params": Object { - "param1": 123, - }, - "payload": Object { - "payload1": 123, - }, - "pre": undefined, - "query": undefined, - "route": undefined, - } - `); - }); - - test('getRawRequest', () => { - const getBasePath = () => 'basebase'; - const context = ({ - getSavedObjectsClient: () => {}, - } as unknown) as RequestHandlerContext; - const request = ({ - getBasePath: () => 'basebase', - params: { - param1: 123, - }, - payload: { - payload1: 123, - }, - headers: { - user: 123, - }, - } as unknown) as KibanaRequest; - - expect(makeRequestFacade(context, request, getBasePath).getRawRequest()).toBe(request); - }); -}); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts deleted file mode 100644 index 3342e757388f6..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts +++ /dev/null @@ -1,29 +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 { KibanaRequest, IBasePath, RequestHandlerContext } from 'src/core/server'; -import { - RequestFacade, - ReportingRequestPayload, - ReportingRequestQuery, -} from '../../../server/types'; - -export function makeRequestFacade( - context: RequestHandlerContext, - request: KibanaRequest, - basePath: IBasePath['get'] -): RequestFacade { - return { - getSavedObjectsClient: () => context.core.savedObjects.client, - headers: request.headers as Record, - params: request.params as Record, - body: (request.body as object) as ReportingRequestPayload, - query: (request.query as object) as ReportingRequestQuery, - getBasePath: () => basePath(request), - route: request.route, - getRawRequest: () => request, - }; -} diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts index e787152137433..e2c212741634c 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts @@ -4,21 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory } from 'src/core/server'; -import { JobDocPayload, RequestFacade } from '../types'; +import { KibanaResponseFactory, KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { JobDocPayload } from '../types'; export type HandlerFunction = ( username: string, exportType: string, jobParams: object, - request: RequestFacade, - h: typeof kibanaResponseFactory + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory ) => any; export type HandlerErrorFunction = ( exportType: string, err: Error, - res: typeof kibanaResponseFactory + res: KibanaResponseFactory ) => any; export interface QueuedJobPayload { diff --git a/x-pack/legacy/plugins/reporting/server/types.ts b/x-pack/legacy/plugins/reporting/server/types.ts index e1458fd712908..4ed5a67566011 100644 --- a/x-pack/legacy/plugins/reporting/server/types.ts +++ b/x-pack/legacy/plugins/reporting/server/types.ts @@ -5,7 +5,7 @@ */ import { Legacy } from 'kibana'; -import { SavedObjectsClientContract, KibanaRequest } from 'src/core/server'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { ElasticsearchServiceSetup } from 'kibana/server'; import * as Rx from 'rxjs'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -19,7 +19,7 @@ import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { LayoutInstance } from '../export_types/common/layouts'; import { ReportingConfigType } from './config'; -import { ReportingCore } from './core'; +import { ReportingCore, ReportingInternalSetup } from './core'; import { LevelLogger } from './lib'; /* @@ -190,21 +190,10 @@ export interface LegacySetup { * Internal Types */ -export interface RequestFacade { - getBasePath: () => string; - getSavedObjectsClient: () => SavedObjectsClientContract; - headers: Record; - params: KibanaRequest['params']; - body: JobParamPostPayload | GenerateExportTypePayload; - query: ReportingRequestQuery; - route: KibanaRequest['route']; - getRawRequest: () => KibanaRequest; -} - export type ESQueueCreateJobFn = ( jobParams: JobParamsType, - headers: Record, - request: RequestFacade + context: RequestHandlerContext, + request: KibanaRequest ) => Promise; export type ESQueueWorkerExecuteFn = ( @@ -220,12 +209,12 @@ export type ScrollConfig = ReportingConfigType['csv']['scroll']; export type CreateJobFactory = ( reporting: ReportingCore, - logger: LevelLogger + deps: ReportingInternalSetup ) => CreateJobFnType; export type ExecuteJobFactory = ( reporting: ReportingCore, - logger: LevelLogger + deps: ReportingInternalSetup ) => Promise; // FIXME: does not "need" to be async export interface ExportTypeDefinition< diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts index 9c89962d83bfc..d9bb0688717e6 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts @@ -12,7 +12,7 @@ jest.mock('../server/lib/create_queue'); jest.mock('../server/lib/enqueue_job'); jest.mock('../server/lib/validate'); -import * as Rx from 'rxjs'; +import { of } from 'rxjs'; import { EventEmitter } from 'events'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { coreMock } from 'src/core/server/mocks'; @@ -26,7 +26,7 @@ const createMockSetupDeps = (setupMock?: any): ReportingSetupDeps => { usageCollection: {} as any, __LEGACY: { plugins: { xpack_main: { status: new EventEmitter() } } } as any, licensing: { - license$: Rx.of({ isAvailable: true, isActive: true, type: 'basic' }), + license$: of({ isAvailable: true, isActive: true, type: 'basic' }), } as any, }; }; From 82a17670dbda43b75569fc570785a8363827ea27 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Wed, 27 May 2020 11:48:36 -0700 Subject: [PATCH 10/31] Fix validation for dates in immediate exports --- .../plugins/reporting/server/lib/iso_string_validate.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts b/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts index 6dd8f81bffa90..01acbe5508557 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts @@ -6,11 +6,7 @@ const isoValidationMessage = `value must be a valid ISO format`; export const isoStringValidate = (input: string): string | undefined => { - if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(input)) { - return isoValidationMessage; - } - - if (new Date(input).toISOString() !== input) { + if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-\d{2}:\d{2}/.test(input)) { return isoValidationMessage; } }; From bb0e7ee2227c444873a4057aac484347cf34283f Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Wed, 27 May 2020 12:26:05 -0700 Subject: [PATCH 11/31] Linter fix on check license test --- .../legacy/plugins/reporting/server/lib/check_license.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts b/x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts index 07a86452a9367..366a8d94286f1 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.test.ts @@ -8,7 +8,7 @@ import { checkLicense } from './check_license'; import { ILicense } from '../../../../../plugins/licensing/server'; import { ExportTypesRegistry } from './export_types_registry'; -describe('check_license', function() { +describe('check_license', () => { let exportTypesRegistry: ExportTypesRegistry; let license: ILicense; From ec94204699d8fcf117b6890fe3eff2bfb41e3c01 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Wed, 27 May 2020 14:22:54 -0700 Subject: [PATCH 12/31] Fix job generation tests --- .../server/routes/generation.test.ts | 249 +++++++++--------- .../reporting/server/routes/jobs.test.ts | 9 +- 2 files changed, 127 insertions(+), 131 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts index d767d37a477ab..7d45f8922be97 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts @@ -4,140 +4,137 @@ * you may not use this file except in compliance with the Elastic License. */ -import Hapi from 'hapi'; -import { ReportingConfig, ReportingCore } from '../'; -import { createMockReportingCore } from '../../test_helpers'; -import { LevelLogger as Logger } from '../lib'; -import { ReportingSetupDeps, ServerFacade } from '../types'; +import supertest from 'supertest'; +import { UnwrapPromise } from '@kbn/utility-types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setupServer } from 'src/core/server/saved_objects/routes/integration_tests/test_utils'; import { registerJobGenerationRoutes } from './generation'; +import { createMockReportingCore } from '../../test_helpers'; +import { ReportingCore } from '..'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; +import { ExportTypeDefinition } from '../types'; +import { LevelLogger } from '../lib'; +import { ReportingInternalSetup } from '../core'; + +type setupServerReturn = UnwrapPromise>; + +describe('POST /api/reporting/generate', () => { + let mockDeps: ReportingInternalSetup; + let server: setupServerReturn['server']; + let httpSetup: setupServerReturn['httpSetup']; + let exportTypesRegistry: ExportTypesRegistry; + let core: ReportingCore; + + const config = { + get: jest.fn().mockImplementation((...args) => { + const key = args.join('.'); + switch (key) { + case 'queue.indexInterval': + return 10000; + case 'queue.timeout': + return 10000; + case 'index': + return '.reporting'; + case 'queue.pollEnabled': + return false; + default: + return; + } + }), + kbnConfig: { get: jest.fn() }, + }; + const mockLogger = ({ + error: jest.fn(), + debug: jest.fn(), + } as unknown) as jest.Mocked; + + beforeEach(async () => { + core = await createMockReportingCore(config); + // @ts-ignore + core.license = { + isActive: true, + isAvailable: true, + type: 'gold', + }; + exportTypesRegistry = new ExportTypesRegistry(); + exportTypesRegistry.register({ + id: 'printablePdf', + jobType: 'printable_pdf', + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + validLicenses: ['basic', 'gold'], + } as ExportTypeDefinition); + core.getExportTypesRegistry = () => exportTypesRegistry; + ({ server, httpSetup } = await setupServer()); + mockDeps = ({ + elasticsearch: { + adminClient: { callAsInternalUser: jest.fn() }, + }, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + router: httpSetup.createRouter(''), + } as unknown) as ReportingInternalSetup; + }); -jest.mock('./lib/authorized_user_pre_routing', () => ({ - authorizedUserPreRoutingFactory: () => () => ({}), -})); -jest.mock('./lib/reporting_feature_pre_routing', () => ({ - reportingFeaturePreRoutingFactory: () => () => () => ({ - jobTypes: ['unencodedJobType', 'base64EncodedJobType'], - }), -})); - -let mockServer: Hapi.Server; -let mockReportingPlugin: ReportingCore; -let mockReportingConfig: ReportingConfig; - -const mockLogger = ({ - error: jest.fn(), - debug: jest.fn(), -} as unknown) as Logger; - -beforeEach(async () => { - mockServer = new Hapi.Server({ - debug: false, - port: 8080, - routes: { log: { collect: true } }, + afterEach(async () => { + mockLogger.debug.mockReset(); + mockLogger.error.mockReset(); + await server.stop(); }); - mockReportingConfig = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; - mockReportingPlugin = await createMockReportingCore(mockReportingConfig); - mockReportingPlugin.getEnqueueJob = async () => - jest.fn().mockImplementation(() => ({ toJSON: () => '{ "job": "data" }' })); -}); + it('returns 400 if there are no job params', async () => { + registerJobGenerationRoutes(core, mockDeps); -const mockPlugins = { - elasticsearch: { - adminClient: { callAsInternalUser: jest.fn() }, - }, - security: null, -}; - -const getErrorsFromRequest = (request: Hapi.Request) => { - // @ts-ignore error property doesn't exist on RequestLog - return request.logs.filter((log) => log.tags.includes('error')).map((log) => log.error); // NOTE: error stack is available -}; - -test(`returns 400 if there are no job params`, async () => { - registerJobGenerationRoutes( - mockReportingPlugin, - (mockServer as unknown) as ServerFacade, - (mockPlugins as unknown) as ReportingSetupDeps, - mockLogger - ); - - const options = { - method: 'POST', - url: '/api/reporting/generate/printablePdf', - }; + await server.start(); - const { payload, request } = await mockServer.inject(options); - expect(payload).toMatchInlineSnapshot( - `"{\\"statusCode\\":400,\\"error\\":\\"Bad Request\\",\\"message\\":\\"A jobParams RISON string is required\\"}"` - ); - - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toMatchInlineSnapshot(` - Array [ - [Error: A jobParams RISON string is required], - ] - `); -}); + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf') + .expect(400) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot( + '"[request body]: expected a plain object value, but found [null] instead."' + ) + ); + }); -test(`returns 400 if job params is invalid`, async () => { - registerJobGenerationRoutes( - mockReportingPlugin, - (mockServer as unknown) as ServerFacade, - (mockPlugins as unknown) as ReportingSetupDeps, - mockLogger - ); - - const options = { - method: 'POST', - url: '/api/reporting/generate/printablePdf', - payload: { jobParams: `foo:` }, - }; + it('returns 400 if job params is invalid', async () => { + registerJobGenerationRoutes(core, mockDeps); - const { payload, request } = await mockServer.inject(options); - expect(payload).toMatchInlineSnapshot( - `"{\\"statusCode\\":400,\\"error\\":\\"Bad Request\\",\\"message\\":\\"invalid rison: foo:\\"}"` - ); - - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toMatchInlineSnapshot(` - Array [ - [Error: invalid rison: foo:], - ] - `); -}); + await server.start(); -test(`returns 500 if job handler throws an error`, async () => { - mockReportingPlugin.getEnqueueJob = async () => - jest.fn().mockImplementation(() => ({ - toJSON: () => { - throw new Error('you found me'); - }, - })); - - registerJobGenerationRoutes( - mockReportingPlugin, - (mockServer as unknown) as ServerFacade, - (mockPlugins as unknown) as ReportingSetupDeps, - mockLogger - ); - - const options = { - method: 'POST', - url: '/api/reporting/generate/printablePdf', - payload: { jobParams: `abc` }, - }; + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf') + .send({ jobParams: `foo:` }) + .expect(400) + .then(({ body }) => expect(body.message).toMatchInlineSnapshot('"invalid rison: foo:"')); + }); - const { payload, request } = await mockServer.inject(options); - expect(payload).toMatchInlineSnapshot( - `"{\\"statusCode\\":500,\\"error\\":\\"Internal Server Error\\",\\"message\\":\\"An internal server error occurred\\"}"` - ); - - const errorLogs = getErrorsFromRequest(request); - expect(errorLogs).toMatchInlineSnapshot(` - Array [ - [Error: you found me], - [Error: you found me], - ] - `); + it('returns 400 if job handler throws an error', async () => { + const errorText = 'you found me'; + core.getEnqueueJob = async () => + jest.fn().mockImplementation(() => ({ + toJSON: () => { + throw new Error(errorText); + }, + })); + + registerJobGenerationRoutes(core, mockDeps); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf') + .send({ jobParams: `abc` }) + .expect(400) + .then(({ body }) => { + expect(body.message).toMatchInlineSnapshot(`"${errorText}"`); + }); + }); }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index fe4da0cae21ae..c5cf8c70b1222 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -40,7 +40,6 @@ describe('GET /api/reporting/jobs/download', () => { }; beforeEach(async () => { - basePath = () => '/'; core = await createMockReportingCore(config); // @ts-ignore core.license = { @@ -331,12 +330,12 @@ describe('GET /api/reporting/jobs/download', () => { await server.start(); await supertest(httpSetup.server.listener) .get('/api/reporting/jobs/download/dank') - .expect(500) + .expect(400) .then(({ body }) => { expect(body).toEqual({ - error: 'Internal Server Error', - message: 'An internal server error occurred', - statusCode: 500, + error: 'Bad Request', + message: 'Unsupported content-type of application/html specified by job output', + statusCode: 400, }); }); }); From 39a1313b3b01291bb7ed6ae0f208bc00acd0c297 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Wed, 27 May 2020 14:35:48 -0700 Subject: [PATCH 13/31] Move deps => setupDeps --- .../reporting/export_types/csv/server/create_job.ts | 4 ++-- .../reporting/export_types/csv/server/execute_job.ts | 7 +++++-- .../server/create_job/create_job.ts | 4 ++-- .../csv_from_savedobject/server/execute_job.ts | 9 ++++++--- .../csv_from_savedobject/server/lib/generate_csv.ts | 4 ++-- .../server/lib/generate_csv_search.ts | 4 ++-- .../export_types/png/server/execute_job/index.ts | 4 ++-- .../printable_pdf/server/execute_job/index.ts | 4 ++-- .../plugins/reporting/server/lib/create_queue.ts | 6 +++--- .../plugins/reporting/server/lib/create_worker.ts | 10 +++++----- .../plugins/reporting/server/lib/enqueue_job.ts | 6 +++--- .../server/routes/generate_from_jobparams.ts | 6 +++--- .../server/routes/generate_from_savedobject.ts | 6 +++--- .../routes/generate_from_savedobject_immediate.ts | 12 ++++++------ .../plugins/reporting/server/routes/generation.ts | 8 ++++---- .../legacy/plugins/reporting/server/routes/index.ts | 6 +++--- .../legacy/plugins/reporting/server/routes/jobs.ts | 6 +++--- .../server/routes/lib/authorized_user_pre_routing.ts | 4 ++-- x-pack/legacy/plugins/reporting/server/types.ts | 4 ++-- 19 files changed, 60 insertions(+), 54 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts index 3e258cd8567a8..ecc488e89cd59 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts @@ -13,7 +13,7 @@ import { ReportingInternalSetup } from '../../../server/core'; export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { +>> = function createJobFactoryFn(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); @@ -33,7 +33,7 @@ export const createJobFactory: CreateJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { +>> = async function executeJobFactoryFn( + reporting: ReportingCore, + setupDeps: ReportingInternalSetup +) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = deps.logger.clone([CSV_JOB_TYPE, 'execute-job']); + const logger = setupDeps.logger.clone([CSV_JOB_TYPE, 'execute-job']); const serverBasePath = config.kbnConfig.get('server', 'basePath'); return async function executeJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts index 9bf28d99320b1..ed670ad3c55c2 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts @@ -42,10 +42,10 @@ interface VisData { export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { +>> = function createJobFactoryFn(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = deps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); + const logger = setupDeps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); return async function createJob( jobParams: JobParamsPanelCsv, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index 09222aab9ccab..e340585d140c9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -28,11 +28,14 @@ export type ImmediateExecuteFn = ( export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, deps: ReportingInternalSetup) { +>> = async function executeJobFactoryFn( + reporting: ReportingCore, + setupDeps: ReportingInternalSetup +) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = deps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); - const generateCsv = createGenerateCsv(reporting, deps); + const logger = setupDeps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); + const generateCsv = createGenerateCsv(reporting, setupDeps); return async function executeJob( jobId: string | null, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts index 37ef1885bb8f1..27807dc769bb0 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts @@ -11,7 +11,7 @@ import { ReportingCore } from '../../../../server'; import { FakeRequest, JobParamsPanelCsv, SearchPanel, VisPanel } from '../../types'; import { generateCsvSearch } from './generate_csv_search'; -export function createGenerateCsv(reporting: ReportingCore, deps: ReportingInternalSetup) { +export function createGenerateCsv(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { return async function generateCsv( context: RequestHandlerContext, request: KibanaRequest | FakeRequest, @@ -28,7 +28,7 @@ export function createGenerateCsv(reporting: ReportingCore, deps: ReportingInter case 'search': return await generateCsvSearch( reporting, - deps, + setupDeps, context, request as KibanaRequest, panel as SearchPanel, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index 796354aa0a5f2..8f36aec8e15e2 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -52,7 +52,7 @@ const getUiSettings = async (config: IUiSettingsClient) => { export async function generateCsvSearch( reporting: ReportingCore, - deps: ReportingInternalSetup, + setupDeps: ReportingInternalSetup, context: RequestHandlerContext, req: KibanaRequest, searchPanel: SearchPanel, @@ -170,7 +170,7 @@ export async function generateCsvSearch( }, }; - const generateCsv = createGenerateCsv(deps.logger); + const generateCsv = createGenerateCsv(setupDeps.logger); return { type: 'CSV from Saved Search', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index 465b72de29609..c787c3163f072 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -24,11 +24,11 @@ type QueuedPngExecutorFactory = ExecuteJobFactory = ( export async function createQueueFactory( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ): Promise { const config = reporting.getConfig(); - const { logger } = deps; + const { logger } = setupDeps; const queueIndexInterval = config.get('queue', 'indexInterval'); const queueTimeout = config.get('queue', 'timeout'); const queueIndex = config.get('index'); @@ -60,7 +60,7 @@ export async function createQueueFactory( if (isPollingEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed - const createWorker = createWorkerFactory(reporting, deps); + const createWorker = createWorkerFactory(reporting, setupDeps); await createWorker(queue); } else { logger.info( diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index 6c7bf6bb93907..cb15bdc7ba009 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -15,7 +15,7 @@ import { ReportingInternalSetup } from '../core'; export function createWorkerFactory( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ) { const config = reporting.getConfig(); const queueConfig = config.get('queue'); @@ -30,7 +30,7 @@ export function createWorkerFactory( for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< ExportTypeDefinition> >) { - const jobExecutor = await exportType.executeJobFactory(reporting, deps); // FIXME: does not "need" to be async + const jobExecutor = await exportType.executeJobFactory(reporting, setupDeps); // FIXME: does not "need" to be async jobExecutors.set(exportType.jobType, jobExecutor); } @@ -66,13 +66,13 @@ export function createWorkerFactory( const worker = queue.registerWorker(PLUGIN_ID, workerFn, workerOptions); worker.on(esqueueEvents.EVENT_WORKER_COMPLETE, (res: any) => { - deps.logger.debug(`Worker completed: (${res.job.id})`); + setupDeps.logger.debug(`Worker completed: (${res.job.id})`); }); worker.on(esqueueEvents.EVENT_WORKER_JOB_EXECUTION_ERROR, (res: any) => { - deps.logger.debug(`Worker error: (${res.job.id})`); + setupDeps.logger.debug(`Worker error: (${res.job.id})`); }); worker.on(esqueueEvents.EVENT_WORKER_JOB_TIMEOUT, (res: any) => { - deps.logger.debug(`Job timeout exceeded: (${res.job.id})`); + setupDeps.logger.debug(`Job timeout exceeded: (${res.job.id})`); }); }; } diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 26ce8951497c6..291432f08f4e6 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -35,13 +35,13 @@ export type EnqueueJobFn = ( export function enqueueJobFactory( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ): EnqueueJobFn { const config = reporting.getConfig(); const queueTimeout = config.get('queue', 'timeout'); const browserType = config.get('capture', 'browser', 'type'); const maxAttempts = config.get('capture', 'maxAttempts'); - const logger = deps.logger.clone(['queue-job']); + const logger = setupDeps.logger.clone(['queue-job']); return async function enqueueJob( exportTypeId: string, @@ -59,7 +59,7 @@ export function enqueueJobFactory( throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); } - const createJob = exportType.createJobFactory(reporting, deps) as CreateJobFn; + const createJob = exportType.createJobFactory(reporting, setupDeps) as CreateJobFn; const payload = await createJob(jobParams, context, request); const options = { diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 832278285d49f..57fce5ec08194 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -16,12 +16,12 @@ const BASE_GENERATE = `${API_BASE_URL}/generate`; export function registerGenerateFromJobParams( reporting: ReportingCore, - deps: ReportingInternalSetup, + setupDeps: ReportingInternalSetup, handler: HandlerFunction, handleError: HandlerErrorFunction ) { - const userHandler = authorizedUserPreRoutingFactory(reporting, deps); - const { router } = deps; + const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const { router } = setupDeps; router.post( { diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 9d1c5f81ae357..c0726e23efd88 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -25,12 +25,12 @@ import { ReportingInternalSetup } from '../core'; */ export function registerGenerateCsvFromSavedObject( reporting: ReportingCore, - deps: ReportingInternalSetup, + setupDeps: ReportingInternalSetup, handleRoute: HandlerFunction, handleRouteError: HandlerErrorFunction ) { - const userHandler = authorizedUserPreRoutingFactory(reporting, deps); - const { router } = deps; + const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const { router } = setupDeps; router.post( { path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index a88683ea3e34c..36bf0e2804074 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -26,10 +26,10 @@ import { ReportingInternalSetup } from '../core'; */ export function registerGenerateCsvFromSavedObjectImmediate( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ) { - const userHandler = authorizedUserPreRoutingFactory(reporting, deps); - const { router } = deps; + const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const { router } = setupDeps; /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: @@ -59,10 +59,10 @@ export function registerGenerateCsvFromSavedObjectImmediate( }, }, userHandler(async (user, context, req, res) => { - const logger = deps.logger.clone(['savedobject-csv']); + const logger = setupDeps.logger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); - const createJobFn = createJobFactory(reporting, deps); - const executeJobFn = await executeJobFactory(reporting, deps); // FIXME: does not "need" to be async + const createJobFn = createJobFactory(reporting, setupDeps); + const executeJobFn = await executeJobFactory(reporting, setupDeps); // FIXME: does not "need" to be async const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( jobParams, req.headers, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index a3c075ab4fde1..f9bfddce2d139 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -18,7 +18,7 @@ const esErrors = elasticsearchErrors as Record; export function registerJobGenerationRoutes( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ) { const config = reporting.getConfig(); const downloadBaseUrl = @@ -76,11 +76,11 @@ export function registerJobGenerationRoutes( }); } - registerGenerateFromJobParams(reporting, deps, handler, handleError); + registerGenerateFromJobParams(reporting, setupDeps, handler, handleError); // Register beta panel-action download-related API's if (config.get('csv', 'enablePanelActionDownload')) { - registerGenerateCsvFromSavedObject(reporting, deps, handler, handleError); - registerGenerateCsvFromSavedObjectImmediate(reporting, deps); + registerGenerateCsvFromSavedObject(reporting, setupDeps, handler, handleError); + registerGenerateCsvFromSavedObjectImmediate(reporting, setupDeps); } } diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index 9241b3cd3b16b..b074079184b80 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -8,7 +8,7 @@ import { registerJobGenerationRoutes } from './generation'; import { registerJobInfoRoutes } from './jobs'; import { ReportingCore, ReportingInternalSetup } from '../core'; -export function registerRoutes(reporting: ReportingCore, deps: ReportingInternalSetup) { - registerJobGenerationRoutes(reporting, deps); - registerJobInfoRoutes(reporting, deps); +export function registerRoutes(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { + registerJobGenerationRoutes(reporting, setupDeps); + registerJobInfoRoutes(reporting, setupDeps); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 97b633cacc1e1..a065dd7fce96c 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -25,11 +25,11 @@ const MAIN_ENTRY = `${API_BASE_URL}/jobs`; export async function registerJobInfoRoutes( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ) { const config = reporting.getConfig(); - const userHandler = authorizedUserPreRoutingFactory(reporting, deps); - const { elasticsearch, router } = deps; + const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const { elasticsearch, router } = setupDeps; const jobsQuery = jobsQueryFactory(config, elasticsearch); // list jobs in the queue, paginated diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index a71b0ff4f7c94..5bd175b8999d6 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -17,10 +17,10 @@ export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( core: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ) { const config = core.getConfig(); - const getUser = getUserFactory(deps.security); + const getUser = getUserFactory(setupDeps.security); return (handler: RequestHandlerUser): RequestHandler => { return (context, req, res) => { diff --git a/x-pack/legacy/plugins/reporting/server/types.ts b/x-pack/legacy/plugins/reporting/server/types.ts index 4ed5a67566011..c78ca67c720b2 100644 --- a/x-pack/legacy/plugins/reporting/server/types.ts +++ b/x-pack/legacy/plugins/reporting/server/types.ts @@ -209,12 +209,12 @@ export type ScrollConfig = ReportingConfigType['csv']['scroll']; export type CreateJobFactory = ( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ) => CreateJobFnType; export type ExecuteJobFactory = ( reporting: ReportingCore, - deps: ReportingInternalSetup + setupDeps: ReportingInternalSetup ) => Promise; // FIXME: does not "need" to be async export interface ExportTypeDefinition< From b4e54e1e54e67a181d8f501c491f5a17b52621fd Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 26 May 2020 16:13:32 -0700 Subject: [PATCH 14/31] fix api test --- .../test/reporting_api_integration/reporting/csv_job_params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/reporting_api_integration/reporting/csv_job_params.ts b/x-pack/test/reporting_api_integration/reporting/csv_job_params.ts index 7d11403add136..90f97d44da224 100644 --- a/x-pack/test/reporting_api_integration/reporting/csv_job_params.ts +++ b/x-pack/test/reporting_api_integration/reporting/csv_job_params.ts @@ -46,7 +46,7 @@ export default function ({ getService }: FtrProviderContext) { jobParams: 0, })) as supertest.Response; - expect(resText).to.match(/\\\"jobParams\\\" must be a string/); + expect(resText).to.match(/expected value of type \[string\] but got \[number\]/); expect(resStatus).to.eql(400); }); From ee7db9ae1df5a7e1c6e4e7782fc6f312decb4a45 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 27 May 2020 13:50:14 -0700 Subject: [PATCH 15/31] fix jobs test --- x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index c5cf8c70b1222..5db2ba896e4c4 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -24,6 +24,7 @@ describe('GET /api/reporting/jobs/download', () => { let httpSetup: setupServerReturn['httpSetup']; let exportTypesRegistry: ExportTypesRegistry; let core: ReportingCore; + let basePath: () => string; const config = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; const mockLogger = ({ From e7262f460f8191e39e3c12c0a3746114543d1f4e Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 27 May 2020 15:40:21 -0700 Subject: [PATCH 16/31] authorized_user_pre_routing and tests --- .../lib/authorized_user_pre_routing.test.js | 175 ------------------ .../lib/authorized_user_pre_routing.test.ts | 128 +++++++++++++ .../routes/lib/authorized_user_pre_routing.ts | 32 ++-- 3 files changed, 147 insertions(+), 188 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.js create mode 100644 x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.js b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.js deleted file mode 100644 index 2c80965432cd2..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.js +++ /dev/null @@ -1,175 +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 { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; - -describe('authorized_user_pre_routing', function () { - const createMockConfig = (mockConfig = {}) => { - return { - get: (...keys) => mockConfig[keys.join('.')], - kbnConfig: { get: (...keys) => mockConfig[keys.join('.')] }, - }; - }; - const createMockPlugins = (function () { - const getUserStub = jest.fn(); - - return function ({ - securityEnabled = true, - xpackInfoUndefined = false, - xpackInfoAvailable = true, - getCurrentUser = undefined, - user = undefined, - }) { - getUserStub.mockReset(); - getUserStub.mockResolvedValue(user); - return { - security: securityEnabled - ? { - authc: { getCurrentUser }, - } - : null, - __LEGACY: { - plugins: { - xpack_main: { - info: !xpackInfoUndefined && { - isAvailable: () => xpackInfoAvailable, - feature(featureName) { - if (featureName === 'security') { - return { - isEnabled: () => securityEnabled, - isAvailable: () => xpackInfoAvailable, - }; - } - }, - }, - }, - }, - }, - }; - }; - })(); - - const mockRequestRaw = { - body: {}, - events: {}, - headers: {}, - isSystemRequest: false, - params: {}, - query: {}, - route: { settings: { payload: 'abc' }, options: { authRequired: true, body: {}, tags: [] } }, - withoutSecretHeaders: true, - }; - const getMockRequest = () => ({ - ...mockRequestRaw, - raw: { req: mockRequestRaw }, - }); - - const getMockLogger = () => ({ - warn: jest.fn(), - error: (msg) => { - throw new Error(msg); - }, - }); - - it('should return with boom notFound when xpackInfo is undefined', async function () { - const authorizedUserPreRouting = authorizedUserPreRoutingFactory( - createMockConfig(), - createMockPlugins({ xpackInfoUndefined: true }), - getMockLogger() - ); - const response = await authorizedUserPreRouting(getMockRequest()); - expect(response.isBoom).toBe(true); - expect(response.output.statusCode).toBe(404); - }); - - it(`should return with boom notFound when xpackInfo isn't available`, async function () { - const authorizedUserPreRouting = authorizedUserPreRoutingFactory( - createMockConfig(), - createMockPlugins({ xpackInfoAvailable: false }), - getMockLogger() - ); - const response = await authorizedUserPreRouting(getMockRequest()); - expect(response.isBoom).toBe(true); - expect(response.output.statusCode).toBe(404); - }); - - it('should return with null user when security is disabled in Elasticsearch', async function () { - const authorizedUserPreRouting = authorizedUserPreRoutingFactory( - createMockConfig(), - createMockPlugins({ securityEnabled: false }), - getMockLogger() - ); - const response = await authorizedUserPreRouting(getMockRequest()); - expect(response).toBe(null); - }); - - it('should return with boom unauthenticated when security is enabled but no authenticated user', async function () { - const mockPlugins = createMockPlugins({ - user: null, - config: { 'xpack.reporting.roles.allow': ['.reporting_user'] }, - }); - mockPlugins.security = { authc: { getCurrentUser: () => null } }; - - const authorizedUserPreRouting = authorizedUserPreRoutingFactory( - createMockConfig(), - mockPlugins, - getMockLogger() - ); - const response = await authorizedUserPreRouting(getMockRequest()); - expect(response.isBoom).toBe(true); - expect(response.output.statusCode).toBe(401); - }); - - it(`should return with boom forbidden when security is enabled but user doesn't have allowed role`, async function () { - const mockConfig = createMockConfig({ 'roles.allow': ['.reporting_user'] }); - const mockPlugins = createMockPlugins({ - user: { roles: [] }, - getCurrentUser: () => ({ roles: ['something_else'] }), - }); - - const authorizedUserPreRouting = authorizedUserPreRoutingFactory( - mockConfig, - mockPlugins, - getMockLogger() - ); - const response = await authorizedUserPreRouting(getMockRequest()); - expect(response.isBoom).toBe(true); - expect(response.output.statusCode).toBe(403); - }); - - it('should return with user when security is enabled and user has explicitly allowed role', async function () { - const user = { roles: ['.reporting_user', 'something_else'] }; - const mockConfig = createMockConfig({ 'roles.allow': ['.reporting_user'] }); - const mockPlugins = createMockPlugins({ - user, - getCurrentUser: () => ({ roles: ['.reporting_user', 'something_else'] }), - }); - - const authorizedUserPreRouting = authorizedUserPreRoutingFactory( - mockConfig, - mockPlugins, - getMockLogger() - ); - const response = await authorizedUserPreRouting(getMockRequest()); - expect(response).toEqual(user); - }); - - it('should return with user when security is enabled and user has superuser role', async function () { - const user = { roles: ['superuser', 'something_else'] }; - const mockConfig = createMockConfig({ 'roles.allow': [] }); - const mockPlugins = createMockPlugins({ - getCurrentUser: () => ({ roles: ['superuser', 'something_else'] }), - }); - - const authorizedUserPreRouting = authorizedUserPreRoutingFactory( - mockConfig, - mockPlugins, - getMockLogger() - ); - const response = await authorizedUserPreRouting(getMockRequest()); - expect(response).toEqual(user); - }); -}); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts new file mode 100644 index 0000000000000..95f58614ba97e --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -0,0 +1,128 @@ +/* + * 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 { KibanaRequest, RequestHandlerContext, KibanaResponseFactory } from 'kibana/server'; +import sinon from 'sinon'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { ReportingConfig, ReportingCore } from '../../'; +import { createMockReportingCore } from '../../../test_helpers'; +import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; +import { ReportingInternalSetup } from '../../core'; + +let mockConfig: ReportingConfig; +let mockCore: ReportingCore; + +const getMockConfig = (mockConfigGet: sinon.SinonStub) => ({ + get: mockConfigGet, + kbnConfig: { get: mockConfigGet }, +}); + +const getMockContext = () => + (({ + core: coreMock.createRequestHandlerContext(), + } as unknown) as RequestHandlerContext); + +const getMockRequest = () => + ({ + url: { port: '5601', query: '', path: '/foo' }, + route: { path: '/foo', options: {} }, + } as KibanaRequest); + +const getMockResponseFactory = () => + (({ + ...httpServerMock.createResponseFactory(), + forbidden: (obj: unknown) => obj, + unauthorized: (obj: unknown) => obj, + } as unknown) as KibanaResponseFactory); + +beforeEach(async () => { + const mockConfigGet = sinon.stub().withArgs('roles', 'allow').returns(['reporting_user']); + mockConfig = getMockConfig(mockConfigGet); + mockCore = await createMockReportingCore(mockConfig); +}); + +describe('authorized_user_pre_routing', function () { + it('should return badRequest when license is undefined', async function () {}); + + it(`should return with badRequest when license isn't available`, async function () {}); + + it('should return from handler with null user when security is disabled', async function () { + const mockSetupDeps = ({ + ...(await mockCore.getPluginSetupDeps()), + security: undefined, // disable security + } as unknown) as ReportingInternalSetup; + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + const mockResponseFactory = httpServerMock.createResponseFactory() as KibanaResponseFactory; + + let handlerCalled = false; + authorizedUserPreRouting((user: unknown) => { + expect(user).toBe(null); // verify the user is a null value + handlerCalled = true; + return Promise.resolve({ status: 200, options: {} }); + })(getMockContext(), getMockRequest(), mockResponseFactory); + + expect(handlerCalled).toBe(true); + }); + + it('should return with 401 when security is enabled but no authenticated user', async function () { + const mockSetupDeps = ({ + ...(await mockCore.getPluginSetupDeps()), + security: { + authc: { getCurrentUser: () => null }, + }, + } as unknown) as ReportingInternalSetup; + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + const mockHandler = () => { + throw new Error('Handler callback should not be called'); + }; + const requestHandler = authorizedUserPreRouting(mockHandler); + const mockResponseFactory = getMockResponseFactory(); + + expect(requestHandler(getMockContext(), getMockRequest(), mockResponseFactory)).toMatchObject({ + body: `Sorry, you aren't authenticated`, + }); + }); + + it(`should return with 403 when security is enabled but user doesn't have allowed role`, async function () { + const mockSetupDeps = ({ + ...(await mockCore.getPluginSetupDeps()), + security: { + authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['cowboy'] }) }, + }, + } as unknown) as ReportingInternalSetup; + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + const mockResponseFactory = getMockResponseFactory(); + + const mockHandler = () => { + throw new Error('Handler callback should not be called'); + }; + expect( + authorizedUserPreRouting(mockHandler)(getMockContext(), getMockRequest(), mockResponseFactory) + ).toMatchObject({ body: `Sorry, you don't have access to Reporting` }); + }); + + it('should return from handler when security is enabled and user has explicitly allowed role', async function () { + const mockSetupDeps = ({ + ...(await mockCore.getPluginSetupDeps()), + security: { + authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['reporting_user'] }) }, + }, + } as unknown) as ReportingInternalSetup; + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + const mockResponseFactory = getMockResponseFactory(); + + let handlerCalled = false; + authorizedUserPreRouting((user: unknown) => { + expect(user).toMatchObject({ roles: ['reporting_user'], username: 'friendlyuser' }); + handlerCalled = true; + return Promise.resolve({ status: 200, options: {} }); + })(getMockContext(), getMockRequest(), mockResponseFactory); + + expect(handlerCalled).toBe(true); + }); + + it('should return from handler when security is enabled and user has superuser role', async function () {}); +}); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 5bd175b8999d6..f8c063f517a97 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -12,7 +12,7 @@ import { ReportingInternalSetup, ReportingCore } from '../../core'; const superuserRole = 'superuser'; export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer R - ? (user: AuthenticatedUser, ...a: U) => R + ? (user: AuthenticatedUser | null, ...a: U) => R : never; export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( @@ -24,21 +24,27 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting return (handler: RequestHandlerUser): RequestHandler => { return (context, req, res) => { - const user = getUser(req); - - if (!user) { - return res.unauthorized({ - body: `Sorry, you aren't authenticated`, - }); + // TODO: license checking + + let user: AuthenticatedUser | null = null; + if (deps.security) { + user = getUser(req); + if (!user) { + return res.unauthorized({ + body: `Sorry, you aren't authenticated`, + }); + } } - const allowedRoles = config.get('roles', 'allow') || []; - const authorizedRoles = [superuserRole, ...allowedRoles]; + if (user) { + const allowedRoles = config.get('roles', 'allow') || []; + const authorizedRoles = [superuserRole, ...allowedRoles]; - if (!user.roles.find((role) => authorizedRoles.includes(role))) { - return res.forbidden({ - body: `Sorry, you don't have access to Reporting`, - }); + if (user && !user.roles.find((role) => authorizedRoles.includes(role))) { + return res.forbidden({ + body: `Sorry, you don't have access to Reporting`, + }); + } } return handler(user, context, req, res); From d69be5f2c0d133cd4f12ac3c81a323b9aa4e9a9a Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 08:08:30 -0700 Subject: [PATCH 17/31] Fixing duplicate identifiers --- x-pack/legacy/plugins/reporting/server/legacy.ts | 2 -- x-pack/legacy/plugins/reporting/server/types.ts | 1 - .../reporting/test_helpers/create_mock_reportingplugin.ts | 5 ++--- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/legacy.ts b/x-pack/legacy/plugins/reporting/server/legacy.ts index 0684a236e5be2..14abd53cc83d9 100644 --- a/x-pack/legacy/plugins/reporting/server/legacy.ts +++ b/x-pack/legacy/plugins/reporting/server/legacy.ts @@ -9,7 +9,6 @@ import { take } from 'rxjs/operators'; import { PluginInitializerContext } from 'src/core/server'; import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; import { ReportingPluginSpecOptions } from '../'; -import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; import { PluginsSetup } from '../../../../plugins/reporting/server'; import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { buildConfig } from './config'; @@ -49,7 +48,6 @@ export const legacyInit = async ( licensing: server.newPlatform.setup.plugins.licensing as LicensingPluginSetup, security: server.newPlatform.setup.plugins.security as SecurityPluginSetup, usageCollection: server.newPlatform.setup.plugins.usageCollection, - licensing: server.newPlatform.setup.plugins.licensing as LicensingPluginSetup, __LEGACY, }); diff --git a/x-pack/legacy/plugins/reporting/server/types.ts b/x-pack/legacy/plugins/reporting/server/types.ts index 69624d96774b6..0aa10e59053cd 100644 --- a/x-pack/legacy/plugins/reporting/server/types.ts +++ b/x-pack/legacy/plugins/reporting/server/types.ts @@ -165,7 +165,6 @@ export interface ReportingSetupDeps { licensing: LicensingPluginSetup; security: SecurityPluginSetup; usageCollection?: UsageCollectionSetup; - licensing: LicensingPluginSetup; __LEGACY: LegacySetup; } diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts index 549bcde8fad62..5f5ac281be6d2 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts @@ -23,12 +23,11 @@ const createMockSetupDeps = (setupMock?: any): ReportingSetupDeps => { return { elasticsearch: setupMock.elasticsearch, security: setupMock.security, - licensing: {} as any, - usageCollection: {} as any, - __LEGACY: { plugins: { xpack_main: { status: new EventEmitter() } } } as any, licensing: { license$: of({ isAvailable: true, isActive: true, type: 'basic' }), } as any, + usageCollection: {} as any, + __LEGACY: { plugins: { xpack_main: { status: new EventEmitter() } } } as any, }; }; From a8f4bcd2e560d3c4e6609c52af9d4c3a8d9d019f Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 08:35:05 -0700 Subject: [PATCH 18/31] Fix licensing implementation changes --- x-pack/legacy/plugins/reporting/server/core.ts | 18 +++++++++++------- .../reporting/server/routes/generation.ts | 2 +- .../plugins/reporting/server/routes/jobs.ts | 12 ++++++------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index 3f909bf04bff0..77349b09dfce3 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; -import { first, mapTo } from 'rxjs/operators'; +import { first, mapTo, map } from 'rxjs/operators'; import { ElasticsearchServiceSetup, KibanaRequest, @@ -19,7 +19,7 @@ import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { ReportingPluginSpecOptions } from '../'; // @ts-ignore no module definition import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; -import { ILicense } from '../../../../plugins/licensing/server'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { screenshotsObservableFactory } from '../export_types/common/lib/screenshots'; import { ServerFacade, ScreenshotsObservableFn } from '../server/types'; @@ -33,7 +33,7 @@ import { registerRoutes } from './routes'; export interface ReportingInternalSetup { browserDriverFactory: HeadlessChromiumDriverFactory; elasticsearch: ElasticsearchServiceSetup; - license$: Rx.Observable; + licensing: LicensingPluginSetup; basePath: BasePath['get']; router: IRouter; security: SecurityPluginSetup; @@ -49,7 +49,6 @@ interface ReportingInternalStart { export class ReportingCore { pluginSetupDeps?: ReportingInternalSetup; - private license?: ILicense; private pluginStartDeps?: ReportingInternalStart; private readonly pluginSetup$ = new Rx.ReplaySubject(); private readonly pluginStart$ = new Rx.ReplaySubject(); @@ -73,7 +72,6 @@ export class ReportingCore { public pluginSetup(reportingSetupDeps: ReportingInternalSetup) { this.pluginSetup$.next(reportingSetupDeps); - reportingSetupDeps.license$.subscribe((license) => (this.license = license)); } public pluginStart(reportingStartDeps: ReportingInternalStart) { @@ -99,8 +97,14 @@ export class ReportingCore { return (await this.getPluginStartDeps()).enqueueJob; } - public getLicenseInfo() { - return checkLicense(this.getExportTypesRegistry(), this.license); + public async getLicenseInfo() { + const { licensing } = await this.getPluginSetupDeps(); + return await licensing.license$ + .pipe( + map((license) => checkLicense(this.getExportTypesRegistry(), license)), + first() + ) + .toPromise(); } public getConfig(): ReportingConfig { diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index f9bfddce2d139..7dee5fba2d6a4 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -28,7 +28,7 @@ export function registerJobGenerationRoutes( * Generates enqueued job details to use in responses */ const handler: HandlerFunction = async (username, exportTypeId, jobParams, context, req, res) => { - const licenseInfo = reporting.getLicenseInfo(); + const licenseInfo = await reporting.getLicenseInfo(); const licenseResults = licenseInfo[exportTypeId]; if (!licenseResults.enableLinks) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index a065dd7fce96c..ab40157e27312 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -41,7 +41,7 @@ export async function registerJobInfoRoutes( userHandler(async (user, context, req, res) => { const { management: { jobTypes = [] }, - } = reporting.getLicenseInfo(); + } = await reporting.getLicenseInfo(); const { username } = user; const { page: queryPage = '0', @@ -72,7 +72,7 @@ export async function registerJobInfoRoutes( const { username } = user; const { management: { jobTypes = [] }, - } = reporting.getLicenseInfo(); + } = await reporting.getLicenseInfo(); const count = await jobsQuery.count(jobTypes, username); @@ -100,7 +100,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, - } = reporting.getLicenseInfo(); + } = await reporting.getLicenseInfo(); const result = await jobsQuery.get(username, docId, { includeContent: true }); @@ -140,7 +140,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, - } = reporting.getLicenseInfo(); + } = await reporting.getLicenseInfo(); const result = await jobsQuery.get(username, docId); @@ -192,7 +192,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, - } = reporting.getLicenseInfo(); + } = await reporting.getLicenseInfo(); return downloadResponseHandler(res, jobTypes, username, { docId }); }) @@ -214,7 +214,7 @@ export async function registerJobInfoRoutes( const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, - } = reporting.getLicenseInfo(); + } = await reporting.getLicenseInfo(); return deleteResponseHandler(res, jobTypes, username, { docId }); }) From 86920d38ed6babf9fe32a24d528d417e576aed65 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 09:21:18 -0700 Subject: [PATCH 19/31] WIP: Moving license over to async/observables --- x-pack/legacy/plugins/reporting/server/plugin.ts | 9 ++------- .../legacy/plugins/reporting/server/routes/jobs.test.ts | 1 - .../server/routes/lib/authorized_user_pre_routing.ts | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index 0d76d014cd845..27e9f0fa34284 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -27,12 +27,7 @@ export class ReportingPlugin public async setup(core: CoreSetup, plugins: ReportingSetupDeps) { const { config } = this; - const { - elasticsearch, - __LEGACY, - licensing: { license$ }, - security, - } = plugins; + const { elasticsearch, __LEGACY, licensing, security } = plugins; const router = core.http.createRouter(); const basePath = core.http.basePath.get; const { xpack_main: xpackMainLegacy, reporting: reportingLegacy } = __LEGACY.plugins; @@ -50,7 +45,7 @@ export class ReportingPlugin this.reportingCore.pluginSetup({ browserDriverFactory, elasticsearch, - license$, + licensing, basePath, router, security, diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index 5db2ba896e4c4..c5cf8c70b1222 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -24,7 +24,6 @@ describe('GET /api/reporting/jobs/download', () => { let httpSetup: setupServerReturn['httpSetup']; let exportTypesRegistry: ExportTypesRegistry; let core: ReportingCore; - let basePath: () => string; const config = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; const mockLogger = ({ diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index f8c063f517a97..78581078615d0 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -25,9 +25,9 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting return (handler: RequestHandlerUser): RequestHandler => { return (context, req, res) => { // TODO: license checking - let user: AuthenticatedUser | null = null; - if (deps.security) { + + if (setupDeps.security) { user = getUser(req); if (!user) { return res.unauthorized({ From 7532d550f7b94b7e8238a0b7ff8ef9ace6f48493 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 10:24:46 -0700 Subject: [PATCH 20/31] Fix disabled-security case --- .../reporting/server/lib/enqueue_job.ts | 7 ++-- .../reporting/server/lib/jobs_query.ts | 21 ++++++---- .../server/routes/generate_from_jobparams.ts | 3 +- .../routes/generate_from_savedobject.ts | 4 +- .../reporting/server/routes/generation.ts | 4 +- .../reporting/server/routes/jobs.test.ts | 41 +++++++++++-------- .../plugins/reporting/server/routes/jobs.ts | 18 +++----- .../server/routes/lib/job_response_handler.ts | 9 ++-- .../reporting/server/routes/types.d.ts | 3 +- 9 files changed, 58 insertions(+), 52 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 291432f08f4e6..87f714e00c898 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -6,6 +6,7 @@ import { EventEmitter } from 'events'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { AuthenticatedUser } from '../../../../../plugins/security/server'; import { ESQueueCreateJobFn } from '../../server/types'; import { ReportingCore, ReportingInternalSetup } from '../core'; // @ts-ignore @@ -28,7 +29,7 @@ export type Job = EventEmitter & { export type EnqueueJobFn = ( exportTypeId: string, jobParams: JobParamsType, - username: string, + user: AuthenticatedUser | null, context: RequestHandlerContext, request: KibanaRequest ) => Promise; @@ -46,12 +47,12 @@ export function enqueueJobFactory( return async function enqueueJob( exportTypeId: string, jobParams: JobParamsType, - username: string, + user: AuthenticatedUser | null, context: RequestHandlerContext, request: KibanaRequest ): Promise { type CreateJobFn = ESQueueCreateJobFn; - + const username = user ? user.username : false; const esqueue = await reporting.getEsqueue(); const exportType = reporting.getExportTypesRegistry().getById(exportTypeId); diff --git a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts index 0d7e50d66780e..5153fd0f4e5b8 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts @@ -5,10 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import Boom from 'boom'; import { errors as elasticsearchErrors } from 'elasticsearch'; import { ElasticsearchServiceSetup } from 'kibana/server'; import { get } from 'lodash'; +import { AuthenticatedUser } from '../../../../../plugins/security/server'; import { ReportingConfig } from '../'; import { JobSource } from '../types'; @@ -40,6 +40,8 @@ interface CountAggResult { count: number; } +const getUsername = (user: AuthenticatedUser | null) => (user ? user.username : false); + export function jobsQueryFactory( config: ReportingConfig, elasticsearch: ElasticsearchServiceSetup @@ -80,11 +82,12 @@ export function jobsQueryFactory( return { list( jobTypes: string[], - username: string, + user: AuthenticatedUser | null, page = 0, size = defaultSize, jobIds: string[] | null ) { + const username = getUsername(user); const body: QueryBody = { size, from: size * page, @@ -108,7 +111,8 @@ export function jobsQueryFactory( return getHits(execQuery('search', body)); }, - count(jobTypes: string[], username: string) { + count(jobTypes: string[], user: AuthenticatedUser | null) { + const username = getUsername(user); const body: QueryBody = { query: { constant_score: { @@ -127,8 +131,13 @@ export function jobsQueryFactory( }); }, - get(username: string, id: string, opts: GetOpts = {}): Promise | void> { + get( + user: AuthenticatedUser | null, + id: string, + opts: GetOpts = {} + ): Promise | void> { if (!id) return Promise.resolve(); + const username = getUsername(user); const body: QueryBody = { query: { @@ -160,14 +169,12 @@ export function jobsQueryFactory( const query = { id, index: deleteIndex }; return callAsInternalUser('delete', query); } catch (error) { - const wrappedError = new Error( + throw new Error( i18n.translate('xpack.reporting.jobsQuery.deleteError', { defaultMessage: 'Could not delete the report: {error}', values: { error: error.message }, }) ); - - throw Boom.boomify(wrappedError, { statusCode: error.status }); } }, }; diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 57fce5ec08194..273b0840f7296 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -43,7 +43,6 @@ export function registerGenerateFromJobParams( }, }, userHandler(async (user, context, req, res) => { - const { username } = user; let jobParamsRison: string | null; if (req.body) { @@ -84,7 +83,7 @@ export function registerGenerateFromJobParams( } try { - return await handler(username, exportType, jobParams, context, req, res); + return await handler(user, exportType, jobParams, context, req, res); } catch (err) { return handleError(exportType, err, res); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index c0726e23efd88..046893f3d244a 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -54,8 +54,6 @@ export function registerGenerateCsvFromSavedObject( }, }, userHandler(async (user, context, req, res) => { - const { username } = user; - /* * 1. Build `jobParams` object: job data that execution will need to reference in various parts of the lifecycle * 2. Pass the jobParams and other common params to `handleRoute`, a shared function to enqueue the job with the params @@ -65,7 +63,7 @@ export function registerGenerateCsvFromSavedObject( try { const jobParams = getJobParamsFromRequest(req, { isImmediate: false }); result = await handleRoute( - username, + user, CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, context, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 7dee5fba2d6a4..498a8fae988d3 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -27,7 +27,7 @@ export function registerJobGenerationRoutes( /* * Generates enqueued job details to use in responses */ - const handler: HandlerFunction = async (username, exportTypeId, jobParams, context, req, res) => { + const handler: HandlerFunction = async (user, exportTypeId, jobParams, context, req, res) => { const licenseInfo = await reporting.getLicenseInfo(); const licenseResults = licenseInfo[exportTypeId]; @@ -36,7 +36,7 @@ export function registerJobGenerationRoutes( } const enqueueJob = await reporting.getEnqueueJob(); - const job = await enqueueJob(exportTypeId, jobParams, username, context, req); + const job = await enqueueJob(exportTypeId, jobParams, user, context, req); // return the queue's job information const jobJson = job.toJSON(); diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index c5cf8c70b1222..6206bcfaf6e9b 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -127,24 +127,6 @@ describe('GET /api/reporting/jobs/download', () => { ); }); - it('fails when security is not there', async () => { - // @ts-ignore - const noSecurityPlugins = ({ - ...mockDeps, - security: null, - } as unknown) as ReportingInternalSetup; - registerJobInfoRoutes(core, noSecurityPlugins); - - await server.start(); - - await supertest(httpSetup.server.listener) - .get('/api/reporting/jobs/download/dope') - .expect(401) - .then(({ body }) => - expect(body.message).toMatchInlineSnapshot(`"Sorry, you aren't authenticated"`) - ); - }); - it('fails on users without the appropriate role', async () => { // @ts-ignore const peasantUser = ({ @@ -275,6 +257,29 @@ describe('GET /api/reporting/jobs/download', () => { .expect('content-disposition', 'inline; filename="report.csv"'); }); + it('succeeds when security is not there or disabled', async () => { + const hits = getCompleteHits(); + // @ts-ignore + mockDeps.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; + + // @ts-ignore + const noSecurityPlugins = ({ + ...mockDeps, + security: null, + } as unknown) as ReportingInternalSetup; + registerJobInfoRoutes(core, noSecurityPlugins); + + await server.start(); + + await supertest(httpSetup.server.listener) + .get('/api/reporting/jobs/download/dope') + .expect(200) + .expect('Content-Type', 'text/plain; charset=utf-8') + .expect('content-disposition', 'inline; filename="report.csv"'); + }); + it(`doesn't encode output-content for non-specified job-types`, async () => { const hits = getCompleteHits({ jobType: 'unencodedJobType', diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index ab40157e27312..428f53fb81d9d 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -42,7 +42,6 @@ export async function registerJobInfoRoutes( const { management: { jobTypes = [] }, } = await reporting.getLicenseInfo(); - const { username } = user; const { page: queryPage = '0', size: querySize = '10', @@ -51,7 +50,7 @@ export async function registerJobInfoRoutes( const page = parseInt(queryPage, 10) || 0; const size = Math.min(100, parseInt(querySize, 10) || 10); const jobIds = queryIds ? queryIds.split(',') : null; - const results = await jobsQuery.list(jobTypes, username, page, size, jobIds); + const results = await jobsQuery.list(jobTypes, user, page, size, jobIds); return res.ok({ body: results, @@ -69,12 +68,11 @@ export async function registerJobInfoRoutes( validate: false, }, userHandler(async (user, context, req, res) => { - const { username } = user; const { management: { jobTypes = [] }, } = await reporting.getLicenseInfo(); - const count = await jobsQuery.count(jobTypes, username); + const count = await jobsQuery.count(jobTypes, user); return res.ok({ body: count.toString(), @@ -96,13 +94,12 @@ export async function registerJobInfoRoutes( }, }, userHandler(async (user, context, req, res) => { - const { username } = user; const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = await reporting.getLicenseInfo(); - const result = await jobsQuery.get(username, docId, { includeContent: true }); + const result = await jobsQuery.get(user, docId, { includeContent: true }); if (!result) { throw Boom.notFound(); @@ -136,13 +133,12 @@ export async function registerJobInfoRoutes( }, }, userHandler(async (user, context, req, res) => { - const { username } = user; const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = await reporting.getLicenseInfo(); - const result = await jobsQuery.get(username, docId); + const result = await jobsQuery.get(user, docId); if (!result) { throw Boom.notFound(); @@ -188,13 +184,12 @@ export async function registerJobInfoRoutes( }, }, userHandler(async (user, context, req, res) => { - const { username } = user; const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = await reporting.getLicenseInfo(); - return downloadResponseHandler(res, jobTypes, username, { docId }); + return downloadResponseHandler(res, jobTypes, user, { docId }); }) ); @@ -210,13 +205,12 @@ export async function registerJobInfoRoutes( }, }, userHandler(async (user, context, req, res) => { - const { username } = user; const { docId } = req.params as { docId: string }; const { management: { jobTypes = [] }, } = await reporting.getLicenseInfo(); - return deleteResponseHandler(res, jobTypes, username, { docId }); + return deleteResponseHandler(res, jobTypes, user, { docId }); }) ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts index 5e94302a2c149..990af2d0aca80 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts @@ -5,6 +5,7 @@ */ import { ElasticsearchServiceSetup, kibanaResponseFactory } from 'kibana/server'; +import { AuthenticatedUser } from '../../../../../../plugins/security/server'; import { ReportingConfig } from '../../'; import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; import { ExportTypesRegistry } from '../../lib/export_types_registry'; @@ -30,13 +31,13 @@ export function downloadJobResponseHandlerFactory( return async function jobResponseHandler( res: typeof kibanaResponseFactory, validJobTypes: string[], - username: string, + user: AuthenticatedUser | null, params: JobResponseHandlerParams, opts: JobResponseHandlerOpts = {} ) { const { docId } = params; - const doc = await jobsQuery.get(username, docId, { includeContent: !opts.excludeContent }); + const doc = await jobsQuery.get(user, docId, { includeContent: !opts.excludeContent }); if (!doc) { return res.notFound(); } @@ -77,11 +78,11 @@ export function deleteJobResponseHandlerFactory( return async function deleteJobResponseHander( res: typeof kibanaResponseFactory, validJobTypes: string[], - username: string, + user: AuthenticatedUser | null, params: JobResponseHandlerParams ) { const { docId } = params; - const doc = await jobsQuery.get(username, docId, { includeContent: false }); + const doc = await jobsQuery.get(user, docId, { includeContent: false }); if (!doc) { return res.notFound(); diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts index e2c212741634c..396eec7e61e86 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts @@ -5,10 +5,11 @@ */ import { KibanaResponseFactory, KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { AuthenticatedUser } from '../../../../../plugins/security/common/model/authenticated_user'; import { JobDocPayload } from '../types'; export type HandlerFunction = ( - username: string, + user: AuthenticatedUser | null, exportType: string, jobParams: object, context: RequestHandlerContext, From f764bf84353f3f0c8eca9c947bfad85b83554366 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 28 May 2020 10:45:43 -0700 Subject: [PATCH 21/31] finish auth_user_pre_routing cleanup - no more license check --- .../lib/authorized_user_pre_routing.test.ts | 4 ---- .../routes/lib/authorized_user_pre_routing.ts | 21 +++++++++---------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts index 95f58614ba97e..4f4f00960b5a2 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -45,10 +45,6 @@ beforeEach(async () => { }); describe('authorized_user_pre_routing', function () { - it('should return badRequest when license is undefined', async function () {}); - - it(`should return with badRequest when license isn't available`, async function () {}); - it('should return from handler with null user when security is disabled', async function () { const mockSetupDeps = ({ ...(await mockCore.getPluginSetupDeps()), diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 78581078615d0..976e122cf0fc8 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -9,10 +9,11 @@ import { AuthenticatedUser } from '../../../../../../plugins/security/server'; import { getUserFactory } from '../../lib/get_user'; import { ReportingInternalSetup, ReportingCore } from '../../core'; +type ReportingUser = AuthenticatedUser | null; const superuserRole = 'superuser'; export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer R - ? (user: AuthenticatedUser | null, ...a: U) => R + ? (user: ReportingUser, ...a: U) => R : never; export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( @@ -24,26 +25,24 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting return (handler: RequestHandlerUser): RequestHandler => { return (context, req, res) => { - // TODO: license checking - let user: AuthenticatedUser | null = null; - + let user: ReportingUser = null; if (setupDeps.security) { + // find the authenticated user, or null if security is not enabled user = getUser(req); if (!user) { - return res.unauthorized({ - body: `Sorry, you aren't authenticated`, - }); + // security is enabled but the user is null + return res.unauthorized({ body: `Sorry, you aren't authenticated` }); } } if (user) { + // check allowance with the configured set of roleas + "superuser" const allowedRoles = config.get('roles', 'allow') || []; const authorizedRoles = [superuserRole, ...allowedRoles]; - if (user && !user.roles.find((role) => authorizedRoles.includes(role))) { - return res.forbidden({ - body: `Sorry, you don't have access to Reporting`, - }); + if (!user.roles.find((role) => authorizedRoles.includes(role))) { + // user's roles do not allow + return res.forbidden({ body: `Sorry, you don't have access to Reporting` }); } } From 874222f9b2508e50ff8eade7dcfcecbdb75f15b2 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 15:03:13 -0700 Subject: [PATCH 22/31] WIP: Fixing final api tests --- .../server/lib/iso_string_validate.ts | 12 --- .../server/routes/generate_from_jobparams.ts | 16 ++-- .../routes/generate_from_savedobject.ts | 9 +-- .../generate_from_savedobject_immediate.ts | 74 ++++++++++--------- .../reporting/server/routes/generation.ts | 14 +++- .../reporting/server/routes/types.d.ts | 6 +- 6 files changed, 61 insertions(+), 70 deletions(-) delete mode 100644 x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts diff --git a/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts b/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts deleted file mode 100644 index 01acbe5508557..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/lib/iso_string_validate.ts +++ /dev/null @@ -1,12 +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. - */ -const isoValidationMessage = `value must be a valid ISO format`; - -export const isoStringValidate = (input: string): string | undefined => { - if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-\d{2}:\d{2}/.test(input)) { - return isoValidationMessage; - } -}; diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 273b0840f7296..5fc37ea12cc72 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -30,16 +30,18 @@ export function registerGenerateFromJobParams( params: schema.object({ exportType: schema.string({ minLength: 2 }), }), - body: schema.maybe( + body: schema.nullable( schema.object({ jobParams: schema.maybe(schema.string()), }) ), - query: schema.object({ - jobParams: schema.string({ - defaultValue: '', - }), - }), + query: schema.nullable( + schema.object({ + jobParams: schema.string({ + defaultValue: '', + }), + }) + ), }, }, userHandler(async (user, context, req, res) => { @@ -85,7 +87,7 @@ export function registerGenerateFromJobParams( try { return await handler(user, exportType, jobParams, context, req, res); } catch (err) { - return handleError(exportType, err, res); + return handleError(res, err); } }) ); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 046893f3d244a..753a58d1293d2 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -6,7 +6,6 @@ import { schema } from '@kbn/config-schema'; import { get } from 'lodash'; -import { isoStringValidate } from '../lib/iso_string_validate'; import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types'; import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../common/constants'; @@ -43,12 +42,8 @@ export function registerGenerateCsvFromSavedObject( state: schema.object({}), timerange: schema.object({ timezone: schema.string({ defaultValue: 'UTC' }), - min: schema.string({ - validate: isoStringValidate, - }), - max: schema.string({ - validate: isoStringValidate, - }), + min: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), + max: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), }), }), }, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 36bf0e2804074..36e03dd798bf7 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -6,11 +6,11 @@ import { schema } from '@kbn/config-schema'; import { ReportingCore } from '../'; +import { HandlerErrorFunction } from './types'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; import { createJobFactory, executeJobFactory } from '../../export_types/csv_from_savedobject'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types'; -import { isoStringValidate } from '../lib/iso_string_validate'; import { JobDocOutput } from '../types'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { ReportingInternalSetup } from '../core'; @@ -26,7 +26,8 @@ import { ReportingInternalSetup } from '../core'; */ export function registerGenerateCsvFromSavedObjectImmediate( reporting: ReportingCore, - setupDeps: ReportingInternalSetup + setupDeps: ReportingInternalSetup, + handleError: HandlerErrorFunction ) { const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); const { router } = setupDeps; @@ -48,12 +49,8 @@ export function registerGenerateCsvFromSavedObjectImmediate( state: schema.object({}, { unknowns: 'allow' }), timerange: schema.object({ timezone: schema.string({ defaultValue: 'UTC' }), - min: schema.string({ - validate: isoStringValidate, - }), - max: schema.string({ - validate: isoStringValidate, - }), + min: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), + max: schema.nullable(schema.oneOf([schema.number(), schema.string({ minLength: 5 })])), }), }), }, @@ -63,37 +60,42 @@ export function registerGenerateCsvFromSavedObjectImmediate( const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); const createJobFn = createJobFactory(reporting, setupDeps); const executeJobFn = await executeJobFactory(reporting, setupDeps); // FIXME: does not "need" to be async - const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( - jobParams, - req.headers, - context, - req - ); - const { - content_type: jobOutputContentType, - content: jobOutputContent, - size: jobOutputSize, - }: JobDocOutput = await executeJobFn(null, jobDocPayload, context, req); - logger.info(`Job output size: ${jobOutputSize} bytes`); + try { + const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( + jobParams, + req.headers, + context, + req + ); + const { + content_type: jobOutputContentType, + content: jobOutputContent, + size: jobOutputSize, + }: JobDocOutput = await executeJobFn(null, jobDocPayload, context, req); - /* - * ESQueue worker function defaults `content` to null, even if the - * executeJob returned undefined. - * - * This converts null to undefined so the value can be sent to h.response() - */ - if (jobOutputContent === null) { - logger.warn('CSV Job Execution created empty content result'); - } + logger.info(`Job output size: ${jobOutputSize} bytes`); + + /* + * ESQueue worker function defaults `content` to null, even if the + * executeJob returned undefined. + * + * This converts null to undefined so the value can be sent to h.response() + */ + if (jobOutputContent === null) { + logger.warn('CSV Job Execution created empty content result'); + } - return res.ok({ - body: jobOutputContent || '', - headers: { - 'content-type': jobOutputContentType, - 'accept-ranges': 'none', - }, - }); + return res.ok({ + body: jobOutputContent || '', + headers: { + 'content-type': jobOutputContentType, + 'accept-ranges': 'none', + }, + }); + } catch (err) { + return handleError(res, err); + } }) ); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 498a8fae988d3..23aa20b15c32f 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; import { errors as elasticsearchErrors } from 'elasticsearch'; import { kibanaResponseFactory } from 'src/core/server'; import { ReportingCore } from '../'; @@ -52,7 +53,14 @@ export function registerJobGenerationRoutes( }); }; - function handleError(exportTypeId: string, err: Error, res: typeof kibanaResponseFactory) { + function handleError(res: typeof kibanaResponseFactory, err: Error | Boom) { + if (err instanceof Boom) { + return res.customError({ + statusCode: err.output.statusCode, + body: err.output.payload.message, + }); + } + if (err instanceof esErrors['401']) { return res.unauthorized({ body: `Sorry, you aren't authenticated`, @@ -61,7 +69,7 @@ export function registerJobGenerationRoutes( if (err instanceof esErrors['403']) { return res.forbidden({ - body: `Sorry, you are not authorized to create ${exportTypeId} reports`, + body: `Sorry, you are not authorized`, }); } @@ -81,6 +89,6 @@ export function registerJobGenerationRoutes( // Register beta panel-action download-related API's if (config.get('csv', 'enablePanelActionDownload')) { registerGenerateCsvFromSavedObject(reporting, setupDeps, handler, handleError); - registerGenerateCsvFromSavedObjectImmediate(reporting, setupDeps); + registerGenerateCsvFromSavedObjectImmediate(reporting, setupDeps, handleError); } } diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts index 396eec7e61e86..afa3fd3358fc1 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts @@ -17,11 +17,7 @@ export type HandlerFunction = ( res: KibanaResponseFactory ) => any; -export type HandlerErrorFunction = ( - exportType: string, - err: Error, - res: KibanaResponseFactory -) => any; +export type HandlerErrorFunction = (res: KibanaResponseFactory, err: Error) => any; export interface QueuedJobPayload { error?: boolean; From dce408389c8a3eee79b1c7cc6e7d03555d2131e1 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 15:38:35 -0700 Subject: [PATCH 23/31] Trying to get schema differences in alignment --- .../server/lib/get_job_params_from_request.ts | 7 ++++++- .../reporting/server/routes/generate_from_savedobject.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts index 1e3303cbd1a9a..32a12b6d1ae5b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts @@ -16,7 +16,12 @@ export function getJobParamsFromRequest( savedObjectId: string; }; const { timerange, state } = request.body as JobParamsPostPayloadPanelCsv; - const post = timerange || state ? { timerange, state } : undefined; + + const post = Object.assign( + {}, + state ? { state } : {}, + timerange && timerange.min && timerange.max ? { timerange } : { timerange: {} } + ); return { isImmediate: opts.isImmediate, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 753a58d1293d2..047a02a96fdc9 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -66,7 +66,7 @@ export function registerGenerateCsvFromSavedObject( res ); } catch (err) { - return handleRouteError(CSV_FROM_SAVEDOBJECT_JOB_TYPE, err, res); + return handleRouteError(res, err); } if (get(result, 'source.job') == null) { From 2a5f9a0b8c7c348fb42dc348aead174e86a4e648 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 16:07:27 -0700 Subject: [PATCH 24/31] Reverting back to previous generation handler --- .../server/lib/get_job_params_from_request.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts index 32a12b6d1ae5b..5aed02c10b961 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts @@ -17,11 +17,7 @@ export function getJobParamsFromRequest( }; const { timerange, state } = request.body as JobParamsPostPayloadPanelCsv; - const post = Object.assign( - {}, - state ? { state } : {}, - timerange && timerange.min && timerange.max ? { timerange } : { timerange: {} } - ); + const post = timerange || state ? { timerange, state } : undefined; return { isImmediate: opts.isImmediate, From f7d1134333c96e6ce3475d02e4bd9d71a2bd2bcb Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 16:29:47 -0700 Subject: [PATCH 25/31] Fix final API tests --- .../csv_from_savedobject/server/lib/get_filters.ts | 2 +- x-pack/legacy/plugins/reporting/server/types.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.ts index 071427f4dab64..4695bbd922581 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_filters.ts @@ -22,7 +22,7 @@ export function getFilters( let timezone: string | null; if (indexPatternTimeField) { - if (!timerange) { + if (!timerange || !timerange.min || !timerange.max) { throw badRequest( `Time range params are required for index pattern [${indexPatternId}], using time field [${indexPatternTimeField}]` ); diff --git a/x-pack/legacy/plugins/reporting/server/types.ts b/x-pack/legacy/plugins/reporting/server/types.ts index 0aa10e59053cd..b83c87cfefe53 100644 --- a/x-pack/legacy/plugins/reporting/server/types.ts +++ b/x-pack/legacy/plugins/reporting/server/types.ts @@ -54,8 +54,8 @@ export type ReportingRequestPayload = GenerateExportTypePayload | JobParamPostPa export interface TimeRangeParams { timezone: string; - min: Date | string | number; - max: Date | string | number; + min: Date | string | number | null; + max: Date | string | number | null; } export interface JobParamPostPayload { From 0aca67caa28ffb6227563a966bb0d3c3b997c421 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 28 May 2020 20:52:35 -0700 Subject: [PATCH 26/31] Final API test fixes, few more hardening tests and better error messages --- .../server/routes/generate_from_jobparams.ts | 2 +- .../server/routes/generation.test.ts | 29 +++++++++++++++++-- .../reporting/server/routes/generation.ts | 4 +++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 5fc37ea12cc72..f20dd58d1cd32 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -62,7 +62,7 @@ export function registerGenerateFromJobParams( if (!jobParamsRison) { return res.customError({ statusCode: 400, - body: 'A jobParams RISON string is required', + body: 'A jobParams RISON string is required in the querystring or POST body', }); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts index 7d45f8922be97..1e8f6306c2761 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts @@ -99,12 +99,23 @@ describe('POST /api/reporting/generate', () => { .expect(400) .then(({ body }) => expect(body.message).toMatchInlineSnapshot( - '"[request body]: expected a plain object value, but found [null] instead."' + '"A jobParams RISON string is required in the querystring or POST body"' ) ); }); - it('returns 400 if job params is invalid', async () => { + it('returns 400 if job params query is invalid', async () => { + registerJobGenerationRoutes(core, mockDeps); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/printablePdf?jobParams=foo:') + .expect(400) + .then(({ body }) => expect(body.message).toMatchInlineSnapshot('"invalid rison: foo:"')); + }); + + it('returns 400 if job params body is invalid', async () => { registerJobGenerationRoutes(core, mockDeps); await server.start(); @@ -116,6 +127,20 @@ describe('POST /api/reporting/generate', () => { .then(({ body }) => expect(body.message).toMatchInlineSnapshot('"invalid rison: foo:"')); }); + it('returns 400 export type is invalid', async () => { + registerJobGenerationRoutes(core, mockDeps); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post('/api/reporting/generate/TonyHawksProSkater2') + .send({ jobParams: `abc` }) + .expect(400) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot('"Invalid export-type of TonyHawksProSkater2"') + ); + }); + it('returns 400 if job handler throws an error', async () => { const errorText = 'you found me'; core.getEnqueueJob = async () => diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 23aa20b15c32f..f04386b6192b8 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -32,6 +32,10 @@ export function registerJobGenerationRoutes( const licenseInfo = await reporting.getLicenseInfo(); const licenseResults = licenseInfo[exportTypeId]; + if (!licenseResults) { + return res.badRequest({ body: `Invalid export-type of ${exportTypeId}` }); + } + if (!licenseResults.enableLinks) { return res.forbidden({ body: licenseResults.message }); } From 27040a9d0410d5abe3a3d7192c3c588f4160b430 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Fri, 29 May 2020 10:37:26 -0700 Subject: [PATCH 27/31] Simplify lower-level module implementation (core only interface) + test updates --- .../export_types/csv/server/create_job.ts | 4 +- .../csv/server/execute_job.test.ts | 170 +++++------------- .../export_types/csv/server/execute_job.ts | 7 +- .../server/create_job/create_job.ts | 4 +- .../server/execute_job.ts | 9 +- .../server/lib/generate_csv.ts | 4 +- .../server/lib/generate_csv_search.ts | 3 +- .../png/server/create_job/index.ts | 5 +- .../png/server/execute_job/index.test.ts | 61 ++----- .../png/server/execute_job/index.ts | 5 +- .../printable_pdf/server/create_job/index.ts | 5 +- .../server/execute_job/index.test.ts | 67 +++---- .../printable_pdf/server/execute_job/index.ts | 5 +- .../legacy/plugins/reporting/server/core.ts | 20 +-- .../reporting/server/lib/create_queue.ts | 8 +- .../server/lib/create_worker.test.ts | 5 +- .../reporting/server/lib/create_worker.ts | 9 +- .../reporting/server/lib/enqueue_job.ts | 10 +- .../legacy/plugins/reporting/server/plugin.ts | 6 +- .../server/routes/generate_from_jobparams.ts | 5 +- .../routes/generate_from_savedobject.ts | 5 +- .../generate_from_savedobject_immediate.ts | 9 +- .../server/routes/generation.test.ts | 47 ++--- .../reporting/server/routes/generation.ts | 12 +- .../plugins/reporting/server/routes/index.ts | 8 +- .../reporting/server/routes/jobs.test.ts | 108 ++++++----- .../plugins/reporting/server/routes/jobs.ts | 9 +- .../lib/authorized_user_pre_routing.test.ts | 58 +++--- .../routes/lib/authorized_user_pre_routing.ts | 9 +- .../legacy/plugins/reporting/server/types.ts | 10 +- .../create_mock_reportingplugin.ts | 14 +- 31 files changed, 274 insertions(+), 427 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts index ecc488e89cd59..c76b4afe727da 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts @@ -9,13 +9,13 @@ import { ReportingCore } from '../../../server'; import { cryptoFactory } from '../../../server/lib'; import { CreateJobFactory, ESQueueCreateJobFn } from '../../../server/types'; import { JobParamsDiscoverCsv } from '../types'; -import { ReportingInternalSetup } from '../../../server/core'; export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { +>> = function createJobFactoryFn(reporting: ReportingCore) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); + const setupDeps = reporting.getPluginSetupDeps(); return async function createJob( jobParams: JobParamsDiscoverCsv, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts index 3c074c0864aca..a8955f774b2ff 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts @@ -11,12 +11,10 @@ import sinon from 'sinon'; import { fieldFormats } from '../../../../../../../src/plugins/data/server'; import { CancellationToken } from '../../../../../../plugins/reporting/common'; import { CSV_BOM_CHARS } from '../../../common/constants'; -import { LevelLogger } from '../../../server/lib'; import { setFieldFormats } from '../../../server/services'; import { createMockReportingCore } from '../../../test_helpers'; import { JobDocPayloadDiscoverCsv } from '../types'; import { executeJobFactory } from './execute_job'; -import { ReportingInternalSetup } from '../../../server/core'; const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms)); @@ -32,14 +30,6 @@ describe('CSV Execute Job', function () { const headers = { sid: 'test', }; - const mockLogger = new LevelLogger({ - get: () => - ({ - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - } as any), - }); let defaultElasticsearchResponse: any; let encryptedHeaders: any; @@ -115,9 +105,7 @@ describe('CSV Execute Job', function () { describe('basic Elasticsearch call behavior', function () { it('should decrypt encrypted headers and pass to callAsCurrentUser', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); await executeJob( 'job456', getJobDocPayload({ @@ -137,9 +125,7 @@ describe('CSV Execute Job', function () { testBody: true, }; - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const job = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -166,9 +152,7 @@ describe('CSV Execute Job', function () { _scroll_id: scrollId, }); callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); await executeJob( 'job456', getJobDocPayload({ @@ -186,9 +170,7 @@ describe('CSV Execute Job', function () { }); it('should not execute scroll if there are no hits from the search', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); await executeJob( 'job456', getJobDocPayload({ @@ -222,9 +204,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); await executeJob( 'job456', getJobDocPayload({ @@ -263,9 +243,7 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); await executeJob( 'job456', getJobDocPayload({ @@ -297,9 +275,7 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -326,9 +302,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -353,9 +327,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -381,9 +353,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -409,9 +379,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -437,9 +405,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -466,9 +432,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -489,9 +453,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -514,9 +476,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -537,9 +497,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -555,9 +513,7 @@ describe('CSV Execute Job', function () { describe('Elasticsearch call errors', function () { it('should reject Promise if search call errors out', async function () { callAsCurrentUserStub.rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -576,9 +532,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); callAsCurrentUserStub.onSecondCall().rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -599,9 +553,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -622,9 +574,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -652,9 +602,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -682,9 +630,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -720,9 +666,7 @@ describe('CSV Execute Job', function () { }); it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); executeJob( 'job345', getJobDocPayload({ @@ -741,9 +685,7 @@ describe('CSV Execute Job', function () { }); it(`shouldn't call clearScroll if it never got a scrollId`, async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); executeJob( 'job345', getJobDocPayload({ @@ -761,9 +703,7 @@ describe('CSV Execute Job', function () { }); it('should call clearScroll if it got a scrollId', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); executeJob( 'job345', getJobDocPayload({ @@ -785,9 +725,7 @@ describe('CSV Execute Job', function () { describe('csv content', function () { it('should write column headers to output, even if there are no results', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -799,9 +737,7 @@ describe('CSV Execute Job', function () { it('should use custom uiSettings csv:separator for header', async function () { mockUiSettingsClient.get.withArgs('csv:separator').returns(';'); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -813,9 +749,7 @@ describe('CSV Execute Job', function () { it('should escape column headers if uiSettings csv:quoteValues is true', async function () { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -827,9 +761,7 @@ describe('CSV Execute Job', function () { it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(false); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -840,9 +772,7 @@ describe('CSV Execute Job', function () { }); it('should write column headers to output, when there are results', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ one: '1', two: '2' }], @@ -862,9 +792,7 @@ describe('CSV Execute Job', function () { }); it('should use comma separated values of non-nested fields from _source', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -885,9 +813,7 @@ describe('CSV Execute Job', function () { }); it('should concatenate the hits from multiple responses', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -915,9 +841,7 @@ describe('CSV Execute Job', function () { }); it('should use field formatters to format fields', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -959,9 +883,7 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(1); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -991,9 +913,7 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1030,9 +950,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1071,9 +989,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1110,9 +1026,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1138,9 +1052,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1166,9 +1078,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin, { - logger: mockLogger, - } as ReportingInternalSetup); + const executeJob = await executeJobFactory(mockReportingPlugin); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index afb350b0d065b..497b9b3510572 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -15,15 +15,12 @@ import { ESQueueWorkerExecuteFn, ExecuteJobFactory } from '../../../server/types import { JobDocPayloadDiscoverCsv } from '../types'; import { fieldFormatMapFactory } from './lib/field_format_map'; import { createGenerateCsv } from './lib/generate_csv'; -import { ReportingInternalSetup } from '../../../server/core'; export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup -) { +>> = async function executeJobFactoryFn(reporting: ReportingCore) { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); const logger = setupDeps.logger.clone([CSV_JOB_TYPE, 'execute-job']); const serverBasePath = config.kbnConfig.get('server', 'basePath'); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts index ed670ad3c55c2..c90f1ce8aa7d9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts @@ -7,7 +7,6 @@ import { notFound, notImplemented } from 'boom'; import { get } from 'lodash'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { ReportingInternalSetup } from '../../../../server/core'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; import { cryptoFactory } from '../../../../server/lib'; @@ -42,9 +41,10 @@ interface VisData { export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { +>> = function createJobFactoryFn(reporting: ReportingCore) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); + const setupDeps = reporting.getPluginSetupDeps(); const logger = setupDeps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); return async function createJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index e340585d140c9..5d9e2a4113e3d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -13,7 +13,6 @@ import { ExecuteJobFactory, JobDocOutput, JobDocPayload } from '../../../server/ import { CsvResultFromSearch } from '../../csv/types'; import { FakeRequest, JobDocPayloadPanelCsv, JobParamsPanelCsv, SearchPanel } from '../types'; import { createGenerateCsv } from './lib'; -import { ReportingInternalSetup } from '../../../server/core'; /* * ImmediateExecuteFn receives the job doc payload because the payload was @@ -28,14 +27,12 @@ export type ImmediateExecuteFn = ( export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup -) { +>> = async function executeJobFactoryFn(reporting: ReportingCore) { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); const logger = setupDeps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); - const generateCsv = createGenerateCsv(reporting, setupDeps); + const generateCsv = createGenerateCsv(reporting); return async function executeJob( jobId: string | null, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts index 27807dc769bb0..8cf456676ee67 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts @@ -6,12 +6,11 @@ import { badRequest } from 'boom'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { ReportingInternalSetup } from '../../../../server/core'; import { ReportingCore } from '../../../../server'; import { FakeRequest, JobParamsPanelCsv, SearchPanel, VisPanel } from '../../types'; import { generateCsvSearch } from './generate_csv_search'; -export function createGenerateCsv(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { +export function createGenerateCsv(reporting: ReportingCore) { return async function generateCsv( context: RequestHandlerContext, request: KibanaRequest | FakeRequest, @@ -28,7 +27,6 @@ export function createGenerateCsv(reporting: ReportingCore, setupDeps: Reporting case 'search': return await generateCsvSearch( reporting, - setupDeps, context, request as KibanaRequest, panel as SearchPanel, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index 8f36aec8e15e2..867eaddf5a3cc 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReportingInternalSetup } from '../../../../server/core'; import { IUiSettingsClient, KibanaRequest, @@ -52,12 +51,12 @@ const getUiSettings = async (config: IUiSettingsClient) => { export async function generateCsvSearch( reporting: ReportingCore, - setupDeps: ReportingInternalSetup, context: RequestHandlerContext, req: KibanaRequest, searchPanel: SearchPanel, jobParams: JobParamsDiscoverCsv ): Promise { + const setupDeps = reporting.getPluginSetupDeps(); const savedObjectsClient = context.core.savedObjects.client; const { indexPatternSavedObjectId, timerange } = searchPanel; const savedSearchObjectAttr = searchPanel.attributes; diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts index 815158cddb0f6..ab492c21256eb 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts @@ -11,8 +11,9 @@ import { JobParamsPNG } from '../../types'; export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting, deps) { +>> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); return async function createJob( @@ -31,7 +32,7 @@ export const createJobFactory: CreateJobFactory ({ generatePngObservableFactory: jest.fn() })); @@ -22,18 +21,6 @@ const cancellationToken = ({ on: jest.fn(), } as unknown) as CancellationToken; -const mockLoggerFactory = { - get: jest.fn().mockImplementation(() => ({ - error: jest.fn(), - debug: jest.fn(), - warn: jest.fn(), - })), -}; -const getMockDeps = () => - ({ - logger: new LevelLogger(mockLoggerFactory), - } as ReportingInternalSetup); - const mockEncryptionKey = 'abcabcsecuresecret'; const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); @@ -81,7 +68,7 @@ test(`passes browserTimezone to generatePng`, async () => { const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting, getMockDeps()); + const executeJob = await executeJobFactory(mockReporting); const browserTimezone = 'UTC'; await executeJob( 'pngJobId', @@ -93,39 +80,29 @@ test(`passes browserTimezone to generatePng`, async () => { cancellationToken ); - expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(` + // Don't snapshot logger + const [, ...rest] = generatePngObservable.mock.calls[0]; + + expect(rest).toMatchInlineSnapshot(` Array [ - Array [ - LevelLogger { - "_logger": Object { - "get": [MockFunction], - }, - "_tags": Array [ - "PNG", - "execute", - "pngJobId", - ], - "warning": [Function], - }, - "http://localhost:5601/sbp/app/kibana#/something", - "UTC", - Object { - "conditions": Object { - "basePath": "/sbp", - "hostname": "localhost", - "port": 5601, - "protocol": "http", - }, - "headers": Object {}, + "http://localhost:5601/sbp/app/kibana#/something", + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", }, - undefined, - ], + "headers": Object {}, + }, + undefined, ] `); }); test(`returns content_type of application/png`, async () => { - const executeJob = await executeJobFactory(mockReporting, getMockDeps()); + const executeJob = await executeJobFactory(mockReporting); const encryptedHeaders = await encryptHeaders({}); const generatePngObservable = await generatePngObservableFactory(mockReporting); @@ -144,7 +121,7 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => { const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ base64: testContent })); - const executeJob = await executeJobFactory(mockReporting, getMockDeps()); + const executeJob = await executeJobFactory(mockReporting); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pngJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index c787c3163f072..0fd86bef2bfbe 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -7,7 +7,6 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; -import { ReportingInternalSetup } from '../../../../server/core'; import { PNG_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../server/types'; @@ -23,10 +22,10 @@ import { generatePngObservableFactory } from '../lib/generate_png'; type QueuedPngExecutorFactory = ExecuteJobFactory>; export const executeJobFactory: QueuedPngExecutorFactory = async function executeJobFactoryFn( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup + reporting: ReportingCore ) { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const encryptionKey = config.get('encryptionKey'); const logger = setupDeps.logger.clone([PNG_JOB_TYPE, 'execute']); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts index b6ba908e2c86f..ef597cfb45f78 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts @@ -11,8 +11,9 @@ import { JobParamsPDF } from '../../types'; export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting, deps) { +>> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); return async function createJobFn( @@ -25,7 +26,7 @@ export const createJobFactory: CreateJobFactory ({ generatePdfObservableFactory: jest.fn( import * as Rx from 'rxjs'; import { CancellationToken } from '../../../../../../../plugins/reporting/common'; import { ReportingCore } from '../../../../server'; -import { cryptoFactory, LevelLogger } from '../../../../server/lib'; +import { cryptoFactory } from '../../../../server/lib'; import { createMockReportingCore } from '../../../../test_helpers'; import { JobDocPayloadPDF } from '../../types'; import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { executeJobFactory } from './index'; -import { ReportingInternalSetup } from '../../../../server/core'; let mockReporting: ReportingCore; @@ -22,18 +21,6 @@ const cancellationToken = ({ on: jest.fn(), } as unknown) as CancellationToken; -const mockLoggerFactory = { - get: jest.fn().mockImplementation(() => ({ - error: jest.fn(), - debug: jest.fn(), - warn: jest.fn(), - })), -}; -const getMockDeps = () => - ({ - logger: new LevelLogger(mockLoggerFactory), - } as ReportingInternalSetup); - const mockEncryptionKey = 'testencryptionkey'; const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); @@ -79,7 +66,7 @@ test(`passes browserTimezone to generatePdf`, async () => { const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting, getMockDeps()); + const executeJob = await executeJobFactory(mockReporting); const browserTimezone = 'UTC'; await executeJob( 'pdfJobId', @@ -91,43 +78,33 @@ test(`passes browserTimezone to generatePdf`, async () => { cancellationToken ); - expect(generatePdfObservable.mock.calls).toMatchInlineSnapshot(` + // Don't snapshot logger + const [, ...rest] = generatePdfObservable.mock.calls[0]; + + expect(rest).toMatchInlineSnapshot(` Array [ + undefined, Array [ - LevelLogger { - "_logger": Object { - "get": [MockFunction], - }, - "_tags": Array [ - "printable_pdf", - "execute", - "pdfJobId", - ], - "warning": [Function], - }, - undefined, - Array [ - "http://localhost:5601/sbp/app/kibana#/something", - ], - "UTC", - Object { - "conditions": Object { - "basePath": "/sbp", - "hostname": "localhost", - "port": 5601, - "protocol": "http", - }, - "headers": Object {}, - }, - undefined, - false, + "http://localhost:5601/sbp/app/kibana#/something", ], + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", + }, + "headers": Object {}, + }, + undefined, + false, ] `); }); test(`returns content_type of application/pdf`, async () => { - const executeJob = await executeJobFactory(mockReporting, getMockDeps()); + const executeJob = await executeJobFactory(mockReporting); const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = await generatePdfObservableFactory(mockReporting); @@ -146,7 +123,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); - const executeJob = await executeJobFactory(mockReporting, getMockDeps()); + const executeJob = await executeJobFactory(mockReporting); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pdfJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index 7b26b4bf48b77..ccdff54a4e1ae 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -7,7 +7,6 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; -import { ReportingInternalSetup } from '../../../../server/core'; import { PDF_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../server/types'; @@ -24,10 +23,10 @@ import { generatePdfObservableFactory } from '../lib/generate_pdf'; type QueuedPdfExecutorFactory = ExecuteJobFactory>; export const executeJobFactory: QueuedPdfExecutorFactory = async function executeJobFactoryFn( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup + reporting: ReportingCore ) { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const encryptionKey = config.get('encryptionKey'); const logger = setupDeps.logger.clone([PDF_JOB_TYPE, 'execute']); diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index 77349b09dfce3..ac7e1e04b74c3 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -66,11 +66,11 @@ export class ReportingCore { } public async setupRoutes() { - const dep = await this.getPluginSetupDeps(); - registerRoutes(this, dep); + registerRoutes(this); } public pluginSetup(reportingSetupDeps: ReportingInternalSetup) { + this.pluginSetupDeps = reportingSetupDeps; this.pluginSetup$.next(reportingSetupDeps); } @@ -98,7 +98,7 @@ export class ReportingCore { } public async getLicenseInfo() { - const { licensing } = await this.getPluginSetupDeps(); + const { licensing } = this.getPluginSetupDeps(); return await licensing.license$ .pipe( map((license) => checkLicense(this.getExportTypesRegistry(), license)), @@ -111,16 +111,16 @@ export class ReportingCore { return this.config; } - public async getScreenshotsObservable(): Promise { - const { browserDriverFactory } = await this.getPluginSetupDeps(); + public getScreenshotsObservable(): ScreenshotsObservableFn { + const { browserDriverFactory } = this.getPluginSetupDeps(); return screenshotsObservableFactory(this.config.get('capture'), browserDriverFactory); } - public async getPluginSetupDeps() { - if (this.pluginSetupDeps) { - return this.pluginSetupDeps; + public getPluginSetupDeps() { + if (!this.pluginSetupDeps) { + throw new Error(`"pluginSetupDeps" dependencies haven't initialized yet`); } - return await this.pluginSetup$.pipe(first()).toPromise(); + return this.pluginSetupDeps; } /* @@ -135,7 +135,7 @@ export class ReportingCore { } public async getElasticsearchService() { - return (await this.getPluginSetupDeps()).elasticsearch; + return this.getPluginSetupDeps().elasticsearch; } public async getSavedObjectsClient(fakeRequest: KibanaRequest) { diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 8bb8e19ae294e..3fb2d274e407f 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReportingCore, ReportingInternalSetup } from '../core'; +import { ReportingCore } from '../core'; import { JobDocOutput, JobSource } from '../types'; import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed import { createWorkerFactory } from './create_worker'; @@ -37,10 +37,10 @@ type GenericWorkerFn = ( ) => void | Promise; export async function createQueueFactory( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup + reporting: ReportingCore ): Promise { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const { logger } = setupDeps; const queueIndexInterval = config.get('queue', 'indexInterval'); const queueTimeout = config.get('queue', 'timeout'); @@ -60,7 +60,7 @@ export async function createQueueFactory( if (isPollingEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed - const createWorker = createWorkerFactory(reporting, setupDeps); + const createWorker = createWorkerFactory(reporting); await createWorker(queue); } else { logger.info( diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts index 1193091075e3e..71c55083894dd 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts @@ -23,7 +23,6 @@ configGetStub.withArgs('server', 'name').returns('test-server-123'); configGetStub.withArgs('server', 'uuid').returns('g9ymiujthvy6v8yrh7567g6fwzgzftzfr'); const executeJobFactoryStub = sinon.stub(); -const getMockLogger = sinon.stub(); const getMockExportTypesRegistry = ( exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] @@ -48,7 +47,7 @@ describe('Create Worker', () => { }); test('Creates a single Esqueue worker for Reporting', async () => { - const createWorker = createWorkerFactory(mockReporting, getMockLogger()); + const createWorker = createWorkerFactory(mockReporting); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); await createWorker(queue); @@ -80,7 +79,7 @@ Object { { executeJobFactory: executeJobFactoryStub }, ]); mockReporting.getExportTypesRegistry = () => exportTypesRegistry; - const createWorker = createWorkerFactory(mockReporting, getMockLogger()); + const createWorker = createWorkerFactory(mockReporting); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); await createWorker(queue); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index cb15bdc7ba009..7581c663f7a76 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -11,13 +11,10 @@ import { ESQueueWorkerExecuteFn, ExportTypeDefinition, JobSource } from '../../s import { ESQueueInstance } from './create_queue'; // @ts-ignore untyped dependency import { events as esqueueEvents } from './esqueue'; -import { ReportingInternalSetup } from '../core'; -export function createWorkerFactory( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup -) { +export function createWorkerFactory(reporting: ReportingCore) { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const queueConfig = config.get('queue'); const kibanaName = config.kbnConfig.get('server', 'name'); const kibanaId = config.kbnConfig.get('server', 'uuid'); @@ -30,7 +27,7 @@ export function createWorkerFactory( for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< ExportTypeDefinition> >) { - const jobExecutor = await exportType.executeJobFactory(reporting, setupDeps); // FIXME: does not "need" to be async + const jobExecutor = await exportType.executeJobFactory(reporting); // FIXME: does not "need" to be async jobExecutors.set(exportType.jobType, jobExecutor); } diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 87f714e00c898..5c6c36cce86f7 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -8,7 +8,7 @@ import { EventEmitter } from 'events'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { AuthenticatedUser } from '../../../../../plugins/security/server'; import { ESQueueCreateJobFn } from '../../server/types'; -import { ReportingCore, ReportingInternalSetup } from '../core'; +import { ReportingCore } from '../core'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; @@ -34,11 +34,9 @@ export type EnqueueJobFn = ( request: KibanaRequest ) => Promise; -export function enqueueJobFactory( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup -): EnqueueJobFn { +export function enqueueJobFactory(reporting: ReportingCore): EnqueueJobFn { const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const queueTimeout = config.get('queue', 'timeout'); const browserType = config.get('capture', 'browser', 'type'); const maxAttempts = config.get('capture', 'maxAttempts'); @@ -60,7 +58,7 @@ export function enqueueJobFactory( throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); } - const createJob = exportType.createJobFactory(reporting, setupDeps) as CreateJobFn; + const createJob = exportType.createJobFactory(reporting) as CreateJobFn; const payload = await createJob(jobParams, context, request); const options = { diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index 27e9f0fa34284..d0ab33c0f126a 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -61,10 +61,8 @@ export class ReportingPlugin public async start(core: CoreStart, plugins: ReportingStartDeps) { const { reportingCore } = this; - const deps = await reportingCore.getPluginSetupDeps(); - const esqueue = await createQueueFactory(reportingCore, deps); - - const enqueueJob = enqueueJobFactory(reportingCore, deps); + const esqueue = await createQueueFactory(reportingCore); + const enqueueJob = enqueueJobFactory(reportingCore); this.reportingCore.pluginStart({ savedObjects: core.savedObjects, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index f20dd58d1cd32..2a12a64d67a35 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -10,17 +10,16 @@ import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routi import { HandlerErrorFunction, HandlerFunction } from './types'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; -import { ReportingInternalSetup } from '../core'; const BASE_GENERATE = `${API_BASE_URL}/generate`; export function registerGenerateFromJobParams( reporting: ReportingCore, - setupDeps: ReportingInternalSetup, handler: HandlerFunction, handleError: HandlerErrorFunction ) { - const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); const { router } = setupDeps; router.post( diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 047a02a96fdc9..4bc143b911572 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -11,7 +11,6 @@ import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../common/constants'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; -import { ReportingInternalSetup } from '../core'; /* * This function registers API Endpoints for queuing Reporting jobs. The API inputs are: @@ -24,11 +23,11 @@ import { ReportingInternalSetup } from '../core'; */ export function registerGenerateCsvFromSavedObject( reporting: ReportingCore, - setupDeps: ReportingInternalSetup, handleRoute: HandlerFunction, handleRouteError: HandlerErrorFunction ) { - const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); const { router } = setupDeps; router.post( { diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 36e03dd798bf7..9ff2e472491ba 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -13,7 +13,6 @@ import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types'; import { JobDocOutput } from '../types'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; -import { ReportingInternalSetup } from '../core'; /* * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: @@ -26,10 +25,10 @@ import { ReportingInternalSetup } from '../core'; */ export function registerGenerateCsvFromSavedObjectImmediate( reporting: ReportingCore, - setupDeps: ReportingInternalSetup, handleError: HandlerErrorFunction ) { - const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); const { router } = setupDeps; /* @@ -58,8 +57,8 @@ export function registerGenerateCsvFromSavedObjectImmediate( userHandler(async (user, context, req, res) => { const logger = setupDeps.logger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); - const createJobFn = createJobFactory(reporting, setupDeps); - const executeJobFn = await executeJobFactory(reporting, setupDeps); // FIXME: does not "need" to be async + const createJobFn = createJobFactory(reporting); + const executeJobFn = await executeJobFactory(reporting); // FIXME: does not "need" to be async try { const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts index 1e8f6306c2761..0bbaed6a23c9f 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts @@ -15,11 +15,11 @@ import { ExportTypesRegistry } from '../lib/export_types_registry'; import { ExportTypeDefinition } from '../types'; import { LevelLogger } from '../lib'; import { ReportingInternalSetup } from '../core'; +import { of } from 'rxjs'; type setupServerReturn = UnwrapPromise>; describe('POST /api/reporting/generate', () => { - let mockDeps: ReportingInternalSetup; let server: setupServerReturn['server']; let httpSetup: setupServerReturn['httpSetup']; let exportTypesRegistry: ExportTypesRegistry; @@ -49,24 +49,8 @@ describe('POST /api/reporting/generate', () => { } as unknown) as jest.Mocked; beforeEach(async () => { - core = await createMockReportingCore(config); - // @ts-ignore - core.license = { - isActive: true, - isAvailable: true, - type: 'gold', - }; - exportTypesRegistry = new ExportTypesRegistry(); - exportTypesRegistry.register({ - id: 'printablePdf', - jobType: 'printable_pdf', - jobContentEncoding: 'base64', - jobContentExtension: 'pdf', - validLicenses: ['basic', 'gold'], - } as ExportTypeDefinition); - core.getExportTypesRegistry = () => exportTypesRegistry; ({ server, httpSetup } = await setupServer()); - mockDeps = ({ + const mockDeps = ({ elasticsearch: { adminClient: { callAsInternalUser: jest.fn() }, }, @@ -80,7 +64,24 @@ describe('POST /api/reporting/generate', () => { }, }, router: httpSetup.createRouter(''), + licensing: { + license$: of({ + isActive: true, + isAvailable: true, + type: 'gold', + }), + }, } as unknown) as ReportingInternalSetup; + core = await createMockReportingCore(config, mockDeps); + exportTypesRegistry = new ExportTypesRegistry(); + exportTypesRegistry.register({ + id: 'printablePdf', + jobType: 'printable_pdf', + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + validLicenses: ['basic', 'gold'], + } as ExportTypeDefinition); + core.getExportTypesRegistry = () => exportTypesRegistry; }); afterEach(async () => { @@ -90,7 +91,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 if there are no job params', async () => { - registerJobGenerationRoutes(core, mockDeps); + registerJobGenerationRoutes(core); await server.start(); @@ -105,7 +106,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 if job params query is invalid', async () => { - registerJobGenerationRoutes(core, mockDeps); + registerJobGenerationRoutes(core); await server.start(); @@ -116,7 +117,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 if job params body is invalid', async () => { - registerJobGenerationRoutes(core, mockDeps); + registerJobGenerationRoutes(core); await server.start(); @@ -128,7 +129,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 export type is invalid', async () => { - registerJobGenerationRoutes(core, mockDeps); + registerJobGenerationRoutes(core); await server.start(); @@ -150,7 +151,7 @@ describe('POST /api/reporting/generate', () => { }, })); - registerJobGenerationRoutes(core, mockDeps); + registerJobGenerationRoutes(core); await server.start(); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index f04386b6192b8..d0b90679217a5 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -13,14 +13,10 @@ import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; import { HandlerFunction } from './types'; -import { ReportingInternalSetup } from '../core'; const esErrors = elasticsearchErrors as Record; -export function registerJobGenerationRoutes( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup -) { +export function registerJobGenerationRoutes(reporting: ReportingCore) { const config = reporting.getConfig(); const downloadBaseUrl = config.kbnConfig.get('server', 'basePath') + `${API_BASE_URL}/jobs/download`; @@ -88,11 +84,11 @@ export function registerJobGenerationRoutes( }); } - registerGenerateFromJobParams(reporting, setupDeps, handler, handleError); + registerGenerateFromJobParams(reporting, handler, handleError); // Register beta panel-action download-related API's if (config.get('csv', 'enablePanelActionDownload')) { - registerGenerateCsvFromSavedObject(reporting, setupDeps, handler, handleError); - registerGenerateCsvFromSavedObjectImmediate(reporting, setupDeps, handleError); + registerGenerateCsvFromSavedObject(reporting, handler, handleError); + registerGenerateCsvFromSavedObjectImmediate(reporting, handleError); } } diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index b074079184b80..3fc71e4462e44 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -6,9 +6,9 @@ import { registerJobGenerationRoutes } from './generation'; import { registerJobInfoRoutes } from './jobs'; -import { ReportingCore, ReportingInternalSetup } from '../core'; +import { ReportingCore } from '../core'; -export function registerRoutes(reporting: ReportingCore, setupDeps: ReportingInternalSetup) { - registerJobGenerationRoutes(reporting, setupDeps); - registerJobInfoRoutes(reporting, setupDeps); +export function registerRoutes(reporting: ReportingCore) { + registerJobGenerationRoutes(reporting); + registerJobInfoRoutes(reporting); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index 6206bcfaf6e9b..e1b75b6872826 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -15,11 +15,11 @@ import { ExportTypesRegistry } from '../lib/export_types_registry'; import { ExportTypeDefinition } from '../types'; import { LevelLogger } from '../lib'; import { ReportingInternalSetup } from '../core'; +import { of } from 'rxjs'; type setupServerReturn = UnwrapPromise>; describe('GET /api/reporting/jobs/download', () => { - let mockDeps: ReportingInternalSetup; let server: setupServerReturn['server']; let httpSetup: setupServerReturn['httpSetup']; let exportTypesRegistry: ExportTypesRegistry; @@ -40,13 +40,30 @@ describe('GET /api/reporting/jobs/download', () => { }; beforeEach(async () => { - core = await createMockReportingCore(config); + ({ server, httpSetup } = await setupServer()); + core = await createMockReportingCore(config, ({ + elasticsearch: { + adminClient: { callAsInternalUser: jest.fn() }, + }, + security: { + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + router: httpSetup.createRouter(''), + licensing: { + license$: of({ + isActive: true, + isAvailable: true, + type: 'gold', + }), + }, + } as unknown) as ReportingInternalSetup); // @ts-ignore - core.license = { - isActive: true, - isAvailable: true, - type: 'gold', - }; exportTypesRegistry = new ExportTypesRegistry(); exportTypesRegistry.register({ id: 'unencoded', @@ -62,22 +79,6 @@ describe('GET /api/reporting/jobs/download', () => { validLicenses: ['basic', 'gold'], } as ExportTypeDefinition); core.getExportTypesRegistry = () => exportTypesRegistry; - ({ server, httpSetup } = await setupServer()); - mockDeps = ({ - elasticsearch: { - adminClient: { callAsInternalUser: jest.fn() }, - }, - security: { - authc: { - getCurrentUser: () => ({ - id: '123', - roles: ['superuser'], - username: 'Tom Riddle', - }), - }, - }, - router: httpSetup.createRouter(''), - } as unknown) as ReportingInternalSetup; }); afterEach(async () => { @@ -88,10 +89,10 @@ describe('GET /api/reporting/jobs/download', () => { it('fails on malformed download IDs', async () => { // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); @@ -106,16 +107,15 @@ describe('GET /api/reporting/jobs/download', () => { }); it('fails on unauthenticated users', async () => { - // @ts-ignore - const unauthenticatedPlugin = ({ - ...mockDeps, + core.pluginSetupDeps = ({ + ...core.pluginSetupDeps, security: { authc: { getCurrentUser: () => undefined, }, }, } as unknown) as ReportingInternalSetup; - registerJobInfoRoutes(core, unauthenticatedPlugin); + registerJobInfoRoutes(core); await server.start(); @@ -128,9 +128,8 @@ describe('GET /api/reporting/jobs/download', () => { }); it('fails on users without the appropriate role', async () => { - // @ts-ignore - const peasantUser = ({ - ...mockDeps, + core.pluginSetupDeps = ({ + ...core.pluginSetupDeps, security: { authc: { getCurrentUser: () => ({ @@ -141,7 +140,7 @@ describe('GET /api/reporting/jobs/download', () => { }, }, } as unknown) as ReportingInternalSetup; - registerJobInfoRoutes(core, peasantUser); + registerJobInfoRoutes(core); await server.start(); @@ -155,10 +154,11 @@ describe('GET /api/reporting/jobs/download', () => { it('returns 404 if job not found', async () => { // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), }; - registerJobInfoRoutes(core, mockDeps); + + registerJobInfoRoutes(core); await server.start(); @@ -167,12 +167,12 @@ describe('GET /api/reporting/jobs/download', () => { it('returns a 401 if not a valid job type', async () => { // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest .fn() .mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); @@ -181,14 +181,14 @@ describe('GET /api/reporting/jobs/download', () => { it('when a job is incomplete', async () => { // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest .fn() .mockReturnValue( Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' })) ), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); await supertest(httpSetup.server.listener) @@ -201,7 +201,7 @@ describe('GET /api/reporting/jobs/download', () => { it('when a job fails', async () => { // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue( Promise.resolve( getHits({ @@ -212,7 +212,7 @@ describe('GET /api/reporting/jobs/download', () => { ) ), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); await supertest(httpSetup.server.listener) @@ -244,10 +244,10 @@ describe('GET /api/reporting/jobs/download', () => { it('when a known job-type is complete', async () => { const hits = getCompleteHits(); // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); await supertest(httpSetup.server.listener) @@ -260,16 +260,14 @@ describe('GET /api/reporting/jobs/download', () => { it('succeeds when security is not there or disabled', async () => { const hits = getCompleteHits(); // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; // @ts-ignore - const noSecurityPlugins = ({ - ...mockDeps, - security: null, - } as unknown) as ReportingInternalSetup; - registerJobInfoRoutes(core, noSecurityPlugins); + core.pluginSetupDeps.security = null; + + registerJobInfoRoutes(core); await server.start(); @@ -286,10 +284,10 @@ describe('GET /api/reporting/jobs/download', () => { outputContent: 'test', }); // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); await supertest(httpSetup.server.listener) @@ -306,10 +304,10 @@ describe('GET /api/reporting/jobs/download', () => { outputContentType: 'application/pdf', }); // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); await supertest(httpSetup.server.listener) @@ -327,10 +325,10 @@ describe('GET /api/reporting/jobs/download', () => { outputContentType: 'application/html', }); // @ts-ignore - mockDeps.elasticsearch.adminClient = { + core.pluginSetupDeps.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; - registerJobInfoRoutes(core, mockDeps); + registerJobInfoRoutes(core); await server.start(); await supertest(httpSetup.server.listener) diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 428f53fb81d9d..8c35f79ec0fb4 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -14,7 +14,6 @@ import { downloadJobResponseHandlerFactory, } from './lib/job_response_handler'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; -import { ReportingInternalSetup } from '../core'; interface ListQuery { page: string; @@ -23,12 +22,10 @@ interface ListQuery { } const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -export async function registerJobInfoRoutes( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup -) { +export async function registerJobInfoRoutes(reporting: ReportingCore) { const config = reporting.getConfig(); - const userHandler = authorizedUserPreRoutingFactory(reporting, setupDeps); + const setupDeps = reporting.getPluginSetupDeps(); + const userHandler = authorizedUserPreRoutingFactory(reporting); const { elasticsearch, router } = setupDeps; const jobsQuery = jobsQueryFactory(config, elasticsearch); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts index 4f4f00960b5a2..3dd9ba7e0e505 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -46,11 +46,12 @@ beforeEach(async () => { describe('authorized_user_pre_routing', function () { it('should return from handler with null user when security is disabled', async function () { - const mockSetupDeps = ({ - ...(await mockCore.getPluginSetupDeps()), - security: undefined, // disable security - } as unknown) as ReportingInternalSetup; - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + mockCore.getPluginSetupDeps = () => + (({ + ...mockCore.pluginSetupDeps, + security: undefined, // disable security + } as unknown) as ReportingInternalSetup); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); const mockResponseFactory = httpServerMock.createResponseFactory() as KibanaResponseFactory; let handlerCalled = false; @@ -64,13 +65,14 @@ describe('authorized_user_pre_routing', function () { }); it('should return with 401 when security is enabled but no authenticated user', async function () { - const mockSetupDeps = ({ - ...(await mockCore.getPluginSetupDeps()), - security: { - authc: { getCurrentUser: () => null }, - }, - } as unknown) as ReportingInternalSetup; - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + mockCore.getPluginSetupDeps = () => + (({ + ...mockCore.pluginSetupDeps, + security: { + authc: { getCurrentUser: () => null }, + }, + } as unknown) as ReportingInternalSetup); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); const mockHandler = () => { throw new Error('Handler callback should not be called'); }; @@ -83,13 +85,14 @@ describe('authorized_user_pre_routing', function () { }); it(`should return with 403 when security is enabled but user doesn't have allowed role`, async function () { - const mockSetupDeps = ({ - ...(await mockCore.getPluginSetupDeps()), - security: { - authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['cowboy'] }) }, - }, - } as unknown) as ReportingInternalSetup; - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + mockCore.getPluginSetupDeps = () => + (({ + ...mockCore.pluginSetupDeps, + security: { + authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['cowboy'] }) }, + }, + } as unknown) as ReportingInternalSetup); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); const mockResponseFactory = getMockResponseFactory(); const mockHandler = () => { @@ -101,13 +104,16 @@ describe('authorized_user_pre_routing', function () { }); it('should return from handler when security is enabled and user has explicitly allowed role', async function () { - const mockSetupDeps = ({ - ...(await mockCore.getPluginSetupDeps()), - security: { - authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['reporting_user'] }) }, - }, - } as unknown) as ReportingInternalSetup; - const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore, mockSetupDeps); + mockCore.getPluginSetupDeps = () => + (({ + ...mockCore.pluginSetupDeps, + security: { + authc: { + getCurrentUser: () => ({ username: 'friendlyuser', roles: ['reporting_user'] }), + }, + }, + } as unknown) as ReportingInternalSetup); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(mockCore); const mockResponseFactory = getMockResponseFactory(); let handlerCalled = false; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 976e122cf0fc8..87582ca3ca239 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -7,7 +7,7 @@ import { RequestHandler, RouteMethod } from 'src/core/server'; import { AuthenticatedUser } from '../../../../../../plugins/security/server'; import { getUserFactory } from '../../lib/get_user'; -import { ReportingInternalSetup, ReportingCore } from '../../core'; +import { ReportingCore } from '../../core'; type ReportingUser = AuthenticatedUser | null; const superuserRole = 'superuser'; @@ -17,12 +17,11 @@ export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer : never; export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( - core: ReportingCore, - setupDeps: ReportingInternalSetup + reporting: ReportingCore ) { - const config = core.getConfig(); + const config = reporting.getConfig(); + const setupDeps = reporting.getPluginSetupDeps(); const getUser = getUserFactory(setupDeps.security); - return (handler: RequestHandlerUser): RequestHandler => { return (context, req, res) => { let user: ReportingUser = null; diff --git a/x-pack/legacy/plugins/reporting/server/types.ts b/x-pack/legacy/plugins/reporting/server/types.ts index b83c87cfefe53..5000b19e257cf 100644 --- a/x-pack/legacy/plugins/reporting/server/types.ts +++ b/x-pack/legacy/plugins/reporting/server/types.ts @@ -19,7 +19,7 @@ import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { LayoutInstance } from '../export_types/common/layouts'; import { ReportingConfigType } from './config'; -import { ReportingCore, ReportingInternalSetup } from './core'; +import { ReportingCore } from './core'; import { LevelLogger } from './lib'; /* @@ -207,14 +207,10 @@ export type ServerFacade = LegacySetup; export type CaptureConfig = ReportingConfigType['capture']; export type ScrollConfig = ReportingConfigType['csv']['scroll']; -export type CreateJobFactory = ( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup -) => CreateJobFnType; +export type CreateJobFactory = (reporting: ReportingCore) => CreateJobFnType; export type ExecuteJobFactory = ( - reporting: ReportingCore, - setupDeps: ReportingInternalSetup + reporting: ReportingCore ) => Promise; // FIXME: does not "need" to be async export interface ExportTypeDefinition< diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts index 5f5ac281be6d2..b585d944c7230 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts @@ -18,6 +18,7 @@ import { EventEmitter } from 'events'; import { coreMock } from 'src/core/server/mocks'; import { ReportingConfig, ReportingCore, ReportingPlugin } from '../server'; import { ReportingSetupDeps, ReportingStartDeps } from '../server/types'; +import { ReportingInternalSetup } from '../server/core'; const createMockSetupDeps = (setupMock?: any): ReportingSetupDeps => { return { @@ -52,8 +53,17 @@ const createMockReportingPlugin = async (config: ReportingConfig): Promise => { +export const createMockReportingCore = async ( + config: ReportingConfig, + setupDepsMock?: ReportingInternalSetup +): Promise => { config = config || {}; const plugin = await createMockReportingPlugin(config); - return plugin.getReportingCore(); + const core = plugin.getReportingCore(); + + if (setupDepsMock) { + core.pluginSetupDeps = setupDepsMock; + } + + return core; }; From 8ad57ffab5e71d01cf525d50b3059e082cd9b426 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Fri, 29 May 2020 12:52:56 -0700 Subject: [PATCH 28/31] Push some core logic into plugin --- .../legacy/plugins/reporting/server/core.ts | 20 +------------------ .../legacy/plugins/reporting/server/plugin.ts | 13 ++++++++---- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index ac7e1e04b74c3..f144806765582 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -16,19 +16,14 @@ import { BasePath, } from 'src/core/server'; import { SecurityPluginSetup } from '../../../../plugins/security/server'; -import { ReportingPluginSpecOptions } from '../'; -// @ts-ignore no module definition -import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; -import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { screenshotsObservableFactory } from '../export_types/common/lib/screenshots'; -import { ServerFacade, ScreenshotsObservableFn } from '../server/types'; +import { ScreenshotsObservableFn } from '../server/types'; import { ReportingConfig } from './'; import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory'; import { checkLicense, getExportTypesRegistry, LevelLogger } from './lib'; import { ESQueueInstance } from './lib/create_queue'; import { EnqueueJobFn } from './lib/enqueue_job'; -import { registerRoutes } from './routes'; export interface ReportingInternalSetup { browserDriverFactory: HeadlessChromiumDriverFactory; @@ -56,19 +51,6 @@ export class ReportingCore { constructor(private config: ReportingConfig) {} - legacySetup( - xpackMainPlugin: XPackMainPlugin, - reporting: ReportingPluginSpecOptions, - __LEGACY: ServerFacade - ) { - // legacy plugin status - mirrorPluginStatus(xpackMainPlugin, reporting); - } - - public async setupRoutes() { - registerRoutes(this); - } - public pluginSetup(reportingSetupDeps: ReportingInternalSetup) { this.pluginSetupDeps = reportingSetupDeps; this.pluginSetup$.next(reportingSetupDeps); diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index d0ab33c0f126a..8f3bf5a0f71f4 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -8,10 +8,13 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core import { createBrowserDriverFactory } from './browsers'; import { ReportingConfig } from './config'; import { ReportingCore } from './core'; +import { registerRoutes } from './routes'; import { createQueueFactory, enqueueJobFactory, LevelLogger, runValidations } from './lib'; import { setFieldFormats } from './services'; import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types'; import { registerReportingUsageCollector } from './usage'; +// @ts-ignore no module definition +import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; export class ReportingPlugin implements Plugin { @@ -33,7 +36,8 @@ export class ReportingPlugin const { xpack_main: xpackMainLegacy, reporting: reportingLegacy } = __LEGACY.plugins; const { logger } = this; - this.reportingCore.legacySetup(xpackMainLegacy, reportingLegacy, __LEGACY); + // legacy plugin status + mirrorPluginStatus(xpackMainLegacy, reportingLegacy); const browserDriverFactory = await createBrowserDriverFactory(config, this.logger); // required for validations :( runValidations(config, elasticsearch, browserDriverFactory, this.logger); @@ -42,7 +46,7 @@ export class ReportingPlugin registerReportingUsageCollector(this.reportingCore, plugins); // regsister setup internals - this.reportingCore.pluginSetup({ + const deps = { browserDriverFactory, elasticsearch, licensing, @@ -50,10 +54,11 @@ export class ReportingPlugin router, security, logger, - }); + }; + this.reportingCore.pluginSetup(deps); // Setup routing - this.reportingCore.setupRoutes(); + registerRoutes(this.reportingCore); return {}; } From 4f12c8a70edb78a9a98a885c8ac1581332ce4ae2 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Fri, 29 May 2020 13:08:04 -0700 Subject: [PATCH 29/31] Move some core logic up to plugin --- x-pack/legacy/plugins/reporting/server/plugin.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index 8f3bf5a0f71f4..2100085cb374c 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -39,13 +39,7 @@ export class ReportingPlugin // legacy plugin status mirrorPluginStatus(xpackMainLegacy, reportingLegacy); - const browserDriverFactory = await createBrowserDriverFactory(config, this.logger); // required for validations :( - runValidations(config, elasticsearch, browserDriverFactory, this.logger); - - // Register a function with server to manage the collection of usage stats - registerReportingUsageCollector(this.reportingCore, plugins); - - // regsister setup internals + const browserDriverFactory = await createBrowserDriverFactory(config, this.logger); const deps = { browserDriverFactory, elasticsearch, @@ -55,9 +49,11 @@ export class ReportingPlugin security, logger, }; - this.reportingCore.pluginSetup(deps); - // Setup routing + runValidations(config, elasticsearch, browserDriverFactory, this.logger); + + this.reportingCore.pluginSetup(deps); + registerReportingUsageCollector(this.reportingCore, plugins); registerRoutes(this.reportingCore); return {}; From 3ab80951947ebccb53c8040a9fb767dc9f6a8878 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Fri, 29 May 2020 13:19:44 -0700 Subject: [PATCH 30/31] Marking private setupDeps + downstream fixes --- x-pack/legacy/plugins/reporting/server/core.ts | 2 +- .../legacy/plugins/reporting/server/routes/generation.test.ts | 3 +-- x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts | 4 ++++ .../server/routes/lib/authorized_user_pre_routing.test.ts | 4 ++++ .../reporting/test_helpers/create_mock_reportingplugin.ts | 1 + 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index f144806765582..11ff75a8eb7b4 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -43,7 +43,7 @@ interface ReportingInternalStart { } export class ReportingCore { - pluginSetupDeps?: ReportingInternalSetup; + private pluginSetupDeps?: ReportingInternalSetup; private pluginStartDeps?: ReportingInternalStart; private readonly pluginSetup$ = new Rx.ReplaySubject(); private readonly pluginStart$ = new Rx.ReplaySubject(); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts index 0bbaed6a23c9f..1e1ebc92b533c 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts @@ -14,7 +14,6 @@ import { ReportingCore } from '..'; import { ExportTypesRegistry } from '../lib/export_types_registry'; import { ExportTypeDefinition } from '../types'; import { LevelLogger } from '../lib'; -import { ReportingInternalSetup } from '../core'; import { of } from 'rxjs'; type setupServerReturn = UnwrapPromise>; @@ -71,7 +70,7 @@ describe('POST /api/reporting/generate', () => { type: 'gold', }), }, - } as unknown) as ReportingInternalSetup; + } as unknown) as any; core = await createMockReportingCore(config, mockDeps); exportTypesRegistry = new ExportTypesRegistry(); exportTypesRegistry.register({ diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index e1b75b6872826..73f3c660141c1 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -107,7 +107,9 @@ describe('GET /api/reporting/jobs/download', () => { }); it('fails on unauthenticated users', async () => { + // @ts-ignore core.pluginSetupDeps = ({ + // @ts-ignore ...core.pluginSetupDeps, security: { authc: { @@ -128,7 +130,9 @@ describe('GET /api/reporting/jobs/download', () => { }); it('fails on users without the appropriate role', async () => { + // @ts-ignore core.pluginSetupDeps = ({ + // @ts-ignore ...core.pluginSetupDeps, security: { authc: { diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts index 3dd9ba7e0e505..4cb7af3d0d409 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -48,6 +48,7 @@ describe('authorized_user_pre_routing', function () { it('should return from handler with null user when security is disabled', async function () { mockCore.getPluginSetupDeps = () => (({ + // @ts-ignore ...mockCore.pluginSetupDeps, security: undefined, // disable security } as unknown) as ReportingInternalSetup); @@ -67,6 +68,7 @@ describe('authorized_user_pre_routing', function () { it('should return with 401 when security is enabled but no authenticated user', async function () { mockCore.getPluginSetupDeps = () => (({ + // @ts-ignore ...mockCore.pluginSetupDeps, security: { authc: { getCurrentUser: () => null }, @@ -87,6 +89,7 @@ describe('authorized_user_pre_routing', function () { it(`should return with 403 when security is enabled but user doesn't have allowed role`, async function () { mockCore.getPluginSetupDeps = () => (({ + // @ts-ignore ...mockCore.pluginSetupDeps, security: { authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['cowboy'] }) }, @@ -106,6 +109,7 @@ describe('authorized_user_pre_routing', function () { it('should return from handler when security is enabled and user has explicitly allowed role', async function () { mockCore.getPluginSetupDeps = () => (({ + // @ts-ignore ...mockCore.pluginSetupDeps, security: { authc: { diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts index b585d944c7230..f6dbccdfe3980 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts @@ -62,6 +62,7 @@ export const createMockReportingCore = async ( const core = plugin.getReportingCore(); if (setupDepsMock) { + // @ts-ignore overwriting private properties core.pluginSetupDeps = setupDepsMock; } From 1cf1546f3a100a276bb7adfaf84c39d91cd0a507 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 29 May 2020 13:29:53 -0700 Subject: [PATCH 31/31] revert logger as a param --- .../csv/server/execute_job.test.ts | 89 ++++++++++--------- .../export_types/csv/server/execute_job.ts | 7 +- .../server/create_job/create_job.ts | 7 +- .../server/execute_job.ts | 9 +- .../server/lib/generate_csv.ts | 6 +- .../server/lib/generate_csv_search.ts | 7 +- .../png/server/execute_job/index.test.ts | 57 ++++++++---- .../png/server/execute_job/index.ts | 7 +- .../server/execute_job/index.test.ts | 64 ++++++++----- .../printable_pdf/server/execute_job/index.ts | 7 +- .../legacy/plugins/reporting/server/core.ts | 3 +- .../reporting/server/lib/create_queue.ts | 8 +- .../server/lib/create_worker.test.ts | 5 +- .../reporting/server/lib/create_worker.ts | 12 +-- .../reporting/server/lib/enqueue_job.ts | 11 ++- .../legacy/plugins/reporting/server/plugin.ts | 10 +-- .../generate_from_savedobject_immediate.ts | 10 ++- .../server/routes/generation.test.ts | 10 +-- .../reporting/server/routes/generation.ts | 5 +- .../plugins/reporting/server/routes/index.ts | 5 +- .../legacy/plugins/reporting/server/types.ts | 8 +- 21 files changed, 203 insertions(+), 144 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts index a8955f774b2ff..f6ae8edb9d5cb 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts @@ -11,6 +11,7 @@ import sinon from 'sinon'; import { fieldFormats } from '../../../../../../../src/plugins/data/server'; import { CancellationToken } from '../../../../../../plugins/reporting/common'; import { CSV_BOM_CHARS } from '../../../common/constants'; +import { LevelLogger } from '../../../server/lib'; import { setFieldFormats } from '../../../server/services'; import { createMockReportingCore } from '../../../test_helpers'; import { JobDocPayloadDiscoverCsv } from '../types'; @@ -30,6 +31,14 @@ describe('CSV Execute Job', function () { const headers = { sid: 'test', }; + const mockLogger = new LevelLogger({ + get: () => + ({ + debug: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + } as any), + }); let defaultElasticsearchResponse: any; let encryptedHeaders: any; @@ -105,7 +114,7 @@ describe('CSV Execute Job', function () { describe('basic Elasticsearch call behavior', function () { it('should decrypt encrypted headers and pass to callAsCurrentUser', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -125,7 +134,7 @@ describe('CSV Execute Job', function () { testBody: true, }; - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const job = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -152,7 +161,7 @@ describe('CSV Execute Job', function () { _scroll_id: scrollId, }); callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -170,7 +179,7 @@ describe('CSV Execute Job', function () { }); it('should not execute scroll if there are no hits from the search', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -204,7 +213,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -243,7 +252,7 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', getJobDocPayload({ @@ -275,7 +284,7 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -302,7 +311,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -327,7 +336,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -353,7 +362,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -379,7 +388,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -405,7 +414,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -432,7 +441,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -453,7 +462,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -476,7 +485,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -497,7 +506,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -513,7 +522,7 @@ describe('CSV Execute Job', function () { describe('Elasticsearch call errors', function () { it('should reject Promise if search call errors out', async function () { callAsCurrentUserStub.rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -532,7 +541,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); callAsCurrentUserStub.onSecondCall().rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -553,7 +562,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -574,7 +583,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -602,7 +611,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -630,7 +639,7 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], @@ -666,7 +675,7 @@ describe('CSV Execute Job', function () { }); it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); executeJob( 'job345', getJobDocPayload({ @@ -685,7 +694,7 @@ describe('CSV Execute Job', function () { }); it(`shouldn't call clearScroll if it never got a scrollId`, async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); executeJob( 'job345', getJobDocPayload({ @@ -703,7 +712,7 @@ describe('CSV Execute Job', function () { }); it('should call clearScroll if it got a scrollId', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); executeJob( 'job345', getJobDocPayload({ @@ -725,7 +734,7 @@ describe('CSV Execute Job', function () { describe('csv content', function () { it('should write column headers to output, even if there are no results', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -737,7 +746,7 @@ describe('CSV Execute Job', function () { it('should use custom uiSettings csv:separator for header', async function () { mockUiSettingsClient.get.withArgs('csv:separator').returns(';'); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -749,7 +758,7 @@ describe('CSV Execute Job', function () { it('should escape column headers if uiSettings csv:quoteValues is true', async function () { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -761,7 +770,7 @@ describe('CSV Execute Job', function () { it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(false); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -772,7 +781,7 @@ describe('CSV Execute Job', function () { }); it('should write column headers to output, when there are results', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ one: '1', two: '2' }], @@ -792,7 +801,7 @@ describe('CSV Execute Job', function () { }); it('should use comma separated values of non-nested fields from _source', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -813,7 +822,7 @@ describe('CSV Execute Job', function () { }); it('should concatenate the hits from multiple responses', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -841,7 +850,7 @@ describe('CSV Execute Job', function () { }); it('should use field formatters to format fields', async function () { - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -883,7 +892,7 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(1); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -913,7 +922,7 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -950,7 +959,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -989,7 +998,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1026,7 +1035,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1052,7 +1061,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], @@ -1078,7 +1087,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingPlugin); + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index 497b9b3510572..7d95c45d5d233 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -9,7 +9,7 @@ import Hapi from 'hapi'; import { IUiSettingsClient, KibanaRequest } from '../../../../../../../src/core/server'; import { CSV_BOM_CHARS, CSV_JOB_TYPE } from '../../../common/constants'; import { ReportingCore } from '../../../server'; -import { cryptoFactory } from '../../../server/lib'; +import { cryptoFactory, LevelLogger } from '../../../server/lib'; import { getFieldFormats } from '../../../server/services'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory } from '../../../server/types'; import { JobDocPayloadDiscoverCsv } from '../types'; @@ -18,11 +18,10 @@ import { createGenerateCsv } from './lib/generate_csv'; export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore) { +>> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { const config = reporting.getConfig(); - const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = setupDeps.logger.clone([CSV_JOB_TYPE, 'execute-job']); + const logger = parentLogger.clone([CSV_JOB_TYPE, 'execute-job']); const serverBasePath = config.kbnConfig.get('server', 'basePath'); return async function executeJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts index c90f1ce8aa7d9..d23f60d9c2480 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts @@ -9,7 +9,7 @@ import { get } from 'lodash'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; -import { cryptoFactory } from '../../../../server/lib'; +import { cryptoFactory, LevelLogger } from '../../../../server/lib'; import { CreateJobFactory, TimeRangeParams } from '../../../../server/types'; import { JobDocPayloadPanelCsv, @@ -41,11 +41,10 @@ interface VisData { export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore) { +>> = function createJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - const setupDeps = reporting.getPluginSetupDeps(); - const logger = setupDeps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); + const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); return async function createJob( jobParams: JobParamsPanelCsv, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index 5d9e2a4113e3d..4ef7b8514b363 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; import { ReportingCore } from '../../../server'; -import { cryptoFactory } from '../../../server/lib'; +import { cryptoFactory, LevelLogger } from '../../../server/lib'; import { ExecuteJobFactory, JobDocOutput, JobDocPayload } from '../../../server/types'; import { CsvResultFromSearch } from '../../csv/types'; import { FakeRequest, JobDocPayloadPanelCsv, JobParamsPanelCsv, SearchPanel } from '../types'; @@ -27,12 +27,11 @@ export type ImmediateExecuteFn = ( export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore) { +>> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { const config = reporting.getConfig(); - const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = setupDeps.logger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); - const generateCsv = createGenerateCsv(reporting); + const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); + const generateCsv = createGenerateCsv(reporting, parentLogger); return async function executeJob( jobId: string | null, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts index 8cf456676ee67..2397da9337890 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts @@ -6,11 +6,12 @@ import { badRequest } from 'boom'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { LevelLogger } from '../../../../server/lib'; import { ReportingCore } from '../../../../server'; import { FakeRequest, JobParamsPanelCsv, SearchPanel, VisPanel } from '../../types'; import { generateCsvSearch } from './generate_csv_search'; -export function createGenerateCsv(reporting: ReportingCore) { +export function createGenerateCsv(reporting: ReportingCore, logger: LevelLogger) { return async function generateCsv( context: RequestHandlerContext, request: KibanaRequest | FakeRequest, @@ -30,7 +31,8 @@ export function createGenerateCsv(reporting: ReportingCore) { context, request as KibanaRequest, panel as SearchPanel, - jobParams + jobParams, + logger ); default: throw badRequest(`Unsupported or unrecognized saved object type: ${visType}`); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index 867eaddf5a3cc..51e0ddad53355 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -17,6 +17,7 @@ import { Query, } from '../../../../../../../../src/plugins/data/server'; import { CancellationToken } from '../../../../../../../plugins/reporting/common'; +import { LevelLogger } from '../../../../server/lib'; import { ReportingCore } from '../../../../server'; import { createGenerateCsv } from '../../../csv/server/lib/generate_csv'; import { @@ -54,9 +55,9 @@ export async function generateCsvSearch( context: RequestHandlerContext, req: KibanaRequest, searchPanel: SearchPanel, - jobParams: JobParamsDiscoverCsv + jobParams: JobParamsDiscoverCsv, + logger: LevelLogger ): Promise { - const setupDeps = reporting.getPluginSetupDeps(); const savedObjectsClient = context.core.savedObjects.client; const { indexPatternSavedObjectId, timerange } = searchPanel; const savedSearchObjectAttr = searchPanel.attributes; @@ -169,7 +170,7 @@ export async function generateCsvSearch( }, }; - const generateCsv = createGenerateCsv(setupDeps.logger); + const generateCsv = createGenerateCsv(logger); return { type: 'CSV from Saved Search', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts index 3321271d6b237..72695545e3b5f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts @@ -7,7 +7,7 @@ import * as Rx from 'rxjs'; import { CancellationToken } from '../../../../../../../plugins/reporting/common'; import { ReportingCore } from '../../../../server'; -import { cryptoFactory } from '../../../../server/lib'; +import { cryptoFactory, LevelLogger } from '../../../../server/lib'; import { createMockReportingCore } from '../../../../test_helpers'; import { JobDocPayloadPNG } from '../../types'; import { generatePngObservableFactory } from '../lib/generate_png'; @@ -21,6 +21,15 @@ const cancellationToken = ({ on: jest.fn(), } as unknown) as CancellationToken; +const mockLoggerFactory = { + get: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + })), +}; +const getMockLogger = () => new LevelLogger(mockLoggerFactory); + const mockEncryptionKey = 'abcabcsecuresecret'; const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); @@ -68,7 +77,7 @@ test(`passes browserTimezone to generatePng`, async () => { const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting); + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; await executeJob( 'pngJobId', @@ -80,29 +89,39 @@ test(`passes browserTimezone to generatePng`, async () => { cancellationToken ); - // Don't snapshot logger - const [, ...rest] = generatePngObservable.mock.calls[0]; - - expect(rest).toMatchInlineSnapshot(` + expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(` Array [ - "http://localhost:5601/sbp/app/kibana#/something", - "UTC", - Object { - "conditions": Object { - "basePath": "/sbp", - "hostname": "localhost", - "port": 5601, - "protocol": "http", + Array [ + LevelLogger { + "_logger": Object { + "get": [MockFunction], + }, + "_tags": Array [ + "PNG", + "execute", + "pngJobId", + ], + "warning": [Function], + }, + "http://localhost:5601/sbp/app/kibana#/something", + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", + }, + "headers": Object {}, }, - "headers": Object {}, - }, - undefined, + undefined, + ], ] `); }); test(`returns content_type of application/png`, async () => { - const executeJob = await executeJobFactory(mockReporting); + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); const generatePngObservable = await generatePngObservableFactory(mockReporting); @@ -121,7 +140,7 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => { const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ base64: testContent })); - const executeJob = await executeJobFactory(mockReporting); + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pngJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index 0fd86bef2bfbe..0d0a9e748682a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -9,6 +9,7 @@ import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PNG_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; +import { LevelLogger } from '../../../../server/lib'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../server/types'; import { decryptJobHeaders, @@ -22,12 +23,12 @@ import { generatePngObservableFactory } from '../lib/generate_png'; type QueuedPngExecutorFactory = ExecuteJobFactory>; export const executeJobFactory: QueuedPngExecutorFactory = async function executeJobFactoryFn( - reporting: ReportingCore + reporting: ReportingCore, + parentLogger: LevelLogger ) { const config = reporting.getConfig(); - const setupDeps = reporting.getPluginSetupDeps(); const encryptionKey = config.get('encryptionKey'); - const logger = setupDeps.logger.clone([PNG_JOB_TYPE, 'execute']); + const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']); return async function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) { const apmTrans = apm.startTransaction('reporting execute_job png', 'reporting'); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts index 44abf05d19259..b081521fef8dd 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts @@ -9,7 +9,7 @@ jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn( import * as Rx from 'rxjs'; import { CancellationToken } from '../../../../../../../plugins/reporting/common'; import { ReportingCore } from '../../../../server'; -import { cryptoFactory } from '../../../../server/lib'; +import { cryptoFactory, LevelLogger } from '../../../../server/lib'; import { createMockReportingCore } from '../../../../test_helpers'; import { JobDocPayloadPDF } from '../../types'; import { generatePdfObservableFactory } from '../lib/generate_pdf'; @@ -21,6 +21,15 @@ const cancellationToken = ({ on: jest.fn(), } as unknown) as CancellationToken; +const mockLoggerFactory = { + get: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + })), +}; +const getMockLogger = () => new LevelLogger(mockLoggerFactory); + const mockEncryptionKey = 'testencryptionkey'; const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); @@ -66,7 +75,7 @@ test(`passes browserTimezone to generatePdf`, async () => { const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting); + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; await executeJob( 'pdfJobId', @@ -78,33 +87,44 @@ test(`passes browserTimezone to generatePdf`, async () => { cancellationToken ); - // Don't snapshot logger - const [, ...rest] = generatePdfObservable.mock.calls[0]; - - expect(rest).toMatchInlineSnapshot(` + expect(generatePdfObservable.mock.calls).toMatchInlineSnapshot(` Array [ - undefined, Array [ - "http://localhost:5601/sbp/app/kibana#/something", - ], - "UTC", - Object { - "conditions": Object { - "basePath": "/sbp", - "hostname": "localhost", - "port": 5601, - "protocol": "http", + LevelLogger { + "_logger": Object { + "get": [MockFunction], + }, + "_tags": Array [ + "printable_pdf", + "execute", + "pdfJobId", + ], + "warning": [Function], + }, + undefined, + Array [ + "http://localhost:5601/sbp/app/kibana#/something", + ], + "UTC", + Object { + "conditions": Object { + "basePath": "/sbp", + "hostname": "localhost", + "port": 5601, + "protocol": "http", + }, + "headers": Object {}, }, - "headers": Object {}, - }, - undefined, - false, + undefined, + false, + ], ] `); }); test(`returns content_type of application/pdf`, async () => { - const executeJob = await executeJobFactory(mockReporting); + const logger = getMockLogger(); + const executeJob = await executeJobFactory(mockReporting, logger); const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = await generatePdfObservableFactory(mockReporting); @@ -123,7 +143,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); - const executeJob = await executeJobFactory(mockReporting); + const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pdfJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index ccdff54a4e1ae..b0b2d02305b9b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -9,6 +9,7 @@ import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PDF_JOB_TYPE } from '../../../../common/constants'; import { ReportingCore } from '../../../../server'; +import { LevelLogger } from '../../../../server/lib'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../server/types'; import { decryptJobHeaders, @@ -23,13 +24,13 @@ import { generatePdfObservableFactory } from '../lib/generate_pdf'; type QueuedPdfExecutorFactory = ExecuteJobFactory>; export const executeJobFactory: QueuedPdfExecutorFactory = async function executeJobFactoryFn( - reporting: ReportingCore + reporting: ReportingCore, + parentLogger: LevelLogger ) { const config = reporting.getConfig(); - const setupDeps = reporting.getPluginSetupDeps(); const encryptionKey = config.get('encryptionKey'); - const logger = setupDeps.logger.clone([PDF_JOB_TYPE, 'execute']); + const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']); return async function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) { const apmTrans = apm.startTransaction('reporting execute_job pdf', 'reporting'); diff --git a/x-pack/legacy/plugins/reporting/server/core.ts b/x-pack/legacy/plugins/reporting/server/core.ts index 11ff75a8eb7b4..b89ef9e06b961 100644 --- a/x-pack/legacy/plugins/reporting/server/core.ts +++ b/x-pack/legacy/plugins/reporting/server/core.ts @@ -21,7 +21,7 @@ import { screenshotsObservableFactory } from '../export_types/common/lib/screens import { ScreenshotsObservableFn } from '../server/types'; import { ReportingConfig } from './'; import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory'; -import { checkLicense, getExportTypesRegistry, LevelLogger } from './lib'; +import { checkLicense, getExportTypesRegistry } from './lib'; import { ESQueueInstance } from './lib/create_queue'; import { EnqueueJobFn } from './lib/enqueue_job'; @@ -32,7 +32,6 @@ export interface ReportingInternalSetup { basePath: BasePath['get']; router: IRouter; security: SecurityPluginSetup; - logger: LevelLogger; } interface ReportingInternalStart { diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 3fb2d274e407f..2cac4bd654487 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -11,6 +11,7 @@ import { createWorkerFactory } from './create_worker'; import { Job } from './enqueue_job'; // @ts-ignore import { Esqueue } from './esqueue'; +import { LevelLogger } from './level_logger'; interface ESQueueWorker { on: (event: string, handler: any) => void; @@ -37,11 +38,10 @@ type GenericWorkerFn = ( ) => void | Promise; export async function createQueueFactory( - reporting: ReportingCore + reporting: ReportingCore, + logger: LevelLogger ): Promise { const config = reporting.getConfig(); - const setupDeps = reporting.getPluginSetupDeps(); - const { logger } = setupDeps; const queueIndexInterval = config.get('queue', 'indexInterval'); const queueTimeout = config.get('queue', 'timeout'); const queueIndex = config.get('index'); @@ -60,7 +60,7 @@ export async function createQueueFactory( if (isPollingEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed - const createWorker = createWorkerFactory(reporting); + const createWorker = createWorkerFactory(reporting, logger); await createWorker(queue); } else { logger.info( diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts index 71c55083894dd..1193091075e3e 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts @@ -23,6 +23,7 @@ configGetStub.withArgs('server', 'name').returns('test-server-123'); configGetStub.withArgs('server', 'uuid').returns('g9ymiujthvy6v8yrh7567g6fwzgzftzfr'); const executeJobFactoryStub = sinon.stub(); +const getMockLogger = sinon.stub(); const getMockExportTypesRegistry = ( exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] @@ -47,7 +48,7 @@ describe('Create Worker', () => { }); test('Creates a single Esqueue worker for Reporting', async () => { - const createWorker = createWorkerFactory(mockReporting); + const createWorker = createWorkerFactory(mockReporting, getMockLogger()); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); await createWorker(queue); @@ -79,7 +80,7 @@ Object { { executeJobFactory: executeJobFactoryStub }, ]); mockReporting.getExportTypesRegistry = () => exportTypesRegistry; - const createWorker = createWorkerFactory(mockReporting); + const createWorker = createWorkerFactory(mockReporting, getMockLogger()); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); await createWorker(queue); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index 7581c663f7a76..57bd61aee7195 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -7,14 +7,14 @@ import { CancellationToken } from '../../../../../plugins/reporting/common'; import { PLUGIN_ID } from '../../common/constants'; import { ReportingCore } from '../../server'; +import { LevelLogger } from '../../server/lib'; import { ESQueueWorkerExecuteFn, ExportTypeDefinition, JobSource } from '../../server/types'; import { ESQueueInstance } from './create_queue'; // @ts-ignore untyped dependency import { events as esqueueEvents } from './esqueue'; -export function createWorkerFactory(reporting: ReportingCore) { +export function createWorkerFactory(reporting: ReportingCore, logger: LevelLogger) { const config = reporting.getConfig(); - const setupDeps = reporting.getPluginSetupDeps(); const queueConfig = config.get('queue'); const kibanaName = config.kbnConfig.get('server', 'name'); const kibanaId = config.kbnConfig.get('server', 'uuid'); @@ -27,7 +27,7 @@ export function createWorkerFactory(reporting: ReportingCore) { for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< ExportTypeDefinition> >) { - const jobExecutor = await exportType.executeJobFactory(reporting); // FIXME: does not "need" to be async + const jobExecutor = await exportType.executeJobFactory(reporting, logger); // FIXME: does not "need" to be async jobExecutors.set(exportType.jobType, jobExecutor); } @@ -63,13 +63,13 @@ export function createWorkerFactory(reporting: ReportingCore) { const worker = queue.registerWorker(PLUGIN_ID, workerFn, workerOptions); worker.on(esqueueEvents.EVENT_WORKER_COMPLETE, (res: any) => { - setupDeps.logger.debug(`Worker completed: (${res.job.id})`); + logger.debug(`Worker completed: (${res.job.id})`); }); worker.on(esqueueEvents.EVENT_WORKER_JOB_EXECUTION_ERROR, (res: any) => { - setupDeps.logger.debug(`Worker error: (${res.job.id})`); + logger.debug(`Worker error: (${res.job.id})`); }); worker.on(esqueueEvents.EVENT_WORKER_JOB_TIMEOUT, (res: any) => { - setupDeps.logger.debug(`Job timeout exceeded: (${res.job.id})`); + logger.debug(`Job timeout exceeded: (${res.job.id})`); }); }; } diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 5c6c36cce86f7..6367c8a1da98a 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -11,6 +11,7 @@ import { ESQueueCreateJobFn } from '../../server/types'; import { ReportingCore } from '../core'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; +import { LevelLogger } from './level_logger'; interface ConfirmedJob { id: string; @@ -34,13 +35,15 @@ export type EnqueueJobFn = ( request: KibanaRequest ) => Promise; -export function enqueueJobFactory(reporting: ReportingCore): EnqueueJobFn { +export function enqueueJobFactory( + reporting: ReportingCore, + parentLogger: LevelLogger +): EnqueueJobFn { const config = reporting.getConfig(); - const setupDeps = reporting.getPluginSetupDeps(); const queueTimeout = config.get('queue', 'timeout'); const browserType = config.get('capture', 'browser', 'type'); const maxAttempts = config.get('capture', 'maxAttempts'); - const logger = setupDeps.logger.clone(['queue-job']); + const logger = parentLogger.clone(['queue-job']); return async function enqueueJob( exportTypeId: string, @@ -58,7 +61,7 @@ export function enqueueJobFactory(reporting: ReportingCore): EnqueueJobFn { throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); } - const createJob = exportType.createJobFactory(reporting) as CreateJobFn; + const createJob = exportType.createJobFactory(reporting, logger) as CreateJobFn; const payload = await createJob(jobParams, context, request); const options = { diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index 2100085cb374c..5a407ad3e4c4a 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -34,7 +34,6 @@ export class ReportingPlugin const router = core.http.createRouter(); const basePath = core.http.basePath.get; const { xpack_main: xpackMainLegacy, reporting: reportingLegacy } = __LEGACY.plugins; - const { logger } = this; // legacy plugin status mirrorPluginStatus(xpackMainLegacy, reportingLegacy); @@ -47,23 +46,22 @@ export class ReportingPlugin basePath, router, security, - logger, }; runValidations(config, elasticsearch, browserDriverFactory, this.logger); this.reportingCore.pluginSetup(deps); registerReportingUsageCollector(this.reportingCore, plugins); - registerRoutes(this.reportingCore); + registerRoutes(this.reportingCore, this.logger); return {}; } public async start(core: CoreStart, plugins: ReportingStartDeps) { - const { reportingCore } = this; + const { reportingCore, logger } = this; - const esqueue = await createQueueFactory(reportingCore); - const enqueueJob = enqueueJobFactory(reportingCore); + const esqueue = await createQueueFactory(reportingCore, logger); + const enqueueJob = enqueueJobFactory(reportingCore, logger); this.reportingCore.pluginStart({ savedObjects: core.savedObjects, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 9ff2e472491ba..8a6d4553dfa9c 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -11,6 +11,7 @@ import { API_BASE_GENERATE_V1 } from '../../common/constants'; import { createJobFactory, executeJobFactory } from '../../export_types/csv_from_savedobject'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types'; +import { LevelLogger as Logger } from '../lib'; import { JobDocOutput } from '../types'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; @@ -25,7 +26,8 @@ import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routi */ export function registerGenerateCsvFromSavedObjectImmediate( reporting: ReportingCore, - handleError: HandlerErrorFunction + handleError: HandlerErrorFunction, + parentLogger: Logger ) { const setupDeps = reporting.getPluginSetupDeps(); const userHandler = authorizedUserPreRoutingFactory(reporting); @@ -55,10 +57,10 @@ export function registerGenerateCsvFromSavedObjectImmediate( }, }, userHandler(async (user, context, req, res) => { - const logger = setupDeps.logger.clone(['savedobject-csv']); + const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); - const createJobFn = createJobFactory(reporting); - const executeJobFn = await executeJobFactory(reporting); // FIXME: does not "need" to be async + const createJobFn = createJobFactory(reporting, logger); + const executeJobFn = await executeJobFactory(reporting, logger); // FIXME: does not "need" to be async try { const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts index 1e1ebc92b533c..fdde3253cf28e 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.test.ts @@ -90,7 +90,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 if there are no job params', async () => { - registerJobGenerationRoutes(core); + registerJobGenerationRoutes(core, mockLogger); await server.start(); @@ -105,7 +105,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 if job params query is invalid', async () => { - registerJobGenerationRoutes(core); + registerJobGenerationRoutes(core, mockLogger); await server.start(); @@ -116,7 +116,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 if job params body is invalid', async () => { - registerJobGenerationRoutes(core); + registerJobGenerationRoutes(core, mockLogger); await server.start(); @@ -128,7 +128,7 @@ describe('POST /api/reporting/generate', () => { }); it('returns 400 export type is invalid', async () => { - registerJobGenerationRoutes(core); + registerJobGenerationRoutes(core, mockLogger); await server.start(); @@ -150,7 +150,7 @@ describe('POST /api/reporting/generate', () => { }, })); - registerJobGenerationRoutes(core); + registerJobGenerationRoutes(core, mockLogger); await server.start(); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index d0b90679217a5..f2e616c0803a7 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -9,6 +9,7 @@ import { errors as elasticsearchErrors } from 'elasticsearch'; import { kibanaResponseFactory } from 'src/core/server'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; +import { LevelLogger as Logger } from '../lib'; import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; @@ -16,7 +17,7 @@ import { HandlerFunction } from './types'; const esErrors = elasticsearchErrors as Record; -export function registerJobGenerationRoutes(reporting: ReportingCore) { +export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Logger) { const config = reporting.getConfig(); const downloadBaseUrl = config.kbnConfig.get('server', 'basePath') + `${API_BASE_URL}/jobs/download`; @@ -89,6 +90,6 @@ export function registerJobGenerationRoutes(reporting: ReportingCore) { // Register beta panel-action download-related API's if (config.get('csv', 'enablePanelActionDownload')) { registerGenerateCsvFromSavedObject(reporting, handler, handleError); - registerGenerateCsvFromSavedObjectImmediate(reporting, handleError); + registerGenerateCsvFromSavedObjectImmediate(reporting, handleError, logger); } } diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index 3fc71e4462e44..005d82086665c 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { LevelLogger as Logger } from '../lib'; import { registerJobGenerationRoutes } from './generation'; import { registerJobInfoRoutes } from './jobs'; import { ReportingCore } from '../core'; -export function registerRoutes(reporting: ReportingCore) { - registerJobGenerationRoutes(reporting); +export function registerRoutes(reporting: ReportingCore, logger: Logger) { + registerJobGenerationRoutes(reporting, logger); registerJobInfoRoutes(reporting); } diff --git a/x-pack/legacy/plugins/reporting/server/types.ts b/x-pack/legacy/plugins/reporting/server/types.ts index 5000b19e257cf..2ccc209c3ce50 100644 --- a/x-pack/legacy/plugins/reporting/server/types.ts +++ b/x-pack/legacy/plugins/reporting/server/types.ts @@ -207,10 +207,14 @@ export type ServerFacade = LegacySetup; export type CaptureConfig = ReportingConfigType['capture']; export type ScrollConfig = ReportingConfigType['csv']['scroll']; -export type CreateJobFactory = (reporting: ReportingCore) => CreateJobFnType; +export type CreateJobFactory = ( + reporting: ReportingCore, + logger: LevelLogger +) => CreateJobFnType; export type ExecuteJobFactory = ( - reporting: ReportingCore + reporting: ReportingCore, + logger: LevelLogger ) => Promise; // FIXME: does not "need" to be async export interface ExportTypeDefinition<