diff --git a/fabric-client/lib/Client.js b/fabric-client/lib/Client.js index df8caa7732..65f5cc59cb 100644 --- a/fabric-client/lib/Client.js +++ b/fabric-client/lib/Client.js @@ -283,7 +283,7 @@ const Client = class extends BaseClient { return chaincode; } - /** + /** * Returns a {@link TokenClient} instance for the specific channel and targets. * The TokenClient provides APIs for applications to perform token functions. * @@ -1895,11 +1895,12 @@ function _stringToSignature(string_signatures) { function _getNetworkConfig(loadConfig, client) { let network_config = null; let network_data = null; + let network_config_loc = null; if (typeof loadConfig === 'string') { - const config_loc = path.resolve(loadConfig); - logger.debug('%s - looking at absolute path of ==>%s<==', '_getNetworkConfig', config_loc); - const file_data = fs.readFileSync(config_loc); - const file_ext = path.extname(config_loc); + network_config_loc = path.resolve(loadConfig); + logger.debug('%s - looking at absolute path of ==>%s<==', '_getNetworkConfig', network_config_loc); + const file_data = fs.readFileSync(network_config_loc); + const file_ext = path.extname(network_config_loc); // maybe the file is yaml else has to be JSON if ((/(yml|yaml)$/i).test(file_ext)) { network_data = yaml.safeLoad(file_data); @@ -1910,31 +1911,26 @@ function _getNetworkConfig(loadConfig, client) { network_data = loadConfig; } - let error_msg = null; - if (network_data) { - if (network_data.version) { - const parsing = Client.getConfigSetting('network-config-schema'); - if (parsing) { - const pieces = network_data.version.toString().split('.'); - const version = pieces[0] + '.' + pieces[1]; - if (parsing[version]) { - const NetworkConfig = require(parsing[version]); - network_config = new NetworkConfig(network_data, client); - } else { - error_msg = 'common connection profile has an unknown "version"'; - } - } else { - error_msg = 'missing "network-config-schema" configuration setting'; - } - } else { - error_msg = '"version" is missing'; + try { + if (!network_data) { + throw new Error('missing configuration data'); } - } else { - error_msg = 'missing configuration data'; - } - - if (error_msg) { - throw new Error(util.format('Invalid common connection profile due to %s', error_msg)); + if (!network_data.version) { + throw new Error('"version" is missing'); + } + const parsing = Client.getConfigSetting('network-config-schema'); + if (!parsing) { + throw new Error('missing "network-config-schema" configuration setting'); + } + const pieces = network_data.version.toString().split('.'); + const version = pieces[0] + '.' + pieces[1]; + if (!parsing[version]) { + throw new Error('common connection profile has an unknown "version"'); + } + const NetworkConfig = require(parsing[version]); + network_config = new NetworkConfig(network_data, client, network_config_loc); + } catch (error) { + throw new Error(util.format('Invalid common connection profile due to %s', error.message)); } return network_config; diff --git a/fabric-client/lib/impl/NetworkConfig_1_0.js b/fabric-client/lib/impl/NetworkConfig_1_0.js index 7ba882b5af..6d9f481d36 100644 --- a/fabric-client/lib/impl/NetworkConfig_1_0.js +++ b/fabric-client/lib/impl/NetworkConfig_1_0.js @@ -54,15 +54,25 @@ const NetworkConfig_1_0 = class { * * @param {Object} network_config - Common Connection Profile as represented in a JSON object */ - constructor(network_config, client_context) { + constructor(network_config, client_context, network_config_loc) { logger.debug('constructor, network_config: ' + JSON.stringify(network_config)); this._network_config = network_config; this._client_context = client_context; + this._network_config_loc = network_config_loc; this._peers = new Map(); this._channel = new Map(); this._orderers = new Map(); } + /** + * Get the file system path that the network config was loaded from, if any. + * @returns {string} The file system path that the network config was loaded + * from, or null if it was not loaded directly from the file system. + */ + getNetworkConfigLocation() { + return this._network_config_loc; + } + mergeSettings(additions) { const method = 'mergeSettings'; logger.debug('%s - additions start', method); @@ -163,7 +173,7 @@ const NetworkConfig_1_0 = class { const peer_config = this._network_config[PEERS_CONFIG][name]; if (peer_config) { const opts = {name: name}; - opts.pem = getTLSCACert(peer_config); + opts.pem = getTLSCACert(peer_config, this._network_config_loc); Object.assign(opts, peer_config[GRPC_CONNECTION_OPTIONS]); this.addTimeout(opts, ENDORSER); peer = this._client_context.newPeer(peer_config[URL], opts); @@ -212,7 +222,7 @@ const NetworkConfig_1_0 = class { const orderer_config = this._network_config[ORDERERS_CONFIG][name]; if (orderer_config) { const opts = {name: name}; - opts.pem = getTLSCACert(orderer_config); + opts.pem = getTLSCACert(orderer_config, this._network_config_loc); Object.assign(opts, orderer_config[GRPC_CONNECTION_OPTIONS]); this.addTimeout(opts, ORDERER); orderer = this._client_context.newOrderer(orderer_config[URL], opts); @@ -300,7 +310,7 @@ const NetworkConfig_1_0 = class { certificateAuthority_config[CANAME], certificateAuthority_config[URL], certificateAuthority_config[HTTP_CONNECTION_OPTIONS], - getTLSCACert(certificateAuthority_config), + getTLSCACert(certificateAuthority_config, this._network_config_loc), certificateAuthority_config[REGISTRAR] ); } @@ -396,14 +406,14 @@ const NetworkConfig_1_0 = class { } }; -function getTLSCACert(config) { +function getTLSCACert(config, network_config_loc) { if (config && config[TLS_CACERTS]) { - return getPEMfromConfig(config[TLS_CACERTS]); + return getPEMfromConfig(config[TLS_CACERTS], network_config_loc); } return null; } -function getPEMfromConfig(config) { +function getPEMfromConfig(config, network_config_loc) { let result = null; if (config) { if (config[PEM]) { @@ -411,7 +421,7 @@ function getPEMfromConfig(config) { result = config[PEM]; } else if (config[PATH]) { // cert value is in a file - result = readFileSync(config[PATH]); + result = readFileSync(config[PATH], network_config_loc); result = utils.normalizeX509(result); } } @@ -419,15 +429,24 @@ function getPEMfromConfig(config) { return result; } -function readFileSync(config_path) { - try { - const config_loc = path.resolve(config_path); - const data = fs.readFileSync(config_loc); - return Buffer.from(data).toString(); - } catch (err) { - logger.error('NetworkConfig101 - problem reading the PEM file :: ' + err); - throw err; +function readFileSync(config_path, network_config_loc) { + const possiblePaths = [ + path.resolve(config_path) // relative to cwd + ]; + if (network_config_loc) { + possiblePaths.push(path.resolve(path.dirname(network_config_loc), config_path)); + } + let lastError; + for (const possiblePath of possiblePaths) { + try { + const data = fs.readFileSync(possiblePath); + return Buffer.from(data).toString(); + } catch (error) { + lastError = error; + } } + logger.error('NetworkConfig101 - problem reading the PEM file :: ' + lastError); + throw lastError; } module.exports = NetworkConfig_1_0; diff --git a/fabric-client/test/Client.js b/fabric-client/test/Client.js index 93a9d4c8f7..307bf074c1 100644 --- a/fabric-client/test/Client.js +++ b/fabric-client/test/Client.js @@ -3103,7 +3103,7 @@ describe('Client', () => { MockNetworkConfig = sinon.stub(); readFileSyncStub = sinon.stub(); revert.push(Client.__set__('fs.readFileSync', readFileSyncStub)); - revert.push(Client.__set__('path.resolve', (v) => v)); + revert.push(Client.__set__('path.resolve', (v) => `/path/to/${v}`)); safeLoadStub = sinon.stub(); revert.push(Client.__set__('yaml.safeLoad', safeLoadStub)); }); @@ -3136,7 +3136,7 @@ describe('Client', () => { sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema'); }); - it('should return the new network config when config is an object', () => { + it('should return the new network config when config is a 1.0 object', () => { readFileSyncStub.returns('file-data'); getConfigSettingStub.returns({'1.0': 'network-config-file'}); requireStub.returns(MockNetworkConfig); @@ -3144,11 +3144,11 @@ describe('Client', () => { const networkConfig = _getNetworkConfig({version: '1.0'}, 'client'); sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema'); sinon.assert.calledWith(requireStub, 'network-config-file'); - sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client'); + sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', null); networkConfig.should.deep.equal(new MockNetworkConfig()); }); - it('should return the new network config when config is a yaml file path', () => { + it('should return the new network config when config is a 1.0 yaml file path', () => { readFileSyncStub.returns('file-data'); safeLoadStub.returns({version: '1.0'}); getConfigSettingStub.returns({'1.0': 'network-config-file'}); @@ -3157,13 +3157,13 @@ describe('Client', () => { const networkConfig = _getNetworkConfig('config.yaml', 'client'); sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema'); sinon.assert.calledWith(requireStub, 'network-config-file'); - sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client'); - sinon.assert.calledWith(readFileSyncStub, 'config.yaml'); + sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', '/path/to/config.yaml'); + sinon.assert.calledWith(readFileSyncStub, '/path/to/config.yaml'); sinon.assert.calledWith(safeLoadStub, 'file-data'); networkConfig.should.deep.equal(new MockNetworkConfig()); }); - it('should return the new network config when config is a yml file path', () => { + it('should return the new network config when config is a 1.0 yml file path', () => { readFileSyncStub.returns('file-data'); safeLoadStub.returns({version: '1.0'}); getConfigSettingStub.returns({'1.0': 'network-config-file'}); @@ -3172,13 +3172,13 @@ describe('Client', () => { const networkConfig = _getNetworkConfig('config.yml', 'client'); sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema'); sinon.assert.calledWith(requireStub, 'network-config-file'); - sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client'); - sinon.assert.calledWith(readFileSyncStub, 'config.yml'); + sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', '/path/to/config.yml'); + sinon.assert.calledWith(readFileSyncStub, '/path/to/config.yml'); sinon.assert.calledWith(safeLoadStub, 'file-data'); networkConfig.should.deep.equal(new MockNetworkConfig()); }); - it('should return the new network config when config is a yaml file path', () => { + it('should return the new network config when config is a 1.0 json file path', () => { readFileSyncStub.returns('{"version": "1.0"}'); safeLoadStub.returns({version: '1.0'}); getConfigSettingStub.returns({'1.0': 'network-config-file'}); @@ -3187,12 +3187,12 @@ describe('Client', () => { const networkConfig = _getNetworkConfig('config.json', 'client'); sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema'); sinon.assert.calledWith(requireStub, 'network-config-file'); - sinon.assert.calledWith(MockNetworkConfig, {version: '1.0'}, 'client'); - sinon.assert.calledWith(readFileSyncStub, 'config.json'); + sinon.assert.calledWithExactly(MockNetworkConfig, {version: '1.0'}, 'client', '/path/to/config.json'); + sinon.assert.calledWith(readFileSyncStub, '/path/to/config.json'); networkConfig.should.deep.equal(new MockNetworkConfig()); }); - it('should return the new network config when config is a yaml file path', () => { + it('should return the new network config when config is a 1.1 json file path', () => { readFileSyncStub.returns('{"version": 1.1}'); safeLoadStub.returns({version: 1.1}); getConfigSettingStub.returns({'1.1': 'network-config-file'}); @@ -3201,8 +3201,8 @@ describe('Client', () => { const networkConfig = _getNetworkConfig('config.json', 'client'); sinon.assert.calledWith(getConfigSettingStub, 'network-config-schema'); sinon.assert.calledWith(requireStub, 'network-config-file'); - sinon.assert.calledWith(MockNetworkConfig, {version: 1.1}, 'client'); - sinon.assert.calledWith(readFileSyncStub, 'config.json'); + sinon.assert.calledWithExactly(MockNetworkConfig, {version: 1.1}, 'client', '/path/to/config.json'); + sinon.assert.calledWith(readFileSyncStub, '/path/to/config.json'); networkConfig.should.deep.equal(new MockNetworkConfig()); }); }); diff --git a/fabric-client/test/data/ccp/connection-embedded-pems.json b/fabric-client/test/data/ccp/connection-embedded-pems.json new file mode 100644 index 0000000000..3d79163a37 --- /dev/null +++ b/fabric-client/test/data/ccp/connection-embedded-pems.json @@ -0,0 +1,51 @@ +{ + "name": "basic-network", + "version": "1.0.0", + "client": { + "organization": "Org1", + "connection": { + "timeout": { + "peer": { + "endorser": "300" + }, + "orderer": "300" + } + } + }, + "organizations": { + "Org1": { + "mspid": "Org1MSP", + "peers": [ + "peer0.org1.example.com" + ], + "certificateAuthorities": [ + "ca.example.com" + ] + } + }, + "orderers": { + "orderer.example.com": { + "url": "grpcs://localhost:7050", + "tlsCACerts": { + "pem": "-----BEGIN CERTIFICATE-----\nMIICSTCCAe+gAwIBAgIQPHXmPqjzn2bon7JrBRPS2DAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTAyMjExNDI4MDBaFw0yOTAyMTgxNDI4\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAELAsSPvzK3EdhGPZAMKYh67s02WqfYUe09xMzy7BzNODUKcbyIW5i7GVQ\n3YurSkR/auRsk6FG45Q1zTZaEvwVH6NfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud\nJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQg8HHn3ScArMdH\nlkp+jpcDXtIAzWnVf4F9rBHvUNjcC1owCgYIKoZIzj0EAwIDSAAwRQIhAMi+R+ZI\nXgZV40IztD8aQDr/sntDTu/8Nw7Y0DGEhwaQAiBEnBCdRXaBcENWnAnastAg+RA5\nXALSidlQqZKrK4L3Yg==\n-----END CERTIFICATE-----\n" + } + } + }, + "peers": { + "peer0.org1.example.com": { + "url": "grpcs://localhost:7051", + "tlsCACerts": { + "pem": "-----BEGIN CERTIFICATE-----\nMIICSTCCAe+gAwIBAgIQPHXmPqjzn2bon7JrBRPS2DAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTAyMjExNDI4MDBaFw0yOTAyMTgxNDI4\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAELAsSPvzK3EdhGPZAMKYh67s02WqfYUe09xMzy7BzNODUKcbyIW5i7GVQ\n3YurSkR/auRsk6FG45Q1zTZaEvwVH6NfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud\nJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQg8HHn3ScArMdH\nlkp+jpcDXtIAzWnVf4F9rBHvUNjcC1owCgYIKoZIzj0EAwIDSAAwRQIhAMi+R+ZI\nXgZV40IztD8aQDr/sntDTu/8Nw7Y0DGEhwaQAiBEnBCdRXaBcENWnAnastAg+RA5\nXALSidlQqZKrK4L3Yg==\n-----END CERTIFICATE-----\n" + } + } + }, + "certificateAuthorities": { + "ca.example.com": { + "url": "https://localhost:7054", + "caName": "ca.example.com", + "tlsCACerts": { + "pem": "-----BEGIN CERTIFICATE-----\nMIICSTCCAe+gAwIBAgIQPHXmPqjzn2bon7JrBRPS2DAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTAyMjExNDI4MDBaFw0yOTAyMTgxNDI4\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAELAsSPvzK3EdhGPZAMKYh67s02WqfYUe09xMzy7BzNODUKcbyIW5i7GVQ\n3YurSkR/auRsk6FG45Q1zTZaEvwVH6NfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud\nJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQg8HHn3ScArMdH\nlkp+jpcDXtIAzWnVf4F9rBHvUNjcC1owCgYIKoZIzj0EAwIDSAAwRQIhAMi+R+ZI\nXgZV40IztD8aQDr/sntDTu/8Nw7Y0DGEhwaQAiBEnBCdRXaBcENWnAnastAg+RA5\nXALSidlQqZKrK4L3Yg==\n-----END CERTIFICATE-----\n" + } + } + } +} \ No newline at end of file diff --git a/fabric-client/test/data/ccp/connection-missing-relative-ccp-pems.json b/fabric-client/test/data/ccp/connection-missing-relative-ccp-pems.json new file mode 100644 index 0000000000..93dee59a1e --- /dev/null +++ b/fabric-client/test/data/ccp/connection-missing-relative-ccp-pems.json @@ -0,0 +1,51 @@ +{ + "name": "basic-network", + "version": "1.0.0", + "client": { + "organization": "Org1", + "connection": { + "timeout": { + "peer": { + "endorser": "300" + }, + "orderer": "300" + } + } + }, + "organizations": { + "Org1": { + "mspid": "Org1MSP", + "peers": [ + "peer0.org1.example.com" + ], + "certificateAuthorities": [ + "ca.example.com" + ] + } + }, + "orderers": { + "orderer.example.com": { + "url": "grpcs://localhost:7050", + "tlsCACerts": { + "path": "pems/notherelulz.pem" + } + } + }, + "peers": { + "peer0.org1.example.com": { + "url": "grpcs://localhost:7051", + "tlsCACerts": { + "path": "pems/notherelulz.pem" + } + } + }, + "certificateAuthorities": { + "ca.example.com": { + "url": "https://localhost:7054", + "caName": "ca.example.com", + "tlsCACerts": { + "path": "pems/notherelulz.pem" + } + } + } +} \ No newline at end of file diff --git a/fabric-client/test/data/ccp/connection-relative-ccp-pems.json b/fabric-client/test/data/ccp/connection-relative-ccp-pems.json new file mode 100644 index 0000000000..32491a0ea3 --- /dev/null +++ b/fabric-client/test/data/ccp/connection-relative-ccp-pems.json @@ -0,0 +1,51 @@ +{ + "name": "basic-network", + "version": "1.0.0", + "client": { + "organization": "Org1", + "connection": { + "timeout": { + "peer": { + "endorser": "300" + }, + "orderer": "300" + } + } + }, + "organizations": { + "Org1": { + "mspid": "Org1MSP", + "peers": [ + "peer0.org1.example.com" + ], + "certificateAuthorities": [ + "ca.example.com" + ] + } + }, + "orderers": { + "orderer.example.com": { + "url": "grpcs://localhost:7050", + "tlsCACerts": { + "path": "pems/ca.pem" + } + } + }, + "peers": { + "peer0.org1.example.com": { + "url": "grpcs://localhost:7051", + "tlsCACerts": { + "path": "pems/ca.pem" + } + } + }, + "certificateAuthorities": { + "ca.example.com": { + "url": "https://localhost:7054", + "caName": "ca.example.com", + "tlsCACerts": { + "path": "pems/ca.pem" + } + } + } +} \ No newline at end of file diff --git a/fabric-client/test/data/ccp/connection-relative-cwd-pems.json b/fabric-client/test/data/ccp/connection-relative-cwd-pems.json new file mode 100644 index 0000000000..2cb06211ca --- /dev/null +++ b/fabric-client/test/data/ccp/connection-relative-cwd-pems.json @@ -0,0 +1,51 @@ +{ + "name": "basic-network", + "version": "1.0.0", + "client": { + "organization": "Org1", + "connection": { + "timeout": { + "peer": { + "endorser": "300" + }, + "orderer": "300" + } + } + }, + "organizations": { + "Org1": { + "mspid": "Org1MSP", + "peers": [ + "peer0.org1.example.com" + ], + "certificateAuthorities": [ + "ca.example.com" + ] + } + }, + "orderers": { + "orderer.example.com": { + "url": "grpcs://localhost:7050", + "tlsCACerts": { + "path": "fabric-client/test/data/ccp/pems/ca.pem" + } + } + }, + "peers": { + "peer0.org1.example.com": { + "url": "grpcs://localhost:7051", + "tlsCACerts": { + "path": "fabric-client/test/data/ccp/pems/ca.pem" + } + } + }, + "certificateAuthorities": { + "ca.example.com": { + "url": "https://localhost:7054", + "caName": "ca.example.com", + "tlsCACerts": { + "path": "fabric-client/test/data/ccp/pems/ca.pem" + } + } + } +} \ No newline at end of file diff --git a/fabric-client/test/data/ccp/connection.json b/fabric-client/test/data/ccp/connection.json new file mode 100644 index 0000000000..2f1639cc18 --- /dev/null +++ b/fabric-client/test/data/ccp/connection.json @@ -0,0 +1,42 @@ +{ + "name": "basic-network", + "version": "1.0.0", + "client": { + "organization": "Org1", + "connection": { + "timeout": { + "peer": { + "endorser": "300" + }, + "orderer": "300" + } + } + }, + "organizations": { + "Org1": { + "mspid": "Org1MSP", + "peers": [ + "peer0.org1.example.com" + ], + "certificateAuthorities": [ + "ca.example.com" + ] + } + }, + "orderers": { + "orderer.example.com": { + "url": "grpc://localhost:7050" + } + }, + "peers": { + "peer0.org1.example.com": { + "url": "grpc://localhost:7051" + } + }, + "certificateAuthorities": { + "ca.example.com": { + "url": "http://localhost:7054", + "caName": "ca.example.com" + } + } +} \ No newline at end of file diff --git a/fabric-client/test/data/ccp/pems/ca.pem b/fabric-client/test/data/ccp/pems/ca.pem new file mode 100644 index 0000000000..a03cbf2c4d --- /dev/null +++ b/fabric-client/test/data/ccp/pems/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSTCCAe+gAwIBAgIQPHXmPqjzn2bon7JrBRPS2DAKBggqhkjOPQQDAjB2MQsw +CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy +YW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz +Y2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTAyMjExNDI4MDBaFw0yOTAyMTgxNDI4 +MDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD +VQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAELAsSPvzK3EdhGPZAMKYh67s02WqfYUe09xMzy7BzNODUKcbyIW5i7GVQ +3YurSkR/auRsk6FG45Q1zTZaEvwVH6NfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud +JQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQg8HHn3ScArMdH +lkp+jpcDXtIAzWnVf4F9rBHvUNjcC1owCgYIKoZIzj0EAwIDSAAwRQIhAMi+R+ZI +XgZV40IztD8aQDr/sntDTu/8Nw7Y0DGEhwaQAiBEnBCdRXaBcENWnAnastAg+RA5 +XALSidlQqZKrK4L3Yg== +-----END CERTIFICATE----- diff --git a/fabric-client/test/impl/NetworkConfig_1_0.js b/fabric-client/test/impl/NetworkConfig_1_0.js new file mode 100644 index 0000000000..c3fe586431 --- /dev/null +++ b/fabric-client/test/impl/NetworkConfig_1_0.js @@ -0,0 +1,290 @@ +/* + * 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. + */ + +'use strict'; + +const rewire = require('rewire'); + +const Client = require('../../lib/Client'); +const fs = require('fs'); +const NetworkConfig = rewire('../../lib/impl/NetworkConfig_1_0'); +const path = require('path'); + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinon = require('sinon'); +const sinonChai = require('sinon-chai'); +chai.should(); +chai.use(chaiAsPromised); +chai.use(sinonChai); + +const pem = fs.readFileSync(path.resolve(__dirname, '..', 'data', 'ccp', 'pems', 'ca.pem'), 'utf8'); + +describe('NetworkConfig_1_0', () => { + + let mockClient; + let CertificateAuthorityStub; + let revert; + + beforeEach(() => { + mockClient = sinon.createStubInstance(Client); + CertificateAuthorityStub = sinon.stub(); + revert = [ + NetworkConfig.__set__('CertificateAuthority', CertificateAuthorityStub) + ]; + }); + + afterEach(() => { + revert.forEach(Function.prototype.call, Function.prototype.call); + }); + + describe('#getNetworkConfigLocation', () => { + + it('should return the network config location', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getNetworkConfigLocation().should.equal(ccpPath); + }); + + }); + + describe('#getPeer', () => { + + it('should return correct peer config for a peer with no TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getPeer('peer0.org1.example.com'); + mockClient.newPeer.should.have.been.calledOnceWithExactly('grpc://localhost:7051', { + name: 'peer0.org1.example.com', + pem: null, + 'request-timeout': 300000 + }); + }); + + it('should return correct peer config for a peer with embedded TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-embedded-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getPeer('peer0.org1.example.com'); + mockClient.newPeer.should.have.been.calledOnceWithExactly('grpcs://localhost:7051', { + name: 'peer0.org1.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + it('should return correct peer config for a peer with absolute paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + ccp.peers['peer0.org1.example.com'].tlsCACerts.path = path.resolve(__dirname, '..', 'data', 'ccp', 'pems', 'ca.pem'); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getPeer('peer0.org1.example.com'); + mockClient.newPeer.should.have.been.calledOnceWithExactly('grpcs://localhost:7051', { + name: 'peer0.org1.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + + it('should return correct peer config for a peer with relative (to CCP) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getPeer('peer0.org1.example.com'); + mockClient.newPeer.should.have.been.calledOnceWithExactly('grpcs://localhost:7051', { + name: 'peer0.org1.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + + it('should return correct peer config for a peer with relative (to CWD) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-cwd-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getPeer('peer0.org1.example.com'); + mockClient.newPeer.should.have.been.calledOnceWithExactly('grpcs://localhost:7051', { + name: 'peer0.org1.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + it('should throw when peer config for a peer has missing relative (to CCP) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-missing-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + (() => { + networkConfig.getPeer('peer0.org1.example.com'); + }).should.throw(/ENOENT/); + }); + + }); + + describe('#getCertificateAuthority', () => { + + it('should return correct certificate authority config for a peer with no TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getCertificateAuthority('ca.example.com'); + CertificateAuthorityStub.should.have.been.calledOnceWithExactly('ca.example.com', 'ca.example.com', 'http://localhost:7054', undefined, null, undefined); + }); + + it('should return correct certificate authority config for a certificate authority with embedded TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-embedded-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getCertificateAuthority('ca.example.com'); + CertificateAuthorityStub.should.have.been.calledOnceWithExactly('ca.example.com', 'ca.example.com', 'https://localhost:7054', undefined, pem, undefined); + }); + + it('should return correct certificate authority config for a certificate authority with absolute paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + ccp.certificateAuthorities['ca.example.com'].tlsCACerts.path = path.resolve(__dirname, '..', 'data', 'ccp', 'pems', 'ca.pem'); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getCertificateAuthority('ca.example.com'); + CertificateAuthorityStub.should.have.been.calledOnceWithExactly('ca.example.com', 'ca.example.com', 'https://localhost:7054', undefined, pem, undefined); + }); + + + it('should return correct certificate authority config for a certificate authority with relative (to CCP) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getCertificateAuthority('ca.example.com'); + CertificateAuthorityStub.should.have.been.calledOnceWithExactly('ca.example.com', 'ca.example.com', 'https://localhost:7054', undefined, pem, undefined); + }); + + + it('should return correct certificate authority config for a certificate authority with relative (to CWD) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-cwd-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getCertificateAuthority('ca.example.com'); + CertificateAuthorityStub.should.have.been.calledOnceWithExactly('ca.example.com', 'ca.example.com', 'https://localhost:7054', undefined, pem, undefined); + }); + + it('should throw when certificate authority config for a certificate authority has missing relative (to CCP) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-missing-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + (() => { + networkConfig.getCertificateAuthority('ca.example.com'); + }).should.throw(/ENOENT/); + }); + + }); + + describe('#getOrderer', () => { + + it('should return correct orderer config for a orderer with no TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getOrderer('orderer.example.com'); + mockClient.newOrderer.should.have.been.calledOnceWithExactly('grpc://localhost:7050', { + name: 'orderer.example.com', + pem: null, + 'request-timeout': 300000 + }); + }); + + it('should return correct orderer config for a orderer with embedded TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-embedded-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getOrderer('orderer.example.com'); + mockClient.newOrderer.should.have.been.calledOnceWithExactly('grpcs://localhost:7050', { + name: 'orderer.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + it('should return correct orderer config for a orderer with absolute paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + ccp.peers['peer0.org1.example.com'].tlsCACerts.path = path.resolve(__dirname, '..', 'data', 'ccp', 'pems', 'ca.pem'); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getOrderer('orderer.example.com'); + mockClient.newOrderer.should.have.been.calledOnceWithExactly('grpcs://localhost:7050', { + name: 'orderer.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + + it('should return correct orderer config for a orderer with relative (to CCP) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getOrderer('orderer.example.com'); + mockClient.newOrderer.should.have.been.calledOnceWithExactly('grpcs://localhost:7050', { + name: 'orderer.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + + it('should return correct orderer config for a orderer with relative (to CWD) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-relative-cwd-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + networkConfig.getOrderer('orderer.example.com'); + mockClient.newOrderer.should.have.been.calledOnceWithExactly('grpcs://localhost:7050', { + name: 'orderer.example.com', + pem, + 'request-timeout': 300000 + }); + }); + + it('should throw when orderer config for a orderer has missing relative (to CCP) paths to TLS CA certs', () => { + const ccpPath = path.resolve(__dirname, '..', 'data', 'ccp', 'connection-missing-relative-ccp-pems.json'); + const ccpContents = fs.readFileSync(ccpPath, 'utf8'); + const ccp = JSON.parse(ccpContents); + const networkConfig = new NetworkConfig(ccp, mockClient, ccpPath); + (() => { + networkConfig.getOrderer('orderer.example.com'); + }).should.throw(/ENOENT/); + }); + + }); + +}); \ No newline at end of file diff --git a/package.json b/package.json index f1f96f180c..8ebeb8e4b3 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "test:common": "npm run coverage -- fabric-common/test", "test:ca-client": "npm run coverage -- fabric-ca-client/test", "test:client": "npm run coverage -- fabric-client/test", - "test:network": "npm run coverage -- fabric-network/test", - "test:protos": "npm run coverage -- fabric-protos/test", + "test:network": "npm run coverage -- fabric-network/test", + "test:protos": "npm run coverage -- fabric-protos/test", "test:cucumber": "cucumber-js ./test/scenario/features/*.feature", "test:all": "nyc npm run unit-test:all", "unit-test:all": "npm run unit-test -- fabric-ca-client/test && npm run unit-test -- fabric-client/test && npm run unit-test -- fabric-network/test",