Skip to content

Commit

Permalink
[Reporting] Use spaceId from request in export generation
Browse files Browse the repository at this point in the history
  • Loading branch information
tsullivan committed Sep 9, 2020
1 parent f456331 commit a16c44c
Show file tree
Hide file tree
Showing 29 changed files with 238 additions and 340 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/reporting/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"kibanaVersion": "kibana",
"optionalPlugins": [
"security",
"spaces",
"usageCollection"
],
"configPath": ["xpack", "reporting"],
Expand Down
60 changes: 50 additions & 10 deletions x-pack/plugins/reporting/server/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/

import Hapi from 'hapi';
import * as Rx from 'rxjs';
import { first, map, take } from 'rxjs/operators';
import {
BasePath,
ElasticsearchServiceSetup,
HttpServiceSetup,
IRouter,
KibanaRequest,
SavedObjectsClientContract,
Expand All @@ -17,19 +19,25 @@ import {
} from 'src/core/server';
import { LicensingPluginSetup } from '../../licensing/server';
import { SecurityPluginSetup } from '../../security/server';
import { SpacesPluginSetup } from '../../spaces/server';
import { ReportingConfig } from './';
import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory';
import { checkLicense, getExportTypesRegistry } from './lib';
import { ESQueueInstance } from './lib/create_queue';
import { screenshotsObservableFactory, ScreenshotsObservableFn } from './lib/screenshots';
import { ReportingStore } from './lib/store';

export interface ReportingInternalSetup {
export interface PluginSetupOpts {
elasticsearch: ElasticsearchServiceSetup;
licensing: LicensingPluginSetup;
basePath: BasePath['get'];
router: IRouter;
security?: SecurityPluginSetup;
spaces?: SpacesPluginSetup;
http: HttpServiceSetup;
}

export interface ReportingInternalSetup extends Omit<PluginSetupOpts, 'http'> {
basePath: Pick<BasePath, 'set' | 'get'>;
router: IRouter;
}

export interface ReportingInternalStart {
Expand All @@ -53,9 +61,17 @@ export class ReportingCore {
/*
* Register setupDeps
*/
public pluginSetup(setupDeps: ReportingInternalSetup) {
public pluginSetup({ http, ...setup }: PluginSetupOpts) {
const router = http.createRouter();
const basePath = http.basePath;

const internalSetup: ReportingInternalSetup = {
router,
basePath,
...setup,
};
this.pluginSetup$.next(true); // trigger the observer
this.pluginSetupDeps = setupDeps; // cache
this.pluginSetupDeps = internalSetup; // cache
}

/*
Expand Down Expand Up @@ -158,14 +174,38 @@ export class ReportingCore {
return this.getPluginSetupDeps().elasticsearch;
}

public async getSavedObjectsClient(fakeRequest: KibanaRequest) {
const { savedObjects } = await this.getPluginStartDeps();
return savedObjects.getScopedClient(fakeRequest) as SavedObjectsClientContract;
}

public async getUiSettingsServiceFactory(savedObjectsClient: SavedObjectsClientContract) {
const { uiSettings: uiSettingsService } = await this.getPluginStartDeps();
const scopedUiSettingsService = uiSettingsService.asScopedToClient(savedObjectsClient);
return scopedUiSettingsService;
}

public getSpaceId(request: KibanaRequest) {
return this.getPluginSetupDeps().spaces?.spacesService?.getSpaceId(request);
}

public getFakeRequest(baseRequest: unknown, spaceId?: string) {
const fakeRequest = KibanaRequest.from({
path: '/',
route: { settings: {} },
url: { href: '/' },
raw: { req: { url: '/' } },
...baseRequest,
} as Hapi.Request);

if (spaceId) {
this.getPluginSetupDeps().basePath.set(fakeRequest, `/s/${spaceId}`);
}
return fakeRequest;
}

private async getSavedObjectsClient(fakeRequest: KibanaRequest) {
const { savedObjects } = await this.getPluginStartDeps();
return savedObjects.getScopedClient(fakeRequest) as SavedObjectsClientContract;
}

public async getFakeUiSettingsClient(fakeRequest: KibanaRequest, spaceId?: string) {
const savedObjectsClient = await this.getSavedObjectsClient(fakeRequest);
return await this.getUiSettingsServiceFactory(savedObjectsClient);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { cryptoFactory, LevelLogger } from '../../lib';
import { cryptoFactory } from '../../lib';
import { createMockLevelLogger } from '../../test_helpers';
import { decryptJobHeaders } from './';

const encryptHeaders = async (encryptionKey: string, headers: Record<string, string>) => {
const crypto = cryptoFactory(encryptionKey);
return await crypto.encrypt(headers);
};

const logger = createMockLevelLogger();

describe('headers', () => {
test(`fails if it can't decrypt headers`, async () => {
const getDecryptedHeaders = () =>
decryptJobHeaders({
encryptionKey: 'abcsecretsauce',
job: {
headers: 'Q53+9A+zf+Xe+ceR/uB/aR/Sw/8e+M+qR+WiG+8z+EY+mo+HiU/zQL+Xn',
},
logger: ({
error: jest.fn(),
} as unknown) as LevelLogger,
});
decryptJobHeaders(
'abcsecretsauce',
'Q53+9A+zf+Xe+ceR/uB/aR/Sw/8e+M+qR+WiG+8z+EY+mo+HiU/zQL+Xn',
logger
);
await expect(getDecryptedHeaders()).rejects.toMatchInlineSnapshot(
`[Error: Failed to decrypt report job data. Please ensure that xpack.reporting.encryptionKey is set and re-generate this report. Error: Invalid IV length]`
);
Expand All @@ -36,15 +35,7 @@ describe('headers', () => {
};

const encryptedHeaders = await encryptHeaders('abcsecretsauce', headers);
const decryptedHeaders = await decryptJobHeaders({
encryptionKey: 'abcsecretsauce',
job: {
title: 'cool-job-bro',
type: 'csv',
headers: encryptedHeaders,
},
logger: {} as LevelLogger,
});
const decryptedHeaders = await decryptJobHeaders('abcsecretsauce', encryptedHeaders, logger);
expect(decryptedHeaders).toEqual(headers);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,21 @@
import { i18n } from '@kbn/i18n';
import { cryptoFactory, LevelLogger } from '../../lib';

interface HasEncryptedHeaders {
headers?: string;
}

// TODO merge functionality with CSV execute job
export const decryptJobHeaders = async <
JobParamsType,
TaskPayloadType extends HasEncryptedHeaders
>({
encryptionKey,
job,
logger,
}: {
encryptionKey?: string;
job: TaskPayloadType;
logger: LevelLogger;
}): Promise<Record<string, string>> => {
export const decryptJobHeaders = async (
encryptionKey: string | undefined,
encryptedHeaders: string,
logger: LevelLogger
): Promise<Record<string, string>> => {
try {
if (typeof job.headers !== 'string') {
if (typeof encryptedHeaders !== 'string') {
throw new Error(
i18n.translate('xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage', {
defaultMessage: 'Job headers are missing',
})
);
}
const crypto = cryptoFactory(encryptionKey);
const decryptedHeaders = (await crypto.decrypt(job.headers)) as Record<string, string>;
const decryptedHeaders = (await crypto.decrypt(encryptedHeaders)) as Record<string, string>;
return decryptedHeaders;
} catch (err) {
logger.error(err);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@

import sinon from 'sinon';
import { ReportingConfig } from '../../';
import { ReportingCore } from '../../core';
import { createMockReportingCore } from '../../test_helpers';
import { BasePayload } from '../../types';
import { TaskPayloadPDF } from '../printable_pdf/types';
import { getConditionalHeaders, getCustomLogo } from './';
import { getConditionalHeaders } from './';

let mockConfig: ReportingConfig;
let mockReportingPlugin: ReportingCore;

const getMockConfig = (mockConfigGet: sinon.SinonStub) => ({
get: mockConfigGet,
Expand All @@ -26,7 +22,6 @@ beforeEach(async () => {
.withArgs('kibanaServer', 'hostname')
.returns('custom-hostname');
mockConfig = getMockConfig(mockConfigGet);
mockReportingPlugin = await createMockReportingCore(mockConfig);
});

describe('conditions', () => {
Expand All @@ -36,7 +31,7 @@ describe('conditions', () => {
baz: 'quix',
};

const conditionalHeaders = await getConditionalHeaders({
const conditionalHeaders = getConditionalHeaders({
job: {} as BasePayload<any>,
filteredHeaders: permittedHeaders,
config: mockConfig,
Expand All @@ -55,90 +50,12 @@ describe('conditions', () => {
});
});

test('uses basePath from job when creating saved object service', async () => {
const mockGetSavedObjectsClient = jest.fn();
mockReportingPlugin.getSavedObjectsClient = mockGetSavedObjectsClient;

const permittedHeaders = {
foo: 'bar',
baz: 'quix',
};
const conditionalHeaders = await getConditionalHeaders({
job: {} as BasePayload<any>,
filteredHeaders: permittedHeaders,
config: mockConfig,
});
const jobBasePath = '/sbp/s/marketing';
await getCustomLogo({
reporting: mockReportingPlugin,
job: { basePath: jobBasePath } as TaskPayloadPDF,
conditionalHeaders,
config: mockConfig,
});

const getBasePath = mockGetSavedObjectsClient.mock.calls[0][0].getBasePath;
expect(getBasePath()).toBe(jobBasePath);
});

test(`uses basePath from server if job doesn't have a basePath when creating saved object service`, async () => {
const mockGetSavedObjectsClient = jest.fn();
mockReportingPlugin.getSavedObjectsClient = mockGetSavedObjectsClient;

const mockConfigGet = sinon.stub();
mockConfigGet.withArgs('kibanaServer', 'hostname').returns('localhost');
mockConfigGet.withArgs('server', 'basePath').returns('/sbp');
mockConfig = getMockConfig(mockConfigGet);

const permittedHeaders = {
foo: 'bar',
baz: 'quix',
};
const conditionalHeaders = await getConditionalHeaders({
job: {} as BasePayload<any>,
filteredHeaders: permittedHeaders,
config: mockConfig,
});

await getCustomLogo({
reporting: mockReportingPlugin,
job: {} as TaskPayloadPDF,
conditionalHeaders,
config: mockConfig,
});

const getBasePath = mockGetSavedObjectsClient.mock.calls[0][0].getBasePath;
expect(getBasePath()).toBe(`/sbp`);
expect(mockGetSavedObjectsClient.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"getBasePath": [Function],
"headers": Object {
"baz": "quix",
"foo": "bar",
},
"path": "/",
"raw": Object {
"req": Object {
"url": "/",
},
},
"route": Object {
"settings": Object {},
},
"url": Object {
"href": "/",
},
},
]
`);
});

describe('config formatting', () => {
test(`lowercases server.host`, async () => {
const mockConfigGet = sinon.stub().withArgs('server', 'host').returns('COOL-HOSTNAME');
mockConfig = getMockConfig(mockConfigGet);

const conditionalHeaders = await getConditionalHeaders({
const conditionalHeaders = getConditionalHeaders({
job: {} as BasePayload<any>,
filteredHeaders: {},
config: mockConfig,
Expand All @@ -152,7 +69,7 @@ describe('config formatting', () => {
.withArgs('kibanaServer', 'hostname')
.returns('GREAT-HOSTNAME');
mockConfig = getMockConfig(mockConfigGet);
const conditionalHeaders = await getConditionalHeaders({
const conditionalHeaders = getConditionalHeaders({
job: {
title: 'cool-job-bro',
type: 'csv',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ test(`gets logo from uiSettings`, async () => {
config: mockConfig,
});

const { logo } = await getCustomLogo({
reporting: mockReportingPlugin,
config: mockConfig,
job: {} as TaskPayloadPDF,
conditionalHeaders,
});
const { logo } = await getCustomLogo(
mockReportingPlugin,
{} as TaskPayloadPDF,
conditionalHeaders
);

expect(mockGet).toBeCalledWith('xpackReporting:customPdfLogo');
expect(logo).toBe('purple pony');
Expand Down
Loading

0 comments on commit a16c44c

Please sign in to comment.