From 2a8b4b4ff5cf55b82fe2af8f8390724c6688467f Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Fri, 6 Jul 2018 20:49:54 +0100 Subject: [PATCH 01/13] Implementation of Sasl authentication. --- .../lib/driver/authenticator.js | 14 ++++++ .../lib/driver/driver-remote-connection.js | 44 ++++++++---------- .../lib/driver/sasl-authenticator.js | 45 +++++++++++++++++++ .../gremlin-javascript/lib/utils.js | 23 +++++++++- 4 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js create mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js new file mode 100644 index 00000000000..053aecd552a --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js @@ -0,0 +1,14 @@ +'use strict'; + +/** @abstract */ +class Authenticator { + constructor(credentials) { + this._credentials = credentials; + } + + evaluateChallenge(ws, header) { + throw new Error("evaluateChallenge should be implemented"); + } +} + +module.exports = Authenticator; \ No newline at end of file diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js index 0f7cedb1cc7..153c2782e2b 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -22,7 +22,6 @@ */ 'use strict'; -const crypto = require('crypto'); const WebSocket = require('ws'); const util = require('util'); const RemoteConnection = require('./remote-connection').RemoteConnection; @@ -31,7 +30,8 @@ const serializer = require('../structure/io/graph-serializer'); const responseStatusCode = { success: 200, noContent: 204, - partialContent: 206 + partialContent: 206, + authenticationChallenge: 407, }; const defaultMimeType = 'application/vnd.gremlin-v2.0+json'; @@ -48,6 +48,7 @@ class DriverRemoteConnection extends RemoteConnection { * @param {Boolean} [options.rejectUnauthorized] Determines whether to verify or not the server certificate. * @param {String} [options.traversalSource] The traversal source. Defaults to: 'g'. * @param {GraphSONWriter} [options.writer] The writer to use. + * @param {Authenticator} [options.authenticator] The authentication handler to use. * @constructor */ constructor(url, options) { @@ -76,7 +77,13 @@ class DriverRemoteConnection extends RemoteConnection { const mimeType = options.mimeType || defaultMimeType; this._header = String.fromCharCode(mimeType.length) + mimeType; this.isOpen = false; + this.connectionError = false; + this.connectionErrorMessage = ''; this.traversalSource = options.traversalSource || 'g'; + + if (options.authenticator) { + this._authenticator = options.authenticator; + } } /** @@ -102,7 +109,7 @@ class DriverRemoteConnection extends RemoteConnection { /** @override */ submit(bytecode) { return this.open().then(() => new Promise((resolve, reject) => { - const requestId = getUuid(); + const requestId = utils.getUuid(); this._responseHandlers[requestId] = { callback: (err, result) => err ? reject(err) : resolve(result), result: null @@ -112,12 +119,12 @@ class DriverRemoteConnection extends RemoteConnection { })); } - _getRequest(id, bytecode) { + _getRequest(id, bytecode, op, args) { return ({ 'requestId': { '@type': 'g:UUID', '@value': id }, - 'op': 'bytecode', + 'op': op || 'bytecode', 'processor': 'traversal', - 'args': { + 'args': args || { 'gremlin': this._writer.adaptObject(bytecode), 'aliases': { 'g': this.traversalSource } } @@ -151,7 +158,11 @@ class DriverRemoteConnection extends RemoteConnection { return; } - if (response.status.code >= 400) { + if (response.status.code === responseStatusCode.authenticationChallenge && this._authenticator) { + this._authenticator.evaluateChallenge(this._ws, this._header); + return; + } + else if (response.status.code >= 400) { // callback in error return handler.callback( new Error(util.format('Server error: %s (%d)', response.status.message, response.status.code))); @@ -203,25 +214,6 @@ class DriverRemoteConnection extends RemoteConnection { } } -function getUuid() { - const buffer = crypto.randomBytes(16); - //clear the version - buffer[6] &= 0x0f; - //set the version 4 - buffer[6] |= 0x40; - //clear the variant - buffer[8] &= 0x3f; - //set the IETF variant - buffer[8] |= 0x80; - const hex = buffer.toString('hex'); - return ( - hex.substr(0, 8) + '-' + - hex.substr(8, 4) + '-' + - hex.substr(12, 4) + '-' + - hex.substr(16, 4) + '-' + - hex.substr(20, 12)); -} - const bufferFromString = (Int8Array.from !== Buffer.from && Buffer.from) || function newBuffer(text) { return new Buffer(text, 'utf8'); }; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js new file mode 100644 index 00000000000..1f08b7de8c1 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js @@ -0,0 +1,45 @@ +'use strict'; + +const Authenticator = require('./authenticator'); +const utils = require('../utils'); + +class SaslAuthenticator extends Authenticator { + /** + * Creates a new instance of SaslAuthenticator. + * @param {Object} [credentials] The authentication credential options. + * @param {String} [credentials.username] The user for the authentication response. + * @param {String} [credentials.password] The plaintext password for authentication response. + * @constructor + */ + constructor(credentials) { + super(credentials); + } + + evaluateChallenge(ws, header) { + const message = bufferFromString(header + JSON.stringify({ + 'requestId': { '@type': 'g:UUID', '@value': utils.getUuid() }, + 'op': 'authentication', + 'processor': 'traversal', + 'args': { + 'sasl': this.saslArgument() + } + })); + + return ws.send(message); + } + + saslArgument() { + if (this._credentials.username === null || this._credentials.username.length === 0 + || this._credentials.password === null || this._credentials.password.length === 0 ) { + return ''; + } + return new Buffer(`\0${this._credentials.username}\0${this._credentials.password}`).toString('base64'); + } +} + + +const bufferFromString = (Int8Array.from !== Buffer.from && Buffer.from) || function newBuffer(text) { + return new Buffer(text, 'utf8'); +}; + +module.exports = SaslAuthenticator; \ No newline at end of file diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js index e3a001c51d4..7abc5fe2a81 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js @@ -23,6 +23,8 @@ */ 'use strict'; +const crypto = require('crypto'); + exports.toLong = function toLong(value) { return new Long(value); }; @@ -32,4 +34,23 @@ const Long = exports.Long = function Long(value) { throw new TypeError('Ty') } this.value = value.toString(); -}; \ No newline at end of file +}; + +exports.getUuid = function getUuid() { + const buffer = crypto.randomBytes(16); + //clear the version + buffer[6] &= 0x0f; + //set the version 4 + buffer[6] |= 0x40; + //clear the variant + buffer[8] &= 0x3f; + //set the IETF variant + buffer[8] |= 0x80; + const hex = buffer.toString('hex'); + return ( + hex.substr(0, 8) + '-' + + hex.substr(8, 4) + '-' + + hex.substr(12, 4) + '-' + + hex.substr(16, 4) + '-' + + hex.substr(20, 12)); +} \ No newline at end of file From c8ae3c8cae9cbe7963fa4aea55ba4409cb73fd86 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Fri, 6 Jul 2018 21:12:16 +0100 Subject: [PATCH 02/13] Unsaved changes uploaded --- .../lib/driver/driver-remote-connection.js | 10 ++-- .../integration/sasl-authentication-tests.js | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js index 153c2782e2b..0f4674542a9 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -33,7 +33,7 @@ const responseStatusCode = { partialContent: 206, authenticationChallenge: 407, }; -const defaultMimeType = 'application/vnd.gremlin-v2.0+json'; +const defaultMimeType = 'application/vnd.gremlin-v3.0+json'; class DriverRemoteConnection extends RemoteConnection { /** @@ -77,8 +77,6 @@ class DriverRemoteConnection extends RemoteConnection { const mimeType = options.mimeType || defaultMimeType; this._header = String.fromCharCode(mimeType.length) + mimeType; this.isOpen = false; - this.connectionError = false; - this.connectionErrorMessage = ''; this.traversalSource = options.traversalSource || 'g'; if (options.authenticator) { @@ -119,12 +117,12 @@ class DriverRemoteConnection extends RemoteConnection { })); } - _getRequest(id, bytecode, op, args) { + _getRequest(id, bytecode) { return ({ 'requestId': { '@type': 'g:UUID', '@value': id }, - 'op': op || 'bytecode', + 'op': 'bytecode', 'processor': 'traversal', - 'args': args || { + 'args': { 'gremlin': this._writer.adaptObject(bytecode), 'aliases': { 'g': this.traversalSource } } diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js new file mode 100644 index 00000000000..c5dc48f0045 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +const assert = require('assert'); +const Bytecode = require('../../lib/process/bytecode'); +const graphModule = require('../../lib/structure/graph'); +const helper = require('../helper'); + +let connection; + +describe('DriverRemoteConnectionWithSaslAuthenticator', function () { + before(function () { + connection = helper.getSecureConnectionWithAuthenticator('gmodern'); + return connection.open(); + }); + after(function () { + return connection.close(); + }); + describe('#submit()', function () { + it('should send the request with valid credentials and parse the response', function () { + return connection.submit(new Bytecode().addStep('V', []).addStep('tail', [])) + .then(function (response) { + assert.ok(response); + assert.ok(response.traversers); + assert.strictEqual(response.traversers.length, 1); + assert.ok(response.traversers[0].object instanceof graphModule.Vertex); + }); + }); + it('should send the request with invaid credentials and parse the response error', function () { + connection._authenticator.username = 'Bob'; + return connection.submit(new Bytecode().addStep('V', []).addStep('tail', [])) + .catch(function (err) { + assert.ok(err); + assert.ok(err.message.indexOf('401') > 0); + }); + }); + }); +}); \ No newline at end of file From 6f65b92e7ac92cf4da105d9fef71ac51c6121013 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Mon, 9 Jul 2018 22:49:41 +0100 Subject: [PATCH 03/13] Abstracted transport out of the authenticator class. Added authenticator and sasl implmentation to global export. Added more testing of the sasl authenticator implementation. --- .../javascript/gremlin-javascript/index.js | 6 ++++- .../lib/driver/authenticator.js | 4 +-- .../lib/driver/driver-remote-connection.js | 17 +++++++----- .../lib/driver/remote-connection.js | 4 ++- .../lib/driver/sasl-authenticator.js | 27 +++++-------------- .../integration/sasl-authentication-tests.js | 10 ++++++- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js index d4c6d88de9d..5ada391f8a6 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js @@ -31,13 +31,17 @@ const rc = require('./lib/driver/remote-connection'); const Bytecode = require('./lib/process/bytecode'); const utils = require('./lib/utils'); const DriverRemoteConnection = require('./lib/driver/driver-remote-connection'); +const Auth = require('./lib/driver/authenticator'); +const SaslAuth = require('./lib/driver/sasl-authenticator'); module.exports = { driver: { RemoteConnection: rc.RemoteConnection, RemoteStrategy: rc.RemoteStrategy, RemoteTraversal: rc.RemoteTraversal, - DriverRemoteConnection: DriverRemoteConnection + DriverRemoteConnection: DriverRemoteConnection, + Authenticator: Auth, + SaslAuthenticator: SaslAuth }, process: { Bytecode: Bytecode, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js index 053aecd552a..fe830773de3 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js @@ -6,9 +6,9 @@ class Authenticator { this._credentials = credentials; } - evaluateChallenge(ws, header) { + async evaluateChallenge(challenge) { throw new Error("evaluateChallenge should be implemented"); } } -module.exports = Authenticator; \ No newline at end of file +module.exports = Authenticator; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js index 0f4674542a9..facc0b863e9 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -105,24 +105,24 @@ class DriverRemoteConnection extends RemoteConnection { } /** @override */ - submit(bytecode) { + submit(bytecode, op, args) { return this.open().then(() => new Promise((resolve, reject) => { const requestId = utils.getUuid(); this._responseHandlers[requestId] = { callback: (err, result) => err ? reject(err) : resolve(result), result: null }; - const message = bufferFromString(this._header + JSON.stringify(this._getRequest(requestId, bytecode))); + const message = bufferFromString(this._header + JSON.stringify(this._getRequest(requestId, bytecode, op, args))); this._ws.send(message); })); } - _getRequest(id, bytecode) { + _getRequest(id, bytecode, op, args) { return ({ 'requestId': { '@type': 'g:UUID', '@value': id }, - 'op': 'bytecode', + 'op': op || 'bytecode', 'processor': 'traversal', - 'args': { + 'args': args || { 'gremlin': this._writer.adaptObject(bytecode), 'aliases': { 'g': this.traversalSource } } @@ -157,7 +157,12 @@ class DriverRemoteConnection extends RemoteConnection { } if (response.status.code === responseStatusCode.authenticationChallenge && this._authenticator) { - this._authenticator.evaluateChallenge(this._ws, this._header); + this._authenticator.evaluateChallenge(response).then(res => { + this.submit('', 'authentication', res); + }, err => { + return handler.callback(err); + }); + return; } else if (response.status.code >= 400) { diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js index 2672ff45c3d..46918df7f65 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js @@ -33,9 +33,11 @@ class RemoteConnection { /** * @abstract * @param {Bytecode} bytecode + * @param {String} op Operation to perform, defaults to bytecode. + * @param {Object} args The arguments for the operation. Defaults to * @returns {Promise} */ - submit(bytecode) { + submit(bytecode, op, args) { throw new Error('submit() was not implemented'); }; } diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js index 1f08b7de8c1..d7db3de3cdd 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js @@ -1,7 +1,6 @@ 'use strict'; const Authenticator = require('./authenticator'); -const utils = require('../utils'); class SaslAuthenticator extends Authenticator { /** @@ -15,31 +14,17 @@ class SaslAuthenticator extends Authenticator { super(credentials); } - evaluateChallenge(ws, header) { - const message = bufferFromString(header + JSON.stringify({ - 'requestId': { '@type': 'g:UUID', '@value': utils.getUuid() }, - 'op': 'authentication', - 'processor': 'traversal', - 'args': { - 'sasl': this.saslArgument() - } - })); - - return ws.send(message); + async evaluateChallenge(challenge) { + return Promise.resolve({ 'sasl': this.saslArgument() }); } saslArgument() { - if (this._credentials.username === null || this._credentials.username.length === 0 - || this._credentials.password === null || this._credentials.password.length === 0 ) { - return ''; + if (typeof this._credentials.username === "undefined" || this._credentials.username.length === 0 + || typeof this._credentials.password === "undefined" || this._credentials.password.length === 0 ) { + throw new Error('No Credentials Supplied'); } return new Buffer(`\0${this._credentials.username}\0${this._credentials.password}`).toString('base64'); } } - -const bufferFromString = (Int8Array.from !== Buffer.from && Buffer.from) || function newBuffer(text) { - return new Buffer(text, 'utf8'); -}; - -module.exports = SaslAuthenticator; \ No newline at end of file +module.exports = SaslAuthenticator; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js index c5dc48f0045..d517d228088 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js @@ -55,5 +55,13 @@ describe('DriverRemoteConnectionWithSaslAuthenticator', function () { assert.ok(err.message.indexOf('401') > 0); }); }); + it('should send incorrect conifugration to the authenticator and parse the response error', function () { + delete connection._authenticator.username; + delete connection._authenticator.password; + return connection.submit(new Bytecode().addStep('V', []).addStep('tail', [])) + .catch(function (err) { + assert.ok(err); + }); + }); }); -}); \ No newline at end of file +}); From 0e05499d1e131c318cc7bb8e486792ba38773dd5 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Tue, 10 Jul 2018 10:47:14 +0100 Subject: [PATCH 04/13] Removed async keyword from Authenticator class for JS compatability. --- .../javascript/gremlin-javascript/lib/driver/authenticator.js | 2 +- .../gremlin-javascript/lib/driver/sasl-authenticator.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js index fe830773de3..c57cec5de15 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js @@ -6,7 +6,7 @@ class Authenticator { this._credentials = credentials; } - async evaluateChallenge(challenge) { + evaluateChallenge(challenge) { throw new Error("evaluateChallenge should be implemented"); } } diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js index d7db3de3cdd..fb4c5afd493 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js @@ -14,7 +14,7 @@ class SaslAuthenticator extends Authenticator { super(credentials); } - async evaluateChallenge(challenge) { + evaluateChallenge(challenge) { return Promise.resolve({ 'sasl': this.saslArgument() }); } From f67fea49da3ca788f39c3824c3398f1a7aae65f0 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Wed, 11 Jul 2018 21:38:56 +0100 Subject: [PATCH 05/13] Submit can accept an existing requestId. Args supplied to _getRequest are run through the GraphSONWriter. Improved promise error handling on authenticator challenge. Tidying up of code formatting and documentation. Integration test updated to use secure gremlin server port. --- .../lib/driver/driver-remote-connection.js | 50 ++++++++++++++----- .../lib/driver/remote-connection.js | 5 +- .../gremlin-javascript/test/helper.js | 8 +++ 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js index facc0b863e9..bec599b1ce2 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -105,19 +105,25 @@ class DriverRemoteConnection extends RemoteConnection { } /** @override */ - submit(bytecode, op, args) { + submit(bytecode, op, args, requestId) { return this.open().then(() => new Promise((resolve, reject) => { - const requestId = utils.getUuid(); - this._responseHandlers[requestId] = { - callback: (err, result) => err ? reject(err) : resolve(result), - result: null - }; + if (requestId === null || requestId === undefined) { + requestId = utils.getUuid(); + this._responseHandlers[requestId] = { + callback: (err, result) => err ? reject(err) : resolve(result), + result: null + }; + } const message = bufferFromString(this._header + JSON.stringify(this._getRequest(requestId, bytecode, op, args))); this._ws.send(message); })); } _getRequest(id, bytecode, op, args) { + if (args) { + args = this._adaptArgs(args); + } + return ({ 'requestId': { '@type': 'g:UUID', '@value': id }, 'op': op || 'bytecode', @@ -158,12 +164,10 @@ class DriverRemoteConnection extends RemoteConnection { if (response.status.code === responseStatusCode.authenticationChallenge && this._authenticator) { this._authenticator.evaluateChallenge(response).then(res => { - this.submit('', 'authentication', res); - }, err => { - return handler.callback(err); - }); - - return; + return this.submit(null, 'authentication', res, response.requestId); + }).catch(handler.callback); + + return; } else if (response.status.code >= 400) { // callback in error @@ -199,6 +203,28 @@ class DriverRemoteConnection extends RemoteConnection { delete this._responseHandlers[requestId]; } + /** + * Takes the given args map and ensures all arguments are passed through to _write.adaptObject + * @param {Object} args Map of arguments to process + * @returns {Object} + * @private + */ + _adaptArgs(args) { + if (Array.isArray(args)) { + return args.map(val => this._adaptArgs(val)); + } + + if (args instanceof Object) { + let newObj = {}; + Object.keys(args).forEach((key, val) => { + newObj[key] = this._adaptArgs(val); + }); + return newObj; + } + + return this._writer.adaptObject(args); + } + /** * Closes the Connection. * @return {Promise} diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js index 46918df7f65..be6f962a6a5 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js @@ -34,10 +34,11 @@ class RemoteConnection { * @abstract * @param {Bytecode} bytecode * @param {String} op Operation to perform, defaults to bytecode. - * @param {Object} args The arguments for the operation. Defaults to + * @param {Object} args The arguments for the operation. Defaults to an associative array containing values for "aliases" and "gremlin" keyss. + * @param {String} requestId A requestId for the current request. If none provided then a requestId is generated internally. * @returns {Promise} */ - submit(bytecode, op, args) { + submit(bytecode, op, args, requestId) { throw new Error('submit() was not implemented'); }; } diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js index 546840efd8a..99fea7f497f 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js @@ -26,4 +26,12 @@ const DriverRemoteConnection = require('../lib/driver/driver-remote-connection') exports.getConnection = function getConnection(traversalSource) { return new DriverRemoteConnection('ws://localhost:45940/gremlin', { traversalSource: traversalSource }); +<<<<<<< HEAD +======= +}; + +exports.getSecureConnectionWithAuthenticator = function getConnection(traversalSource) { + const authenticator = new SaslAuthenticator({ username: 'stephen', password: 'password' }); + return new DriverRemoteConnection('wss://localhost:45941/gremlin', { traversalSource: traversalSource, authenticator: authenticator, rejectUnauthorized: false }); +>>>>>>> 65de11c3c8... Submit can accept an existing requestId. }; \ No newline at end of file From 80987330fb9a724e7c94cf1c49f1bec1c8d125da Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Fri, 13 Jul 2018 00:58:52 +0100 Subject: [PATCH 06/13] Fix for incorrect code in _adaptArgs function. --- .../gremlin-javascript/lib/driver/driver-remote-connection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js index bec599b1ce2..c60492e6c1a 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -216,8 +216,8 @@ class DriverRemoteConnection extends RemoteConnection { if (args instanceof Object) { let newObj = {}; - Object.keys(args).forEach((key, val) => { - newObj[key] = this._adaptArgs(val); + Object.keys(args).forEach((key) => { + newObj[key] = this._adaptArgs(args[key]); }); return newObj; } From 5e5a09b35c4e897979c35be0dd3485c5900964e6 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Wed, 18 Jul 2018 23:06:38 +0100 Subject: [PATCH 07/13] Updated Sasl test to remove gmodern traversal --- .../test/integration/sasl-authentication-tests.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js index d517d228088..f38d0873cad 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js @@ -31,7 +31,8 @@ let connection; describe('DriverRemoteConnectionWithSaslAuthenticator', function () { before(function () { - connection = helper.getSecureConnectionWithAuthenticator('gmodern'); + this.timeout(20000); + connection = helper.getSecureConnectionWithAuthenticator(null); return connection.open(); }); after(function () { @@ -43,11 +44,11 @@ describe('DriverRemoteConnectionWithSaslAuthenticator', function () { .then(function (response) { assert.ok(response); assert.ok(response.traversers); - assert.strictEqual(response.traversers.length, 1); - assert.ok(response.traversers[0].object instanceof graphModule.Vertex); + //assert.strictEqual(response.traversers.length, 1); + //assert.ok(response.traversers[0].object instanceof graphModule.Vertex); }); }); - it('should send the request with invaid credentials and parse the response error', function () { + it('should send the request with invalid credentials and parse the response error', function () { connection._authenticator.username = 'Bob'; return connection.submit(new Bytecode().addStep('V', []).addStep('tail', [])) .catch(function (err) { From bdecf85fb5a0df9d11bffc948bd213921125e0de Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Wed, 18 Jul 2018 23:35:12 +0100 Subject: [PATCH 08/13] Switched from TLS sockets to normal socket on connection to gremlin secure server. --- .../src/main/javascript/gremlin-javascript/test/helper.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js index 99fea7f497f..5a902966e30 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js @@ -26,12 +26,9 @@ const DriverRemoteConnection = require('../lib/driver/driver-remote-connection') exports.getConnection = function getConnection(traversalSource) { return new DriverRemoteConnection('ws://localhost:45940/gremlin', { traversalSource: traversalSource }); -<<<<<<< HEAD -======= }; exports.getSecureConnectionWithAuthenticator = function getConnection(traversalSource) { const authenticator = new SaslAuthenticator({ username: 'stephen', password: 'password' }); - return new DriverRemoteConnection('wss://localhost:45941/gremlin', { traversalSource: traversalSource, authenticator: authenticator, rejectUnauthorized: false }); ->>>>>>> 65de11c3c8... Submit can accept an existing requestId. + return new DriverRemoteConnection('ws://localhost:45941/gremlin', { traversalSource: traversalSource, authenticator: authenticator, rejectUnauthorized: false }); }; \ No newline at end of file From 7a5cb9c8053dbd303f70f5bbca95dcde528cf886 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Sun, 29 Jul 2018 15:59:08 +0100 Subject: [PATCH 09/13] Added support for mechanism plugins for Sasl handler --- .../auth/mechanisms/sasl-mechanism-base.js | 36 ++++++++++++++++++ .../auth/mechanisms/sasl-mechanism-plain.js | 37 +++++++++++++++++++ .../lib/driver/driver-remote-connection.js | 2 +- .../lib/driver/sasl-authenticator.js | 28 +++++++------- .../gremlin-javascript/test/helper.js | 11 +++++- 5 files changed, 97 insertions(+), 17 deletions(-) create mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js create mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js new file mode 100644 index 00000000000..4b75778165a --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +'use strict'; + +/** @abstract */ +class SaslMechanismBase { + get name() { + return null; + } + + setopts(options) { + this._options = options; + } + + evaluateChallenge(challenge) { + throw new Error("evaluateChallenge should be implemented"); + } +} + +module.exports = SaslMechanismBase; \ No newline at end of file diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js new file mode 100644 index 00000000000..be418bed401 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js @@ -0,0 +1,37 @@ +'use strict'; + +const SaslMechanismBase = require('./sasl-mechanism-base'); + +class SaslMechanismPlain extends SaslMechanismBase { + get name() { + return 'PLAIN'; + } + + evaluateChallenge(challenge) { + if (this._hasInitialResponse(challenge)) { + return Promise.resolve({ 'saslMechanism': this.name, 'sasl': this._saslArgument() }); + } + + return Promise.resolve({ 'sasl': this._saslArgument() }); + } + + _saslArgument() { + if (this._options.username === undefined || this._options.username.length === 0 + || this._options.password === undefined || this._options.password.length === 0 ) { + throw new Error('No Credentials Supplied'); + } + + const authstr = ((this._options.authId !== undefined && this._options.authId.length) ? this._options.authId : '') + + `\0${this._options.username}\0${this._options.password}`; + return new Buffer(authstr).toString('base64'); + } + + _hasInitialResponse(challenge) { + if (challenge === undefined || challenge === null) { + return false; + } + return true; + } +} + +module.exports = SaslMechanismPlain; \ No newline at end of file diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js index c60492e6c1a..04deee7fbb1 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -163,7 +163,7 @@ class DriverRemoteConnection extends RemoteConnection { } if (response.status.code === responseStatusCode.authenticationChallenge && this._authenticator) { - this._authenticator.evaluateChallenge(response).then(res => { + this._authenticator.evaluateChallenge(response.result.data).then(res => { return this.submit(null, 'authentication', res, response.requestId); }).catch(handler.callback); diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js index fb4c5afd493..4b63ab536a5 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js @@ -5,25 +5,25 @@ const Authenticator = require('./authenticator'); class SaslAuthenticator extends Authenticator { /** * Creates a new instance of SaslAuthenticator. - * @param {Object} [credentials] The authentication credential options. - * @param {String} [credentials.username] The user for the authentication response. - * @param {String} [credentials.password] The plaintext password for authentication response. + * @param {Object} [options] The authentication options. + * @param {Object} [options.mechanism] The mechanism to be used for authentication. + * @param {String} [options.hostname] The hostname of the client. + * @param {*} [options] Other mechanism specific options. * @constructor */ - constructor(credentials) { - super(credentials); + constructor(options) { + super(options); + + if (options.mechanism === null || options.mechanism === undefined) { + throw new Error('No Sasl Mechanism Specified'); + } + + this._options = options; + this._options.mechanism.setopts(this._options); } evaluateChallenge(challenge) { - return Promise.resolve({ 'sasl': this.saslArgument() }); - } - - saslArgument() { - if (typeof this._credentials.username === "undefined" || this._credentials.username.length === 0 - || typeof this._credentials.password === "undefined" || this._credentials.password.length === 0 ) { - throw new Error('No Credentials Supplied'); - } - return new Buffer(`\0${this._credentials.username}\0${this._credentials.password}`).toString('base64'); + return Promise.resolve(this._options.mechanism.evaluateChallenge(challenge)); } } diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js index 5a902966e30..25e9557f78d 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js @@ -21,14 +21,21 @@ * @author Jorge Bay Gondra */ 'use strict'; +const os = require('os'); const DriverRemoteConnection = require('../lib/driver/driver-remote-connection'); +const SaslAuthenticator = require('../lib/driver/sasl-authenticator'); +const SaslMechanismPlain = require('../lib/driver/auth/mechanisms/sasl-mechanism-plain'); exports.getConnection = function getConnection(traversalSource) { return new DriverRemoteConnection('ws://localhost:45940/gremlin', { traversalSource: traversalSource }); }; exports.getSecureConnectionWithAuthenticator = function getConnection(traversalSource) { - const authenticator = new SaslAuthenticator({ username: 'stephen', password: 'password' }); - return new DriverRemoteConnection('ws://localhost:45941/gremlin', { traversalSource: traversalSource, authenticator: authenticator, rejectUnauthorized: false }); + const authenticator = new SaslAuthenticator({ mechanism: new SaslMechanismPlain(), username: 'stephen', password: 'password', authId: os.hostname() }); + return new DriverRemoteConnection('wss://localhost:45941/gremlin', { + traversalSource: traversalSource, + authenticator: authenticator, + rejectUnauthorized: false + }); }; \ No newline at end of file From 8453b61cfd398fe53387439056315304bc22ea4f Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Sun, 29 Jul 2018 16:02:12 +0100 Subject: [PATCH 10/13] Fix for integration test Gremlin Server connection. --- .../src/main/javascript/gremlin-javascript/test/helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js index 25e9557f78d..e1c24a39b07 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js @@ -33,7 +33,7 @@ exports.getConnection = function getConnection(traversalSource) { exports.getSecureConnectionWithAuthenticator = function getConnection(traversalSource) { const authenticator = new SaslAuthenticator({ mechanism: new SaslMechanismPlain(), username: 'stephen', password: 'password', authId: os.hostname() }); - return new DriverRemoteConnection('wss://localhost:45941/gremlin', { + return new DriverRemoteConnection('ws://localhost:45941/gremlin', { traversalSource: traversalSource, authenticator: authenticator, rejectUnauthorized: false From 18598baff6039dd281bb18d42524c3f317d97218 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Wed, 1 Aug 2018 20:30:13 +0100 Subject: [PATCH 11/13] Tidying-up of authenticator and integeration tests. --- .../javascript/gremlin-javascript/index.js | 6 +- .../lib/driver/auth/authenticator.js | 19 +++++ .../auth/mechanisms/sasl-mechanism-base.js | 18 ++++- .../auth/mechanisms/sasl-mechanism-plain.js | 81 ++++++++++++++++--- .../driver/{ => auth}/sasl-authenticator.js | 10 +-- .../lib/driver/authenticator.js | 14 ---- .../gremlin-javascript/test/helper.js | 4 +- .../integration/sasl-authentication-tests.js | 8 +- 8 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/authenticator.js rename gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/{ => auth}/sasl-authenticator.js (73%) delete mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js index 5ada391f8a6..d4c6d88de9d 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js @@ -31,17 +31,13 @@ const rc = require('./lib/driver/remote-connection'); const Bytecode = require('./lib/process/bytecode'); const utils = require('./lib/utils'); const DriverRemoteConnection = require('./lib/driver/driver-remote-connection'); -const Auth = require('./lib/driver/authenticator'); -const SaslAuth = require('./lib/driver/sasl-authenticator'); module.exports = { driver: { RemoteConnection: rc.RemoteConnection, RemoteStrategy: rc.RemoteStrategy, RemoteTraversal: rc.RemoteTraversal, - DriverRemoteConnection: DriverRemoteConnection, - Authenticator: Auth, - SaslAuthenticator: SaslAuth + DriverRemoteConnection: DriverRemoteConnection }, process: { Bytecode: Bytecode, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/authenticator.js new file mode 100644 index 00000000000..b57e3853f12 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/authenticator.js @@ -0,0 +1,19 @@ +'use strict'; + +/** @abstract */ +class Authenticator { + constructor(options) { + this._options = options; + } + + /** + * @abstract + * Evaluates the challenge from the server and returns appropriate response. + * @param {String} challenge Challenge string presented by the server. + */ + evaluateChallenge(challenge) { + throw new Error("evaluateChallenge should be implemented"); + } +} + +module.exports = Authenticator; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js index 4b75778165a..b909cc8b0a9 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-base.js @@ -20,17 +20,33 @@ /** @abstract */ class SaslMechanismBase { + constructor(options) { + this.setopts(options); + } + + /** + * Returns the name of the mechanism + */ get name() { return null; } + /** + * Set the options for the mechanism + * @param {object} options Options specific to the mechanism + */ setopts(options) { this._options = options; } + /** + * @abstract + * Evaluates the challenge from the server and returns appropriate response + * @param {String} challenge Challenge string presented by the server + */ evaluateChallenge(challenge) { throw new Error("evaluateChallenge should be implemented"); } } -module.exports = SaslMechanismBase; \ No newline at end of file +module.exports = SaslMechanismBase; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js index be418bed401..597a9293cbc 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/mechanisms/sasl-mechanism-plain.js @@ -1,31 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ 'use strict'; const SaslMechanismBase = require('./sasl-mechanism-base'); class SaslMechanismPlain extends SaslMechanismBase { + /** + * Creates a new instance of SaslMechanismPlain. + * @param {Object} [options] The mechanism options. + * @param {String} [options.authzid] The identity of the client. + * @param {String} [options.username] The identity of user with access to server. + * @param {String} [options.password] The password of user with access to server. + * @constructor + */ + constructor(options) { + super(options); + + if (this._options.username === undefined + || this._options.username === null + || this._options.username.length == 0 + || this._options.password === undefined + || this._options.password === null + || this._options.password.length == 0 + ) { + throw new Error('Missing credentials for SASL PLAIN mechanism'); + } + } + + /** + * Returns the name of the mechanism + */ get name() { return 'PLAIN'; } + /** + * Evaluates the challenge from the server and returns appropriate response. + * @param {String} challenge Challenge string presented by the server. + * @return {Object} A Promise that resolves to a valid sasl response object. + */ evaluateChallenge(challenge) { if (this._hasInitialResponse(challenge)) { - return Promise.resolve({ 'saslMechanism': this.name, 'sasl': this._saslArgument() }); + return Promise.resolve({ + 'saslMechanism': this.name, + 'sasl': this._saslArgument(this._options.authzid, this._options.username, this._options.password) + }); } - return Promise.resolve({ 'sasl': this._saslArgument() }); + return Promise.resolve({ 'sasl': this._saslArgument(this._options.authzid, this._options.username, this._options.password) }); } - _saslArgument() { - if (this._options.username === undefined || this._options.username.length === 0 - || this._options.password === undefined || this._options.password.length === 0 ) { - throw new Error('No Credentials Supplied'); - } + /** + * Generates a base64 encoded sasl argument based on the given parameters. + * @param {String} authzid Identitiy of the client. + * @param {String} username The identity of user with access to server. + * @param {String} password The password of user with access to server. + */ + _saslArgument(authzid, username, password) { + if (authzid === undefined || authzid === null) authzid = ''; + if (username === undefined || username === null) username = ''; + if (password === undefined || password.length === null) password = ''; - const authstr = ((this._options.authId !== undefined && this._options.authId.length) ? this._options.authId : '') - + `\0${this._options.username}\0${this._options.password}`; - return new Buffer(authstr).toString('base64'); + return new Buffer(`${authzid}\0${username}\0${password}`).toString('base64'); } + /** + * Checks challenge to see if we have the initial sasl response from the server. + * @param {String} challenge The challenge string from the server. + * @return {Boolean} + */ _hasInitialResponse(challenge) { if (challenge === undefined || challenge === null) { return false; @@ -34,4 +93,4 @@ class SaslMechanismPlain extends SaslMechanismBase { } } -module.exports = SaslMechanismPlain; \ No newline at end of file +module.exports = SaslMechanismPlain; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/sasl-authenticator.js similarity index 73% rename from gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js rename to gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/sasl-authenticator.js index 4b63ab536a5..eb1fbe82999 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/sasl-authenticator.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/sasl-authenticator.js @@ -7,8 +7,6 @@ class SaslAuthenticator extends Authenticator { * Creates a new instance of SaslAuthenticator. * @param {Object} [options] The authentication options. * @param {Object} [options.mechanism] The mechanism to be used for authentication. - * @param {String} [options.hostname] The hostname of the client. - * @param {*} [options] Other mechanism specific options. * @constructor */ constructor(options) { @@ -17,11 +15,13 @@ class SaslAuthenticator extends Authenticator { if (options.mechanism === null || options.mechanism === undefined) { throw new Error('No Sasl Mechanism Specified'); } - - this._options = options; - this._options.mechanism.setopts(this._options); } + /** + * Evaluates the challenge from the server and returns appropriate response. + * @param {String} challenge Challenge string presented by the server. + * @return {Object} A Promise that resolves to a valid sasl response object. + */ evaluateChallenge(challenge) { return Promise.resolve(this._options.mechanism.evaluateChallenge(challenge)); } diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js deleted file mode 100644 index c57cec5de15..00000000000 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/authenticator.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -/** @abstract */ -class Authenticator { - constructor(credentials) { - this._credentials = credentials; - } - - evaluateChallenge(challenge) { - throw new Error("evaluateChallenge should be implemented"); - } -} - -module.exports = Authenticator; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js index e1c24a39b07..82c47be9a2d 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js @@ -24,7 +24,7 @@ const os = require('os'); const DriverRemoteConnection = require('../lib/driver/driver-remote-connection'); -const SaslAuthenticator = require('../lib/driver/sasl-authenticator'); +const SaslAuthenticator = require('../lib/driver/auth/sasl-authenticator'); const SaslMechanismPlain = require('../lib/driver/auth/mechanisms/sasl-mechanism-plain'); exports.getConnection = function getConnection(traversalSource) { @@ -32,7 +32,7 @@ exports.getConnection = function getConnection(traversalSource) { }; exports.getSecureConnectionWithAuthenticator = function getConnection(traversalSource) { - const authenticator = new SaslAuthenticator({ mechanism: new SaslMechanismPlain(), username: 'stephen', password: 'password', authId: os.hostname() }); + const authenticator = new SaslAuthenticator({ mechanism: new SaslMechanismPlain({ username: 'stephen', password: 'password', authzid: os.hostname() }) }); return new DriverRemoteConnection('ws://localhost:45941/gremlin', { traversalSource: traversalSource, authenticator: authenticator, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js index f38d0873cad..c9450f889ef 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js @@ -49,16 +49,16 @@ describe('DriverRemoteConnectionWithSaslAuthenticator', function () { }); }); it('should send the request with invalid credentials and parse the response error', function () { - connection._authenticator.username = 'Bob'; + connection._authenticator._options.mechanism._options.username = 'Bob'; return connection.submit(new Bytecode().addStep('V', []).addStep('tail', [])) .catch(function (err) { assert.ok(err); assert.ok(err.message.indexOf('401') > 0); }); }); - it('should send incorrect conifugration to the authenticator and parse the response error', function () { - delete connection._authenticator.username; - delete connection._authenticator.password; + it('should send incorrect configuration to the authenticator and parse the response error', function () { + delete connection._authenticator._options.mechanism._options.username; + delete connection._authenticator._options.mechanism._options.password; return connection.submit(new Bytecode().addStep('V', []).addStep('tail', [])) .catch(function (err) { assert.ok(err); From cc57c2b886652bcdad127bb0358c9b85156b5156 Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Mon, 13 Aug 2018 21:08:25 +0100 Subject: [PATCH 12/13] Added PlainTextSaslAuthenticator helper and updated the tests to use it. --- .../auth/plain-text-sasl-authenticator.js | 53 +++++++++++++++++++ .../lib/driver/auth/sasl-authenticator.js | 2 +- .../gremlin-javascript/test/helper.js | 7 ++- .../integration/sasl-authentication-tests.js | 9 +--- 4 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/plain-text-sasl-authenticator.js diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/plain-text-sasl-authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/plain-text-sasl-authenticator.js new file mode 100644 index 00000000000..b8f104d9944 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/plain-text-sasl-authenticator.js @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +'use strict'; + +const Authenticator = require('./authenticator'); +const SaslMechanismPlain = require('./mechanisms/sasl-mechanism-plain'); + +class PlainTextSaslAuthenticator extends Authenticator { + /** + * Creates a new instance of PlainTextSaslAuthenticator. + * @param {string} username Username to log into the server. + * @param {string} password Password for the user. + * @constructor + */ + constructor(username, password, authzid) { + const options = { + mechanism: new SaslMechanismPlain({ + 'username': username, + 'password': password, + 'authzid': authzid + }) + }; + + super(options); + } + + /** + * Evaluates the challenge from the server and returns appropriate response. + * @param {String} challenge Challenge string presented by the server. + * @return {Object} A Promise that resolves to a valid sasl response object. + */ + evaluateChallenge(challenge) { + return this._options.mechanism.evaluateChallenge(challenge); + } +} + +module.exports = PlainTextSaslAuthenticator; diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/sasl-authenticator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/sasl-authenticator.js index eb1fbe82999..cdf56e18ae1 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/sasl-authenticator.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/auth/sasl-authenticator.js @@ -23,7 +23,7 @@ class SaslAuthenticator extends Authenticator { * @return {Object} A Promise that resolves to a valid sasl response object. */ evaluateChallenge(challenge) { - return Promise.resolve(this._options.mechanism.evaluateChallenge(challenge)); + return this._options.mechanism.evaluateChallenge(challenge); } } diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js index 82c47be9a2d..899a8add675 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js @@ -24,15 +24,14 @@ const os = require('os'); const DriverRemoteConnection = require('../lib/driver/driver-remote-connection'); -const SaslAuthenticator = require('../lib/driver/auth/sasl-authenticator'); -const SaslMechanismPlain = require('../lib/driver/auth/mechanisms/sasl-mechanism-plain'); +const PlainTextSaslAuthenticator = require('../lib/driver/auth/plain-text-sasl-authenticator'); exports.getConnection = function getConnection(traversalSource) { return new DriverRemoteConnection('ws://localhost:45940/gremlin', { traversalSource: traversalSource }); }; -exports.getSecureConnectionWithAuthenticator = function getConnection(traversalSource) { - const authenticator = new SaslAuthenticator({ mechanism: new SaslMechanismPlain({ username: 'stephen', password: 'password', authzid: os.hostname() }) }); +exports.getSecureConnectionWithPlainTextSaslAuthenticator = function getConnection(traversalSource) { + const authenticator = new PlainTextSaslAuthenticator('stephen', 'password'); return new DriverRemoteConnection('ws://localhost:45941/gremlin', { traversalSource: traversalSource, authenticator: authenticator, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js index c9450f889ef..2cf1dff1379 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js @@ -17,9 +17,6 @@ * under the License. */ -/** - * @author Jorge Bay Gondra - */ 'use strict'; const assert = require('assert'); @@ -29,10 +26,10 @@ const helper = require('../helper'); let connection; -describe('DriverRemoteConnectionWithSaslAuthenticator', function () { +describe('DriverRemoteConnectionWithPlainTextSaslAuthenticator', function () { before(function () { this.timeout(20000); - connection = helper.getSecureConnectionWithAuthenticator(null); + connection = helper.getSecureConnectionWithPlainTextSaslAuthenticator(null); return connection.open(); }); after(function () { @@ -44,8 +41,6 @@ describe('DriverRemoteConnectionWithSaslAuthenticator', function () { .then(function (response) { assert.ok(response); assert.ok(response.traversers); - //assert.strictEqual(response.traversers.length, 1); - //assert.ok(response.traversers[0].object instanceof graphModule.Vertex); }); }); it('should send the request with invalid credentials and parse the response error', function () { From a561ee01455757ed2948aab3eb46e327310ce73a Mon Sep 17 00:00:00 2001 From: Matthew Allen Date: Thu, 23 Aug 2018 06:44:58 +0100 Subject: [PATCH 13/13] Updated documentation reflecting TINKERPOP-1977 changes. Added helper for auth to the gremlin-javascript exports. --- CHANGELOG.asciidoc | 1 + docs/src/reference/gremlin-variants.asciidoc | 9 +++++++++ docs/src/upgrade/release-3.2.x-incubating.asciidoc | 2 ++ .../src/main/javascript/gremlin-javascript/index.js | 6 +++++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index eb1a6c5c68a..f12c4ad2c8b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -51,6 +51,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima * Fixed a bug in `TinkerGraphCountStrategy`, which didn't consider that certain map steps may not emit an element. * Fixed a bug in JavaScript GLV where DriverRemoteConnection close() method didn't returned a Promise instance. * Bumped to Jackson 2.9.6. +* Sasl Plain Text Authentication added to Gremlin Javascript. [[release-3-2-9]] === TinkerPop 3.2.9 (Release Date: May 8, 2018) diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index e8b4c212888..290b39b7d8f 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -484,6 +484,15 @@ const graph = new Graph(); const g = graph.traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin')); ---- +or for Gremlin Servers requiring SASL authentication + +[source,javascript] +---- +const graph = new Graph(); +const Authenicator = gremlin.driver.auth.PlainTextSaslAuthenicator; +const g = graph.traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin', { authenticator: new Authenticator(username, password, optionalAuthzid) }); +---- + When a traversal from the `GraphTraversalSource` is iterated, the traversal’s `Bytecode` is sent over the wire via the registered `RemoteConnection`. The bytecode is used to construct the equivalent traversal at the remote traversal source. diff --git a/docs/src/upgrade/release-3.2.x-incubating.asciidoc b/docs/src/upgrade/release-3.2.x-incubating.asciidoc index af039374346..6f2aff849d0 100644 --- a/docs/src/upgrade/release-3.2.x-incubating.asciidoc +++ b/docs/src/upgrade/release-3.2.x-incubating.asciidoc @@ -29,6 +29,8 @@ Please see the link:https://github.com/apache/tinkerpop/blob/3.2.10/CHANGELOG.as === Upgrading for Users +The Gremlin Javascript Driver now supports SASL Plain Text authentication against a Gremlin Server. See: link:http://tinkerpop.apache.org/docs/current/reference#gremlin-javascript[Reference Documentation - Gremlin Javasctipt] + ==== Bulk Import and Export TinkerPop has provided some general methods for importing and exporting data, but more and more graph providers are diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js index d4c6d88de9d..9cc634920a1 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js @@ -31,13 +31,17 @@ const rc = require('./lib/driver/remote-connection'); const Bytecode = require('./lib/process/bytecode'); const utils = require('./lib/utils'); const DriverRemoteConnection = require('./lib/driver/driver-remote-connection'); +const PlainTextSaslAuthenticator = require('./lib/driver/auth/plain-text-sasl-authenticator'); module.exports = { driver: { RemoteConnection: rc.RemoteConnection, RemoteStrategy: rc.RemoteStrategy, RemoteTraversal: rc.RemoteTraversal, - DriverRemoteConnection: DriverRemoteConnection + DriverRemoteConnection: DriverRemoteConnection, + auth: { + PlainTextSaslAuthenticator: PlainTextSaslAuthenticator + } }, process: { Bytecode: Bytecode,