Merge branch 'master' of github.com:elastic/kibana into implement/storeStateInLocalstorage
This commit is contained in:
commit
bf35d8aa1b
|
@ -128,7 +128,7 @@
|
|||
"less": "2.7.0",
|
||||
"less-loader": "2.2.3",
|
||||
"lodash": "3.10.1",
|
||||
"marked": "0.3.5",
|
||||
"marked": "0.3.6",
|
||||
"minimatch": "2.0.10",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "2.13.0",
|
||||
|
@ -148,7 +148,7 @@
|
|||
"trunc-text": "1.0.2",
|
||||
"url-loader": "0.5.6",
|
||||
"validate-npm-package-name": "2.2.2",
|
||||
"webpack": "1.12.1",
|
||||
"webpack": "1.12.15",
|
||||
"webpack-directory-name-as-main": "1.0.0",
|
||||
"whatwg-fetch": "0.9.0",
|
||||
"wreck": "6.2.0",
|
||||
|
@ -203,7 +203,7 @@
|
|||
"mocha": "2.5.3",
|
||||
"murmurhash3js": "3.0.1",
|
||||
"ncp": "2.0.0",
|
||||
"nock": "2.10.0",
|
||||
"nock": "8.0.0",
|
||||
"npm": "3.10.3",
|
||||
"portscanner": "1.0.0",
|
||||
"proxyquire": "1.7.10",
|
||||
|
|
76
src/server/http/__tests__/version_check.js
Normal file
76
src/server/http/__tests__/version_check.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import expect from 'expect.js';
|
||||
import { fromNode } from 'bluebird';
|
||||
import { resolve } from 'path';
|
||||
import * as kbnTestServer from '../../../../test/utils/kbn_server';
|
||||
|
||||
const src = resolve.bind(null, __dirname, '../../../../src');
|
||||
|
||||
const versionHeader = 'kbn-version';
|
||||
const version = require(src('../package.json')).version;
|
||||
|
||||
describe('version_check request filter', function () {
|
||||
function makeRequest(kbnServer, opts) {
|
||||
return fromNode(cb => {
|
||||
kbnTestServer.makeRequest(kbnServer, opts, (resp) => {
|
||||
cb(null, resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function makeServer() {
|
||||
const kbnServer = kbnTestServer.createServer();
|
||||
|
||||
await kbnServer.ready();
|
||||
|
||||
kbnServer.server.route({
|
||||
path: '/version_check/test/route',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
reply(null, 'ok');
|
||||
}
|
||||
});
|
||||
|
||||
return kbnServer;
|
||||
};
|
||||
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer());
|
||||
afterEach(async () => await kbnServer.close());
|
||||
|
||||
it('accepts requests with the correct version passed in the version header', async function () {
|
||||
const resp = await makeRequest(kbnServer, {
|
||||
url: '/version_check/test/route',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[versionHeader]: version,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('rejects requests with an incorrect version passed in the version header', async function () {
|
||||
const resp = await makeRequest(kbnServer, {
|
||||
url: '/version_check/test/route',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[versionHeader]: `invalid:${version}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(400);
|
||||
expect(resp.headers).to.have.property(versionHeader, version);
|
||||
expect(resp.payload).to.match(/"Browser client is out of date/);
|
||||
});
|
||||
|
||||
it('accepts requests that do not include a version header', async function () {
|
||||
const resp = await makeRequest(kbnServer, {
|
||||
url: '/version_check/test/route',
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
});
|
|
@ -7,8 +7,9 @@ const nonDestructiveMethods = ['GET', 'HEAD'];
|
|||
const destructiveMethods = ['POST', 'PUT', 'DELETE'];
|
||||
const src = resolve.bind(null, __dirname, '../../../../src');
|
||||
|
||||
const xsrfHeader = 'kbn-version';
|
||||
const version = require(src('../package.json')).version;
|
||||
const xsrfHeader = 'kbn-xsrf';
|
||||
const versionHeader = 'kbn-version';
|
||||
const actualVersion = require(src('../package.json')).version;
|
||||
|
||||
describe('xsrf request filter', function () {
|
||||
function inject(kbnServer, opts) {
|
||||
|
@ -57,31 +58,30 @@ describe('xsrf request filter', function () {
|
|||
else expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('failes on invalid tokens', async function () {
|
||||
it('accepts requests with the xsrf header', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
[xsrfHeader]: `invalid:${version}`,
|
||||
[xsrfHeader]: 'anything',
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(400);
|
||||
expect(resp.headers).to.have.property(xsrfHeader, version);
|
||||
expect(resp.statusCode).to.be(200);
|
||||
if (method === 'HEAD') expect(resp.payload).to.be.empty();
|
||||
else expect(resp.payload).to.match(/"Browser client is out of date/);
|
||||
else expect(resp.payload).to.be('ok');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const method of destructiveMethods) {
|
||||
context(`destructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
|
||||
it('accepts requests with the correct token', async function () {
|
||||
it('accepts requests with the xsrf header', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
[xsrfHeader]: version,
|
||||
[xsrfHeader]: 'anything',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -89,27 +89,29 @@ describe('xsrf request filter', function () {
|
|||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('rejects requests without a token', async function () {
|
||||
// this is still valid for existing csrf protection support
|
||||
// it does not actually do any validation on the version value itself
|
||||
it('accepts requests with the version header', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
[versionHeader]: actualVersion,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('rejects requests without either an xsrf or version header', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(400);
|
||||
expect(resp.payload).to.match(/"Missing kbn-version header/);
|
||||
});
|
||||
|
||||
it('rejects requests with an invalid token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
[xsrfHeader]: `invalid:${version}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(400);
|
||||
expect(resp.payload).to.match(/"Browser client is out of date/);
|
||||
expect(resp.payload).to.match(/"Request must contain an kbn-xsrf header/);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import fs from 'fs';
|
|||
import Boom from 'boom';
|
||||
import Hapi from 'hapi';
|
||||
import getDefaultRoute from './get_default_route';
|
||||
import versionCheckMixin from './version_check';
|
||||
|
||||
module.exports = async function (kbnServer, server, config) {
|
||||
|
||||
|
||||
|
@ -132,5 +134,7 @@ module.exports = async function (kbnServer, server, config) {
|
|||
}
|
||||
});
|
||||
|
||||
kbnServer.mixin(versionCheckMixin);
|
||||
|
||||
return kbnServer.mixin(require('./xsrf'));
|
||||
};
|
||||
|
|
19
src/server/http/version_check.js
Normal file
19
src/server/http/version_check.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { badRequest } from 'boom';
|
||||
|
||||
export default function (kbnServer, server, config) {
|
||||
const versionHeader = 'kbn-version';
|
||||
const actualVersion = config.get('pkg.version');
|
||||
|
||||
server.ext('onPostAuth', function (req, reply) {
|
||||
const versionRequested = req.headers[versionHeader];
|
||||
|
||||
if (versionRequested && versionRequested !== actualVersion) {
|
||||
return reply(badRequest('Browser client is out of date, please refresh the page', {
|
||||
expected: actualVersion,
|
||||
got: versionRequested
|
||||
}));
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
});
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
import { badRequest } from 'boom';
|
||||
|
||||
export default function (kbnServer, server, config) {
|
||||
const version = config.get('pkg.version');
|
||||
const disabled = config.get('server.xsrf.disableProtection');
|
||||
const header = 'kbn-version';
|
||||
const versionHeader = 'kbn-version';
|
||||
const xsrfHeader = 'kbn-xsrf';
|
||||
|
||||
server.ext('onPostAuth', function (req, reply) {
|
||||
const noHeaderGet = (req.method === 'get' || req.method === 'head') && !req.headers[header];
|
||||
if (disabled || noHeaderGet) return reply.continue();
|
||||
if (disabled) {
|
||||
return reply.continue();
|
||||
}
|
||||
|
||||
const submission = req.headers[header];
|
||||
if (!submission) return reply(badRequest(`Missing ${header} header`));
|
||||
if (submission !== version) {
|
||||
return reply(badRequest('Browser client is out of date, please refresh the page', {
|
||||
expected: version,
|
||||
got: submission
|
||||
}));
|
||||
const isSafeMethod = req.method === 'get' || req.method === 'head';
|
||||
const hasVersionHeader = versionHeader in req.headers;
|
||||
const hasXsrfHeader = xsrfHeader in req.headers;
|
||||
|
||||
if (!isSafeMethod && !hasVersionHeader && !hasXsrfHeader) {
|
||||
return reply(badRequest(`Request must contain an ${xsrfHeader} header`));
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
|
|
|
@ -6,12 +6,15 @@ import VisSchemasProvider from 'ui/vis/schemas';
|
|||
import AggTypesBucketsCreateFilterTermsProvider from 'ui/agg_types/buckets/create_filter/terms';
|
||||
import orderAggTemplate from 'ui/agg_types/controls/order_agg.html';
|
||||
import orderAndSizeTemplate from 'ui/agg_types/controls/order_and_size.html';
|
||||
import routeBasedNotifierProvider from 'ui/route_based_notifier';
|
||||
|
||||
export default function TermsAggDefinition(Private) {
|
||||
let BucketAggType = Private(AggTypesBucketsBucketAggTypeProvider);
|
||||
let bucketCountBetween = Private(AggTypesBucketsBucketCountBetweenProvider);
|
||||
let AggConfig = Private(VisAggConfigProvider);
|
||||
let Schemas = Private(VisSchemasProvider);
|
||||
let createFilter = Private(AggTypesBucketsCreateFilterTermsProvider);
|
||||
const routeBasedNotifier = Private(routeBasedNotifierProvider);
|
||||
|
||||
let orderAggSchema = (new Schemas([
|
||||
{
|
||||
|
@ -149,6 +152,9 @@ export default function TermsAggDefinition(Private) {
|
|||
}
|
||||
|
||||
if (orderAgg.type.name === 'count') {
|
||||
if (dir === 'asc') {
|
||||
routeBasedNotifier.warning('Sorting in Ascending order by Count in Terms aggregations is deprecated');
|
||||
}
|
||||
order._count = dir;
|
||||
return;
|
||||
}
|
||||
|
|
76
src/ui/public/route_based_notifier/__tests__/index.js
Normal file
76
src/ui/public/route_based_notifier/__tests__/index.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { filter, find, remove } from 'lodash';
|
||||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
import Notifier from '../../notify/notifier';
|
||||
import routeBasedNotifierProvider from '../index';
|
||||
|
||||
describe('ui/route_based_notifier', function () {
|
||||
let $rootScope;
|
||||
let routeBasedNotifier;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(($injector) => {
|
||||
remove(Notifier.prototype._notifs); // hack to reset the global notification array
|
||||
const Private = $injector.get('Private');
|
||||
routeBasedNotifier = Private(routeBasedNotifierProvider);
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
}));
|
||||
|
||||
describe('#warning()', () => {
|
||||
it('adds a warning notification', () => {
|
||||
routeBasedNotifier.warning('wat');
|
||||
const notification = find(Notifier.prototype._notifs, {
|
||||
type: 'warning',
|
||||
content: 'wat'
|
||||
});
|
||||
expect(notification).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('can be used more than once for different notifications', () => {
|
||||
routeBasedNotifier.warning('wat');
|
||||
routeBasedNotifier.warning('nowai');
|
||||
|
||||
const notification1 = find(Notifier.prototype._notifs, {
|
||||
type: 'warning',
|
||||
content: 'wat'
|
||||
});
|
||||
const notification2 = find(Notifier.prototype._notifs, {
|
||||
type: 'warning',
|
||||
content: 'nowai'
|
||||
});
|
||||
|
||||
expect(notification1).not.to.be(undefined);
|
||||
expect(notification2).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('only adds a notification if it was not previously added in the current route', () => {
|
||||
routeBasedNotifier.warning('wat');
|
||||
routeBasedNotifier.warning('wat');
|
||||
|
||||
const notification = find(Notifier.prototype._notifs, {
|
||||
type: 'warning',
|
||||
content: 'wat'
|
||||
});
|
||||
|
||||
expect(notification.count).to.equal(1);
|
||||
});
|
||||
|
||||
it('can add a previously added notification so long as the route changes', () => {
|
||||
routeBasedNotifier.warning('wat');
|
||||
const notification1 = find(Notifier.prototype._notifs, {
|
||||
type: 'warning',
|
||||
content: 'wat'
|
||||
});
|
||||
expect(notification1.count).to.equal(1);
|
||||
|
||||
$rootScope.$broadcast('$routeChangeSuccess');
|
||||
|
||||
routeBasedNotifier.warning('wat');
|
||||
const notification2 = find(Notifier.prototype._notifs, {
|
||||
type: 'warning',
|
||||
content: 'wat'
|
||||
});
|
||||
expect(notification2.count).to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
45
src/ui/public/route_based_notifier/index.js
Normal file
45
src/ui/public/route_based_notifier/index.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { includes, mapValues } from 'lodash';
|
||||
import Notifier from 'ui/notify/notifier';
|
||||
|
||||
/*
|
||||
* Caches notification attempts so each one is only actually sent to the
|
||||
* notifier service once per route.
|
||||
*/
|
||||
export default function routeBasedNotifierProvider($rootScope) {
|
||||
const notifier = new Notifier();
|
||||
|
||||
let notifications = {
|
||||
warnings: []
|
||||
};
|
||||
|
||||
// empty the tracked notifications whenever the route changes so we can start
|
||||
// fresh for the next route cycle
|
||||
$rootScope.$on('$routeChangeSuccess', () => {
|
||||
notifications = mapValues(notifications, () => []);
|
||||
});
|
||||
|
||||
// Executes the given notify function if the message has not been seen in
|
||||
// this route cycle
|
||||
function executeIfNew(messages, message, notifyFn) {
|
||||
if (includes(messages, message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
messages.push(message);
|
||||
notifyFn.call(notifier, message);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Notify a given warning once in this route cycle
|
||||
* @param {string} message
|
||||
*/
|
||||
warning(message) {
|
||||
executeIfNew(
|
||||
notifications.warnings,
|
||||
message,
|
||||
notifier.warning
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
Loading…
Reference in a new issue