Merge branch 'master' into testDiscover
This commit is contained in:
commit
7d358f2e28
|
@ -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"
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 () {
|
|||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 || {});
|
||||
};
|
||||
|
|
145
src/server/http/__tests__/xsrf.js
Normal file
145
src/server/http/__tests__/xsrf.js
Normal 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"/);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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
20
src/server/http/xsrf.js
Normal 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();
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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, {
|
||||
|
|
132
src/ui/public/chrome/api/__tests__/xsrf.js
Normal file
132
src/ui/public/chrome/api/__tests__/xsrf.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
1
src/ui/public/chrome/api/angular.js
vendored
1
src/ui/public/chrome/api/angular.js
vendored
|
@ -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) {
|
||||
|
|
29
src/ui/public/chrome/api/xsrf.js
Normal file
29
src/ui/public/chrome/api/xsrf.js
Normal 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;
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue