Skip to content

Commit

Permalink
[6.4] Reporting cookies (#24177) (#24236)
Browse files Browse the repository at this point in the history
* Reporting cookies (#24177)

* Switching Reporting to use session cookies explicitly

* Fixing bug when security is explicitly disabled

* Responding to feedback

* Fixing yarn.lock

* Fixing yarn.lock
  • Loading branch information
kobelb committed Oct 19, 2018
1 parent 86706e8 commit 7bac28b
Show file tree
Hide file tree
Showing 23 changed files with 848 additions and 212 deletions.
5 changes: 4 additions & 1 deletion x-pack/package.json
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
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
@@ -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"`;
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);
};
};
}
}
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);
});
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
@@ -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"`;
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);
};
};
}
}

0 comments on commit 7bac28b

Please sign in to comment.