Skip to content

Commit 1ab28f2

Browse files
committed
Extract channel config in a separate class
This commit introduces a dedicated named configuration class for `NodeChannel` and `WebSocketChannel`. It encapsulates information needed to establish a connection and logic of extracting values from the user-defined driver configuration.
1 parent 0414c68 commit 1ab28f2

File tree

6 files changed

+233
-60
lines changed

6 files changed

+233
-60
lines changed

src/v1/internal/ch-config.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright (c) 2002-2017 "Neo Technology,","
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import hasFeature from './features';
21+
import {SERVICE_UNAVAILABLE} from '../error';
22+
23+
export default class ChannelConfig {
24+
25+
constructor(host, port, driverConfig, connectionErrorCode) {
26+
this.host = host;
27+
this.port = port;
28+
this.encrypted = ChannelConfig._extractEncrypted(driverConfig);
29+
this.trust = ChannelConfig._extractTrust(driverConfig);
30+
this.trustedCertificates = ChannelConfig._extractTrustedCertificates(driverConfig);
31+
this.knownHostsPath = ChannelConfig._extractKnownHostsPath(driverConfig);
32+
this.connectionErrorCode = connectionErrorCode || SERVICE_UNAVAILABLE;
33+
}
34+
35+
static _extractEncrypted(driverConfig) {
36+
// check if encryption was configured by the user, use explicit null check because we permit boolean value
37+
const encryptionConfigured = driverConfig.encrypted == null;
38+
// default to using encryption if trust-all-certificates is available
39+
return encryptionConfigured ? hasFeature('trust_all_certificates') : driverConfig.encrypted;
40+
}
41+
42+
static _extractTrust(driverConfig) {
43+
if (driverConfig.trust) {
44+
return driverConfig.trust;
45+
}
46+
// default to using TRUST_ALL_CERTIFICATES if it is available
47+
return hasFeature('trust_all_certificates') ? 'TRUST_ALL_CERTIFICATES' : 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES';
48+
}
49+
50+
static _extractTrustedCertificates(driverConfig) {
51+
return driverConfig.trustedCertificates || [];
52+
}
53+
54+
static _extractKnownHostsPath(driverConfig) {
55+
return driverConfig.knownHosts || null;
56+
}
57+
};

src/v1/internal/ch-dummy.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,32 @@ const observer = {
2626
};
2727

2828
class DummyChannel {
29-
constructor(opts) {
29+
30+
/**
31+
* @constructor
32+
* @param {ChannelConfig} config - configuration for the new channel.
33+
*/
34+
constructor(config) {
3035
this.written = [];
3136
}
37+
3238
isEncrypted() {
3339
return false;
3440
}
41+
3542
write( buf ) {
3643
this.written.push(buf);
3744
observer.updateInstance(this);
3845
}
46+
3947
toHex() {
4048
var out = "";
4149
for( var i=0; i<this.written.length; i++ ) {
4250
out += this.written[i].toHex();
4351
}
4452
return out;
4553
}
54+
4655
toBuffer () {
4756
return new CombinedBuffer( this.written );
4857
}

src/v1/internal/ch-node.js

+36-31
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import path from 'path';
2323
import {EOL} from 'os';
2424
import {NodeBuffer} from './buf';
2525
import {ENCRYPTION_OFF, isEmptyObjectOrNull} from './util';
26-
import {newError, SERVICE_UNAVAILABLE} from './../error';
26+
import {newError} from './../error';
2727

2828
let _CONNECTION_IDGEN = 0;
2929

@@ -107,13 +107,13 @@ const TrustStrategy = {
107107
/**
108108
* @deprecated Since version 1.0. Will be deleted in a future version. {@link #TRUST_CUSTOM_CA_SIGNED_CERTIFICATES}.
109109
*/
110-
TRUST_SIGNED_CERTIFICATES: function( opts, onSuccess, onFailure ) {
110+
TRUST_SIGNED_CERTIFICATES: function( config, onSuccess, onFailure ) {
111111
console.log("`TRUST_SIGNED_CERTIFICATES` has been deprecated as option and will be removed in a future version of " +
112112
"the driver. Please use `TRUST_CUSTOM_CA_SIGNED_CERTIFICATES` instead.");
113-
return TrustStrategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES(opts, onSuccess, onFailure);
113+
return TrustStrategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES(config, onSuccess, onFailure);
114114
},
115-
TRUST_CUSTOM_CA_SIGNED_CERTIFICATES : function( opts, onSuccess, onFailure ) {
116-
if( !opts.trustedCertificates || opts.trustedCertificates.length == 0 ) {
115+
TRUST_CUSTOM_CA_SIGNED_CERTIFICATES : function( config, onSuccess, onFailure ) {
116+
if( !config.trustedCertificates || config.trustedCertificates.length === 0 ) {
117117
onFailure(newError("You are using TRUST_CUSTOM_CA_SIGNED_CERTIFICATES as the method " +
118118
"to verify trust for encrypted connections, but have not configured any " +
119119
"trustedCertificates. You must specify the path to at least one trusted " +
@@ -124,13 +124,13 @@ const TrustStrategy = {
124124
}
125125

126126
let tlsOpts = {
127-
ca: opts.trustedCertificates.map((f) => fs.readFileSync(f)),
127+
ca: config.trustedCertificates.map((f) => fs.readFileSync(f)),
128128
// Because we manually check for this in the connect callback, to give
129129
// a more helpful error to the user
130130
rejectUnauthorized: false
131131
};
132132

133-
let socket = tls.connect(opts.port, opts.host, tlsOpts, function () {
133+
let socket = tls.connect(config.port, config.host, tlsOpts, function () {
134134
if (!socket.authorized) {
135135
onFailure(newError("Server certificate is not trusted. If you trust the database you are connecting to, add" +
136136
" the signing certificate, or the server certificate, to the list of certificates trusted by this driver" +
@@ -145,14 +145,14 @@ const TrustStrategy = {
145145
socket.on('error', onFailure);
146146
return socket;
147147
},
148-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES : function( opts, onSuccess, onFailure ) {
148+
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES : function( config, onSuccess, onFailure ) {
149149

150150
let tlsOpts = {
151151
// Because we manually check for this in the connect callback, to give
152152
// a more helpful error to the user
153153
rejectUnauthorized: false
154154
};
155-
let socket = tls.connect(opts.port, opts.host, tlsOpts, function () {
155+
let socket = tls.connect(config.port, config.host, tlsOpts, function () {
156156
if (!socket.authorized) {
157157
onFailure(newError("Server certificate is not trusted. If you trust the database you are connecting to, use " +
158158
"TRUST_CUSTOM_CA_SIGNED_CERTIFICATES and add" +
@@ -171,7 +171,7 @@ const TrustStrategy = {
171171
/**
172172
* @deprecated in 1.1 in favour of {@link #TRUST_ALL_CERTIFICATES}. Will be deleted in a future version.
173173
*/
174-
TRUST_ON_FIRST_USE : function( opts, onSuccess, onFailure ) {
174+
TRUST_ON_FIRST_USE : function( config, onSuccess, onFailure ) {
175175
console.log("`TRUST_ON_FIRST_USE` has been deprecated as option and will be removed in a future version of " +
176176
"the driver. Please use `TRUST_ALL_CERTIFICATES` instead.");
177177

@@ -180,7 +180,7 @@ const TrustStrategy = {
180180
rejectUnauthorized: false
181181
};
182182

183-
let socket = tls.connect(opts.port, opts.host, tlsOpts, function () {
183+
let socket = tls.connect(config.port, config.host, tlsOpts, function () {
184184
var serverCert = socket.getPeerCertificate(/*raw=*/true);
185185

186186
if( !serverCert.raw ) {
@@ -195,9 +195,9 @@ const TrustStrategy = {
195195
return;
196196
}
197197

198-
var serverFingerprint = require('crypto').createHash('sha512').update(serverCert.raw).digest("hex");
199-
let knownHostsPath = opts.knownHosts || path.join(userHome(), ".neo4j", "known_hosts");
200-
let serverId = opts.host + ":" + opts.port;
198+
const serverFingerprint = require('crypto').createHash('sha512').update(serverCert.raw).digest("hex");
199+
const knownHostsPath = config.knownHostsPath || path.join(userHome(), ".neo4j", "known_hosts");
200+
const serverId = config.host + ":" + config.port;
201201

202202
loadFingerprint(serverId, knownHostsPath, (knownFingerprint) => {
203203
if( knownFingerprint === serverFingerprint ) {
@@ -228,11 +228,11 @@ const TrustStrategy = {
228228
return socket;
229229
},
230230

231-
TRUST_ALL_CERTIFICATES: function (opts, onSuccess, onFailure) {
231+
TRUST_ALL_CERTIFICATES: function (config, onSuccess, onFailure) {
232232
const tlsOpts = {
233233
rejectUnauthorized: false
234234
};
235-
const socket = tls.connect(opts.port, opts.host, tlsOpts, function () {
235+
const socket = tls.connect(config.port, config.host, tlsOpts, function () {
236236
const certificate = socket.getPeerCertificate();
237237
if (isEmptyObjectOrNull(certificate)) {
238238
onFailure(newError("Secure connection was successful but server did not return any valid " +
@@ -249,16 +249,23 @@ const TrustStrategy = {
249249
}
250250
};
251251

252-
function connect( opts, onSuccess, onFailure=(()=>null) ) {
252+
/**
253+
* Connect using node socket.
254+
* @param {ChannelConfig} config - configuration of this channel.
255+
* @param {function} onSuccess - callback to execute on connection success.
256+
* @param {function} onFailure - callback to execute on connection failure.
257+
* @return {*} socket connection.
258+
*/
259+
function connect( config, onSuccess, onFailure=(()=>null) ) {
253260
//still allow boolean for backwards compatibility
254-
if (opts.encrypted === false || opts.encrypted === ENCRYPTION_OFF) {
255-
var conn = net.connect(opts.port, opts.host, onSuccess);
261+
if (config.encrypted === false || config.encrypted === ENCRYPTION_OFF) {
262+
var conn = net.connect(config.port, config.host, onSuccess);
256263
conn.on('error', onFailure);
257264
return conn;
258-
} else if( TrustStrategy[opts.trust]) {
259-
return TrustStrategy[opts.trust](opts, onSuccess, onFailure);
265+
} else if( TrustStrategy[config.trust]) {
266+
return TrustStrategy[config.trust](config, onSuccess, onFailure);
260267
} else {
261-
onFailure(newError("Unknown trust strategy: " + opts.trust + ". Please use either " +
268+
onFailure(newError("Unknown trust strategy: " + config.trust + ". Please use either " +
262269
"trust:'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' or trust:'TRUST_ALL_CERTIFICATES' in your driver " +
263270
"configuration. Alternatively, you can disable encryption by setting " +
264271
"`encrypted:\"" + ENCRYPTION_OFF + "\"`. There is no mechanism to use encryption without trust verification, " +
@@ -277,11 +284,9 @@ class NodeChannel {
277284

278285
/**
279286
* Create new instance
280-
* @param {Object} opts - Options object
281-
* @param {string} opts.host - The host, including protocol to connect to.
282-
* @param {Integer} opts.port - The port to use.
287+
* @param {ChannelConfig} config - configuration for this channel.
283288
*/
284-
constructor (opts) {
289+
constructor (config) {
285290
let self = this;
286291

287292
this.id = _CONNECTION_IDGEN++;
@@ -291,10 +296,10 @@ class NodeChannel {
291296
this._error = null;
292297
this._handleConnectionError = this._handleConnectionError.bind(this);
293298
this._handleConnectionTerminated = this._handleConnectionTerminated.bind(this);
294-
this._errorCode = opts.errorCode || SERVICE_UNAVAILABLE;
299+
this._connectionErrorCode = config.connectionErrorCode;
295300

296-
this._encrypted = opts.encrypted;
297-
this._conn = connect(opts, () => {
301+
this._encrypted = config.encrypted;
302+
this._conn = connect(config, () => {
298303
if(!self._open) {
299304
return;
300305
}
@@ -319,14 +324,14 @@ class NodeChannel {
319324

320325
_handleConnectionError( err ) {
321326
let msg = err.message || 'Failed to connect to server';
322-
this._error = newError(msg, this._errorCode);
327+
this._error = newError(msg, this._connectionErrorCode);
323328
if( this.onerror ) {
324329
this.onerror(this._error);
325330
}
326331
}
327332

328333
_handleConnectionTerminated() {
329-
this._error = newError('Connection was closed by server', this._errorCode);
334+
this._error = newError('Connection was closed by server', this._connectionErrorCode);
330335
if( this.onerror ) {
331336
this.onerror(this._error);
332337
}

src/v1/internal/ch-websocket.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
import {HeapBuffer} from './buf';
21-
import {newError, SERVICE_UNAVAILABLE} from './../error';
21+
import {newError} from './../error';
2222
import {ENCRYPTION_OFF, ENCRYPTION_ON} from './util';
2323

2424
/**
@@ -29,34 +29,32 @@ class WebSocketChannel {
2929

3030
/**
3131
* Create new instance
32-
* @param {Object} opts - Options object
33-
* @param {string} opts.host - The host, including protocol to connect to.
34-
* @param {Integer} opts.port - The port to use.
32+
* @param {ChannelConfig} config - configuration for this channel.
3533
*/
36-
constructor (opts) {
34+
constructor(config) {
3735

3836
this._open = true;
3937
this._pending = [];
4038
this._error = null;
4139
this._handleConnectionError = this._handleConnectionError.bind(this);
42-
this._errorCode = opts.errorCode || SERVICE_UNAVAILABLE;
40+
this._connectionErrorCode = config.connectionErrorCode;
4341

44-
this._encrypted = opts.encrypted;
42+
this._encrypted = config.encrypted;
4543

4644
let scheme = "ws";
4745
//Allow boolean for backwards compatibility
48-
if( opts.encrypted === true || opts.encrypted === ENCRYPTION_ON) {
49-
if((!opts.trust) || opts.trust === "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES" ) {
46+
if (config.encrypted === true || config.encrypted === ENCRYPTION_ON) {
47+
if ((!config.trust) || config.trust === 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES') {
5048
scheme = "wss";
5149
} else {
5250
this._error = newError("The browser version of this driver only supports one trust " +
53-
"strategy, 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES'. "+opts.trust+" is not supported. Please " +
51+
'strategy, \'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES\'. ' + config.trust + ' is not supported. Please ' +
5452
"either use TRUST_CUSTOM_CA_SIGNED_CERTIFICATES or disable encryption by setting " +
5553
"`encrypted:\"" + ENCRYPTION_OFF + "\"` in the driver configuration.");
5654
return;
5755
}
5856
}
59-
this._url = scheme + "://" + opts.host + ":" + opts.port;
57+
this._url = scheme + '://' + config.host + ':' + config.port;
6058
this._ws = new WebSocket(this._url);
6159
this._ws.binaryType = "arraybuffer";
6260

@@ -96,7 +94,7 @@ class WebSocketChannel {
9694
"the root cause of the failure. Common reasons include the database being " +
9795
"unavailable, using the wrong connection URL or temporary network problems. " +
9896
"If you have enabled encryption, ensure your browser is configured to trust the " +
99-
"certificate Neo4j is configured to use. WebSocket `readyState` is: " + this._ws.readyState, this._errorCode );
97+
"certificate Neo4j is configured to use. WebSocket `readyState` is: " + this._ws.readyState, this._connectionErrorCode );
10098
if (this.onerror) {
10199
this.onerror(this._error);
102100
}

src/v1/internal/connector.js

+7-16
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
import WebSocketChannel from './ch-websocket';
2020
import NodeChannel from './ch-node';
2121
import {Chunker, Dechunker} from './chunking';
22-
import hasFeature from './features';
2322
import {Packer, Unpacker} from './packstream';
2423
import {alloc} from './buf';
2524
import {Node, Path, PathSegment, Relationship, UnboundRelationship} from '../graph-types';
26-
import {newError, SERVICE_UNAVAILABLE} from './../error';
25+
import {newError} from './../error';
26+
import ChannelConfig from './ch-config';
2727

2828
let Channel;
2929
if( NodeChannel.available ) {
@@ -476,26 +476,17 @@ class Connection {
476476
* @access private
477477
* @param {string} url - 'neo4j'-prefixed URL to Neo4j Bolt endpoint
478478
* @param {object} config - this driver configuration
479-
* @param {string} errorCode - error code for errors raised on connection errors
479+
* @param {string=null} connectionErrorCode - error code for errors raised on connection errors
480480
* @return {Connection} - New connection
481481
*/
482-
function connect( url, config = {}, errorCode = SERVICE_UNAVAILABLE) {
483-
let Ch = config.channel || Channel;
482+
function connect(url, config = {}, connectionErrorCode = null) {
483+
const Ch = config.channel || Channel;
484484
const host = parseHost(url);
485485
const port = parsePort(url) || 7687;
486486
const completeUrl = host + ':' + port;
487+
const channelConfig = new ChannelConfig(host, port, config, connectionErrorCode);
487488

488-
return new Connection( new Ch({
489-
host: parseHost(url),
490-
port: parsePort(url) || 7687,
491-
// Default to using encryption if trust-on-first-use is available
492-
encrypted : (config.encrypted == null) ? hasFeature("trust_all_certificates") : config.encrypted,
493-
// Default to using TRUST_ALL_CERTIFICATES if it is available
494-
trust : config.trust || (hasFeature("trust_all_certificates") ? "TRUST_ALL_CERTIFICATES" : "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES"),
495-
trustedCertificates : config.trustedCertificates || [],
496-
knownHosts : config.knownHosts,
497-
errorCode: errorCode
498-
}), completeUrl);
489+
return new Connection( new Ch(channelConfig), completeUrl);
499490
}
500491

501492
export {

0 commit comments

Comments
 (0)