Skip to content

Commit

Permalink
Changes for new PNG image output for reports
Browse files Browse the repository at this point in the history
Change PNG button name from Download to Generate

Changes for new PNG image output for reports

Change PNG button name from Download to Generate

Remove compatabilityShim from PNG and move to PDF folders

Changed API parameters to contain only required parameters

Added test cases for PNG reporting and added title back into the PNG API call

Merge of security changes for PNG reporting

Fixed issues with test cases for both PDF and PNG

Updated test snapshots for PNG and PDF test changes
  • Loading branch information
bgaddis56 committed Oct 31, 2018
1 parent e13e47f commit d6368f6
Show file tree
Hide file tree
Showing 36 changed files with 794 additions and 79 deletions.
7 changes: 6 additions & 1 deletion x-pack/plugins/reporting/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ export const JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY =

export const API_BASE_URL = '/api/reporting';

export const WHITELISTED_JOB_CONTENT_TYPES = ['application/json', 'application/pdf', 'text/csv'];
export const WHITELISTED_JOB_CONTENT_TYPES = [
'application/json',
'application/pdf',
'text/csv',
'image/png',
];

export const UI_SETTINGS_CUSTOM_PDF_LOGO = 'xpackReporting:customPdfLogo';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import url from 'url';
import { oncePerServer } from '../../../../server/lib/once_per_server';
import { oncePerServer } from '../../../server/lib/once_per_server';

function getAbsoluteUrlFn(server) {
const config = server.config();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { KbnServer, Size } from '../../../../../types';
import { LayoutTypes } from '../../../common/constants';
import { KbnServer, Size } from '../../../types';
import { LayoutTypes } from '../constants';
import { Layout } from './layout';
import { PreserveLayout } from './preserve_layout';
import { PrintLayout } from './print_layout';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Size, ViewZoomWidthHeight } from '../../../../../types';
import { Size, ViewZoomWidthHeight } from '../../../types';

export interface PageSizeParams {
pageMarginTop: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import path from 'path';
import { Size } from '../../../../../types';
import { LayoutTypes } from '../../../common/constants';
import { Size } from '../../../types';
import { LayoutTypes } from '../constants';
import { Layout, PageSizeParams } from './layout';

// We use a zoom of two to bump up the resolution of the screenshot a bit.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import path from 'path';
import { EvaluateOptions, KbnServer, Size } from '../../../../../types';
import { LayoutTypes } from '../../../common/constants';
import { EvaluateOptions, KbnServer, Size } from '../../../types';
import { LayoutTypes } from '../constants';
import { Layout } from './layout';
import { CaptureConfig } from './types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Size } from '../../../../../types';
import { Size } from '../../../types';

export interface CaptureConfig {
zoom: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { first, tap, mergeMap } from 'rxjs/operators';
import fs from 'fs';
import getPort from 'get-port';
import { promisify } from 'bluebird';
import { LevelLogger } from '../../../../server/lib/level_logger';
import { LevelLogger } from '../../../server/lib/level_logger';

const fsp = {
readFile: promisify(fs.readFile, fs)
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/reporting/export_types/png/metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export const metadata = {
id: 'png',
name: 'PNG'
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { cryptoFactory } from '../../../../server/lib/crypto';
import { oncePerServer } from '../../../../server/lib/once_per_server';

function createJobFn(server) {
const crypto = cryptoFactory(server);

return async function createJob({
objectType,
title,
relativeUrl,
browserTimezone,
layout
}, headers, serializedSession, request) {
const serializedEncryptedHeaders = await crypto.encrypt(headers);
const encryptedSerializedSession = await crypto.encrypt(serializedSession);


return {
type: objectType,
title: title,
relativeUrl,
headers: serializedEncryptedHeaders,
browserTimezone,
session: encryptedSerializedSession,
layout,
basePath: request.getBasePath(),
forceNow: new Date().toISOString(),
};
};
}

export const createJobFactory = oncePerServer(createJobFn);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`sessionCookie Fails if no relativeURL is passed in 1`] = `"Unable to generate report. Url is not defined."`;
138 changes: 138 additions & 0 deletions x-pack/plugins/reporting/export_types/png/server/execute_job/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* 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 url from 'url';
import cookie from 'cookie';
import * as Rx from 'rxjs';
import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators';
import { oncePerServer } from '../../../../server/lib/once_per_server';
import { generatePngObservableFactory } from '../lib/generate_png';
import { cryptoFactory } from '../../../../server/lib/crypto';
import { getAbsoluteUrlFactory } from '../../../common/execute_job/get_absolute_url';

function executeJobFn(server) {
const generatePngObservable = generatePngObservableFactory(server);
const crypto = cryptoFactory(server);
const getAbsoluteUrl = getAbsoluteUrlFactory(server);
const config = server.config();

const decryptJobHeaders = async (job) => {
const decryptedHeaders = await crypto.decrypt(job.headers);
return { job, decryptedHeaders };
};

const getSavedObjectAbsoluteUrl = (job, relativeUrl) => {

if (relativeUrl) {
const { pathname: path, hash, search } = url.parse(relativeUrl);
return getAbsoluteUrl({ basePath: job.basePath, path, hash, search });
}

throw new Error(`Unable to generate report. Url is not defined.`);
};

const getSerializedSession = async ({ job, decryptedHeaders }) => {
if (!server.plugins.security) {
job.serializedSession = null;
return { decryptedHeaders, job };
}

if (job.session) {
try {
job.serializedSession = await crypto.decrypt(job.session);
return { decryptedHeaders, job };
} catch (err) {
throw new Error('Failed to decrypt report job data. Please re-generate this report.');
}
}

const cookies = decryptedHeaders.cookie ? cookie.parse(decryptedHeaders.cookie) : null;
if (cookies === null) {
job.serializedSession = null;
return { decryptedHeaders, job };
}

const cookieName = server.plugins.security.getSessionCookieOptions().name;
if (!cookieName) {
throw new Error('Unable to determine the session cookie name');
}

job.serializedSession = cookies[cookieName];

return { decryptedHeaders, job };
};

const getSessionCookie = async ({ job, logo }) => {
if (!job.serializedSession) {
return { job, logo, sessionCookie: null };
}

const cookieOptions = await server.plugins.security.getSessionCookieOptions();
const { httpOnly, name, path, secure } = cookieOptions;

return { job, logo, sessionCookie: {
domain: config.get('xpack.reporting.kibanaServer.hostname') || config.get('server.host'),
httpOnly,
name,
path,
sameSite: 'Strict',
secure,
value: job.serializedSession,
} };
};

const addForceNowQuerystring = async ({ job, sessionCookie }) => {

const jobUrl = getSavedObjectAbsoluteUrl(job, job.relativeUrl);

if (!job.forceNow) {
return { job, sessionCookie, hashUrl: jobUrl };
}

const parsed = url.parse(jobUrl, true);
const hash = url.parse(parsed.hash.replace(/^#/, ''), true);

const transformedHash = url.format({
pathname: hash.pathname,
query: {
...hash.query,
forceNow: job.forceNow
}
});

const hashUrl = url.format({
...parsed,
hash: transformedHash
});
//});
return { job, sessionCookie, hashUrl };
};

return function executeJob(jobToExecute, cancellationToken) {
const process$ = Rx.of(jobToExecute).pipe(
mergeMap(decryptJobHeaders),
catchError(() => Rx.throwError('Failed to decrypt report job data. Please re-generate this report.')),
mergeMap(getSerializedSession),
mergeMap(getSessionCookie),
mergeMap(addForceNowQuerystring),
mergeMap(({ job, sessionCookie, hashUrl }) => {
return generatePngObservable(hashUrl, job.browserTimezone, sessionCookie, job.layout);
}),
map(buffer => ({
content_type: 'image/png',
content: buffer.toString('base64')
}))
);

const stop$ = Rx.fromEventPattern(cancellationToken.on);

return process$.pipe(
takeUntil(stop$)
).toPromise();
};
}

export const executeJobFactory = oncePerServer(executeJobFn);

0 comments on commit d6368f6

Please sign in to comment.