Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.4] Reporting cookies (#24177) #24236

Merged
merged 3 commits into from
Oct 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion x-pack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers",
"@kbn/test": "link:../packages/kbn-test",
"@types/jest": "^22.2.3",
"@types/cookie": "^0.3.1",
"@types/pngjs": "^3.3.1",
"abab": "^1.0.4",
"ansicolors": "0.3.2",
Expand Down Expand Up @@ -98,9 +99,10 @@
"bluebird": "3.1.1",
"boom": "3.1.1",
"brace": "0.11.1",
"chrome-remote-interface": "0.24.2",
"chrome-remote-interface": "0.26.1",
"classnames": "2.2.5",
"concat-stream": "1.5.1",
"cookie": "^0.3.1",
"d3": "3.5.6",
"d3-scale": "1.0.6",
"dedent": "^0.7.0",
Expand All @@ -115,6 +117,7 @@
"history": "4.7.2",
"humps": "2.0.1",
"icalendar": "0.7.1",
"iron": "4",
"isomorphic-fetch": "2.2.1",
"joi": "6.10.1",
"jquery": "^3.3.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { cryptoFactory } from '../../../server/lib/crypto';
function createJobFn(server) {
const crypto = cryptoFactory(server);

return async function createJob(jobParams, headers, request) {
return async function createJob(jobParams, headers, serializedSession, request) {
const serializedEncryptedHeaders = await crypto.encrypt(headers);

const savedObjectsClient = request.getSavedObjectsClient();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`throw an Error if the objectType, savedObjectId and relativeUrls are provided 1`] = `"objectType and savedObjectId should not be provided in addition to the relativeUrls"`;
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function compatibilityShimFactory(server) {
queryString,
browserTimezone,
layout
}, headers, request) {
}, headers, serializedSession, request) {

if (objectType && savedObjectId && relativeUrls) {
throw new Error('objectType and savedObjectId should not be provided in addition to the relativeUrls');
Expand All @@ -75,7 +75,7 @@ export function compatibilityShimFactory(server) {
layout
};

return await createJob(transformedJobParams, headers, request);
return await createJob(transformedJobParams, headers, serializedSession, request);
};
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test(`passes title through if provided`, async () => {
const title = 'test title';

const createJobMock = jest.fn();
await compatibilityShim(createJobMock)({ title, relativeUrl: '/something' }, null, createMockRequest());
await compatibilityShim(createJobMock)({ title, relativeUrl: '/something' }, null, null, createMockRequest());

expect(createJobMock.mock.calls.length).toBe(1);
expect(createJobMock.mock.calls[0][0].title).toBe(title);
Expand All @@ -48,7 +48,7 @@ test(`gets the title from the savedObject`, async () => {
}
});

await compatibilityShim(createJobMock)({ objectType: 'search', savedObjectId: 'abc' }, null, mockRequest);
await compatibilityShim(createJobMock)({ objectType: 'search', savedObjectId: 'abc' }, null, null, mockRequest);

expect(createJobMock.mock.calls.length).toBe(1);
expect(createJobMock.mock.calls[0][0].title).toBe(title);
Expand All @@ -67,7 +67,7 @@ test(`passes the objectType and savedObjectId to the savedObjectsClient`, async

const objectType = 'search';
const savedObjectId = 'abc';
await compatibilityShim(createJobMock)({ objectType, savedObjectId, }, null, mockRequest);
await compatibilityShim(createJobMock)({ objectType, savedObjectId, }, null, null, mockRequest);

const getMock = mockRequest.getSavedObjectsClient().get.mock;
expect(getMock.calls.length).toBe(1);
Expand All @@ -87,7 +87,7 @@ test(`logs deprecations when generating the title/relativeUrl using the savedObj
}
});

await compatibilityShim(createJobMock)({ objectType: 'search', savedObjectId: 'abc' }, null, mockRequest);
await compatibilityShim(createJobMock)({ objectType: 'search', savedObjectId: 'abc' }, null, null, mockRequest);

expect(mockServer.log.mock.calls.length).toBe(2);
expect(mockServer.log.mock.calls[0][0]).toEqual(['warning', 'reporting', 'deprecation']);
Expand All @@ -101,7 +101,7 @@ test(`passes objectType through`, async () => {
const mockRequest = createMockRequest();

const objectType = 'foo';
await compatibilityShim(createJobMock)({ title: 'test', relativeUrl: '/something', objectType }, null, mockRequest);
await compatibilityShim(createJobMock)({ title: 'test', relativeUrl: '/something', objectType }, null, null, mockRequest);

expect(createJobMock.mock.calls.length).toBe(1);
expect(createJobMock.mock.calls[0][0].objectType).toBe(objectType);
Expand All @@ -113,7 +113,7 @@ test(`passes the relativeUrls through`, async () => {
const createJobMock = jest.fn();

const relativeUrls = ['/app/kibana#something', '/app/kibana#something-else'];
await compatibilityShim(createJobMock)({ title: 'test', relativeUrls }, null, null);
await compatibilityShim(createJobMock)({ title: 'test', relativeUrls }, null, null, null);
expect(createJobMock.mock.calls.length).toBe(1);
expect(createJobMock.mock.calls[0][0].relativeUrls).toBe(relativeUrls);
});
Expand All @@ -123,7 +123,7 @@ const testSavedObjectRelativeUrl = (objectType, expectedUrl) => {
const compatibilityShim = compatibilityShimFactory(createMockServer());
const createJobMock = jest.fn();

await compatibilityShim(createJobMock)({ title: 'test', objectType, savedObjectId: 'abc', }, null, null);
await compatibilityShim(createJobMock)({ title: 'test', objectType, savedObjectId: 'abc', }, null, null, null);
expect(createJobMock.mock.calls.length).toBe(1);
expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual([expectedUrl]);
});
Expand All @@ -137,7 +137,10 @@ test(`appends the queryString to the relativeUrl when generating from the savedO
const compatibilityShim = compatibilityShimFactory(createMockServer());
const createJobMock = jest.fn();

await compatibilityShim(createJobMock)({ title: 'test', objectType: 'search', savedObjectId: 'abc', queryString: 'foo=bar' }, null, null);
await compatibilityShim(createJobMock)(
{ title: 'test', objectType: 'search', savedObjectId: 'abc', queryString: 'foo=bar' },
null, null, null
);
expect(createJobMock.mock.calls.length).toBe(1);
expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual(['/app/kibana#/discover/abc?foo=bar']);
});
Expand All @@ -151,22 +154,24 @@ test(`throw an Error if the objectType, savedObjectId and relativeUrls are provi
objectType: 'something',
relativeUrls: ['/something'],
savedObjectId: 'abc',
}, null, null);
}, null, null, null);

await expect(promise).rejects.toBeDefined();
await expect(promise).rejects.toThrowErrorMatchingSnapshot();
});

test(`passes headers and request through`, async () => {
test(`passes headers, serializedSession and request through`, async () => {
const compatibilityShim = compatibilityShimFactory(createMockServer());

const createJobMock = jest.fn();

const headers = {};
const serializedSession = 'thisoldeserializedsession';
const request = createMockRequest();

await compatibilityShim(createJobMock)({ title: 'test', relativeUrl: '/something' }, headers, request);
await compatibilityShim(createJobMock)({ title: 'test', relativeUrl: '/something' }, headers, serializedSession, request);

expect(createJobMock.mock.calls.length).toBe(1);
expect(createJobMock.mock.calls[0][1]).toBe(headers);
expect(createJobMock.mock.calls[0][2]).toBe(request);
expect(createJobMock.mock.calls[0][2]).toBe(serializedSession);
expect(createJobMock.mock.calls[0][3]).toBe(request);
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ function createJobFn(server) {
relativeUrls,
browserTimezone,
layout
}, headers) {
}, headers, serializedSession) {
const serializedEncryptedHeaders = await crypto.encrypt(headers);
const encryptedSerializedSession = await crypto.encrypt(serializedSession);

return {
type: objectType,
title: title,
objects: relativeUrls.map(u => ({ relativeUrl: u })),
headers: serializedEncryptedHeaders,
session: encryptedSerializedSession,
browserTimezone,
layout,
forceNow: new Date().toISOString(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`headers it fails if it can't decrypt the headers 1`] = `"Failed to decrypt report job data. Please re-generate this report."`;

exports[`sessionCookie it fails if it can't decrypt the session 1`] = `"Failed to decrypt report job data. Please re-generate this report."`;

exports[`sessionCookie it throws error if cookie name can't be determined 1`] = `"Unable to determine the session cookie name"`;

exports[`urls it throw error if full URL is provided that is not a Kibana URL 1`] = `"Unable to generate report for url https://localhost/app/kibana, it's not a Kibana URL"`;
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,22 @@
*/

import url from 'url';
import cookie from 'cookie';
import { getAbsoluteUrlFactory } from './get_absolute_url';
import { cryptoFactory } from '../../../../server/lib/crypto';

export function compatibilityShimFactory(server) {
const getAbsoluteUrl = getAbsoluteUrlFactory(server);
const crypto = cryptoFactory(server);

const decryptJobHeaders = async (job) => {
try {
const decryptedHeaders = await crypto.decrypt(job.headers);
return decryptedHeaders;
} catch (err) {
throw new Error('Failed to decrypt report job data. Please re-generate this report.');
}
};

const getSavedObjectAbsoluteUrl = (savedObj) => {
if (savedObj.urlHash) {
Expand All @@ -27,11 +39,49 @@ export function compatibilityShimFactory(server) {
throw new Error(`Unable to generate report for url ${savedObj.url}, it's not a Kibana URL`);
};

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

if (jobSession) {
try {
return await crypto.decrypt(jobSession);
} 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) {
return null;
}

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

return cookies[cookieName];
};

return function (executeJob) {
return async function (job, cancellationToken) {
const urls = job.objects.map(getSavedObjectAbsoluteUrl);
const decryptedHeaders = await decryptJobHeaders(job);
const authorizationHeader = decryptedHeaders.authorization;
const serializedSession = await getSerializedSession(decryptedHeaders, job.session);

return await executeJob({ ...job, urls }, cancellationToken);
return await executeJob({
title: job.title,
browserTimezone: job.browserTimezone,
layout: job.layout,
basePath: job.basePath,
forceNow: job.forceNow,
urls,
authorizationHeader,
serializedSession,
}, cancellationToken);
};
};
}
}