Revert "Revert "Add Multi-Tenancy for keystone plugin""
This reverts commit 4752d35777
.
Change-Id: Ie377d6675c901eb922b4e07e800e391805f69361
This commit is contained in:
parent
4752d35777
commit
0d84a5afa6
25
index.js
25
index.js
@ -12,11 +12,12 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
module.exports = (kibana) => {
|
||||
import session from './server/session';
|
||||
import binding from './server/binding';
|
||||
import healthCheck from './server/healthcheck';
|
||||
|
||||
const session = require('./server/session');
|
||||
const proxy = require('./server/proxy');
|
||||
const healthCheck = require('./server/healthcheck');
|
||||
export default (kibana) => {
|
||||
const COOKIE_PASSWORD_SIZE = 32;
|
||||
|
||||
return new kibana.Plugin({
|
||||
require: ['elasticsearch'],
|
||||
@ -27,17 +28,19 @@ module.exports = (kibana) => {
|
||||
function config(Joi) {
|
||||
|
||||
const cookie = Joi.object({
|
||||
name : Joi.string()
|
||||
.default('keystone'),
|
||||
password : Joi.string()
|
||||
.min(16)
|
||||
.default(require('crypto').randomBytes(16).toString('hex')),
|
||||
.min(COOKIE_PASSWORD_SIZE)
|
||||
.default(require('crypto').randomBytes(COOKIE_PASSWORD_SIZE).toString('hex')),
|
||||
isSecure : Joi.boolean()
|
||||
.default(false),
|
||||
.default(process.env.NODE_ENV !== 'development'),
|
||||
ignoreErrors: Joi.boolean()
|
||||
.default(true),
|
||||
expiresIn : Joi.number()
|
||||
.positive()
|
||||
.integer()
|
||||
.default(24 * 60 * 60 * 1000) // 1 day
|
||||
.default(60 * 60 * 1000) // 1 hour
|
||||
}).default();
|
||||
|
||||
return Joi.object({
|
||||
@ -51,9 +54,11 @@ module.exports = (kibana) => {
|
||||
}
|
||||
|
||||
function init(server) {
|
||||
session(server);
|
||||
proxy(server);
|
||||
server.log(['status', 'debug', 'keystone'], 'Initializing keystone plugin');
|
||||
binding(server).start();
|
||||
session(server).start();
|
||||
healthCheck(this, server).start();
|
||||
server.log(['status', 'debug', 'keystone'], 'Initialized keystone plugin');
|
||||
}
|
||||
|
||||
};
|
||||
|
17
package.json
17
package.json
@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "fts-keystone",
|
||||
"version": "0.0.1",
|
||||
"description": "Keystone authentication support for Kibana 4.4.x",
|
||||
"version": "0.0.2",
|
||||
"description": "Keystone authentication & multitenancy support for Kibana 4.4.x",
|
||||
"author": "Fujitsu Enabling Software Technology GmbH",
|
||||
"licenses": "Apache-2.0",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"kibana",
|
||||
"authentication",
|
||||
"keystone",
|
||||
"multitenancy",
|
||||
"plugin"
|
||||
],
|
||||
"scripts": {
|
||||
@ -22,8 +23,9 @@
|
||||
},
|
||||
"main": "gulpfile.js",
|
||||
"dependencies": {
|
||||
"yar": "^4.2.0",
|
||||
"keystone-v3-client": "^0.0.7"
|
||||
"hoek": "^4.0.1",
|
||||
"keystone-v3-client": "^0.0.7",
|
||||
"yar": "^7.x.x"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -43,11 +45,14 @@
|
||||
"gulp-mocha": "^2.2.0",
|
||||
"gulp-tar": "^1.8.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
"joi": "^9.0.4",
|
||||
"lodash": "^4.2.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"proxyquire": "^1.7.4",
|
||||
"rimraf": "^2.5.1",
|
||||
"rsync": "^0.4.0",
|
||||
"sinon": "^1.17.3"
|
||||
"semver": "^5.3.0",
|
||||
"sinon": "^1.17.3",
|
||||
"wreck": "^8.0.0"
|
||||
}
|
||||
}
|
||||
|
45
server/__tests__/binding.spec.js
Normal file
45
server/__tests__/binding.spec.js
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
const proxyRequire = require('proxyquire');
|
||||
|
||||
describe('fts-keystone', () => {
|
||||
describe('binding', () => {
|
||||
|
||||
it('should expose tokens & users', () => {
|
||||
|
||||
let tokens = sinon.spy();
|
||||
let users = sinon.spy();
|
||||
|
||||
let server = {
|
||||
config: sinon.stub().returns({
|
||||
get: sinon.spy()
|
||||
}),
|
||||
expose: sinon.spy()
|
||||
};
|
||||
|
||||
proxyRequire('../binding', {
|
||||
'keystone-v3-client/lib/keystone/tokens': tokens,
|
||||
'keystone-v3-client/lib/keystone/users' : users
|
||||
})(server).start();
|
||||
|
||||
chai.expect(server.expose.callCount).to.be.eq(2);
|
||||
chai.expect(server.expose.calledWith('tokens', tokens));
|
||||
chai.expect(server.expose.calledWith('users', users));
|
||||
|
||||
});
|
||||
});
|
||||
});
|
@ -52,6 +52,7 @@ describe('plugins/fts-keystone', ()=> {
|
||||
|
||||
server = {
|
||||
log : sinon.stub(),
|
||||
on : sinon.stub(),
|
||||
config: function () {
|
||||
return {
|
||||
get: configGet
|
||||
@ -63,6 +64,7 @@ describe('plugins/fts-keystone', ()=> {
|
||||
|
||||
it('should set status to green if keystone available', (done)=> {
|
||||
let expectedCode = 200;
|
||||
let expectedStatus = true;
|
||||
let healthcheck = proxyRequire('../healthcheck', {
|
||||
'http': {
|
||||
request: (_, callback)=> {
|
||||
@ -82,8 +84,8 @@ describe('plugins/fts-keystone', ()=> {
|
||||
|
||||
check
|
||||
.run()
|
||||
.then((code) => {
|
||||
chai.expect(expectedCode).to.be.equal(code);
|
||||
.then((status) => {
|
||||
chai.expect(expectedStatus).to.be.equal(status);
|
||||
chai.expect(plugin.status.green.calledWith('Ready')).to.be.ok;
|
||||
})
|
||||
.finally(done);
|
||||
@ -92,6 +94,7 @@ describe('plugins/fts-keystone', ()=> {
|
||||
|
||||
it('should set status to red if keystone not available', (done) => {
|
||||
let expectedCode = 500;
|
||||
let expectedStatus = false;
|
||||
let healthcheck = proxyRequire('../healthcheck', {
|
||||
'http': {
|
||||
request: (_, callback)=> {
|
||||
@ -111,8 +114,8 @@ describe('plugins/fts-keystone', ()=> {
|
||||
|
||||
check
|
||||
.run()
|
||||
.catch((code) => {
|
||||
chai.expect(expectedCode).to.be.equal(code);
|
||||
.catch((status) => {
|
||||
chai.expect(expectedStatus).to.be.equal(status);
|
||||
chai.expect(plugin.status.red.calledWith('Unavailable')).to.be.ok;
|
||||
})
|
||||
.finally(done);
|
||||
|
@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const proxyRequire = require('proxyquire');
|
||||
const Promise = require('bluebird');
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
|
||||
describe('plugins/fts-keystone', ()=> {
|
||||
describe('proxy', ()=> {
|
||||
describe('proxy_check', ()=> {
|
||||
|
||||
const keystoneUrl = 'http://localhost'; // mocking http
|
||||
const keystonePort = 9000;
|
||||
|
||||
let server;
|
||||
let configGet;
|
||||
|
||||
beforeEach(()=> {
|
||||
configGet = sinon.stub();
|
||||
configGet.withArgs('fts-keystone.url').returns(keystoneUrl);
|
||||
configGet.withArgs('fts-keystone.port').returns(keystonePort);
|
||||
|
||||
server = {
|
||||
log : sinon.stub(),
|
||||
config: function () {
|
||||
return {
|
||||
get: configGet
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('should do nothing if not /elasticsearch call', ()=> {
|
||||
let checkSpy = sinon.spy();
|
||||
let retrieveTokenSpy = sinon.spy();
|
||||
let proxy = proxyRequire('../proxy/proxy', {
|
||||
'keystone-v3-client/lib/keystone/tokens': () => {
|
||||
return {check: checkSpy};
|
||||
},
|
||||
'./retrieveToken' : retrieveTokenSpy
|
||||
})(server);
|
||||
let request = {
|
||||
url: {
|
||||
path: '/bundles/styles.css'
|
||||
}
|
||||
};
|
||||
let reply = {
|
||||
'continue': sinon.spy()
|
||||
};
|
||||
|
||||
proxy(request, reply);
|
||||
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
chai.expect(checkSpy.called).to.not.be.ok;
|
||||
chai.expect(retrieveTokenSpy.called).to.not.be.ok;
|
||||
});
|
||||
|
||||
it('should authenticate with keystone', (done)=> {
|
||||
|
||||
let token = '1234567890';
|
||||
let checkStub = sinon.stub().returns(Promise.resolve());
|
||||
let retrieveTokenStub = sinon.stub().returns(token);
|
||||
|
||||
let proxy = proxyRequire('../proxy/proxy', {
|
||||
'keystone-v3-client/lib/keystone/tokens': () => {
|
||||
return {check: checkStub};
|
||||
},
|
||||
'./retrieveToken' : retrieveTokenStub
|
||||
})(server);
|
||||
let request = {
|
||||
session: {
|
||||
'get' : sinon.stub(),
|
||||
'set' : sinon.stub()
|
||||
},
|
||||
url : {
|
||||
path: '/elasticsearch/.kibana'
|
||||
}
|
||||
};
|
||||
|
||||
let reply = {
|
||||
'continue': sinon.spy()
|
||||
};
|
||||
let replyCall;
|
||||
|
||||
proxy(request, reply)
|
||||
.finally(verifyStubs)
|
||||
.done(done);
|
||||
|
||||
function verifyStubs() {
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
replyCall = reply.continue.firstCall.args;
|
||||
|
||||
chai.expect(replyCall).to.be.empty;
|
||||
|
||||
// other stubs
|
||||
chai.expect(checkStub.calledOnce).to.be.ok;
|
||||
chai.expect(checkStub.calledWithExactly({
|
||||
headers: {
|
||||
'X-Auth-Token' : token,
|
||||
'X-Subject-Token': token
|
||||
}
|
||||
})).to.be.ok;
|
||||
|
||||
chai.expect(retrieveTokenStub.calledOnce).to.be.ok;
|
||||
chai.expect(retrieveTokenStub.calledWithExactly(server, request))
|
||||
.to.be.ok;
|
||||
}
|
||||
});
|
||||
|
||||
it('should not authenticate with keystone', (done)=> {
|
||||
let token = '1234567890';
|
||||
let checkStub = sinon.stub().returns(Promise.reject({
|
||||
statusCode: 666
|
||||
}));
|
||||
let retrieveTokenStub = sinon.stub().returns(token);
|
||||
let proxy = proxyRequire('../proxy/proxy', {
|
||||
'keystone-v3-client/lib/keystone/tokens': () => {
|
||||
return {check: checkStub};
|
||||
},
|
||||
'./retrieveToken' : retrieveTokenStub
|
||||
})(server);
|
||||
let request = {
|
||||
session: {
|
||||
'get' : sinon.stub(),
|
||||
'set' : sinon.stub()
|
||||
},
|
||||
url : {
|
||||
path: '/elasticsearch/.kibana'
|
||||
}
|
||||
};
|
||||
let reply = sinon.spy();
|
||||
let replyCall;
|
||||
|
||||
proxy(request, reply)
|
||||
.finally(verifyStubs)
|
||||
.done(done);
|
||||
|
||||
function verifyStubs() {
|
||||
chai.expect(reply.calledOnce).to.be.ok;
|
||||
replyCall = reply.firstCall.args[0];
|
||||
|
||||
chai.expect(replyCall.isBoom).to.be.ok;
|
||||
|
||||
// other stubs
|
||||
chai.expect(checkStub.calledOnce).to.be.ok;
|
||||
chai.expect(checkStub.calledWithExactly({
|
||||
headers: {
|
||||
'X-Auth-Token' : token,
|
||||
'X-Subject-Token': token
|
||||
}
|
||||
})).to.be.ok;
|
||||
|
||||
chai.expect(retrieveTokenStub.calledOnce).to.be.ok;
|
||||
chai.expect(retrieveTokenStub.calledWithExactly(server, request))
|
||||
.to.be.ok;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
@ -15,11 +15,14 @@
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
|
||||
const retrieveToken = require('../proxy/retrieveToken');
|
||||
const retrieveToken = require('../mt/auth/token');
|
||||
const CONSTANTS = require('../const');
|
||||
const RELOAD_SYMBOL = require('../mt/auth/reload');
|
||||
|
||||
describe('plugins/fts-keystone', ()=> {
|
||||
describe('proxy', ()=> {
|
||||
describe('retrieveToken', ()=> {
|
||||
describe('mt', ()=> {
|
||||
describe('auth', () => {
|
||||
describe('token', ()=> {
|
||||
|
||||
let server;
|
||||
|
||||
@ -38,7 +41,7 @@ describe('plugins/fts-keystone', ()=> {
|
||||
}).to.throw(errMsg);
|
||||
|
||||
request = {
|
||||
session: undefined
|
||||
yar: undefined
|
||||
};
|
||||
chai.expect(()=> {
|
||||
retrieveToken(server, request);
|
||||
@ -55,10 +58,10 @@ describe('plugins/fts-keystone', ()=> {
|
||||
it('should Boom with unauthorized if token not in header or session', function () {
|
||||
let expectedMsg = 'You\'re not logged into the OpenStack. Please login via Horizon Dashboard';
|
||||
let request = {
|
||||
session: {
|
||||
yar : {
|
||||
'get': sinon
|
||||
.stub()
|
||||
.withArgs('keystone_token')
|
||||
.withArgs(CONSTANTS.SESSION_TOKEN_KEY)
|
||||
.returns(undefined)
|
||||
},
|
||||
headers: {}
|
||||
@ -73,99 +76,115 @@ describe('plugins/fts-keystone', ()=> {
|
||||
it('should use session token if requested does not have it', () => {
|
||||
let expectedToken = 'SOME_RANDOM_TOKEN';
|
||||
let yar = {
|
||||
'set': sinon
|
||||
.spy(),
|
||||
'get': sinon
|
||||
.stub()
|
||||
.withArgs('keystone_token')
|
||||
.returns(expectedToken)
|
||||
'reset': sinon.spy(),
|
||||
'set' : sinon.spy(),
|
||||
'get' : sinon.stub()
|
||||
};
|
||||
let request = {
|
||||
session: yar,
|
||||
yar : yar,
|
||||
headers: {}
|
||||
};
|
||||
let token;
|
||||
|
||||
yar.get.returns(expectedToken);
|
||||
|
||||
token = retrieveToken(server, request);
|
||||
chai.expect(token).not.to.be.undefined;
|
||||
chai.expect(token).to.be.eql(expectedToken);
|
||||
|
||||
console.log(yar.get.callCount);
|
||||
|
||||
chai.expect(yar.get.callCount).to.be.eq(2);
|
||||
chai.expect(yar.set.calledOnce).not.to.be.ok;
|
||||
chai.expect(
|
||||
yar.get.calledOnce
|
||||
).to.be.ok;
|
||||
chai.expect(
|
||||
yar.set.calledOnce
|
||||
).not.to.be.ok;
|
||||
chai.expect(
|
||||
yar.set.calledWithExactly('keystone_token', expectedToken)
|
||||
yar.set.calledWithExactly(CONSTANTS.SESSION_TOKEN_KEY, expectedToken)
|
||||
).not.to.be.ok;
|
||||
});
|
||||
|
||||
it('should set token in session if not there and request has it', () => {
|
||||
let expectedToken = 'SOME_RANDOM_TOKEN';
|
||||
let yar = {
|
||||
'set': sinon
|
||||
.spy(),
|
||||
'get': sinon
|
||||
.stub()
|
||||
.withArgs('keystone_token')
|
||||
.returns(undefined)
|
||||
'reset': sinon.spy(),
|
||||
'set' : sinon.spy(),
|
||||
'get' : sinon.stub()
|
||||
};
|
||||
let request = {
|
||||
session: yar,
|
||||
yar : yar,
|
||||
headers: {
|
||||
'x-auth-token': expectedToken
|
||||
}
|
||||
};
|
||||
let token;
|
||||
|
||||
yar.get
|
||||
.withArgs(CONSTANTS.SESSION_TOKEN_KEY)
|
||||
.onCall(0).returns(undefined)
|
||||
.onCall(1).returns(expectedToken);
|
||||
|
||||
token = retrieveToken(server, request);
|
||||
chai.expect(token).to.not.be.undefined;
|
||||
chai.expect(token).to.be.eql(expectedToken);
|
||||
|
||||
chai.expect(yar.get.callCount).to.be.eq(2);
|
||||
chai.expect(yar.set.calledOnce).to.be.ok;
|
||||
|
||||
chai.expect(
|
||||
yar.get.calledOnce
|
||||
yar.set.calledWithExactly(
|
||||
CONSTANTS.SESSION_TOKEN_KEY,
|
||||
expectedToken
|
||||
)
|
||||
).to.be.ok;
|
||||
chai.expect(
|
||||
yar.set.calledOnce
|
||||
).to.be.ok;
|
||||
chai.expect(
|
||||
yar.set.calledWithExactly('keystone_token', expectedToken)
|
||||
).to.be.ok;
|
||||
yar.set.calledWithExactly(
|
||||
CONSTANTS.SESSION_TOKEN_CHANGED,
|
||||
CONSTANTS.TOKEN_CHANGED_VALUE
|
||||
)
|
||||
).to.not.be.ok;
|
||||
});
|
||||
|
||||
it('should update token in session if request\'s token is different', ()=> {
|
||||
let expectedToken = 'SOME_RANDOM_TOKEN';
|
||||
let oldToken = 'OLD_TOKEN';
|
||||
|
||||
let headers = {
|
||||
'x-auth-token': expectedToken
|
||||
};
|
||||
let yar = {
|
||||
'reset': sinon.stub(),
|
||||
'get' : sinon
|
||||
.stub()
|
||||
.withArgs('keystone_token')
|
||||
.returns('SOME_OLD_TOKEN'),
|
||||
'set': sinon
|
||||
.spy()
|
||||
.withArgs(CONSTANTS.SESSION_TOKEN_KEY)
|
||||
.returns(oldToken),
|
||||
'set' : sinon.spy()
|
||||
};
|
||||
let token;
|
||||
let request = {
|
||||
session: yar,
|
||||
yar : yar,
|
||||
headers: headers
|
||||
};
|
||||
|
||||
token = retrieveToken(server, request);
|
||||
chai.expect(token).to.not.be.undefined;
|
||||
chai.expect(token).to.be.eql(expectedToken);
|
||||
chai.expect(token).to.be.eql(RELOAD_SYMBOL);
|
||||
|
||||
chai.expect(yar.reset.calledOnce).to.be.ok;
|
||||
chai.expect(yar.get.calledOnce).to.be.ok;
|
||||
|
||||
chai.expect(yar.set.callCount).to.be.eq(2);
|
||||
chai.expect(
|
||||
yar.get.calledOnce
|
||||
yar.set.calledWithExactly(
|
||||
CONSTANTS.SESSION_TOKEN_KEY,
|
||||
expectedToken
|
||||
)
|
||||
).to.be.ok;
|
||||
chai.expect(
|
||||
yar.set.calledOnce
|
||||
).to.be.ok;
|
||||
chai.expect(
|
||||
yar.set.calledWithExactly('keystone_token', expectedToken)
|
||||
yar.set.calledWithExactly(
|
||||
CONSTANTS.SESSION_TOKEN_CHANGED,
|
||||
CONSTANTS.TOKEN_CHANGED_VALUE
|
||||
)
|
||||
).to.be.ok;
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
132
server/__tests__/routing.createProxy.spec.js
Normal file
132
server/__tests__/routing.createProxy.spec.js
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
const proxyRequire = require('proxyquire');
|
||||
|
||||
describe('plugins/fts-keystone', () => {
|
||||
describe('mt', () => {
|
||||
describe('routing', () => {
|
||||
describe('createProxy', () => {
|
||||
const kibanaIndex = '.kibana';
|
||||
const server = {
|
||||
log : sinon.spy(),
|
||||
config: sinon.stub().returns({
|
||||
get: sinon.stub().withArgs('kibana.index').returns(kibanaIndex)
|
||||
})
|
||||
};
|
||||
|
||||
let mgetHandler;
|
||||
let pathsHandler;
|
||||
let kibanaIndexHandler;
|
||||
let defaultHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
mgetHandler = sinon.stub().returns({});
|
||||
pathsHandler = sinon.stub().returns({});
|
||||
kibanaIndexHandler = sinon.stub().returns({});
|
||||
defaultHandler = sinon.stub().returns({});
|
||||
|
||||
server.route = sinon.spy();
|
||||
});
|
||||
|
||||
it('should load mget handler if that is the route', () => {
|
||||
const route = '/_mget';
|
||||
const method = sinon.spy();
|
||||
|
||||
proxyRequire('../mt/routing/_create_proxy', {
|
||||
'./routes/mget' : mgetHandler,
|
||||
'./routes/paths' : pathsHandler,
|
||||
'./routes/kibana_index': kibanaIndexHandler,
|
||||
'./routes/default' : defaultHandler
|
||||
})(server, method, route);
|
||||
|
||||
chai.expect(mgetHandler.calledOnce).to.be.ok;
|
||||
chai.expect(mgetHandler.calledWith(server, method, sinon.match.string)).to.be.ok;
|
||||
|
||||
chai.expect(pathsHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(kibanaIndexHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(defaultHandler.calledOnce).to.not.be.ok;
|
||||
|
||||
chai.expect(server.route.calledWith(sinon.match.object)).to.be.ok;
|
||||
});
|
||||
|
||||
it('should load paths handler if that is the route', () => {
|
||||
const route = '/{paths*}';
|
||||
const method = sinon.spy();
|
||||
|
||||
proxyRequire('../mt/routing/_create_proxy', {
|
||||
'./routes/mget' : mgetHandler,
|
||||
'./routes/paths' : pathsHandler,
|
||||
'./routes/kibana_index': kibanaIndexHandler,
|
||||
'./routes/default' : defaultHandler
|
||||
})(server, method, route);
|
||||
|
||||
chai.expect(pathsHandler.calledOnce).to.be.ok;
|
||||
chai.expect(pathsHandler.calledWith(server, method, sinon.match.string)).to.be.ok;
|
||||
|
||||
chai.expect(mgetHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(kibanaIndexHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(defaultHandler.calledOnce).to.not.be.ok;
|
||||
|
||||
chai.expect(server.route.calledWith(sinon.match.object)).to.be.ok;
|
||||
});
|
||||
|
||||
it('should load default handler if that is the route', () => {
|
||||
const route = '/other';
|
||||
const method = sinon.spy();
|
||||
|
||||
proxyRequire('../mt/routing/_create_proxy', {
|
||||
'./routes/mget' : mgetHandler,
|
||||
'./routes/paths' : pathsHandler,
|
||||
'./routes/kibana_index': kibanaIndexHandler,
|
||||
'./routes/default' : defaultHandler
|
||||
})(server, method, route);
|
||||
|
||||
chai.expect(defaultHandler.calledOnce).to.be.ok;
|
||||
chai.expect(defaultHandler.calledWith(server, method, sinon.match.string)).to.be.ok;
|
||||
|
||||
chai.expect(mgetHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(kibanaIndexHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(pathsHandler.calledOnce).to.not.be.ok;
|
||||
|
||||
chai.expect(server.route.calledWith(sinon.match.object)).to.be.ok;
|
||||
});
|
||||
|
||||
it('should load kibana handler if that is the route', () => {
|
||||
const route = `/${kibanaIndex}/{paths*}`;
|
||||
const method = sinon.spy();
|
||||
|
||||
proxyRequire('../mt/routing/_create_proxy', {
|
||||
'./routes/mget' : mgetHandler,
|
||||
'./routes/paths' : pathsHandler,
|
||||
'./routes/kibana_index': kibanaIndexHandler,
|
||||
'./routes/default' : defaultHandler
|
||||
})(server, method, route);
|
||||
|
||||
chai.expect(kibanaIndexHandler.calledOnce).to.be.ok;
|
||||
chai.expect(kibanaIndexHandler.calledWith(server, method, sinon.match.string)).to.be.ok;
|
||||
|
||||
chai.expect(mgetHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(defaultHandler.calledOnce).to.not.be.ok;
|
||||
chai.expect(pathsHandler.calledOnce).to.not.be.ok;
|
||||
|
||||
chai.expect(server.route.calledWith(sinon.match.object)).to.be.ok;
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
86
server/__tests__/routing.reRouting.spec.js
Normal file
86
server/__tests__/routing.reRouting.spec.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
const proxyRequire = require('proxyquire');
|
||||
|
||||
describe('plugins/fts-keystone', () => {
|
||||
describe('mt', () => {
|
||||
describe('routing', () => {
|
||||
describe('reRoute', () => {
|
||||
const prefix = '/test';
|
||||
const server = {
|
||||
log: sinon.spy()
|
||||
};
|
||||
|
||||
it('should re-route ElasticSearch request', () => {
|
||||
const requestPath = '/elasticsearch/something/a/b/c';
|
||||
|
||||
let request = {
|
||||
setUrl: sinon.spy()
|
||||
};
|
||||
let reply = {
|
||||
continue: sinon.spy()
|
||||
};
|
||||
let utils = {
|
||||
requestPath: sinon.stub().withArgs(request).returns(requestPath),
|
||||
isESRequest: sinon.stub().withArgs(request).returns(true)
|
||||
};
|
||||
|
||||
proxyRequire('../mt/routing/_re_route', {
|
||||
'../../util': utils,
|
||||
'./_utils' : {
|
||||
PREFIX: prefix
|
||||
}
|
||||
})(server)(request, reply);
|
||||
|
||||
chai.expect(request.setUrl.calledOnce).to.be.ok;
|
||||
chai.expect(request.setUrl.calledWith(`${prefix}${requestPath}`)).to.be.ok;
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
|
||||
});
|
||||
|
||||
it('should not re-route non-ElasticSearch request', () => {
|
||||
const requestPath = '/resources/cool.ico';
|
||||
|
||||
let request = {
|
||||
setUrl: sinon.spy()
|
||||
};
|
||||
let reply = {
|
||||
continue: sinon.spy()
|
||||
};
|
||||
let utils = {
|
||||
requestPath: sinon.stub().withArgs(request).returns(requestPath),
|
||||
isESRequest: sinon.stub().withArgs(request).returns(false)
|
||||
};
|
||||
|
||||
proxyRequire('../mt/routing/_re_route', {
|
||||
'../../util': utils,
|
||||
'./_utils' : {
|
||||
PREFIX: prefix
|
||||
}
|
||||
})(server)(request, reply);
|
||||
|
||||
chai.expect(request.setUrl.calledOnce).to.not.be.ok;
|
||||
chai.expect(request.setUrl.calledWith(`${prefix}${requestPath}`)).to.not.be.ok;
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
57
server/__tests__/routing.spec.js
Normal file
57
server/__tests__/routing.spec.js
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
const proxyRequire = require('proxyquire');
|
||||
|
||||
describe('plugins/fts-keystone', ()=> {
|
||||
describe('mt', ()=> {
|
||||
describe('routing', () => {
|
||||
|
||||
const createProxy = sinon.spy();
|
||||
const serverLog = sinon.spy();
|
||||
|
||||
let serverExt;
|
||||
let server;
|
||||
let reRoute;
|
||||
|
||||
beforeEach(() => {
|
||||
serverExt = sinon.spy();
|
||||
server = {
|
||||
log: serverLog,
|
||||
ext: serverExt
|
||||
};
|
||||
reRoute = sinon.spy();
|
||||
});
|
||||
|
||||
it('should load re-route logic', (done) => {
|
||||
|
||||
proxyRequire('../mt/routing', {
|
||||
'./_create_proxy': createProxy,
|
||||
'./_re_route' : reRoute
|
||||
})(server).then(verify);
|
||||
|
||||
function verify(route) {
|
||||
chai.expect(serverExt.calledOnce).to.be.ok;
|
||||
chai.expect(serverExt.calledWith('onRequest', reRoute));
|
||||
chai.expect(createProxy).to.be.eq(route);
|
||||
done();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
114
server/__tests__/verify.spec.js
Normal file
114
server/__tests__/verify.spec.js
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
const proxyRequire = require('proxyquire');
|
||||
|
||||
const CONSTANTS = require('../const');
|
||||
|
||||
describe('plugins/fts-keystone', ()=> {
|
||||
describe('mt', ()=> {
|
||||
describe('verify', () => {
|
||||
|
||||
it('should skip if session not available', () => {
|
||||
let server = {
|
||||
log: sinon.spy()
|
||||
};
|
||||
let request = {
|
||||
yar: {
|
||||
_store: undefined
|
||||
}
|
||||
};
|
||||
let reply = {
|
||||
continue: sinon.spy()
|
||||
};
|
||||
|
||||
require('../mt/verify')(server)(request, reply);
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
});
|
||||
|
||||
it('should skip if session available but user object not found', () => {
|
||||
let server = {
|
||||
log: sinon.spy()
|
||||
};
|
||||
let request = {
|
||||
yar: {
|
||||
_store: {
|
||||
'1': 1
|
||||
}
|
||||
}
|
||||
};
|
||||
let reply = {
|
||||
continue: sinon.spy()
|
||||
};
|
||||
|
||||
require('../mt/verify')(server)(request, reply);
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
});
|
||||
|
||||
it('should skip non ElasticSearch requests', () => {
|
||||
let store = {};
|
||||
|
||||
store[CONSTANTS.SESSION_USER_KEY] = {'id': 1};
|
||||
|
||||
let server = {
|
||||
log: sinon.spy()
|
||||
};
|
||||
let request = {
|
||||
url : {
|
||||
path: '/some/other/path'
|
||||
},
|
||||
yar: {
|
||||
_store: store
|
||||
}
|
||||
};
|
||||
let reply = {
|
||||
continue: sinon.spy()
|
||||
};
|
||||
|
||||
require('../mt/verify')(server)(request, reply);
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
});
|
||||
|
||||
it('should call verify indexPattern', () => {
|
||||
let store = {};
|
||||
let indexPattern = '*';
|
||||
|
||||
store[CONSTANTS.SESSION_USER_KEY] = {'id': 1};
|
||||
|
||||
let server = {
|
||||
log: sinon.spy()
|
||||
};
|
||||
let request = {
|
||||
method: 'GET',
|
||||
url : {
|
||||
path: `/elasticsearch/${indexPattern}/_mapping/field/`
|
||||
},
|
||||
yar: {
|
||||
_store: store
|
||||
}
|
||||
};
|
||||
let verifyIndexPattern = sinon.spy();
|
||||
|
||||
proxyRequire('../mt/verify', {
|
||||
'./_verify_index_pattern': verifyIndexPattern
|
||||
})(server)(request);
|
||||
|
||||
chai.expect(verifyIndexPattern.calledOnce).to.be.ok;
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
103
server/__tests__/verifyIndexPattern.spec.js
Normal file
103
server/__tests__/verifyIndexPattern.spec.js
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
|
||||
const CONSTANTS = require('../const');
|
||||
const verifyIndexPattern = require('../mt/verify/_verify_index_pattern');
|
||||
|
||||
describe('plugins/fts-keystone', ()=> {
|
||||
describe('mt', ()=> {
|
||||
describe('verify', () => {
|
||||
describe('verify_index_pattern', () => {
|
||||
|
||||
it('should reject * as index-pattern', () => {
|
||||
let indexPattern = '*';
|
||||
let request = {
|
||||
url: {
|
||||
path: `/a/${indexPattern}/`
|
||||
},
|
||||
yar: {
|
||||
_store: {}
|
||||
}
|
||||
};
|
||||
verifyIndexPattern(request, (result) => {
|
||||
chai.expect(result.isBoom)
|
||||
.to.be.true;
|
||||
chai.expect(result.output.payload.message)
|
||||
.to.be.eq('* as pattern is not supported at the moment');
|
||||
chai.expect(result.output.statusCode)
|
||||
.to.be.eq(422);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject index-pattern if it does not match user`s projects', () => {
|
||||
let projects = [
|
||||
{ id: 'project_1'}, { id: 'project_2' }, {id: 'project_3'}
|
||||
];
|
||||
let indexPattern = 'test';
|
||||
let store = {};
|
||||
|
||||
store[CONSTANTS.SESSION_PROJECTS_KEY] = projects;
|
||||
|
||||
let request = {
|
||||
url: {
|
||||
path: `/a/${indexPattern}/`
|
||||
},
|
||||
yar: {
|
||||
_store: store
|
||||
}
|
||||
};
|
||||
let reply = sinon.spy();
|
||||
|
||||
verifyIndexPattern(request, (result) => {
|
||||
chai.expect(result.isBoom)
|
||||
.to.be.true;
|
||||
chai.expect(result.output.payload.message)
|
||||
.to.be.eq(`${indexPattern} do not match any project of current user`);
|
||||
chai.expect(result.output.statusCode)
|
||||
.to.be.eq(422);
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept index-pattern it it constaint project id', () => {
|
||||
let projects = [
|
||||
{ id: 'project_1'}, { id: 'project_2' }, {id: 'project_3'}
|
||||
];
|
||||
let indexPattern = projects[1].id;
|
||||
let store = {};
|
||||
|
||||
store[CONSTANTS.SESSION_PROJECTS_KEY] = projects;
|
||||
|
||||
let request = {
|
||||
url: {
|
||||
path: `/a/${indexPattern}/`
|
||||
},
|
||||
yar: {
|
||||
_store: store
|
||||
}
|
||||
};
|
||||
let reply = {
|
||||
continue: sinon.spy()
|
||||
};
|
||||
|
||||
verifyIndexPattern(request, reply);
|
||||
chai.expect(reply.continue.calledOnce).to.be.ok;
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
31
server/binding/index.js
Normal file
31
server/binding/index.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import TokensApi from 'keystone-v3-client/lib/keystone/tokens';
|
||||
import UsersApi from 'keystone-v3-client/lib/keystone/users';
|
||||
|
||||
module.exports = function binding(server) {
|
||||
const config = server.config();
|
||||
const keystoneCfg = {
|
||||
url: `${config.get('fts-keystone.url')}:${config.get('fts-keystone.port')}`
|
||||
};
|
||||
|
||||
return {
|
||||
start: () => {
|
||||
server.expose('tokens', new TokensApi(keystoneCfg));
|
||||
server.expose('users', new UsersApi(keystoneCfg));
|
||||
}
|
||||
};
|
||||
|
||||
};
|
22
server/const.js
Normal file
22
server/const.js
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const NOW_TIME = (new Date().valueOf() / 1000);
|
||||
|
||||
export const SESSION_USER_KEY = `fts-keystone-user-${NOW_TIME}`;
|
||||
export const SESSION_TOKEN_KEY = `fts-keystone-token-${NOW_TIME}`;
|
||||
export const SESSION_PROJECTS_KEY = `fts-keystone-projects-${NOW_TIME}`;
|
||||
export const SESSION_TOKEN_CHANGED = `fts-keystone-token-changed-${NOW_TIME}`;
|
||||
|
||||
export const TOKEN_CHANGED_VALUE = Symbol('token-changed');
|
@ -12,18 +12,19 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const Promise = require('bluebird');
|
||||
const url = require('url');
|
||||
import url from 'url';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
const util = require('../util/');
|
||||
|
||||
module.exports = function (plugin, server) {
|
||||
let timeoutId;
|
||||
import util from '../util';
|
||||
|
||||
module.exports = function healthcheck(plugin, server) {
|
||||
const config = server.config();
|
||||
const keystoneUrl = config.get('fts-keystone.url');
|
||||
const keystonePort = config.get('fts-keystone.port');
|
||||
const request = getRequest();
|
||||
|
||||
let timeoutId;
|
||||
|
||||
const service = {
|
||||
run : check,
|
||||
start : start,
|
||||
@ -33,22 +34,12 @@ module.exports = function (plugin, server) {
|
||||
}
|
||||
};
|
||||
|
||||
server.on('stop', stop);
|
||||
|
||||
return service;
|
||||
|
||||
function getRequest() {
|
||||
let required;
|
||||
if (util.startsWith(keystoneUrl, 'https')) {
|
||||
required = require('https');
|
||||
} else {
|
||||
required = require('http');
|
||||
}
|
||||
return required.request;
|
||||
}
|
||||
|
||||
function check() {
|
||||
|
||||
return new Promise((resolve, reject)=> {
|
||||
|
||||
const req = request({
|
||||
hostname: getHostname(),
|
||||
port : keystonePort,
|
||||
@ -57,12 +48,13 @@ module.exports = function (plugin, server) {
|
||||
const statusCode = res.statusCode;
|
||||
if (statusCode >= 400) {
|
||||
plugin.status.red('Unavailable');
|
||||
reject(statusCode);
|
||||
reject(false);
|
||||
} else {
|
||||
plugin.status.green('Ready');
|
||||
resolve(statusCode);
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', (error)=> {
|
||||
plugin.status.red('Unavailable: Failed to communicate with Keystone');
|
||||
server.log(['keystone', 'healthcheck', 'error'], `${error.message}`);
|
||||
@ -70,14 +62,9 @@ module.exports = function (plugin, server) {
|
||||
});
|
||||
|
||||
req.end();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function getHostname() {
|
||||
return url.parse(keystoneUrl).hostname;
|
||||
}
|
||||
|
||||
function start() {
|
||||
scheduleCheck(service.stop() ? 10000 : 1);
|
||||
}
|
||||
@ -109,4 +96,18 @@ module.exports = function (plugin, server) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getHostname() {
|
||||
return url.parse(keystoneUrl).hostname;
|
||||
}
|
||||
|
||||
function getRequest() {
|
||||
let required;
|
||||
if (util.startsWith(keystoneUrl, 'https')) {
|
||||
required = require('https');
|
||||
} else {
|
||||
required = require('http');
|
||||
}
|
||||
return required.request;
|
||||
}
|
||||
|
||||
};
|
||||
|
91
server/mt/auth/_authenticate.js
Normal file
91
server/mt/auth/_authenticate.js
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import Joi from 'joi';
|
||||
|
||||
import { SESSION_USER_KEY } from '../../const';
|
||||
import lookupToken from './token';
|
||||
import RELOAD from './reload';
|
||||
|
||||
const RELOAD_MARKUP = `<html>
|
||||
<head><script type="text/javascript">window.location.reload();</script></head>
|
||||
<body>reloading...</body>
|
||||
</html>`;
|
||||
const NOOP = ()=> {
|
||||
};
|
||||
const SCHEMA = {
|
||||
tokenOk : Joi.func().default(NOOP),
|
||||
tokenBad: Joi.func().default(NOOP)
|
||||
};
|
||||
|
||||
export default (server, opts) => {
|
||||
Joi.assert(opts, SCHEMA, 'Invalid keystone auth options');
|
||||
|
||||
const tokensApi = server.plugins['fts-keystone'].tokens;
|
||||
const callbackOk = opts.tokenOk;
|
||||
const callbackBad = opts.tokenBad;
|
||||
|
||||
return (request, reply) => {
|
||||
const token = lookupToken(server, request);
|
||||
const session = request.yar;
|
||||
|
||||
let userObj = session.get(SESSION_USER_KEY);
|
||||
|
||||
if (token.isBoom) {
|
||||
server.log(['status', 'debug', 'keystone'],
|
||||
'Received error object from token lookup'
|
||||
);
|
||||
return reply(token);
|
||||
} else if (token === RELOAD) {
|
||||
server.log(['status', 'debug', 'keystone'],
|
||||
'Received reload markup object from token lookup'
|
||||
);
|
||||
return reply(RELOAD_MARKUP).type('text/html');
|
||||
} else if (userObj && 'project' in userObj) {
|
||||
server.log(['status','info','keystone'], `${token} already authorized`);
|
||||
return reply.continue({credentials:token});
|
||||
}
|
||||
|
||||
server.log(['status', 'debug', 'keystone'],
|
||||
'About to validate token with keystone'
|
||||
);
|
||||
|
||||
return tokensApi
|
||||
.validate({
|
||||
headers: {
|
||||
'X-Auth-Token' : token,
|
||||
'X-Subject-Token': token
|
||||
}
|
||||
})
|
||||
.then(
|
||||
(data) => {
|
||||
userObj = data.data.token;
|
||||
return callbackOk(token, userObj, session)
|
||||
.then(()=> {
|
||||
server.log(['status', 'debug', 'keystone'],
|
||||
`Auth process completed for user ${userObj.user.id}`);
|
||||
return reply.continue({credentials: token});
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
return callbackBad(token, error, session)
|
||||
.then((err)=> {
|
||||
server.log(['status', 'error', 'keystone'], `Auth process did not complete for token ${token}`);
|
||||
server.log(['status', 'error', 'keystone'], `${err}`);
|
||||
return reply(Boom.wrap(err));
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
17
server/mt/auth/reload/index.js
Normal file
17
server/mt/auth/reload/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const RELOAD = Symbol('reload-me');
|
||||
|
||||
module.exports = RELOAD;
|
@ -12,14 +12,10 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const proxy = require('./proxy');
|
||||
import authenticateFactory from './_authenticate';
|
||||
|
||||
module.exports = function createProxy(server) {
|
||||
server.ext(
|
||||
'onPreAuth',
|
||||
proxy(server),
|
||||
{
|
||||
after: ['yar']
|
||||
}
|
||||
);
|
||||
export default (server, opts) => {
|
||||
return {
|
||||
authenticate: authenticateFactory(server, opts)
|
||||
};
|
||||
};
|
74
server/mt/auth/strategy.js
Normal file
74
server/mt/auth/strategy.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import kibanaIndex from '../kibana';
|
||||
import userProjects from '../projects';
|
||||
|
||||
import {
|
||||
SESSION_TOKEN_KEY,
|
||||
SESSION_USER_KEY
|
||||
} from '../../const';
|
||||
|
||||
export default (server) => {
|
||||
|
||||
return {
|
||||
tokenOk : tokenOk,
|
||||
tokenBad: tokenBad
|
||||
};
|
||||
|
||||
function tokenOk(token, userObj, session) {
|
||||
session.reset();
|
||||
session.set(SESSION_TOKEN_KEY, token);
|
||||
session.set(SESSION_USER_KEY, userObj);
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
userProjects(server, session, userObj),
|
||||
kibanaIndex(server, userObj)
|
||||
])
|
||||
.then(() => {
|
||||
server.log(['status', 'info', 'keystone'], `User ${userObj.user.id} authorized with keystone`);
|
||||
return token;
|
||||
})
|
||||
.catch(err => {
|
||||
server.log(['status', 'info', 'keystone'],
|
||||
`Error caught in process of authorization, err was ${err}`);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function tokenBad(token, error, session) {
|
||||
return new Promise((resolve)=> {
|
||||
server.log(['keystone', 'error'], `Failed to authenticate token ${token} with keystone, error is ${error.statusCode}.`);
|
||||
session.reset();
|
||||
|
||||
let err;
|
||||
|
||||
if (error.statusCode === 401) {
|
||||
err = Boom.forbidden('\You\'re not logged in as a user who\'s authorized to access log information');
|
||||
} else {
|
||||
err = Boom.internal(
|
||||
error.message || 'Unexpected error during Keystone communication',
|
||||
{},
|
||||
error.statusCode
|
||||
);
|
||||
}
|
||||
return resolve(err);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
@ -12,10 +12,15 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const Boom = require('boom');
|
||||
import Boom from 'boom';
|
||||
import {
|
||||
SESSION_TOKEN_KEY,
|
||||
SESSION_TOKEN_CHANGED,
|
||||
TOKEN_CHANGED_VALUE
|
||||
} from '../../../const';
|
||||
import RELOAD_SYMBOL from '../reload';
|
||||
|
||||
/** @module */
|
||||
module.exports = retrieveToken;
|
||||
const HEADER_NAME = 'x-auth-token';
|
||||
|
||||
/**
|
||||
* Retrieves token from the response header using key <b>X-Keystone-Token</b>.
|
||||
@ -31,21 +36,21 @@ module.exports = retrieveToken;
|
||||
*
|
||||
* @returns {string} current token value
|
||||
*/
|
||||
module.exports = (server, request) => {
|
||||
|
||||
const HEADER_NAME = 'x-auth-token';
|
||||
|
||||
function retrieveToken(server, request) {
|
||||
|
||||
if (!request.session || request.session === null) {
|
||||
server.log(['keystone', 'error'], 'Session is not enabled');
|
||||
if (!request.yar || request.yar === null) {
|
||||
server.log(['status', 'keystone', 'error'], 'Session is not enabled');
|
||||
throw new Error('Session support is missing');
|
||||
}
|
||||
|
||||
let tokenFromSession = request.session.get('keystone_token');
|
||||
// DEV PURPOSE ONLY
|
||||
// request.yar.set(SESSION_TOKEN_KEY, 'a60e832483c34526a0c2bc3c6f8fa320');
|
||||
|
||||
let tokenFromSession = request.yar.get(SESSION_TOKEN_KEY);
|
||||
let token = request.headers[HEADER_NAME];
|
||||
|
||||
if (!token && !tokenFromSession) {
|
||||
server.log(['keystone', 'error'],
|
||||
server.log(['status', 'keystone', 'error'],
|
||||
'Token hasn\'t been located, looked in headers and session');
|
||||
return Boom.unauthorized(
|
||||
'You\'re not logged into the OpenStack. Please login via Horizon Dashboard'
|
||||
@ -54,15 +59,28 @@ function retrieveToken(server, request) {
|
||||
|
||||
if (!token && tokenFromSession) {
|
||||
token = tokenFromSession;
|
||||
server.log(['keystone', 'debug'],
|
||||
server.log(['status', 'debug', 'keystone'],
|
||||
'Token lookup status: Found token in session'
|
||||
);
|
||||
} else if ((token && !tokenFromSession) || (token !== tokenFromSession)) {
|
||||
server.log(['keystone', 'debug'],
|
||||
server.log(['status', 'debug', 'keystone'],
|
||||
'Token lookup status: Token located in header/session or token changed'
|
||||
);
|
||||
request.session.set('keystone_token', token);
|
||||
|
||||
if ((token !== tokenFromSession) && (token && tokenFromSession)) {
|
||||
server.log(['status', 'info', 'keystone'],
|
||||
'Reseting session because token has changed'
|
||||
);
|
||||
request.yar.reset();
|
||||
|
||||
request.yar.set(SESSION_TOKEN_CHANGED, TOKEN_CHANGED_VALUE);
|
||||
request.yar.set(SESSION_TOKEN_KEY, token);
|
||||
|
||||
return RELOAD_SYMBOL;
|
||||
}
|
||||
|
||||
return token;
|
||||
request.yar.set(SESSION_TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
return request.yar.get(SESSION_TOKEN_KEY);
|
||||
};
|
62
server/mt/auth/verify.js
Normal file
62
server/mt/auth/verify.js
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
import {
|
||||
SESSION_PROJECTS_KEY,
|
||||
SESSION_USER_KEY,
|
||||
SESSION_TOKEN_CHANGED,
|
||||
TOKEN_CHANGED_VALUE
|
||||
} from '../../const';
|
||||
import reload from './reload';
|
||||
|
||||
export default () => {
|
||||
return (request, reply) => {
|
||||
|
||||
let session = request.yar;
|
||||
let userObj = session.get(SESSION_USER_KEY);
|
||||
let tokenChanged = session.get(SESSION_TOKEN_CHANGED);
|
||||
|
||||
if (tokenChanged === TOKEN_CHANGED_VALUE) {
|
||||
request.log(['status', 'info', 'keystone'],
|
||||
'Detected that token has been changed, replaying the request'
|
||||
);
|
||||
session.clear(SESSION_TOKEN_CHANGED);
|
||||
return reply(reload.markup).type('text/html');
|
||||
} else if (userObj) {
|
||||
let expiresAt = new Date(userObj.expires_at).valueOf();
|
||||
let now = new Date().valueOf();
|
||||
let diff = now - expiresAt;
|
||||
|
||||
if (diff >= 0) {
|
||||
session.reset();
|
||||
return reply(Boom.unauthorized('User token has expired')).takeover();
|
||||
} else {
|
||||
return reply.continue({
|
||||
credentials: userObj,
|
||||
artifacts : {
|
||||
projects: session.get(SESSION_PROJECTS_KEY)
|
||||
},
|
||||
log : {
|
||||
tags: 'keystone ok'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(trebskit) should actually throw error here I guess
|
||||
return reply.continue();
|
||||
};
|
||||
};
|
64
server/mt/index.js
Normal file
64
server/mt/index.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
export default {
|
||||
bind: (server) => {
|
||||
server.log(['status', 'info', 'keystone'], 'Registering keystone-auth schema');
|
||||
return Promise.all([
|
||||
bindAuthScheme(server),
|
||||
bindExt(server),
|
||||
bindRouting(server)
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
function bindRouting(server) {
|
||||
const kibanaIndex = server.config().get('kibana.index');
|
||||
return require('./routing')(server)
|
||||
.then((route)=> {
|
||||
route(server, 'GET', '/{paths*}');
|
||||
route(server, 'POST', '/_mget');
|
||||
route(server, 'POST', '/{index}/_search');
|
||||
route(server, 'POST', '/{index}/_field_stats');
|
||||
route(server, 'POST', '/_msearch');
|
||||
route(server, 'POST', '/_search/scroll');
|
||||
route(server, ['PUT', 'POST', 'DELETE'], '/' + kibanaIndex + '/{paths*}');
|
||||
});
|
||||
}
|
||||
|
||||
function bindAuthScheme(server) {
|
||||
return Promise.all([
|
||||
server.auth.scheme(
|
||||
'keystone-token',
|
||||
require('./auth/scheme')
|
||||
),
|
||||
server.auth.strategy(
|
||||
'session',
|
||||
'keystone-token',
|
||||
true,
|
||||
require('./auth/strategy')(server)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
function bindExt(server) {
|
||||
return Promise.all([
|
||||
server.ext(
|
||||
'onPreAuth',
|
||||
require('./auth/verify')(server),
|
||||
{after: ['yar']}
|
||||
),
|
||||
server.ext('onRequest', require('./verify')(server))
|
||||
]);
|
||||
}
|
57
server/mt/kibana/_can_upgrade.js
Normal file
57
server/mt/kibana/_can_upgrade.js
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import semver from 'semver';
|
||||
|
||||
const VERSION_REGEX = /(\d+\.\d+\.\d+)\-rc(\d+)/i;
|
||||
|
||||
export default (server, doc) => {
|
||||
const config = server.config();
|
||||
|
||||
if (/beta|snapshot/i.test(doc._id)) {
|
||||
return false;
|
||||
}
|
||||
if (!doc._id) {
|
||||
return false;
|
||||
}
|
||||
if (doc._id === config.get('pkg.version')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let packageRcRelease = Infinity;
|
||||
let rcRelease = Infinity;
|
||||
let packageVersion = config.get('pkg.version');
|
||||
let version = doc._id;
|
||||
let matches = doc._id.match(VERSION_REGEX);
|
||||
let packageMatches = config.get('pkg.version').match(VERSION_REGEX);
|
||||
|
||||
if (matches) {
|
||||
version = matches[1];
|
||||
rcRelease = parseInt(matches[2], 10);
|
||||
}
|
||||
|
||||
if (packageMatches) {
|
||||
packageVersion = packageMatches[1];
|
||||
packageRcRelease = parseInt(packageMatches[2], 10);
|
||||
}
|
||||
|
||||
try {
|
||||
if (semver.gte(version, packageVersion) && rcRelease >= packageRcRelease) {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
107
server/mt/kibana/_configure.js
Normal file
107
server/mt/kibana/_configure.js
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import { find } from 'lodash';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import canUpgradeConfig from './_can_upgrade';
|
||||
|
||||
export default (server, indexName) => {
|
||||
const config = server.config();
|
||||
const client = server.plugins.elasticsearch.client;
|
||||
const options = {
|
||||
index: indexName,
|
||||
type : 'config',
|
||||
body : {
|
||||
size: 1000,
|
||||
sort: [
|
||||
{
|
||||
buildNum: {
|
||||
order : 'desc',
|
||||
ignore_unmapped: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
server.log(['status', 'debug', 'keystone'], `Configuring index ${indexName}`);
|
||||
|
||||
return client
|
||||
.search(options)
|
||||
.then(upgradeConfig(server, indexName))
|
||||
.then(()=>{
|
||||
return Promise
|
||||
.delay(666)
|
||||
.then(() => {
|
||||
server.log(['status', 'debug', 'keystone'], `Index ${indexName} has been configured`);
|
||||
return indexName;
|
||||
});
|
||||
})
|
||||
.catch((err)=> {
|
||||
throw new Error(`Configuring ${indexName} failed, error is ${err}`);
|
||||
});
|
||||
|
||||
function upgradeConfig(server, indexName) {
|
||||
const client = server.plugins.elasticsearch.client;
|
||||
const config = server.config();
|
||||
|
||||
return (response) => {
|
||||
if (response.hits.hits.length === 0) {
|
||||
return client.create({
|
||||
index: indexName,
|
||||
type : 'config',
|
||||
body : {
|
||||
buildNum: config.get('pkg.buildNum')
|
||||
},
|
||||
id : config.get('pkg.version')
|
||||
});
|
||||
}
|
||||
|
||||
// if we already have a the current version in the index then we need to stop
|
||||
var devConfig = find(response.hits.hits, function currentVersion(hit) {
|
||||
return hit._id !== '@@version' && hit._id === config.get('pkg.version');
|
||||
});
|
||||
|
||||
if (devConfig) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Look for upgradeable configs. If none of them are upgradeable
|
||||
// then resolve with null.
|
||||
let body = find(response.hits.hits, canUpgradeConfig.bind(null, server));
|
||||
if (!body) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// if the build number is still the template string (which it wil be in development)
|
||||
// then we need to set it to the max interger. Otherwise we will set it to the build num
|
||||
body._source.buildNum = config.get('pkg.buildNum');
|
||||
|
||||
server.log(['plugin', 'elasticsearch'], {
|
||||
tmpl : 'Upgrade config from <%= prevVersion %> to <%= newVersion %>',
|
||||
prevVersion: body._id,
|
||||
newVersion : config.get('pkg.version')
|
||||
});
|
||||
|
||||
return client.create({
|
||||
index: indexName,
|
||||
type : 'config',
|
||||
body : body._source,
|
||||
id : config.get('pkg.version')
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
};
|
60
server/mt/kibana/_create.js
Normal file
60
server/mt/kibana/_create.js
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
import { exists as indexExists } from './_exists';
|
||||
|
||||
export default (server, indexName) => {
|
||||
const client = server.plugins.elasticsearch.client;
|
||||
|
||||
server.log(['status', 'info', 'keystone'], `Creating user index ${indexName}`);
|
||||
|
||||
return client.indices
|
||||
.create({
|
||||
index: indexName,
|
||||
body : {
|
||||
settings: {
|
||||
number_of_shards: 1
|
||||
},
|
||||
mappings: {
|
||||
config: {
|
||||
properties: {
|
||||
buildNum: {
|
||||
type : 'string',
|
||||
index: 'not_analyzed'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err)=> {
|
||||
throw Boom.wrap(err, 500,
|
||||
`Failed to create index ${indexName}`);
|
||||
})
|
||||
.then(() => {
|
||||
return indexExists(server, indexName, 'yellow')
|
||||
.catch((err)=> {
|
||||
throw Boom.wrap(err, 500,
|
||||
`Waiting for index ${indexName} to come online failed`);
|
||||
})
|
||||
.then(()=> {
|
||||
server.log(['status', 'info', 'keystone'],
|
||||
`Index ${indexName} has been created`);
|
||||
return Promise.resolve(indexName);
|
||||
});
|
||||
});
|
||||
|
||||
};
|
38
server/mt/kibana/_exists.js
Normal file
38
server/mt/kibana/_exists.js
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import kibanaIndex from './kibanaIndex';
|
||||
|
||||
export default (server, userObj) => {
|
||||
const indexName = kibanaIndex(server, userObj);
|
||||
return exists(server, indexName)
|
||||
.then((resp) => {
|
||||
return {indexName, resp};
|
||||
});
|
||||
};
|
||||
|
||||
export function exists(server, indexName, status) {
|
||||
const es = server.plugins.elasticsearch.client;
|
||||
const opts = {
|
||||
timeout : '5s',
|
||||
index : indexName,
|
||||
ignore : [408],
|
||||
waitForActiveShards: 1
|
||||
};
|
||||
if (status) {
|
||||
opts.status = status;
|
||||
}
|
||||
return es.cluster.health(opts);
|
||||
}
|
||||
|
39
server/mt/kibana/index.js
Normal file
39
server/mt/kibana/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import indexExists from './_exists';
|
||||
import createIndex from './_create';
|
||||
import configureIndex from './_configure';
|
||||
|
||||
export default (server, userObj) => {
|
||||
return doCheck();
|
||||
|
||||
function doCheck() {
|
||||
return indexExists(server, userObj)
|
||||
.then(({indexName, resp}) => {
|
||||
if (!resp || resp.timed_out) {
|
||||
server.log(['status', 'warning', 'keystone'], `Index ${indexName} does not exists`);
|
||||
return createIndex(server, indexName);
|
||||
}
|
||||
if (resp.status === 'red') {
|
||||
server.log(['status', 'warning', 'keystone'], `Shards not ready for index ${indexName}`);
|
||||
return Promise.delay(2500).then(doCheck);
|
||||
}
|
||||
return Promise.resolve(indexName);
|
||||
})
|
||||
.then((indexName)=> {
|
||||
return configureIndex(server, indexName);
|
||||
});
|
||||
}
|
||||
};
|
29
server/mt/kibana/kibanaIndex.js
Normal file
29
server/mt/kibana/kibanaIndex.js
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns tenant/project-aware kibana index
|
||||
*
|
||||
* @param server server object
|
||||
* @param userObj user details as retrieved from keystone
|
||||
* @returns {string} project aware kibana index
|
||||
*
|
||||
*/
|
||||
export default (server, userObj) => {
|
||||
return `${server.config().get('kibana.index')}-${getProjectId(userObj)}`;
|
||||
};
|
||||
|
||||
function getProjectId(userObj) {
|
||||
return userObj.project.id;
|
||||
}
|
52
server/mt/projects/index.js
Normal file
52
server/mt/projects/index.js
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import { pick, sortBy } from 'lodash';
|
||||
|
||||
import { SESSION_PROJECTS_KEY, SESSION_TOKEN_KEY } from '../../const';
|
||||
|
||||
export default (server, session, userObj) => {
|
||||
|
||||
const usersApi = server.plugins['fts-keystone'].users;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
return usersApi
|
||||
.allProjects({
|
||||
params : {
|
||||
user_id: userObj.user.id
|
||||
},
|
||||
headers: {
|
||||
'X-Auth-Token' : session.get(SESSION_TOKEN_KEY),
|
||||
'X-Subject-Token': session.get(SESSION_TOKEN_KEY)
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
const projects = data.projects;
|
||||
|
||||
return sortBy(
|
||||
projects.map(
|
||||
project=>pick(project, ['id', 'name', 'description', 'domain_id'])
|
||||
),
|
||||
'name'
|
||||
);
|
||||
})
|
||||
.then((projects) => {
|
||||
session.set(SESSION_PROJECTS_KEY, projects);
|
||||
return resolve(projects);
|
||||
});
|
||||
});
|
||||
|
||||
};
|
58
server/mt/routing/_create_agent.js
Normal file
58
server/mt/routing/_create_agent.js
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
This file was copied and modified for this plugin needs.
|
||||
Original file can be found here => https://github.com/elastic/kibana/blob/4.4/src/plugins/elasticsearch/lib/create_agent.js
|
||||
*/
|
||||
|
||||
import url from 'url';
|
||||
import {memoize, size} from 'lodash';
|
||||
import fs from 'fs';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
|
||||
const readFile = (file) => fs.readFileSync(file, 'utf8');
|
||||
|
||||
module.exports = memoize(function (server) {
|
||||
const config = server.config();
|
||||
const target = url.parse(config.get('elasticsearch.url'));
|
||||
|
||||
if (!/^https/.test(target.protocol)) {
|
||||
return new http.Agent();
|
||||
}
|
||||
|
||||
const agentOptions = {
|
||||
rejectUnauthorized: config.get('elasticsearch.ssl.verify')
|
||||
};
|
||||
|
||||
if (size(config.get('elasticsearch.ssl.ca'))) {
|
||||
agentOptions.ca = config.get('elasticsearch.ssl.ca').map(readFile);
|
||||
}
|
||||
|
||||
if (hasSSLEnabled()) {
|
||||
agentOptions.cert = readFile(config.get('elasticsearch.ssl.cert'));
|
||||
agentOptions.key = readFile(config.get('elasticsearch.ssl.key'));
|
||||
}
|
||||
|
||||
return new https.Agent(agentOptions);
|
||||
|
||||
function hasSSLEnabled() {
|
||||
return config.get('elasticsearch.ssl.cert')
|
||||
&& config.get('elasticsearch.ssl.key');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports.cache = new Map();
|
42
server/mt/routing/_create_proxy.js
Normal file
42
server/mt/routing/_create_proxy.js
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import { PREFIX } from './_utils';
|
||||
|
||||
module.exports = (server, method, route) => {
|
||||
|
||||
const serverConfig = server.config();
|
||||
const pre = '/elasticsearch';
|
||||
const sep = route[0] === '/' ? '' : '/';
|
||||
const path = `${PREFIX}${pre}${sep}${route}`;
|
||||
|
||||
let options;
|
||||
|
||||
switch (route) {
|
||||
case '/_mget':
|
||||
options = require('./routes/mget')(server, method, path);
|
||||
break;
|
||||
case '/{paths*}':
|
||||
options = require('./routes/paths')(server, method, path);
|
||||
break;
|
||||
default:
|
||||
if (route === `/${serverConfig.get('kibana.index')}/{paths*}`) {
|
||||
options = require('./routes/kibana_index')(server, method, path);
|
||||
} else {
|
||||
options = require('./routes/default')(server, method, path);
|
||||
}
|
||||
}
|
||||
|
||||
return server.route(options);
|
||||
};
|
43
server/mt/routing/_map_uri.js
Normal file
43
server/mt/routing/_map_uri.js
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
This file was copied and modified for this plugin needs.
|
||||
Original file can be found here => https://github.com/elastic/kibana/blob/4.4/src/plugins/elasticsearch/lib/map_uri.js
|
||||
*/
|
||||
|
||||
import querystring from 'querystring';
|
||||
|
||||
import { PREFIX } from './_utils';
|
||||
|
||||
export default (server, request) => {
|
||||
const config = server.config();
|
||||
const path = request.path.replace(`${PREFIX}/elasticsearch`, '');
|
||||
const query = querystring.stringify(request.query);
|
||||
|
||||
let url = config.get('elasticsearch.url');
|
||||
|
||||
if (path) {
|
||||
if (/\/$/.test(url)) {
|
||||
url = url.substring(0, url.length - 1);
|
||||
}
|
||||
url += path;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
url += '?' + query;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
27
server/mt/routing/_re_route.js
Normal file
27
server/mt/routing/_re_route.js
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import utils from '../../util';
|
||||
import { PREFIX } from './_utils';
|
||||
|
||||
module.exports = function reRoute(server) {
|
||||
return (request, reply) => {
|
||||
const requestPath = utils.requestPath(request);
|
||||
if (utils.isESRequest(request)) {
|
||||
server.log(['status', 'debug', 'keystone'], `Routing ${requestPath} onto ${PREFIX}${requestPath}`);
|
||||
request.setUrl(`${PREFIX}${requestPath}`);
|
||||
}
|
||||
return reply.continue();
|
||||
};
|
||||
};
|
70
server/mt/routing/_utils.js
Normal file
70
server/mt/routing/_utils.js
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import createAgent from './_create_agent';
|
||||
|
||||
export const PREFIX = '/mt';
|
||||
|
||||
export function getOpts(server, request, url, payload) {
|
||||
|
||||
let options = {
|
||||
headers : {},
|
||||
redirects : true,
|
||||
passThrough : true,
|
||||
xforward : true,
|
||||
timeout : 1000 * 60 * 3,
|
||||
localStatePassThrough: false,
|
||||
agent : createAgent(server),
|
||||
};
|
||||
let protocol = url.split(':', 1)[0];
|
||||
|
||||
if (payload) {
|
||||
options.payload = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
if (options.passThrough) {
|
||||
options.headers = require('hoek').clone(request.headers);
|
||||
delete options.headers.host;
|
||||
if (options.acceptEncoding === false) {
|
||||
delete options.headers['accept-encoding'];
|
||||
}
|
||||
}
|
||||
|
||||
if (options.xforward &&
|
||||
request.info.remoteAddress &&
|
||||
request.info.remotePort) {
|
||||
|
||||
options.headers['x-forwarded-for'] = (options.headers['x-forwarded-for'] ?
|
||||
options.headers['x-forwarded-for'] + ',' : '') + request.info.remoteAddress;
|
||||
options.headers['x-forwarded-port'] = (options.headers['x-forwarded-port'] ?
|
||||
options.headers['x-forwarded-port'] + ',' : '') + request.info.remotePort;
|
||||
options.headers['x-forwarded-proto'] = (options.headers['x-forwarded-proto'] ?
|
||||
options.headers['x-forwarded-proto'] + ',' : '') + protocol;
|
||||
}
|
||||
|
||||
const contentType = request.headers['content-type'];
|
||||
if (contentType) {
|
||||
options.headers['content-type'] = contentType;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
export function parsePayload(request) {
|
||||
let payload = request.payload;
|
||||
if (!payload || payload.length <= 0) {
|
||||
return {};
|
||||
}
|
||||
return JSON.parse(payload.toString('utf-8'));
|
||||
}
|
23
server/mt/routing/index.js
Normal file
23
server/mt/routing/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import reRoute from './_re_route';
|
||||
|
||||
module.exports = function routing(server) {
|
||||
return new Promise((resolve) => {
|
||||
server.ext('onRequest', reRoute(server));
|
||||
resolve(require('./_create_proxy'));
|
||||
});
|
||||
};
|
35
server/mt/routing/routes/default.js
Normal file
35
server/mt/routing/routes/default.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import mapUri from '../_map_uri';
|
||||
import createAgent from '../_create_agent';
|
||||
|
||||
module.exports = function defaultHandler(server, method, path) {
|
||||
return {
|
||||
method : method,
|
||||
path : path,
|
||||
handler: {
|
||||
proxy: {
|
||||
mapUri : (request, done) => {
|
||||
server.log(['status', 'debug', 'keystone'],
|
||||
`mapUri for path ${request.path}`);
|
||||
done(null, mapUri(server, request));
|
||||
},
|
||||
agent : createAgent(server),
|
||||
passThrough: true,
|
||||
xforward : true
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
57
server/mt/routing/routes/kibana_index.js
Normal file
57
server/mt/routing/routes/kibana_index.js
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Wreck from 'wreck';
|
||||
|
||||
import { SESSION_USER_KEY } from '../../../const';
|
||||
import { getOpts, parsePayload } from '../_utils';
|
||||
import kibanaIndex from '../../kibana/kibanaIndex';
|
||||
import mapUri from '../_map_uri';
|
||||
|
||||
export default function (server, method, path) {
|
||||
|
||||
const defaultKibanaIndex = server.config().get('kibana.index');
|
||||
|
||||
return {
|
||||
method : method,
|
||||
path : path,
|
||||
config : {
|
||||
auth : false,
|
||||
payload: {
|
||||
output: 'data',
|
||||
parse : false
|
||||
}
|
||||
},
|
||||
handler: handler
|
||||
};
|
||||
|
||||
function handler(request, reply) {
|
||||
const url = getUrl(request);
|
||||
const opts = getOpts(server, request, url, parsePayload(request));
|
||||
return Wreck.request(request.method, url, opts, (err, res) => {
|
||||
return reply(res).code(res.statusCode).passThrough(!!opts.passThrough);
|
||||
});
|
||||
}
|
||||
|
||||
function getUrl(request) {
|
||||
const session = request.yar._store;
|
||||
|
||||
let url = mapUri(server, request).split('/');
|
||||
let indexPos = url.findIndex((item) => item === defaultKibanaIndex);
|
||||
|
||||
url[indexPos] = kibanaIndex(server, session[SESSION_USER_KEY]);
|
||||
|
||||
return url.join('/');
|
||||
}
|
||||
}
|
69
server/mt/routing/routes/mget.js
Normal file
69
server/mt/routing/routes/mget.js
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import Wreck from 'wreck';
|
||||
|
||||
import { SESSION_USER_KEY } from '../../../const';
|
||||
import { getOpts, parsePayload } from '../_utils';
|
||||
import kibanaIndex from '../../kibana/kibanaIndex';
|
||||
import mapUri from '../_map_uri';
|
||||
|
||||
export default function (server, method, path) {
|
||||
|
||||
return {
|
||||
method : method,
|
||||
path : path,
|
||||
config : {
|
||||
auth : false,
|
||||
payload: {
|
||||
output: 'data',
|
||||
parse : false
|
||||
}
|
||||
},
|
||||
handler: handler
|
||||
};
|
||||
|
||||
function handler(request, reply) {
|
||||
const url = mapUri(server, request);
|
||||
const session = request.yar._store;
|
||||
const payload = parsePayload(request);
|
||||
|
||||
payload.docs.forEach((doc) => {
|
||||
doc._index = kibanaIndex(server, session[SESSION_USER_KEY]);
|
||||
});
|
||||
|
||||
const opts = getOpts(server, request, url, payload);
|
||||
return Wreck.request(request.method, url, opts, (err, res) => {
|
||||
if (err) {
|
||||
server.log(
|
||||
['status', 'error', 'keystone'],
|
||||
`Failed to request ${url}, error is ${err}`);
|
||||
return reply(Boom.wrap(err));
|
||||
}
|
||||
return Wreck.read(res, {json: true}, (err, body)=> {
|
||||
if (err) {
|
||||
server.log(
|
||||
['status', 'error', 'keystone'],
|
||||
`Failed to read response from ${url}, error is ${err}`);
|
||||
return reply(Boom.wrap(err));
|
||||
}
|
||||
|
||||
return reply(body)
|
||||
.code(res.statusCode)
|
||||
.passThrough(!!opts.passThrough);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
66
server/mt/routing/routes/paths.js
Normal file
66
server/mt/routing/routes/paths.js
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Wreck from 'wreck';
|
||||
|
||||
import { SESSION_USER_KEY } from '../../../const';
|
||||
import { getOpts } from '../_utils';
|
||||
import kibanaIndex from '../../kibana/kibanaIndex';
|
||||
import mapUri from '../_map_uri';
|
||||
|
||||
export default function (server, method, path) {
|
||||
const defaultKibanaIndex = defaultKibanaIndex;
|
||||
|
||||
return {
|
||||
method : method,
|
||||
path : path,
|
||||
config : {
|
||||
tags: ['elasticsearch', 'multitenancy'],
|
||||
auth: false
|
||||
},
|
||||
handler: handler
|
||||
};
|
||||
|
||||
function handler(request, reply) {
|
||||
const session = request.yar._store;
|
||||
|
||||
let url = mapUri(server, request).split('/');
|
||||
let kibanaIndexRequest = false;
|
||||
|
||||
let indexPos = url.findIndex((item) => item === defaultKibanaIndex);
|
||||
if (indexPos > -1) {
|
||||
url[indexPos] = kibanaIndex(server, session[SESSION_USER_KEY]);
|
||||
kibanaIndexRequest = true;
|
||||
}
|
||||
url = url.join('/');
|
||||
|
||||
const opts = getOpts(server, request, url);
|
||||
return Wreck.request(request.method, url, opts, (err, res) => {
|
||||
return Wreck.read(res, {json: true}, (err, body)=> {
|
||||
let newData = {};
|
||||
|
||||
if (kibanaIndexRequest) {
|
||||
let tenantAwareIndex = Object.keys(body)[0];
|
||||
newData[defaultKibanaIndex] = body[tenantAwareIndex];
|
||||
} else {
|
||||
newData = body;
|
||||
}
|
||||
|
||||
return reply(newData)
|
||||
.code(res.statusCode)
|
||||
.passThrough(!!opts.passThrough);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
41
server/mt/verify/_verify_index_pattern.js
Normal file
41
server/mt/verify/_verify_index_pattern.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
import { SESSION_PROJECTS_KEY } from '../../const';
|
||||
import util from '../../util';
|
||||
|
||||
const INDEX_PATTER_POS = 2;
|
||||
|
||||
module.exports = (request, reply) => {
|
||||
const session = request.yar._store;
|
||||
const requestPath = util.requestPath(request);
|
||||
const splittedPath = requestPath.split('/');
|
||||
|
||||
let pattern = splittedPath[INDEX_PATTER_POS];
|
||||
let projects = session[SESSION_PROJECTS_KEY];
|
||||
|
||||
if ('*' === pattern) {
|
||||
return reply(Boom.badData('* as pattern is not supported at the moment'));
|
||||
} else if (projects.filter(filter).length === 0) {
|
||||
return reply(Boom.badData(`${pattern} do not match any project of current user`));
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
|
||||
function filter(project) {
|
||||
return new RegExp(`${project.id}.*`, 'gi').test(pattern);
|
||||
}
|
||||
};
|
48
server/mt/verify/index.js
Normal file
48
server/mt/verify/index.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import { SESSION_USER_KEY } from '../../const';
|
||||
import util from '../../util';
|
||||
|
||||
module.exports = (server) => {
|
||||
|
||||
return (request, reply) => {
|
||||
const session = request.yar._store;
|
||||
if (!(session && (SESSION_USER_KEY in session))) {
|
||||
server.log(['status', 'warning', 'keystone'], 'Session not yet available');
|
||||
return reply.continue();
|
||||
}
|
||||
|
||||
let requestPath = util.requestPath(request);
|
||||
let requestMethod = request.method;
|
||||
|
||||
if (util.isESRequest(request)) {
|
||||
let handler;
|
||||
if (isIndexPatternLookup()) {
|
||||
handler = require('./_verify_index_pattern');
|
||||
}
|
||||
if (handler) {
|
||||
return handler(request, reply);
|
||||
}
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
|
||||
function isIndexPatternLookup() {
|
||||
let regExp = /\/elasticsearch\/.*\/_mapping\/field\/.*/;
|
||||
return regExp.test(requestPath) && requestMethod.toLowerCase() === 'get';
|
||||
}
|
||||
|
||||
};
|
||||
};
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 FUJITSU LIMITED
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
const Boom = require('boom');
|
||||
const retrieveToken = require('./retrieveToken');
|
||||
const TokensApi = require('keystone-v3-client/lib/keystone/tokens');
|
||||
|
||||
const util = require('../util/');
|
||||
|
||||
module.exports = function (server) {
|
||||
const config = server.config();
|
||||
const tokensApi = new TokensApi({
|
||||
url: `${config.get('fts-keystone.url')}:${config.get('fts-keystone.port')}`
|
||||
});
|
||||
|
||||
return (request, reply) => {
|
||||
const requestPath = getRequestPath(request);
|
||||
let token;
|
||||
|
||||
if (shouldCallKeystone(requestPath)) {
|
||||
server.log(
|
||||
['keystone', 'debug'],
|
||||
`Call for ${requestPath} detected, authenticating with keystone`
|
||||
);
|
||||
|
||||
token = retrieveToken(server, request);
|
||||
if (token.isBoom) {
|
||||
return reply(token);
|
||||
}
|
||||
|
||||
return tokensApi
|
||||
.check({
|
||||
headers: {
|
||||
'X-Auth-Token' : token,
|
||||
'X-Subject-Token': token
|
||||
}
|
||||
})
|
||||
.then(onFulfilled, onFailed);
|
||||
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
|
||||
function onFulfilled() {
|
||||
reply.continue();
|
||||
}
|
||||
|
||||
function onFailed(error) {
|
||||
|
||||
server.log(
|
||||
['keystone', 'error'],
|
||||
`Failed to authenticate token ${token} with keystone,
|
||||
error is ${error.statusCode}.`
|
||||
);
|
||||
|
||||
if (error.statusCode === 401) {
|
||||
request.session.clear('keystone_token');
|
||||
reply(Boom.forbidden(
|
||||
`
|
||||
You\'re not logged in as a
|
||||
user who\'s authenticated to access log information
|
||||
`
|
||||
));
|
||||
} else {
|
||||
reply(Boom.internal(
|
||||
error.message || 'Unexpected error during Keystone communication',
|
||||
{},
|
||||
error.statusCode
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
function getRequestPath(request) {
|
||||
return request.url.path;
|
||||
}
|
||||
|
||||
function shouldCallKeystone(path) {
|
||||
return util.startsWith(path, '/elasticsearch');
|
||||
}
|
@ -12,13 +12,18 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
module.exports = function initSession(server) {
|
||||
import yarCookie from 'yar';
|
||||
import multiTenancy from '../mt';
|
||||
|
||||
export default (server) => {
|
||||
const config = server.config();
|
||||
const registerOpts = {
|
||||
register: require('yar'),
|
||||
return {
|
||||
start: ()=> {
|
||||
server.register({
|
||||
register: yarCookie,
|
||||
options : {
|
||||
name : 'kibana_session',
|
||||
maxCookieSize: 4096,
|
||||
name : config.get('fts-keystone.cookie.name'),
|
||||
storeBlank : false,
|
||||
cache : {
|
||||
expiresIn: config.get('fts-keystone.cookie.expiresIn')
|
||||
@ -27,19 +32,19 @@ module.exports = function initSession(server) {
|
||||
password : config.get('fts-keystone.cookie.password'),
|
||||
isSecure : config.get('fts-keystone.cookie.isSecure'),
|
||||
ignoreErrors: config.get('fts-keystone.cookie.ignoreErrors'),
|
||||
clearInvalid: true
|
||||
clearInvalid: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const callback = (error) => {
|
||||
}, (error) => {
|
||||
if (!error) {
|
||||
server.log(['session', 'debug'], 'Session registered');
|
||||
server.log(['status', 'info', 'keystone'], 'Session registered');
|
||||
multiTenancy.bind(server);
|
||||
} else {
|
||||
server.log(['session', 'error'], error);
|
||||
server.log(['status', 'error', 'keystone'], error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
server.register(registerOpts, callback);
|
||||
};
|
||||
|
@ -13,7 +13,9 @@
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
startsWith: startsWith
|
||||
startsWith: startsWith,
|
||||
requestPath: getRequestPath,
|
||||
isESRequest: isESRequest
|
||||
};
|
||||
|
||||
function startsWith(str) {
|
||||
@ -25,3 +27,11 @@ function startsWith(str) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getRequestPath(request) {
|
||||
return request.url.path;
|
||||
}
|
||||
|
||||
function isESRequest(request) {
|
||||
return startsWith(getRequestPath(request), '/elasticsearch');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user