From 6c4d699d9ab1d6a3e5297fa5c6e3d333ac2b1d19 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 11 Aug 2021 12:00:40 -0700 Subject: [PATCH 1/2] restore legacy shim Restore the legacy shim for PDF job creation from the 7.x branch. --- .../export_types/common/get_full_urls.test.ts | 44 ++- .../export_types/common/get_full_urls.ts | 8 +- .../export_types/common/validate_urls.ts | 9 + .../create_job/compatibility_shim.test.ts | 273 ++++++++++++++++++ .../create_job/compatibility_shim.ts | 130 +++++++++ .../printable_pdf/create_job/index.ts | 28 +- .../printable_pdf/execute_job/index.test.ts | 6 +- .../export_types/printable_pdf/types.d.ts | 15 +- .../reporting/server/routes/generation.ts | 4 +- .../plugins/reporting/server/routes/legacy.ts | 88 ++++++ .../reporting/server/routes/types.d.ts | 5 +- .../bwc_generation_urls.ts | 64 ++++ .../reporting_and_security/index.ts | 1 + .../services/generation_urls.ts | 14 + 14 files changed, 645 insertions(+), 44 deletions(-) create mode 100644 x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.ts create mode 100644 x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts create mode 100644 x-pack/plugins/reporting/server/routes/legacy.ts create mode 100644 x-pack/test/reporting_api_integration/reporting_and_security/bwc_generation_urls.ts diff --git a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts index 5e53a8c70aa68..a69de1034f5a4 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.test.ts @@ -28,7 +28,7 @@ const getMockJob = (base: object) => base as TaskPayloadPNG & TaskPayloadPDF; test(`fails if no URL is passed`, async () => { const fn = () => getFullUrls(mockConfig, getMockJob({})); expect(fn).toThrowErrorMatchingInlineSnapshot( - `"No valid URL fields found in Job Params! Expected \`job.relativeUrl: string\` or \`job.relativeUrls: string[]\`"` + `"No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`"` ); }); @@ -54,14 +54,7 @@ test(`fails if URLs are absolute for PNGs`, async () => { test(`fails if URLs are file-protocols for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - const fn = () => - getFullUrls( - mockConfig, - getMockJob({ - relativeUrls: [relativeUrl], - forceNow, - }) - ); + const fn = () => getFullUrls(mockConfig, getMockJob({ objects: [{ relativeUrl }], forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); @@ -75,7 +68,7 @@ test(`fails if URLs are absolute for PDF`, async () => { getFullUrls( mockConfig, getMockJob({ - relativeUrls: [relativeUrl], + objects: [{ relativeUrl }], forceNow, }) ); @@ -86,13 +79,16 @@ test(`fails if URLs are absolute for PDF`, async () => { test(`fails if any URLs are absolute or file's for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const relativeUrls = [ - '/app/kibana#/something_aaa', - 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something', - 'file://etc/passwd/#/something', + const objects = [ + { relativeUrl: '/app/kibana#/something_aaa' }, + { + relativeUrl: + 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something', + }, + { relativeUrl: 'file://etc/passwd/#/something' }, ]; - const fn = () => getFullUrls(mockConfig, getMockJob({ relativeUrls, forceNow })); + const fn = () => getFullUrls(mockConfig, getMockJob({ objects, forceNow })); expect(fn).toThrowErrorMatchingInlineSnapshot( `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something file://etc/passwd/#/something"` ); @@ -107,7 +103,7 @@ test(`fails if URL does not route to a visualization`, async () => { test(`adds forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls( + const urls = getFullUrls( mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something', forceNow }) ); @@ -120,7 +116,7 @@ test(`adds forceNow to hash's query, if it exists`, async () => { test(`appends forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls( + const urls = getFullUrls( mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something?_g=something', forceNow }) ); @@ -131,21 +127,21 @@ test(`appends forceNow to hash's query, if it exists`, async () => { }); test(`doesn't append forceNow query to url, if it doesn't exists`, async () => { - const urls = await getFullUrls(mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something' })); + const urls = getFullUrls(mockConfig, getMockJob({ relativeUrl: '/app/kibana#/something' })); expect(urls[0]).toEqual('http://localhost:5601/sbp/app/kibana#/something'); }); test(`adds forceNow to each of multiple urls`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const urls = await getFullUrls( + const urls = getFullUrls( mockConfig, getMockJob({ - relativeUrls: [ - '/app/kibana#/something_aaa', - '/app/kibana#/something_bbb', - '/app/kibana#/something_ccc', - '/app/kibana#/something_ddd', + objects: [ + { relativeUrl: '/app/kibana#/something_aaa' }, + { relativeUrl: '/app/kibana#/something_bbb' }, + { relativeUrl: '/app/kibana#/something_ccc' }, + { relativeUrl: '/app/kibana#/something_ddd' }, ], forceNow, }) diff --git a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts index 0fe1298932b36..5ae4092a466fa 100644 --- a/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts +++ b/x-pack/plugins/reporting/server/export_types/common/get_full_urls.ts @@ -21,7 +21,7 @@ function isPngJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPNG { return (job as TaskPayloadPNG).relativeUrl !== undefined; } function isPdfJob(job: TaskPayloadPNG | TaskPayloadPDF): job is TaskPayloadPDF { - return (job as TaskPayloadPDF).relativeUrls !== undefined; + return (job as TaskPayloadPDF).objects !== undefined; } export function getFullUrls(config: ReportingConfig, job: TaskPayloadPDF | TaskPayloadPNG) { @@ -39,17 +39,17 @@ export function getFullUrls(config: ReportingConfig, job: TaskPayloadPDF | TaskP if (isPngJob(job)) { relativeUrls = [job.relativeUrl]; } else if (isPdfJob(job)) { - relativeUrls = job.relativeUrls; + relativeUrls = job.objects.map((obj) => obj.relativeUrl); } else { throw new Error( - `No valid URL fields found in Job Params! Expected \`job.relativeUrl: string\` or \`job.relativeUrls: string[]\`` + `No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`` ); } validateUrls(relativeUrls); const urls = relativeUrls.map((relativeUrl) => { - const parsedRelative: UrlWithStringQuery = urlParse(relativeUrl); + const parsedRelative: UrlWithStringQuery = urlParse(relativeUrl); // FIXME: '(urlStr: string): UrlWithStringQuery' is deprecated const jobUrl = getAbsoluteUrl({ path: parsedRelative.pathname === null ? undefined : parsedRelative.pathname, hash: parsedRelative.hash === null ? undefined : parsedRelative.hash, diff --git a/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts b/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts index 22e0fc7de8455..e6d392c0bb55c 100644 --- a/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts +++ b/x-pack/plugins/reporting/server/export_types/common/validate_urls.ts @@ -22,6 +22,15 @@ const isBogusUrl = (url: string) => { }; export const validateUrls = (urls: string[]): void => { + if (!Array.isArray(urls)) { + throw new Error('Invalid relativeUrls. String[] is expected.'); + } + urls.forEach((url) => { + if (typeof url !== 'string') { + throw new Error('Invalid Relative URL in relativeUrls. String is expected.'); + } + }); + const badUrls = filter(urls, (url) => isBogusUrl(url)); if (badUrls.length) { diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.ts new file mode 100644 index 0000000000000..4e366f506af2d --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.test.ts @@ -0,0 +1,273 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { createMockLevelLogger } from '../../../test_helpers'; +import { compatibilityShim } from './compatibility_shim'; + +const mockRequestHandlerContext = { + core: coreMock.createRequestHandlerContext(), + reporting: { usesUiCapabilities: () => true }, +}; +const mockLogger = createMockLevelLogger(); + +const mockKibanaRequest = httpServerMock.createKibanaRequest(); +const createMockSavedObject = (body: any) => ({ + id: 'mockSavedObjectId123', + type: 'mockSavedObjectType', + references: [], + ...body, +}); +const createMockJobParams = (body: any) => ({ + ...body, +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +test(`passes title through if provided`, async () => { + const title = 'test title'; + + const createJobMock = jest.fn(); + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title, relativeUrls: ['/something'] }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(0); + expect(mockLogger.error.mock.calls.length).toBe(0); + + expect(createJobMock.mock.calls.length).toBe(1); + expect(createJobMock.mock.calls[0][0].title).toBe(title); +}); + +test(`gets the title from the savedObject`, async () => { + const createJobMock = jest.fn(); + const title = 'savedTitle'; + mockRequestHandlerContext.core.savedObjects.client.get.mockResolvedValue( + createMockSavedObject({ + attributes: { title }, + }) + ); + + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ objectType: 'search', savedObjectId: 'abc' }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(2); + expect(mockLogger.warn.mock.calls[0][0]).toEqual( + 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' + ); + expect(mockLogger.error.mock.calls.length).toBe(0); + + expect(createJobMock.mock.calls.length).toBe(1); + expect(createJobMock.mock.calls[0][0].title).toBe(title); +}); + +test(`passes the objectType and savedObjectId to the savedObjectsClient`, async () => { + const createJobMock = jest.fn(); + const context = mockRequestHandlerContext; + context.core.savedObjects.client.get.mockResolvedValue( + createMockSavedObject({ attributes: { title: '' } }) + ); + + const objectType = 'search'; + const savedObjectId = 'abc'; + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ objectType, savedObjectId }), + context, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(2); + expect(mockLogger.warn.mock.calls[0][0]).toEqual( + 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' + ); + expect(mockLogger.warn.mock.calls[1][0]).toEqual( + 'The title has been derived from saved object parameters. This functionality will be removed with the next major version.' + ); + expect(mockLogger.error.mock.calls.length).toBe(0); + + const getMock = context.core.savedObjects.client.get.mock; + expect(getMock.calls.length).toBe(1); + expect(getMock.calls[0][0]).toBe(objectType); + expect(getMock.calls[0][1]).toBe(savedObjectId); +}); + +test(`logs no warnings when title and relativeUrls is passed`, async () => { + const createJobMock = jest.fn(); + + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'Phenomenal Dashboard', relativeUrls: ['/abc', '/def'] }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(0); + expect(mockLogger.error.mock.calls.length).toBe(0); +}); + +test(`logs warning if title can not be provided`, async () => { + const createJobMock = jest.fn(); + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ relativeUrls: ['/abc'] }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(1); + expect(mockLogger.warn.mock.calls[0][0]).toEqual( + `A title parameter should be provided with the job generation request. Please ` + + `use Kibana to regenerate your POST URL to have a title included in the PDF.` + ); +}); + +test(`logs deprecations when generating the title/relativeUrl using the savedObject`, async () => { + const createJobMock = jest.fn(); + mockRequestHandlerContext.core.savedObjects.client.get.mockResolvedValue( + createMockSavedObject({ + attributes: { title: '' }, + }) + ); + + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ objectType: 'search', savedObjectId: 'abc' }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(2); + expect(mockLogger.warn.mock.calls[0][0]).toEqual( + 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' + ); + expect(mockLogger.warn.mock.calls[1][0]).toEqual( + 'The title has been derived from saved object parameters. This functionality will be removed with the next major version.' + ); +}); + +test(`passes objectType through`, async () => { + const createJobMock = jest.fn(); + + const objectType = 'foo'; + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', relativeUrls: ['/something'], objectType }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(0); + expect(mockLogger.error.mock.calls.length).toBe(0); + + expect(createJobMock.mock.calls.length).toBe(1); + expect(createJobMock.mock.calls[0][0].objectType).toBe(objectType); +}); + +test(`passes the relativeUrls through`, async () => { + const createJobMock = jest.fn(); + + const relativeUrls = ['/app/kibana#something', '/app/kibana#something-else']; + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', relativeUrls }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(0); + expect(mockLogger.error.mock.calls.length).toBe(0); + + expect(createJobMock.mock.calls.length).toBe(1); + expect(createJobMock.mock.calls[0][0].relativeUrls).toBe(relativeUrls); +}); + +const testSavedObjectRelativeUrl = (objectType: string, expectedUrl: string) => { + test(`generates the saved object relativeUrl for ${objectType}`, async () => { + const createJobMock = jest.fn(); + + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', objectType, savedObjectId: 'abc' }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(1); + expect(mockLogger.warn.mock.calls[0][0]).toEqual( + 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' + ); + expect(mockLogger.error.mock.calls.length).toBe(0); + + expect(createJobMock.mock.calls.length).toBe(1); + expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual([expectedUrl]); + }); +}; + +testSavedObjectRelativeUrl('search', '/app/kibana#/discover/abc?'); +testSavedObjectRelativeUrl('visualization', '/app/kibana#/visualize/edit/abc?'); +testSavedObjectRelativeUrl('dashboard', '/app/kibana#/dashboard/abc?'); + +test(`appends the queryString to the relativeUrl when generating from the savedObject`, async () => { + const createJobMock = jest.fn(); + + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ + title: 'test', + objectType: 'search', + savedObjectId: 'abc', + queryString: 'foo=bar', + }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(1); + expect(mockLogger.warn.mock.calls[0][0]).toEqual( + 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' + ); + expect(mockLogger.error.mock.calls.length).toBe(0); + + expect(createJobMock.mock.calls.length).toBe(1); + expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual([ + '/app/kibana#/discover/abc?foo=bar', + ]); +}); + +test(`throw an Error if the objectType, savedObjectId and relativeUrls are provided`, async () => { + const createJobMock = jest.fn(); + + const promise = compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ + title: 'test', + objectType: 'something', + relativeUrls: ['/something'], + savedObjectId: 'abc', + }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + await expect(promise).rejects.toBeDefined(); +}); + +test(`passes headers and request through`, async () => { + const createJobMock = jest.fn(); + + await compatibilityShim(createJobMock, mockLogger)( + createMockJobParams({ title: 'test', relativeUrls: ['/something'] }), + mockRequestHandlerContext, + mockKibanaRequest + ); + + expect(mockLogger.warn.mock.calls.length).toBe(0); + expect(mockLogger.error.mock.calls.length).toBe(0); + + expect(createJobMock.mock.calls.length).toBe(1); + expect(createJobMock.mock.calls[0][1]).toBe(mockRequestHandlerContext); + expect(createJobMock.mock.calls[0][2]).toBe(mockKibanaRequest); +}); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts new file mode 100644 index 0000000000000..f806b8a7e5bca --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/compatibility_shim.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaRequest } from 'kibana/server'; +import { url as urlUtils } from '../../../../../../../src/plugins/kibana_utils/server'; +import type { LevelLogger } from '../../../lib'; +import type { CreateJobFn, ReportingRequestHandlerContext } from '../../../types'; +import type { JobParamsPDF, JobParamsPDFLegacy, TaskPayloadPDF } from '../types'; + +function isLegacyJob( + jobParams: JobParamsPDF | JobParamsPDFLegacy +): jobParams is JobParamsPDFLegacy { + return (jobParams as JobParamsPDFLegacy).savedObjectId != null; +} + +const getSavedObjectTitle = async ( + objectType: string, + savedObjectId: string, + savedObjectsClient: any +) => { + const savedObject = await savedObjectsClient.get(objectType, savedObjectId); + return savedObject.attributes.title; +}; + +const getSavedObjectRelativeUrl = ( + objectType: string, + savedObjectId: string, + queryString: string +) => { + const appPrefixes: Record = { + dashboard: '/dashboard/', + visualization: '/visualize/edit/', + search: '/discover/', + }; + + const appPrefix = appPrefixes[objectType]; + if (!appPrefix) throw new Error('Unexpected app type: ' + objectType); + + const hash = appPrefix + urlUtils.encodeUriQuery(savedObjectId, true); + + return `/app/kibana#${hash}?${queryString || ''}`; +}; + +/* + * The compatibility shim is responsible for migrating an older shape of the + * PDF Job Params into a newer shape, by deriving a report title and relative + * URL from a savedObjectId and queryString. + */ +export function compatibilityShim( + createJobFn: CreateJobFn, + logger: LevelLogger +) { + return async function ( + jobParams: JobParamsPDF | JobParamsPDFLegacy, + context: ReportingRequestHandlerContext, + req: KibanaRequest + ) { + let kibanaRelativeUrls = (jobParams as JobParamsPDF).relativeUrls; + let reportTitle = jobParams.title; + let isDeprecated = false; + + if ( + (jobParams as JobParamsPDFLegacy).savedObjectId && + (jobParams as JobParamsPDF).relativeUrls + ) { + throw new Error(`savedObjectId should not be provided if relativeUrls are provided`); + } + + if (isLegacyJob(jobParams)) { + const { savedObjectId, objectType, queryString } = jobParams; + + // input validation and deprecation logging + if (typeof savedObjectId !== 'string') { + throw new Error('Invalid savedObjectId (deprecated). String is expected.'); + } + if (typeof objectType !== 'string') { + throw new Error('Invalid objectType (deprecated). String is expected.'); + } + + // legacy parameters need to be converted into a relative URL + kibanaRelativeUrls = [getSavedObjectRelativeUrl(objectType, savedObjectId, queryString)]; + logger.warn( + `The relativeUrls have been derived from saved object parameters. ` + + `This functionality will be removed with the next major version.` + ); + + // legacy parameters might need to get the title from the saved object + if (!reportTitle) { + try { + reportTitle = await getSavedObjectTitle( + objectType, + savedObjectId, + context.core.savedObjects.client + ); + logger.warn( + `The title has been derived from saved object parameters. This ` + + `functionality will be removed with the next major version.` + ); + } catch (err) { + logger.error(err); // 404 for the savedObjectId, etc + throw err; + } + } + + isDeprecated = true; + } + + if (typeof reportTitle !== 'string') { + logger.warn( + `A title parameter should be provided with the job generation ` + + `request. Please use Kibana to regenerate your POST URL to have a ` + + `title included in the PDF.` + ); + reportTitle = ''; + } + + const transformedJobParams: JobParamsPDF = { + ...jobParams, + title: reportTitle, + relativeUrls: kibanaRelativeUrls, + isDeprecated, // tack on this flag so it will be saved the TaskPayload + }; + + return await createJobFn(transformedJobParams, context, req); + }; +} diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts index 9dac1560ddbdc..ab95abcce8af8 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts @@ -5,27 +5,41 @@ * 2.0. */ +import { KibanaRequest } from 'src/core/server'; import { cryptoFactory } from '../../../lib'; -import { CreateJobFn, CreateJobFnFactory } from '../../../types'; +import { CreateJobFn, CreateJobFnFactory, ReportingRequestHandlerContext } from '../../../types'; import { validateUrls } from '../../common'; -import { JobParamsPDF, TaskPayloadPDF } from '../types'; +import { JobParamsPDF, JobParamsPDFLegacy, TaskPayloadPDF } from '../types'; +import { compatibilityShim } from './compatibility_shim'; +/* + * Incoming job params can be `JobParamsPDF` or `JobParamsPDFLegacy` depending + * on the version that the POST URL was copied from. + */ export const createJobFnFactory: CreateJobFnFactory< - CreateJobFn + CreateJobFn > = function createJobFactoryFn(reporting, logger) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); - return async function createJob(jobParams, _context, req) { + return compatibilityShim(async function createJobFn( + jobParams: JobParamsPDF, + _context: ReportingRequestHandlerContext, + req: KibanaRequest + ) { const serializedEncryptedHeaders = await crypto.encrypt(req.headers); validateUrls(jobParams.relativeUrls); - return { + const payload: TaskPayloadPDF = { + ...jobParams, headers: serializedEncryptedHeaders, spaceId: reporting.getSpaceId(req, logger), forceNow: new Date().toISOString(), - ...jobParams, + objects: jobParams.relativeUrls.map((u) => ({ relativeUrl: u })), }; - }; + + return payload; + }, + logger); }; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts index d58ec4cde0f3d..7110368475ed1 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts @@ -7,8 +7,8 @@ jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() })); -import { Writable } from 'stream'; import * as Rx from 'rxjs'; +import { Writable } from 'stream'; import { ReportingCore } from '../../../'; import { CancellationToken } from '../../../../common'; import { cryptoFactory, LevelLogger } from '../../../lib'; @@ -95,7 +95,7 @@ test(`returns content_type of application/pdf`, async () => { const { content_type: contentType } = await runTask( 'pdfJobId', - getBasePayload({ relativeUrls: [], headers: encryptedHeaders }), + getBasePayload({ objects: [], headers: encryptedHeaders }), cancellationToken, stream ); @@ -111,7 +111,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const encryptedHeaders = await encryptHeaders({}); await runTask( 'pdfJobId', - getBasePayload({ relativeUrls: [], headers: encryptedHeaders }), + getBasePayload({ objects: [], headers: encryptedHeaders }), cancellationToken, stream ); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts index 5172bf300abc8..8e4c45ad79506 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts @@ -10,13 +10,22 @@ import { BaseParams, BasePayload } from '../../types'; interface BaseParamsPDF { layout: LayoutParams; - forceNow?: string; - // TODO: Add comment explaining this field relativeUrls: string[]; + isDeprecated?: boolean; } // Job params: structure of incoming user request data, after being parsed from RISON export type JobParamsPDF = BaseParamsPDF & BaseParams; // Job payload: structure of stored job data provided by create_job -export type TaskPayloadPDF = BaseParamsPDF & BasePayload; +export interface TaskPayloadPDF extends BasePayload { + layout: LayoutParams; + forceNow?: string; + objects: Array<{ relativeUrl: string }>; +} + +type Legacy = Omit; +export interface JobParamsPDFLegacy extends Legacy { + savedObjectId: string; + queryString: string; +} diff --git a/x-pack/plugins/reporting/server/routes/generation.ts b/x-pack/plugins/reporting/server/routes/generation.ts index 4082084c82fbc..adbfbda727af2 100644 --- a/x-pack/plugins/reporting/server/routes/generation.ts +++ b/x-pack/plugins/reporting/server/routes/generation.ts @@ -11,8 +11,9 @@ import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; import { LevelLogger as Logger } from '../lib'; import { enqueueJob } from '../lib/enqueue_job'; -import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObjectImmediate } from './csv_searchsource_immediate'; +import { registerGenerateFromJobParams } from './generate_from_jobparams'; +import { registerLegacy } from './legacy'; import { HandlerFunction } from './types'; const getDownloadBaseUrl = (reporting: ReportingCore) => { @@ -87,4 +88,5 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo registerGenerateFromJobParams(reporting, handler, handleError); registerGenerateCsvFromSavedObjectImmediate(reporting, handleError, logger); + registerLegacy(reporting, handler, handleError, logger); } diff --git a/x-pack/plugins/reporting/server/routes/legacy.ts b/x-pack/plugins/reporting/server/routes/legacy.ts new file mode 100644 index 0000000000000..79f1b7f17c2da --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/legacy.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import querystring from 'querystring'; +import { authorizedUserPreRouting } from './lib/authorized_user_pre_routing'; +import { API_BASE_URL } from '../../common/constants'; +import { HandlerErrorFunction, HandlerFunction } from './types'; +import { ReportingCore } from '../core'; +import { LevelLogger } from '../lib'; + +const BASE_GENERATE = `${API_BASE_URL}/generate`; + +export function registerLegacy( + reporting: ReportingCore, + handler: HandlerFunction, + handleError: HandlerErrorFunction, + logger: LevelLogger +) { + const { router } = reporting.getPluginSetupDeps(); + + function createLegacyPdfRoute({ path, objectType }: { path: string; objectType: string }) { + const exportTypeId = 'printablePdf'; + + router.post( + { + path, + validate: { + params: schema.object({ + savedObjectId: schema.string({ minLength: 3 }), + }), + query: schema.any(), + }, + }, + + authorizedUserPreRouting(reporting, async (user, context, req, res) => { + const message = `The following URL is deprecated and will stop working in the next major version: ${req.url.pathname}${req.url.search}`; + logger.warn(message, ['deprecation']); + + try { + const { + title, + savedObjectId, + browserTimezone, + }: { title: string; savedObjectId: string; browserTimezone: string } = req.params as any; + const queryString = querystring.stringify(req.query as any); + + return await handler( + user, + exportTypeId, + { + title, + objectType, + savedObjectId, + browserTimezone, + queryString, + version: reporting.getKibanaVersion(), + }, + context, + req, + res + ); + } catch (err) { + throw handleError(res, err); + } + }) + ); + } + + createLegacyPdfRoute({ + path: `${BASE_GENERATE}/visualization/{savedId}`, + objectType: 'visualization', + }); + + createLegacyPdfRoute({ + path: `${BASE_GENERATE}/search/{savedId}`, + objectType: 'search', + }); + + createLegacyPdfRoute({ + path: `${BASE_GENERATE}/dashboard/{savedId}`, + objectType: 'dashboard', + }); +} diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts index 2e8e94eaf265a..336605e6ff9b9 100644 --- a/x-pack/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -8,15 +8,16 @@ import { KibanaRequest, KibanaResponseFactory } from 'src/core/server'; import type { BaseParams, + BaseParamsLegacyPDF, BasePayload, - ReportingUser, ReportingRequestHandlerContext, + ReportingUser, } from '../types'; export type HandlerFunction = ( user: ReportingUser, exportType: string, - jobParams: BaseParams, + jobParams: BaseParams | BaseParamsLegacyPDF, context: ReportingRequestHandlerContext, req: KibanaRequest, res: KibanaResponseFactory diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/bwc_generation_urls.ts b/x-pack/test/reporting_api_integration/reporting_and_security/bwc_generation_urls.ts new file mode 100644 index 0000000000000..fd194a1df1f65 --- /dev/null +++ b/x-pack/test/reporting_api_integration/reporting_and_security/bwc_generation_urls.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { REPO_ROOT } from '@kbn/utils'; +import pathNode from 'path'; +import { FtrProviderContext } from '../ftr_provider_context'; +import * as GenerationUrls from '../services/generation_urls'; + +const OSS_KIBANA_ARCHIVE_PATH = pathNode.resolve( + REPO_ROOT, + 'test/functional/fixtures/es_archiver/dashboard/current/kibana' +); +const OSS_DATA_ARCHIVE_PATH = pathNode.resolve( + REPO_ROOT, + 'test/functional/fixtures/es_archiver/dashboard/current/data' +); + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const reportingAPI = getService('reportingAPI'); + + describe('BWC report generation urls', () => { + before(async () => { + await esArchiver.load(OSS_KIBANA_ARCHIVE_PATH); + await esArchiver.load(OSS_DATA_ARCHIVE_PATH); + + await kibanaServer.uiSettings.update({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + }); + await reportingAPI.deleteAllReports(); + }); + + describe('Pre 6_2', () => { + // The URL being tested was captured from release 6.4 and then the layout section was removed to test structure before + // preserve_layout was introduced. See https://github.com/elastic/kibana/issues/23414 + it('job posted successfully', async () => { + const path = await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_PRE_6_2); + await reportingAPI.waitForJobToFinish(path); + }).timeout(500000); + }); + + describe('6_2', () => { + // Might not be great test practice to lump all these jobs together but reporting takes awhile and it'll be + // more efficient to post them all up front, then sequentially. + it('multiple jobs posted', async () => { + const reportPaths = []; + reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_6_2)); + reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRESERVE_VISUALIZATION_6_2)); + reportPaths.push(await reportingAPI.postJob(GenerationUrls.CSV_DISCOVER_FILTER_QUERY_6_2)); + + await reportingAPI.expectAllJobsToFinishSuccessfully(reportPaths); + }).timeout(1540000); + }); + + // 6.3 urls currently being tested as part of the "bwc_existing_indexes" test suite. Reports are time consuming, + // don't replicate tests if we don't need to, so no specific 6_3 url tests here. + }); +} diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts index 2996f49857ddd..e6fd534274df4 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts @@ -20,6 +20,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await reportingAPI.createTestReportingUser(); }); + loadTestFile(require.resolve('./bwc_generation_urls')); loadTestFile(require.resolve('./bwc_existing_indexes')); loadTestFile(require.resolve('./security_roles_privileges')); loadTestFile(require.resolve('./download_csv_dashboard')); diff --git a/x-pack/test/reporting_api_integration/services/generation_urls.ts b/x-pack/test/reporting_api_integration/services/generation_urls.ts index 7ac8b8396a26e..f6379bc376e76 100644 --- a/x-pack/test/reporting_api_integration/services/generation_urls.ts +++ b/x-pack/test/reporting_api_integration/services/generation_urls.ts @@ -5,6 +5,13 @@ * 2.0. */ +// These all have the domain name portion stripped out. The api infrastructure assumes it when we post to it anyhow. + +// The URL below was captured from release 6.4 and then the layout section was removed to test structure before +// preserve_layout was introduced. See https://github.com/elastic/kibana/issues/23414 +export const PDF_PRINT_DASHBOARD_PRE_6_2 = + '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(pause:!!t,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; + export const PDF_PRINT_DASHBOARD_6_3 = '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; export const PDF_PRESERVE_DASHBOARD_FILTER_6_3 = @@ -15,3 +22,10 @@ export const PDF_PRINT_PIE_VISUALIZATION_FILTER_AND_SAVED_SEARCH_6_3 = '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:visualization,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2Fbefdb6b0-3e59-11e8-9fc3-39e49624228e%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:animal.keyword,negate:!!f,params:(query:dog,type:phrase),type:phrase,value:dog),query:(match:(animal.keyword:(query:dog,type:phrase))))),linked:!!t,query:(language:lucene,query:!%27!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:name.keyword,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Filter%2BTest:%2Banimals:%2Blinked%2Bto%2Bsearch%2Bwith%2Bfilter!%27,type:pie))%27),title:%27Filter%20Test:%20animals:%20linked%20to%20search%20with%20filter%27)'; export const CSV_DISCOVER_KUERY_AND_FILTER_6_3 = '/api/reporting/generate/csv?jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,agent,bytes,clientip),indexPatternId:%270bf35f60-3dc9-11e8-8660-4d65aa086b3c%27,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,agent,bytes,clientip)),docvalue_fields:!(%27@timestamp%27),query:(bool:(filter:!((bool:(minimum_should_match:1,should:!((match:(clientip:%2773.14.212.83%27)))))),must:!((range:(bytes:(gte:100,lt:1000))),(range:(%27@timestamp%27:(format:epoch_millis,gte:1369165215770,lte:1526931615770)))),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,agent,bytes,clientip),version:!t),index:%27logstash-*%27),title:%27Bytes%20and%20kuery%20in%20saved%20search%20with%20filter%27,type:search)'; + +export const PDF_PRINT_DASHBOARD_6_2 = + '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:dashboard,queryString:%27_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,field:isDog,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:isDog,negate:!!f,params:(value:!!t),type:phrase,value:true),script:(script:(inline:!%27boolean%2Bcompare(Supplier%2Bs,%2Bdef%2Bv)%2B%257Breturn%2Bs.get()%2B%253D%253D%2Bv%3B%257Dcompare(()%2B-%253E%2B%257B%2Breturn%2Bdoc%255B!!!%27animal.keyword!!!%27%255D.value%2B%253D%253D%2B!!!%27dog!!!%27%2B%257D,%2Bparams.value)%3B!%27,lang:painless,params:(value:!!t))))),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((gridData:(h:3,i:!%274!%27,w:6,x:6,y:0),id:edb65990-53ca-11e8-b481-c9426d020fcd,panelIndex:!%274!%27,type:visualization,version:!%276.2.4!%27),(gridData:(h:3,i:!%275!%27,w:6,x:0,y:0),id:!%270644f890-53cb-11e8-b481-c9426d020fcd!%27,panelIndex:!%275!%27,type:visualization,version:!%276.2.4!%27)),query:(language:lucene,query:!%27weightLbs:%253E15!%27),timeRestore:!!t,title:!%27Animal%2BWeights%2B(created%2Bin%2B6.2)!%27,viewMode:view)%27,savedObjectId:%271b2f47b0-53cb-11e8-b481-c9426d020fcd%27)'; +export const PDF_PRESERVE_VISUALIZATION_6_2 = + '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(dimensions:(height:441,width:1002),id:preserve_layout),objectType:visualization,queryString:%27_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!(),linked:!!f,query:(language:lucene,query:!%27weightLbs:%253E10!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:weightLbs,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Weight%2Bin%2Blbs%2Bpie%2Bcreated%2Bin%2B6.2!%27,type:pie))%27,savedObjectId:%270644f890-53cb-11e8-b481-c9426d020fcd%27)'; +export const CSV_DISCOVER_FILTER_QUERY_6_2 = + '/api/reporting/generate/csv?jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,animal,sound,weightLbs),indexPatternId:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,animal,sound,weightLbs)),docvalue_fields:!(%27@timestamp%27),query:(bool:(filter:!(),must:!((query_string:(analyze_wildcard:!t,default_field:%27*%27,query:%27weightLbs:%3E10%27)),(match_phrase:(sound.keyword:(query:growl))),(range:(%27@timestamp%27:(format:epoch_millis,gte:1523310968000,lte:1523483768000)))),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,animal,sound,weightLbs),version:!t),index:%27animals-*%27),title:%27Search%20created%20in%206.2%27,type:search)'; From 0007a27c26f4bbbe4c97d9f4eb2c478cef8e8670 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 13 Aug 2021 14:28:32 -0700 Subject: [PATCH 2/2] strip relativeUrls from pdf payload --- .../server/export_types/printable_pdf/create_job/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts index ab95abcce8af8..57265f73fc16a 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/create_job/index.ts @@ -23,20 +23,20 @@ export const createJobFnFactory: CreateJobFnFactory< const crypto = cryptoFactory(config.get('encryptionKey')); return compatibilityShim(async function createJobFn( - jobParams: JobParamsPDF, + { relativeUrls, ...jobParams }: JobParamsPDF, _context: ReportingRequestHandlerContext, req: KibanaRequest ) { - const serializedEncryptedHeaders = await crypto.encrypt(req.headers); + validateUrls(relativeUrls); - validateUrls(jobParams.relativeUrls); + const serializedEncryptedHeaders = await crypto.encrypt(req.headers); const payload: TaskPayloadPDF = { ...jobParams, headers: serializedEncryptedHeaders, spaceId: reporting.getSpaceId(req, logger), forceNow: new Date().toISOString(), - objects: jobParams.relativeUrls.map((u) => ({ relativeUrl: u })), + objects: relativeUrls.map((u) => ({ relativeUrl: u })), }; return payload;