Merge branch 'master' into testDiscover

This commit is contained in:
LeeDr 2015-11-17 10:41:11 -06:00
commit 7d358f2e28
17 changed files with 374 additions and 25 deletions

View file

@ -4,6 +4,10 @@
# The host to bind the server to.
# server.host: "0.0.0.0"
# A value to use as a XSRF token. This token is sent back to the server on each request
# and required if you want to execute requests from other clients (like curl).
# server.xsrf.token: ""
# The Elasticsearch instance to use for all your queries.
# elasticsearch.url: "http://localhost:9200"

View file

@ -16,7 +16,7 @@ bin/kibana plugin --install <org>/<package>/<version>
You can also use `-i` instead of `--install`, as in the following example:
[source,shell]
bin/kibana plugin -i elasticsearch/marvel-ui/latest
bin/kibana plugin -i elasticsearch/marvel/latest
Because the organization given is `elasticsearch`, the plugin management tool automatically downloads the
plugin from `download.elastic.co`.
@ -77,7 +77,7 @@ Use the `--remove` or `-r` option to remove a plugin, including any configuratio
example:
[source,shell]
bin/kibana plugin --remove marvel-ui
bin/kibana plugin --remove marvel
You can also remove a plugin manually by deleting the plugin's subdirectory under the `installedPlugins` directory.

View file

@ -13,7 +13,12 @@ describe('plugins/elasticsearch', function () {
before(function () {
kbnServer = new KbnServer({
server: { autoListen: false },
server: {
autoListen: false,
xsrf: {
disableProtection: true
}
},
logging: { quiet: true },
plugins: {
scanDirs: [
@ -104,5 +109,3 @@ describe('plugins/elasticsearch', function () {
});
});

View file

@ -93,7 +93,7 @@
<h1>No results found <i aria-hidden="true" class="fa fa-meh-o"></i></h1>
<p>
Unfortunately I could not find any results matching your search. I tried really hard. I looked all over the place and frankly, I just couldn't find anything good. Help me, help you. Here's some ideas:
Unfortunately I could not find any results matching your search. I tried really hard. I looked all over the place and frankly, I just couldn't find anything good. Help me, help you. Here are some ideas:
</p>
<div class="shard-failures" ng-show="failures">

View file

@ -5,8 +5,9 @@ let path = require('path');
let utils = require('requirefrom')('src/utils');
let fromRoot = utils('fromRoot');
const randomBytes = require('crypto').randomBytes;
module.exports = Joi.object({
module.exports = () => Joi.object({
pkg: Joi.object({
version: Joi.string().default(Joi.ref('$version')),
buildNum: Joi.number().default(Joi.ref('$buildNum')),
@ -39,7 +40,11 @@ module.exports = Joi.object({
origin: ['*://localhost:9876'] // karma test server
}),
otherwise: Joi.boolean().default(false)
})
}),
xsrf: Joi.object({
token: Joi.string().default(randomBytes(32).toString('hex')),
disableProtection: Joi.boolean().default(false),
}).default(),
}).default(),
logging: Joi.object().keys({
@ -106,4 +111,3 @@ module.exports = Joi.object({
}).default()
}).default();

View file

@ -1,6 +1,6 @@
module.exports = function (kbnServer) {
let Config = require('./Config');
let schema = require('./schema');
let schema = require('./schema')();
kbnServer.config = new Config(schema, kbnServer.settings || {});
};

View file

@ -0,0 +1,145 @@
import expect from 'expect.js';
import { fromNode as fn } from 'bluebird';
import { resolve } from 'path';
import KbnServer from '../../KbnServer';
const nonDestructiveMethods = ['GET'];
const destructiveMethods = ['POST', 'PUT', 'DELETE'];
const src = resolve.bind(null, __dirname, '../../../../src');
describe('xsrf request filter', function () {
function inject(kbnServer, opts) {
return fn(cb => {
kbnServer.server.inject(opts, (resp) => {
cb(null, resp);
});
});
}
const makeServer = async function (token) {
const kbnServer = new KbnServer({
server: { autoListen: false, xsrf: { token } },
plugins: { scanDirs: [src('plugins')] },
logging: { quiet: true },
optimize: { enabled: false },
});
await kbnServer.ready();
kbnServer.server.route({
path: '/xsrf/test/route',
method: [...nonDestructiveMethods, ...destructiveMethods],
handler: function (req, reply) {
reply(null, 'ok');
}
});
return kbnServer;
};
describe('issuing tokens', function () {
const token = 'secur3';
let kbnServer;
beforeEach(async () => kbnServer = await makeServer(token));
afterEach(async () => await kbnServer.close());
it('sends a token when rendering an app', async function () {
var resp = await inject(kbnServer, {
method: 'GET',
url: '/app/kibana',
});
expect(resp.payload).to.contain(`"xsrfToken":"${token}"`);
});
});
context('without configured token', function () {
let kbnServer;
beforeEach(async () => kbnServer = await makeServer());
afterEach(async () => await kbnServer.close());
it('responds with a random token', async function () {
var resp = await inject(kbnServer, {
method: 'GET',
url: '/app/kibana',
});
expect(resp.payload).to.match(/"xsrfToken":".{64}"/);
});
});
context('with configured token', function () {
const token = 'mytoken';
let kbnServer;
beforeEach(async () => kbnServer = await makeServer(token));
afterEach(async () => await kbnServer.close());
for (const method of nonDestructiveMethods) {
context(`nonDestructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
it('accepts requests without a token', async function () {
const resp = await inject(kbnServer, {
url: '/xsrf/test/route',
method: method
});
expect(resp.statusCode).to.be(200);
expect(resp.payload).to.be('ok');
});
it('ignores invalid tokens', async function () {
const resp = await inject(kbnServer, {
url: '/xsrf/test/route',
method: method,
headers: {
'kbn-xsrf-token': `invalid:${token}`,
},
});
expect(resp.statusCode).to.be(200);
expect(resp.headers).to.not.have.property('kbn-xsrf-token');
});
});
}
for (const method of destructiveMethods) {
context(`destructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
it('accepts requests with the correct token', async function () {
const resp = await inject(kbnServer, {
url: '/xsrf/test/route',
method: method,
headers: {
'kbn-xsrf-token': token,
},
});
expect(resp.statusCode).to.be(200);
expect(resp.payload).to.be('ok');
});
it('rejects requests without a token', async function () {
const resp = await inject(kbnServer, {
url: '/xsrf/test/route',
method: method
});
expect(resp.statusCode).to.be(403);
expect(resp.payload).to.match(/"Missing XSRF token"/);
});
it('rejects requests with an invalid token', async function () {
const resp = await inject(kbnServer, {
url: '/xsrf/test/route',
method: method,
headers: {
'kbn-xsrf-token': `invalid:${token}`,
},
});
expect(resp.statusCode).to.be(403);
expect(resp.payload).to.match(/"Invalid XSRF token"/);
});
});
}
});
});

View file

@ -121,4 +121,6 @@ module.exports = function (kbnServer, server, config) {
.permanent(true);
}
});
return kbnServer.mixin(require('./xsrf'));
};

20
src/server/http/xsrf.js Normal file
View file

@ -0,0 +1,20 @@
import { forbidden } from 'boom';
export default function (kbnServer, server, config) {
const token = config.get('server.xsrf.token');
const disabled = config.get('server.xsrf.disableProtection');
server.decorate('reply', 'issueXsrfToken', function () {
return token;
});
server.ext('onPostAuth', function (req, reply) {
if (disabled || req.method === 'get') return reply.continue();
const attempt = req.headers['kbn-xsrf-token'];
if (!attempt) return reply(forbidden('Missing XSRF token'));
if (attempt !== token) return reply(forbidden('Invalid XSRF token'));
return reply.continue();
});
}

View file

@ -21,12 +21,13 @@ module.exports = class KbnLogger {
}
init(readstream, emitter, callback) {
readstream
.pipe(this.squeeze)
.pipe(this.format)
.pipe(this.dest);
emitter.on('stop', _.noop);
this.output = readstream.pipe(this.squeeze).pipe(this.format);
this.output.pipe(this.dest);
emitter.on('stop', () => {
this.output.unpipe(this.dest);
});
callback();
}

View file

@ -19,7 +19,7 @@ module.exports = async function (kbnServer, server, config) {
let path = [];
async function initialize(id) {
const initialize = async function (id) {
let plugin = plugins.byId[id];
if (includes(path, id)) {

View file

@ -66,13 +66,14 @@ module.exports = async (kbnServer, server, config) => {
}
server.decorate('reply', 'renderApp', function (app) {
let payload = {
const payload = {
app: app,
nav: uiExports.apps,
version: kbnServer.version,
buildNum: config.get('pkg.buildNum'),
buildSha: config.get('pkg.buildSha'),
vars: defaults(app.getInjectedVars(), defaultInjectedVars),
xsrfToken: this.issueXsrfToken(),
};
return this.view(app.templateName, {

View file

@ -0,0 +1,132 @@
import $ from 'jquery';
import expect from 'expect.js';
import { stub } from 'auto-release-sinon';
import ngMock from 'ngMock';
import xsrfChromeApi from '../xsrf';
const xsrfHeader = 'kbn-xsrf-token';
const xsrfToken = 'xsrfToken';
describe('chrome xsrf apis', function () {
describe('#getXsrfToken()', function () {
it('exposes the token', function () {
const chrome = {};
xsrfChromeApi(chrome, { xsrfToken });
expect(chrome.getXsrfToken()).to.be(xsrfToken);
});
});
context('jQuery support', function () {
it('adds a global jQuery prefilter', function () {
stub($, 'ajaxPrefilter');
xsrfChromeApi({}, {});
expect($.ajaxPrefilter.callCount).to.be(1);
});
context('jQuery prefilter', function () {
let prefilter;
const xsrfToken = 'xsrfToken';
beforeEach(function () {
stub($, 'ajaxPrefilter');
xsrfChromeApi({}, { xsrfToken });
prefilter = $.ajaxPrefilter.args[0][0];
});
it('sets the kbn-xsrf-token header', function () {
const setHeader = stub();
prefilter({}, {}, { setRequestHeader: setHeader });
expect(setHeader.callCount).to.be(1);
expect(setHeader.args[0]).to.eql([
xsrfHeader,
xsrfToken
]);
});
it('can be canceled by setting the kbnXsrfToken option', function () {
const setHeader = stub();
prefilter({ kbnXsrfToken: false }, {}, { setRequestHeader: setHeader });
expect(setHeader.callCount).to.be(0);
});
});
context('Angular support', function () {
let $http;
let $httpBackend;
beforeEach(function () {
stub($, 'ajaxPrefilter');
const chrome = {};
xsrfChromeApi(chrome, { xsrfToken });
ngMock.module(chrome.$setupXsrfRequestInterceptor);
});
beforeEach(ngMock.inject(function ($injector) {
$http = $injector.get('$http');
$httpBackend = $injector.get('$httpBackend');
$httpBackend
.when('POST', '/api/test')
.respond('ok');
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('injects a kbn-xsrf-token header on every request', function () {
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
return headers[xsrfHeader] === xsrfToken;
}).respond(200, '');
$http.post('/api/test');
$httpBackend.flush();
});
it('skips requests with the kbnXsrfToken set falsey', function () {
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
return !(xsrfHeader in headers);
}).respond(200, '');
$http({
method: 'POST',
url: '/api/test',
kbnXsrfToken: 0
});
$http({
method: 'POST',
url: '/api/test',
kbnXsrfToken: ''
});
$http({
method: 'POST',
url: '/api/test',
kbnXsrfToken: false
});
$httpBackend.flush();
});
it('accepts alternate tokens to use', function () {
const customToken = `custom:${xsrfToken}`;
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
return headers[xsrfHeader] === customToken;
}).respond(200, '');
$http({
method: 'POST',
url: '/api/test',
kbnXsrfToken: customToken
});
$httpBackend.flush();
});
});
});
});

View file

@ -24,6 +24,7 @@ module.exports = function (chrome, internals) {
a.href = '/elasticsearch';
return a.href;
}()))
.config(chrome.$setupXsrfRequestInterceptor)
.directive('kbnChrome', function ($rootScope) {
return {
template: function ($el) {

View file

@ -0,0 +1,29 @@
import $ from 'jquery';
import { set } from 'lodash';
export default function (chrome, internals) {
chrome.getXsrfToken = function () {
return internals.xsrfToken;
};
$.ajaxPrefilter(function ({ kbnXsrfToken = internals.xsrfToken }, originalOptions, jqXHR) {
if (kbnXsrfToken) {
jqXHR.setRequestHeader('kbn-xsrf-token', kbnXsrfToken);
}
});
chrome.$setupXsrfRequestInterceptor = function ($httpProvider) {
$httpProvider.interceptors.push(function () {
return {
request: function (opts) {
const { kbnXsrfToken = internals.xsrfToken } = opts;
if (kbnXsrfToken) {
set(opts, ['headers', 'kbn-xsrf-token'], kbnXsrfToken);
}
return opts;
}
};
});
};
}

View file

@ -18,6 +18,7 @@ var internals = _.defaults(
rootController: null,
rootTemplate: null,
showAppsLink: null,
xsrfToken: null,
brand: null,
nav: [],
applicationClasses: []
@ -30,6 +31,7 @@ $('<link>').attr({
}).appendTo('head');
require('./api/apps')(chrome, internals);
require('./api/xsrf')(chrome, internals);
require('./api/nav')(chrome, internals);
require('./api/angular')(chrome, internals);
require('./api/controls')(chrome, internals);

View file

@ -668,16 +668,21 @@ define(function (require) {
* @return {undefined}
*/
Data.prototype._normalizeOrdered = function () {
if (!this.data.ordered || !this.data.ordered.date) return;
var data = this.getVisData();
var self = this;
var missingMin = this.data.ordered.min == null;
var missingMax = this.data.ordered.max == null;
data.forEach(function (d) {
if (!d.ordered || !d.ordered.date) return;
if (missingMax || missingMin) {
var extent = d3.extent(this.xValues());
if (missingMin) this.data.ordered.min = extent[0];
if (missingMax) this.data.ordered.max = extent[1];
}
var missingMin = d.ordered.min == null;
var missingMax = d.ordered.max == null;
if (missingMax || missingMin) {
var extent = d3.extent(self.xValues());
if (missingMin) d.ordered.min = extent[0];
if (missingMax) d.ordered.max = extent[1];
}
});
};
/**