diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index cefd7b5..0000000 --- a/.jshintrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "esversion": 6, - "esnext": true -} diff --git a/index.js b/index.js index 8a64363..47c68fe 100644 --- a/index.js +++ b/index.js @@ -12,12 +12,11 @@ * the License. */ -import session from './server/session'; -import binding from './server/binding'; -import healthCheck from './server/healthcheck'; +module.exports = (kibana) => { -export default (kibana) => { - const COOKIE_PASSWORD_SIZE = 32; + const session = require('./server/session'); + const proxy = require('./server/proxy'); + const healthCheck = require('./server/healthcheck'); return new kibana.Plugin({ require: ['elasticsearch'], @@ -28,19 +27,17 @@ export default (kibana) => { function config(Joi) { const cookie = Joi.object({ - name : Joi.string() - .default('keystone'), password : Joi.string() - .min(COOKIE_PASSWORD_SIZE) - .default(require('crypto').randomBytes(COOKIE_PASSWORD_SIZE).toString('hex')), + .min(16) + .default(require('crypto').randomBytes(16).toString('hex')), isSecure : Joi.boolean() - .default(process.env.NODE_ENV !== 'development'), + .default(false), ignoreErrors: Joi.boolean() .default(true), expiresIn : Joi.number() .positive() .integer() - .default(60 * 60 * 1000) // 1 hour + .default(24 * 60 * 60 * 1000) // 1 day }).default(); return Joi.object({ @@ -54,11 +51,9 @@ export default (kibana) => { } function init(server) { - server.log(['status', 'debug', 'keystone'], 'Initializing keystone plugin'); - binding(server).start(); - session(server).start(); + session(server); + proxy(server); healthCheck(this, server).start(); - server.log(['status', 'debug', 'keystone'], 'Initialized keystone plugin'); } }; diff --git a/package.json b/package.json index 20b490b..a7af291 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,13 @@ { "name": "fts-keystone", - "version": "0.0.2", - "description": "Keystone authentication & multitenancy support for Kibana 4.4.x", + "version": "0.0.1", + "description": "Keystone authentication support for Kibana 4.4.x", "author": "Fujitsu Enabling Software Technology GmbH", - "license": "Apache-2.0", + "licenses": "Apache-2.0", "keywords": [ "kibana", "authentication", "keystone", - "multitenancy", "plugin" ], "scripts": { @@ -23,9 +22,8 @@ }, "main": "gulpfile.js", "dependencies": { - "hoek": "^4.0.1", - "keystone-v3-client": "^0.0.7", - "yar": "^7.x.x" + "yar": "^4.2.0", + "keystone-v3-client": "^0.0.7" }, "repository": { "type": "git", @@ -45,14 +43,11 @@ "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", - "semver": "^5.3.0", - "sinon": "^1.17.3", - "wreck": "^8.0.0" + "sinon": "^1.17.3" } } diff --git a/server/__tests__/binding.spec.js b/server/__tests__/binding.spec.js deleted file mode 100644 index 1c752e6..0000000 --- a/server/__tests__/binding.spec.js +++ /dev/null @@ -1,45 +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 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)); - - }); - }); -}); diff --git a/server/__tests__/healthcheck.spec.js b/server/__tests__/healthcheck.spec.js index da44619..1da9106 100644 --- a/server/__tests__/healthcheck.spec.js +++ b/server/__tests__/healthcheck.spec.js @@ -52,7 +52,6 @@ describe('plugins/fts-keystone', ()=> { server = { log : sinon.stub(), - on : sinon.stub(), config: function () { return { get: configGet @@ -64,7 +63,6 @@ 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)=> { @@ -84,8 +82,8 @@ describe('plugins/fts-keystone', ()=> { check .run() - .then((status) => { - chai.expect(expectedStatus).to.be.equal(status); + .then((code) => { + chai.expect(expectedCode).to.be.equal(code); chai.expect(plugin.status.green.calledWith('Ready')).to.be.ok; }) .finally(done); @@ -94,7 +92,6 @@ 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)=> { @@ -114,8 +111,8 @@ describe('plugins/fts-keystone', ()=> { check .run() - .catch((status) => { - chai.expect(expectedStatus).to.be.equal(status); + .catch((code) => { + chai.expect(expectedCode).to.be.equal(code); chai.expect(plugin.status.red.calledWith('Unavailable')).to.be.ok; }) .finally(done); diff --git a/server/__tests__/proxy.spec.js b/server/__tests__/proxy.spec.js new file mode 100644 index 0000000..ef07373 --- /dev/null +++ b/server/__tests__/proxy.spec.js @@ -0,0 +1,174 @@ +/* + * 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; + } + + }); + + }); + }); +}); diff --git a/server/__tests__/retrieveToken.spec.js b/server/__tests__/retrieveToken.spec.js index dba95ff..03bb6b8 100644 --- a/server/__tests__/retrieveToken.spec.js +++ b/server/__tests__/retrieveToken.spec.js @@ -15,176 +15,157 @@ const sinon = require('sinon'); const chai = require('chai'); -const retrieveToken = require('../mt/auth/token'); -const CONSTANTS = require('../const'); -const RELOAD_SYMBOL = require('../mt/auth/reload'); +const retrieveToken = require('../proxy/retrieveToken'); describe('plugins/fts-keystone', ()=> { - describe('mt', ()=> { - describe('auth', () => { - describe('token', ()=> { + describe('proxy', ()=> { + describe('retrieveToken', ()=> { - let server; + let server; - beforeEach(()=> { - server = { - log: sinon.stub() - }; - }); + beforeEach(()=> { + server = { + log: sinon.stub() + }; + }); - it('should return isBoom if session not available', ()=> { - let request = {}; - let errMsg = /Session support is missing/; + it('should return isBoom if session not available', ()=> { + let request = {}; + let errMsg = /Session support is missing/; - chai.expect(()=> { - retrieveToken(server, request); - }).to.throw(errMsg); + chai.expect(()=> { + retrieveToken(server, request); + }).to.throw(errMsg); - request = { - yar: undefined - }; - chai.expect(()=> { - retrieveToken(server, request); - }).to.throw(errMsg); + request = { + session: undefined + }; + chai.expect(()=> { + retrieveToken(server, request); + }).to.throw(errMsg); - request = { - session: null - }; - chai.expect(()=> { - retrieveToken(server, request); - }).to.throw(errMsg); - }); + request = { + session: null + }; + chai.expect(()=> { + retrieveToken(server, request); + }).to.throw(errMsg); + }); - 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 = { - yar : { - 'get': sinon - .stub() - .withArgs(CONSTANTS.SESSION_TOKEN_KEY) - .returns(undefined) - }, - headers: {} - }; - - let result = retrieveToken(server, request); - chai.expect(result.isBoom).to.be.true; - chai.expect(result.output.payload.message).to.be.eq(expectedMsg); - chai.expect(result.output.statusCode).to.be.eq(401); - }); - - it('should use session token if requested does not have it', () => { - let expectedToken = 'SOME_RANDOM_TOKEN'; - let yar = { - 'reset': sinon.spy(), - 'set' : sinon.spy(), - 'get' : sinon.stub() - }; - let request = { - 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.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 = { - 'reset': sinon.spy(), - 'set' : sinon.spy(), - 'get' : sinon.stub() - }; - let request = { - 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.set.calledWithExactly( - CONSTANTS.SESSION_TOKEN_KEY, - expectedToken - ) - ).to.be.ok; - chai.expect( - 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 + 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: { + 'get': sinon .stub() - .withArgs(CONSTANTS.SESSION_TOKEN_KEY) - .returns(oldToken), - 'set' : sinon.spy() - }; - let token; - let request = { - yar : yar, - headers: headers - }; + .withArgs('keystone_token') + .returns(undefined) + }, + headers: {} + }; - token = retrieveToken(server, request); - chai.expect(token).to.not.be.undefined; - chai.expect(token).to.be.eql(RELOAD_SYMBOL); + let result = retrieveToken(server, request); + chai.expect(result.isBoom).to.be.true; + chai.expect(result.output.payload.message).to.be.eq(expectedMsg); + chai.expect(result.output.statusCode).to.be.eq(401); + }); - chai.expect(yar.reset.calledOnce).to.be.ok; - chai.expect(yar.get.calledOnce).to.be.ok; + 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) + }; + let request = { + session: yar, + headers: {} + }; + let token; - chai.expect(yar.set.callCount).to.be.eq(2); - chai.expect( - yar.set.calledWithExactly( - CONSTANTS.SESSION_TOKEN_KEY, - expectedToken - ) - ).to.be.ok; - chai.expect( - yar.set.calledWithExactly( - CONSTANTS.SESSION_TOKEN_CHANGED, - CONSTANTS.TOKEN_CHANGED_VALUE - ) - ).to.be.ok; + token = retrieveToken(server, request); + chai.expect(token).not.to.be.undefined; + chai.expect(token).to.be.eql(expectedToken); - }); + 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) + ).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) + }; + let request = { + session: yar, + headers: { + 'x-auth-token': expectedToken + } + }; + let token; + + token = retrieveToken(server, request); + chai.expect(token).to.not.be.undefined; + chai.expect(token).to.be.eql(expectedToken); + + chai.expect( + yar.get.calledOnce + ).to.be.ok; + chai.expect( + yar.set.calledOnce + ).to.be.ok; + chai.expect( + yar.set.calledWithExactly('keystone_token', expectedToken) + ).to.be.ok; + }); + + it('should update token in session if request\'s token is different', ()=> { + let expectedToken = 'SOME_RANDOM_TOKEN'; + let headers = { + 'x-auth-token': expectedToken + }; + let yar = { + 'get': sinon + .stub() + .withArgs('keystone_token') + .returns('SOME_OLD_TOKEN'), + 'set': sinon + .spy() + }; + let token; + let request = { + session: yar, + headers: headers + }; + + token = retrieveToken(server, request); + chai.expect(token).to.not.be.undefined; + chai.expect(token).to.be.eql(expectedToken); + + chai.expect( + yar.get.calledOnce + ).to.be.ok; + chai.expect( + yar.set.calledOnce + ).to.be.ok; + chai.expect( + yar.set.calledWithExactly('keystone_token', expectedToken) + ).to.be.ok; }); }); }); diff --git a/server/__tests__/routing.createProxy.spec.js b/server/__tests__/routing.createProxy.spec.js deleted file mode 100644 index 9f3c95a..0000000 --- a/server/__tests__/routing.createProxy.spec.js +++ /dev/null @@ -1,132 +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 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; - }); - - }); - }); - }); -}); diff --git a/server/__tests__/routing.reRouting.spec.js b/server/__tests__/routing.reRouting.spec.js deleted file mode 100644 index 8704809..0000000 --- a/server/__tests__/routing.reRouting.spec.js +++ /dev/null @@ -1,86 +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 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; - - }); - - - }); - }); - }); -}); diff --git a/server/__tests__/routing.spec.js b/server/__tests__/routing.spec.js deleted file mode 100644 index 4896c82..0000000 --- a/server/__tests__/routing.spec.js +++ /dev/null @@ -1,57 +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 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(); - } - - }); - - }); - }); -}); diff --git a/server/__tests__/verify.spec.js b/server/__tests__/verify.spec.js deleted file mode 100644 index d8e6000..0000000 --- a/server/__tests__/verify.spec.js +++ /dev/null @@ -1,114 +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 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; - }); - - }); - }); -}); diff --git a/server/__tests__/verifyIndexPattern.spec.js b/server/__tests__/verifyIndexPattern.spec.js deleted file mode 100644 index fa23a1d..0000000 --- a/server/__tests__/verifyIndexPattern.spec.js +++ /dev/null @@ -1,103 +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 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; - }); - - }); - }); - }); -}); diff --git a/server/binding/index.js b/server/binding/index.js deleted file mode 100644 index b997e9e..0000000 --- a/server/binding/index.js +++ /dev/null @@ -1,31 +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. - */ - -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)); - } - }; - -}; diff --git a/server/const.js b/server/const.js deleted file mode 100644 index 81850f9..0000000 --- a/server/const.js +++ /dev/null @@ -1,22 +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 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'); diff --git a/server/healthcheck/index.js b/server/healthcheck/index.js index 5c69b2f..ade00be 100644 --- a/server/healthcheck/index.js +++ b/server/healthcheck/index.js @@ -12,19 +12,18 @@ * the License. */ -import url from 'url'; -import Promise from 'bluebird'; +const Promise = require('bluebird'); +const url = require('url'); -import util from '../util'; +const util = require('../util/'); + +module.exports = function (plugin, server) { + let timeoutId; -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, @@ -34,12 +33,22 @@ module.exports = function healthcheck(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, @@ -48,13 +57,12 @@ module.exports = function healthcheck(plugin, server) { const statusCode = res.statusCode; if (statusCode >= 400) { plugin.status.red('Unavailable'); - reject(false); + reject(statusCode); } else { plugin.status.green('Ready'); - resolve(true); + resolve(statusCode); } }); - req.on('error', (error)=> { plugin.status.red('Unavailable: Failed to communicate with Keystone'); server.log(['keystone', 'healthcheck', 'error'], `${error.message}`); @@ -62,9 +70,14 @@ module.exports = function healthcheck(plugin, server) { }); req.end(); + }); } + function getHostname() { + return url.parse(keystoneUrl).hostname; + } + function start() { scheduleCheck(service.stop() ? 10000 : 1); } @@ -96,18 +109,4 @@ module.exports = function healthcheck(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; - } - }; diff --git a/server/mt/auth/_authenticate.js b/server/mt/auth/_authenticate.js deleted file mode 100644 index 2569694..0000000 --- a/server/mt/auth/_authenticate.js +++ /dev/null @@ -1,91 +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. - */ - -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 = ` - -reloading... -`; -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)); - }); - }); - }; -}; diff --git a/server/mt/auth/reload/index.js b/server/mt/auth/reload/index.js deleted file mode 100644 index f3d48d4..0000000 --- a/server/mt/auth/reload/index.js +++ /dev/null @@ -1,17 +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 RELOAD = Symbol('reload-me'); - -module.exports = RELOAD; diff --git a/server/mt/auth/strategy.js b/server/mt/auth/strategy.js deleted file mode 100644 index d176c67..0000000 --- a/server/mt/auth/strategy.js +++ /dev/null @@ -1,74 +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. - */ - -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); - }); - } - -}; diff --git a/server/mt/auth/verify.js b/server/mt/auth/verify.js deleted file mode 100644 index 055da34..0000000 --- a/server/mt/auth/verify.js +++ /dev/null @@ -1,62 +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. - */ - -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(); - }; -}; diff --git a/server/mt/index.js b/server/mt/index.js deleted file mode 100644 index 7c03b3a..0000000 --- a/server/mt/index.js +++ /dev/null @@ -1,64 +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. - */ - -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)) - ]); -} diff --git a/server/mt/kibana/_can_upgrade.js b/server/mt/kibana/_can_upgrade.js deleted file mode 100644 index bfb85f6..0000000 --- a/server/mt/kibana/_can_upgrade.js +++ /dev/null @@ -1,57 +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. - */ - -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; -}; diff --git a/server/mt/kibana/_configure.js b/server/mt/kibana/_configure.js deleted file mode 100644 index d0cc1a5..0000000 --- a/server/mt/kibana/_configure.js +++ /dev/null @@ -1,107 +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. - */ - -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') - }); - }; - } - -}; diff --git a/server/mt/kibana/_create.js b/server/mt/kibana/_create.js deleted file mode 100644 index e2ae0ea..0000000 --- a/server/mt/kibana/_create.js +++ /dev/null @@ -1,60 +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. - */ - -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); - }); - }); - -}; diff --git a/server/mt/kibana/_exists.js b/server/mt/kibana/_exists.js deleted file mode 100644 index f16a989..0000000 --- a/server/mt/kibana/_exists.js +++ /dev/null @@ -1,38 +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. - */ - -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); -} - diff --git a/server/mt/kibana/index.js b/server/mt/kibana/index.js deleted file mode 100644 index 1971457..0000000 --- a/server/mt/kibana/index.js +++ /dev/null @@ -1,39 +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. - */ - -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); - }); - } -}; diff --git a/server/mt/kibana/kibanaIndex.js b/server/mt/kibana/kibanaIndex.js deleted file mode 100644 index 8d914a2..0000000 --- a/server/mt/kibana/kibanaIndex.js +++ /dev/null @@ -1,29 +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. - */ - -/** - * 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; -} diff --git a/server/mt/projects/index.js b/server/mt/projects/index.js deleted file mode 100644 index dec11dd..0000000 --- a/server/mt/projects/index.js +++ /dev/null @@ -1,52 +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. - */ - -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); - }); - }); - -}; diff --git a/server/mt/routing/_create_agent.js b/server/mt/routing/_create_agent.js deleted file mode 100644 index 4fd7d87..0000000 --- a/server/mt/routing/_create_agent.js +++ /dev/null @@ -1,58 +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. - */ - -/* - 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(); diff --git a/server/mt/routing/_create_proxy.js b/server/mt/routing/_create_proxy.js deleted file mode 100644 index e6f962d..0000000 --- a/server/mt/routing/_create_proxy.js +++ /dev/null @@ -1,42 +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. - */ - -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); -}; diff --git a/server/mt/routing/_map_uri.js b/server/mt/routing/_map_uri.js deleted file mode 100644 index 8a478ed..0000000 --- a/server/mt/routing/_map_uri.js +++ /dev/null @@ -1,43 +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. - */ - -/* -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; -}; diff --git a/server/mt/routing/_re_route.js b/server/mt/routing/_re_route.js deleted file mode 100644 index 275d145..0000000 --- a/server/mt/routing/_re_route.js +++ /dev/null @@ -1,27 +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. - */ - -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(); - }; -}; diff --git a/server/mt/routing/_utils.js b/server/mt/routing/_utils.js deleted file mode 100644 index 2408003..0000000 --- a/server/mt/routing/_utils.js +++ /dev/null @@ -1,70 +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. - */ - -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')); -} diff --git a/server/mt/routing/index.js b/server/mt/routing/index.js deleted file mode 100644 index 08d8f16..0000000 --- a/server/mt/routing/index.js +++ /dev/null @@ -1,23 +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. - */ - -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')); - }); -}; diff --git a/server/mt/routing/routes/default.js b/server/mt/routing/routes/default.js deleted file mode 100644 index 28a937c..0000000 --- a/server/mt/routing/routes/default.js +++ /dev/null @@ -1,35 +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. - */ - -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 - } - } - }; -}; diff --git a/server/mt/routing/routes/kibana_index.js b/server/mt/routing/routes/kibana_index.js deleted file mode 100644 index d1ede83..0000000 --- a/server/mt/routing/routes/kibana_index.js +++ /dev/null @@ -1,57 +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. - */ - -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('/'); - } -} diff --git a/server/mt/routing/routes/mget.js b/server/mt/routing/routes/mget.js deleted file mode 100644 index 5dfade5..0000000 --- a/server/mt/routing/routes/mget.js +++ /dev/null @@ -1,69 +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. - */ - -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); - }); - }); - } -} diff --git a/server/mt/routing/routes/paths.js b/server/mt/routing/routes/paths.js deleted file mode 100644 index 4622d22..0000000 --- a/server/mt/routing/routes/paths.js +++ /dev/null @@ -1,66 +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. - */ - -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); - }); - }); - } -} diff --git a/server/mt/verify/_verify_index_pattern.js b/server/mt/verify/_verify_index_pattern.js deleted file mode 100644 index 2384c79..0000000 --- a/server/mt/verify/_verify_index_pattern.js +++ /dev/null @@ -1,41 +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. - */ - -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); - } -}; diff --git a/server/mt/verify/index.js b/server/mt/verify/index.js deleted file mode 100644 index 0b009cb..0000000 --- a/server/mt/verify/index.js +++ /dev/null @@ -1,48 +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. - */ - -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'; - } - - }; -}; diff --git a/server/mt/auth/scheme.js b/server/proxy/index.js similarity index 77% rename from server/mt/auth/scheme.js rename to server/proxy/index.js index fc56580..79abed2 100644 --- a/server/mt/auth/scheme.js +++ b/server/proxy/index.js @@ -12,10 +12,14 @@ * the License. */ -import authenticateFactory from './_authenticate'; +const proxy = require('./proxy'); -export default (server, opts) => { - return { - authenticate: authenticateFactory(server, opts) - }; +module.exports = function createProxy(server) { + server.ext( + 'onPreAuth', + proxy(server), + { + after: ['yar'] + } + ); }; diff --git a/server/proxy/proxy.js b/server/proxy/proxy.js new file mode 100644 index 0000000..29d3f6a --- /dev/null +++ b/server/proxy/proxy.js @@ -0,0 +1,93 @@ +/* + * 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'); +} diff --git a/server/mt/auth/token/index.js b/server/proxy/retrieveToken.js similarity index 63% rename from server/mt/auth/token/index.js rename to server/proxy/retrieveToken.js index 2bbcdbe..af6ba14 100644 --- a/server/mt/auth/token/index.js +++ b/server/proxy/retrieveToken.js @@ -12,15 +12,10 @@ * the License. */ -import Boom from 'boom'; -import { - SESSION_TOKEN_KEY, - SESSION_TOKEN_CHANGED, - TOKEN_CHANGED_VALUE -} from '../../../const'; -import RELOAD_SYMBOL from '../reload'; +const Boom = require('boom'); -const HEADER_NAME = 'x-auth-token'; +/** @module */ +module.exports = retrieveToken; /** * Retrieves token from the response header using key X-Keystone-Token. @@ -36,21 +31,21 @@ const HEADER_NAME = 'x-auth-token'; * * @returns {string} current token value */ -module.exports = (server, request) => { - if (!request.yar || request.yar === null) { - server.log(['status', 'keystone', 'error'], 'Session is not enabled'); +const HEADER_NAME = 'x-auth-token'; + +function retrieveToken(server, request) { + + if (!request.session || request.session === null) { + server.log(['keystone', 'error'], 'Session is not enabled'); throw new Error('Session support is missing'); } - // DEV PURPOSE ONLY - // request.yar.set(SESSION_TOKEN_KEY, 'a60e832483c34526a0c2bc3c6f8fa320'); - - let tokenFromSession = request.yar.get(SESSION_TOKEN_KEY); + let tokenFromSession = request.session.get('keystone_token'); let token = request.headers[HEADER_NAME]; if (!token && !tokenFromSession) { - server.log(['status', 'keystone', 'error'], + server.log(['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' @@ -59,28 +54,15 @@ module.exports = (server, request) => { if (!token && tokenFromSession) { token = tokenFromSession; - server.log(['status', 'debug', 'keystone'], + server.log(['keystone', 'debug'], 'Token lookup status: Found token in session' ); } else if ((token && !tokenFromSession) || (token !== tokenFromSession)) { - server.log(['status', 'debug', 'keystone'], + server.log(['keystone', 'debug'], 'Token lookup status: Token located in header/session or token changed' ); - - 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; - } - - request.yar.set(SESSION_TOKEN_KEY, token); + request.session.set('keystone_token', token); } - return request.yar.get(SESSION_TOKEN_KEY); -}; + return token; +} diff --git a/server/session/index.js b/server/session/index.js index 57a8a35..da04fb3 100644 --- a/server/session/index.js +++ b/server/session/index.js @@ -12,39 +12,34 @@ * the License. */ -import yarCookie from 'yar'; -import multiTenancy from '../mt'; +module.exports = function initSession(server) { -export default (server) => { const config = server.config(); - return { - start: ()=> { - server.register({ - register: yarCookie, - options : { - maxCookieSize: 4096, - name : config.get('fts-keystone.cookie.name'), - storeBlank : false, - cache : { - expiresIn: config.get('fts-keystone.cookie.expiresIn') - }, - cookieOptions: { - password : config.get('fts-keystone.cookie.password'), - isSecure : config.get('fts-keystone.cookie.isSecure'), - ignoreErrors: config.get('fts-keystone.cookie.ignoreErrors'), - clearInvalid: false - } - } - }, (error) => { - if (!error) { - server.log(['status', 'info', 'keystone'], 'Session registered'); - multiTenancy.bind(server); - } else { - server.log(['status', 'error', 'keystone'], error); - throw error; - } - }); + const registerOpts = { + register: require('yar'), + options : { + name : 'kibana_session', + storeBlank : false, + cache : { + expiresIn: config.get('fts-keystone.cookie.expiresIn') + }, + cookieOptions: { + password : config.get('fts-keystone.cookie.password'), + isSecure : config.get('fts-keystone.cookie.isSecure'), + ignoreErrors: config.get('fts-keystone.cookie.ignoreErrors'), + clearInvalid: true + } } }; + const callback = (error) => { + if (!error) { + server.log(['session', 'debug'], 'Session registered'); + } else { + server.log(['session', 'error'], error); + throw error; + } + }; + + server.register(registerOpts, callback); }; diff --git a/server/util/index.js b/server/util/index.js index 3211722..6bffcad 100644 --- a/server/util/index.js +++ b/server/util/index.js @@ -13,9 +13,7 @@ */ module.exports = { - startsWith: startsWith, - requestPath: getRequestPath, - isESRequest: isESRequest + startsWith: startsWith }; function startsWith(str) { @@ -27,11 +25,3 @@ function startsWith(str) { } return false; } - -function getRequestPath(request) { - return request.url.path; -} - -function isESRequest(request) { - return startsWith(getRequestPath(request), '/elasticsearch'); -}