diff --git a/fabric-network/index.js b/fabric-network/index.js index 2bb5c294b3..ca9a00edcb 100644 --- a/fabric-network/index.js +++ b/fabric-network/index.js @@ -7,3 +7,4 @@ module.exports.Network = require('./lib/network'); module.exports.InMemoryWallet = require('./lib/impl/wallet/inmemorywallet'); module.exports.X509WalletMixin = require('./lib/impl/wallet/x509walletmixin'); +module.exports.FileSystemWallet = require('./lib/impl/wallet/filesystemwallet'); diff --git a/fabric-network/lib/channel.js b/fabric-network/lib/channel.js index d9e37579ca..d092007413 100644 --- a/fabric-network/lib/channel.js +++ b/fabric-network/lib/channel.js @@ -7,7 +7,7 @@ 'use strict'; const FabricConstants = require('fabric-client/lib/Constants'); const Contract = require('./contract'); -const logger = require('./logger').getLogger('channel.js'); +const logger = require('./logger').getLogger('FabricNetwork.Channel'); const util = require('util'); class Channel { @@ -166,4 +166,4 @@ class Channel { } -module.exports = Channel; \ No newline at end of file +module.exports = Channel; diff --git a/fabric-network/lib/contract.js b/fabric-network/lib/contract.js index 58c9963dcc..cde0767a64 100644 --- a/fabric-network/lib/contract.js +++ b/fabric-network/lib/contract.js @@ -6,7 +6,7 @@ 'use strict'; -const logger = require('./logger').getLogger('contract.js'); +const logger = require('./logger').getLogger('Contract'); const util = require('util'); class Contract { @@ -148,4 +148,4 @@ class Contract { } } -module.exports = Contract; \ No newline at end of file +module.exports = Contract; diff --git a/fabric-network/lib/impl/wallet/basewallet.js b/fabric-network/lib/impl/wallet/basewallet.js index b948d5bf5f..254eb79ff8 100644 --- a/fabric-network/lib/impl/wallet/basewallet.js +++ b/fabric-network/lib/impl/wallet/basewallet.js @@ -9,14 +9,14 @@ const Client = require('fabric-client'); const X509WalletMixin = require('./x509walletmixin'); const Wallet = require('../../api/wallet'); -const logger = require('../../logger').getLogger('network.js'); +const logger = require('../../logger').getLogger('BaseWallet'); const util = require('util'); class BaseWallet extends Wallet { constructor(walletMixin = new X509WalletMixin()) { super(); - logger.debug(util.format('in BaseWallet constructor, mixin = %O', walletMixin)); + logger.debug('in BaseWallet constructor, mixin = %O', walletMixin); this.storesInitialized = false; this.walletMixin = walletMixin; } @@ -35,7 +35,7 @@ class BaseWallet extends Wallet { * @memberof Wallet */ async setUserContext(client, label) { - logger.debug(util.format('in setUserContext, label = %s', label)); + logger.debug('in setUserContext, label = %s', label); label = this.normalizeLabel(label); @@ -53,7 +53,7 @@ class BaseWallet extends Wallet { } async configureClientStores(client, label) { - logger.debug(util.format('in configureClientStores, label = %s', label)); + logger.debug('in configureClientStores, label = %s', label); label = this.normalizeLabel(label); if (!client) { @@ -104,7 +104,7 @@ class BaseWallet extends Wallet { //========================================================= async import(label, identity) { - logger.debug(util.format('in import, label = %s', label)); + logger.debug('in import, label = %s', label); label = this.normalizeLabel(label); const client = await this.configureClientStores(null, label); @@ -117,7 +117,7 @@ class BaseWallet extends Wallet { } async export(label) { - logger.debug(util.format('in export, label = %s', label)); + logger.debug('in export, label = %s', label); label = this.normalizeLabel(label); const client = await this.configureClientStores(null, label); @@ -155,7 +155,7 @@ class BaseWallet extends Wallet { } } - logger.debug(util.format('list returns %j', idInfoList)); + logger.debug('list returns %j', idInfoList); return idInfoList; } diff --git a/fabric-network/lib/impl/wallet/filesystemwallet.js b/fabric-network/lib/impl/wallet/filesystemwallet.js new file mode 100644 index 0000000000..1a835ba18f --- /dev/null +++ b/fabric-network/lib/impl/wallet/filesystemwallet.js @@ -0,0 +1,167 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +'use strict'; + +const Client = require('fabric-client'); +const rimraf = require('rimraf'); +const fs = require('fs-extra'); +const Path = require('path'); +const BaseWallet = require('./basewallet'); +const FileKVS = require('fabric-client/lib/impl/FileKeyValueStore'); +const logger = require('../../logger').getLogger('FileSystemWallet'); + +/** + * This class defines an implementation of an Identity wallet that persists + * to the file system. + * + * @class FileSystemWallet + * @see See {@link BaseWallet} + * @extends {BaseWallet} + */ +class FileSystemWallet extends BaseWallet { + + /* + * create a new File Key Value Store + * + * @static + * @param {string} path the root path of the key value store + * @returns {Promise} a promise that is resolved when a new File KVS instance is recreated. + * @memberof FileSystemWallet + * @private + */ + static async _createFileKVS(path) { + return await new FileKVS({path}); + } + + /* + * check to see if the label defines a directory in the wallet + * + * @static + * @param {string} label + * @returns {Promise} a promise that returns true if this is a valid directory, false otherwise + * @memberof FileSystemWallet + * @private + */ + async _isDirectory(label) { + const method = '_isDirectory'; + let isDir; + try { + const stat = await fs.lstat(Path.join(this.path, label)); + isDir = stat.isDirectory(); + } catch(err) { + isDir = false; + } + logger.debug('%s - return value: %s', method, isDir); + return isDir; + } + + /** + * Creates an instance of FileSystemWallet. + * @param {string} path The root path for this wallet on the file system + * @param {WalletMixin} [mixin] optionally provide an alternative WalletMixin. Defaults to X509WalletMixin + * @memberof FileSystemWallet + */ + constructor(path, mixin) { + if (!path) { + throw new Error('No path for wallet has been provided'); + } + super(mixin); + this.path = path; + } + + /* + * Get the partitioned path for the provided label + * + * @param {string} label + * @returns {string} the partitioned path + * @memberof FileSystemWallet + * @private + */ + _getPartitionedPath(label) { + label = this.normalizeLabel(label); + const partitionedPath = Path.join(this.path, label); + return partitionedPath; + } + + /** + * @inheritdoc + */ + async getStateStore(label) { + const partitionedPath = this._getPartitionedPath(label); + return FileSystemWallet._createFileKVS(partitionedPath); + } + + /** + * @inheritdoc + */ + async getCryptoSuite(label) { + const partitionedPath = this._getPartitionedPath(label); + const cryptoSuite = Client.newCryptoSuite(); + cryptoSuite.setCryptoKeyStore(Client.newCryptoKeyStore({path: partitionedPath})); + return cryptoSuite; + } + + /** + * @inheritdoc + */ + async getAllLabels() { + let dirList; + const labelList = []; + try { + dirList = await fs.readdir(this.path); + } catch(err) { + return []; + } + + if (dirList && dirList.length > 0) { + for (const label of dirList) { + const reallyExists = await this.exists(label); + if (reallyExists) { + labelList.push(label); + } + } + } + return labelList; + } + + /** + * @inheritdoc + */ + async delete(label) { + const method = 'delete'; + const reallyExists = await this.exists(label); + if (!reallyExists) { + return false; + } + const partitionedPath = this._getPartitionedPath(label); + const rmPromise = new Promise((resolve, reject) => { + rimraf(partitionedPath, (err) => { + if (err) { + logger.debug('%s - error returned trying to rm rf \'%s\': %s', method, partitionedPath, err); + reject(err); + } + resolve(true); + }); + }); + return await rmPromise; + } + + /** + * @inheritdoc + */ + async exists(label) { + const method = 'exists'; + let exists = false; + const isDir = await this._isDirectory(label); + if (isDir) { + exists = await fs.exists(Path.join(this._getPartitionedPath(label), label)); + } + logger.debug('%s - label: %s, isDir: %s, exists: %s', method, label, isDir, exists); + return exists; + } +} + +module.exports = FileSystemWallet; diff --git a/fabric-network/lib/impl/wallet/inmemorywallet.js b/fabric-network/lib/impl/wallet/inmemorywallet.js index 07bf60eaac..9134410e3d 100644 --- a/fabric-network/lib/impl/wallet/inmemorywallet.js +++ b/fabric-network/lib/impl/wallet/inmemorywallet.js @@ -9,7 +9,7 @@ const Client = require('fabric-client'); const BaseWallet = require('./basewallet'); const api = require('fabric-client/lib/api.js'); -const logger = require('../../logger').getLogger('network.js'); +const logger = require('../../logger').getLogger('InMemoryWallet'); const util = require('util'); // this will be shared across all instance of a memory wallet, so really an app should @@ -24,14 +24,14 @@ class InMemoryWallet extends BaseWallet { } async getStateStore(label) { - logger.debug(util.format('in getStateStore, label = %s', label)); + logger.debug('in getStateStore, label = %s', label); label = this.normalizeLabel(label); const store = await new InMemoryKVS(label); return store; } async getCryptoSuite(label) { - logger.debug(util.format('in getCryptoSuite, label = %s', label)); + logger.debug('in getCryptoSuite, label = %s', label); label = this.normalizeLabel(label); const cryptoSuite = Client.newCryptoSuite(); cryptoSuite.setCryptoKeyStore(Client.newCryptoKeyStore(InMemoryKVS, label)); @@ -39,20 +39,24 @@ class InMemoryWallet extends BaseWallet { } async delete(label) { - logger.debug(util.format('in delete, label = %s', label)); + logger.debug('in delete, label = %s', label); label = this.normalizeLabel(label); - memoryStore.delete(label); + if (memoryStore.has(label)) { + memoryStore.delete(label); + return true; + } + return false; } async exists(label) { - logger.debug(util.format('in exists, label = %s', label)); + logger.debug('in exists, label = %s', label); label = this.normalizeLabel(label); return memoryStore.has(label); } async getAllLabels() { const labels = Array.from(memoryStore.keys()); - logger.debug(util.format('getAllLabels returns: %j', labels)); + logger.debug('getAllLabels returns: %j', labels); return labels; } } @@ -93,4 +97,4 @@ class InMemoryKVS extends api.KeyValueStore { } } -module.exports = InMemoryWallet; \ No newline at end of file +module.exports = InMemoryWallet; diff --git a/fabric-network/lib/impl/wallet/x509walletmixin.js b/fabric-network/lib/impl/wallet/x509walletmixin.js index c0021d2c6b..4ee7270f1d 100644 --- a/fabric-network/lib/impl/wallet/x509walletmixin.js +++ b/fabric-network/lib/impl/wallet/x509walletmixin.js @@ -6,7 +6,7 @@ 'use strict'; -const logger = require('../../logger').getLogger('network.js'); +const logger = require('../../logger').getLogger('X509WalletMixin'); const util = require('util'); class X509WalletMixin { @@ -22,7 +22,7 @@ class X509WalletMixin { } async importIdentity(client, label, identity) { - logger.debug(util.format('in importIdentity, label = %s', label)); + logger.debug('in importIdentity, label = %s', label); // check identity type const cryptoContent = { signedCertPEM: identity.certificate, @@ -38,7 +38,7 @@ class X509WalletMixin { } async exportIdentity(client, label) { - logger.debug(util.format('in exportIdentity, label = %s', label)); + logger.debug('in exportIdentity, label = %s', label); const user = await client.getUserContext(label, true); let result = null; if (user) { @@ -52,7 +52,7 @@ class X509WalletMixin { } async getIdentityInfo(client, label) { - logger.debug(util.format('in getIdentityInfo, label = %s', label)); + logger.debug('in getIdentityInfo, label = %s', label); const user = await client.getUserContext(label, true); let result = null; if (user) { @@ -66,4 +66,4 @@ class X509WalletMixin { } } -module.exports = X509WalletMixin; \ No newline at end of file +module.exports = X509WalletMixin; diff --git a/fabric-network/lib/network.js b/fabric-network/lib/network.js index c580e416c0..93882352ed 100644 --- a/fabric-network/lib/network.js +++ b/fabric-network/lib/network.js @@ -8,7 +8,7 @@ const Client = require('fabric-client'); const Channel = require('./channel'); -const logger = require('./logger').getLogger('network.js'); +const logger = require('./logger').getLogger('Network'); class Network { @@ -142,4 +142,4 @@ class Network { } -module.exports = Network; \ No newline at end of file +module.exports = Network; diff --git a/fabric-network/test/impl/wallet/filesystemwallet.js b/fabric-network/test/impl/wallet/filesystemwallet.js new file mode 100644 index 0000000000..d377cd2ddf --- /dev/null +++ b/fabric-network/test/impl/wallet/filesystemwallet.js @@ -0,0 +1,329 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; +const sinon = require('sinon'); +const chai = require('chai'); +chai.use(require('chai-as-promised')); +const should = chai.should(); +const rewire = require('rewire'); + +const FileSystemWallet = rewire('../../../lib/impl/wallet/filesystemwallet'); +const X509WalletMixin = require('../../../lib/impl/wallet/x509walletmixin'); +const Client = require('fabric-client'); +const api = require('fabric-client/lib/api.js'); +const fs = require('fs-extra'); +const Path = require('path'); +const rimraf = require('rimraf'); +const os = require('os'); + +describe('FileSystemWallet', () => { + let testwallet; + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + testwallet = new FileSystemWallet('/somepath'); + sinon.stub(testwallet, 'normalizeLabel').returnsArg(0); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('#constructor', () => { + it('should throw an error if path not defined', () => { + (() => {new FileSystemWallet();}).should.throw(/No path/); + }); + + it('should default to X509 wallet mixin', () => { + testwallet.walletMixin.should.be.an.instanceof(X509WalletMixin); + }); + + it('should accept a mixin parameter', () => { + const wallet = new FileSystemWallet('/somepath','my_mixin'); + wallet.walletMixin.should.equal('my_mixin'); + }); + }); + + describe('#_createFileKVS', () => { + it('should create a File Key Value Store', async () => { + sandbox.stub(fs, 'mkdirs').callsArg(1); + const store = await FileSystemWallet._createFileKVS('test'); + store.should.be.an.instanceof(api.KeyValueStore); + }); + }); + + describe('#_getPartitionedPath', () => { + it('should create partitioned path', () => { + sandbox.stub(Path, 'join').returns('/joined/path'); + testwallet._getPartitionedPath('label'); + sinon.assert.calledOnce(testwallet.normalizeLabel); + sinon.assert.calledOnce(Path.join); + sinon.assert.calledWith(Path.join, '/somepath', 'label'); + }); + }); + + describe('#_isDirectory', () => { + beforeEach(() => { + sandbox.stub(Path, 'join').withArgs('/somepath', 'adir').returns('/somepath/adir'); + }); + + it('should return true if a directory', async () => { + sandbox.stub(fs, 'lstat').withArgs('/somepath/adir').resolves( + { + isDirectory: () => { + return true; + } + } + ); + const isDir = await testwallet._isDirectory('adir'); + isDir.should.be.true; + }); + + it('should return false if not a directory',async () => { + sandbox.stub(fs, 'lstat').withArgs('/somepath/adir').resolves( + { + isDirectory: () => { + return false; + } + } + ); + const isDir = await testwallet._isDirectory('adir'); + isDir.should.be.false; + }); + + it('should return false if an error is thrown', async () => { + sandbox.stub(fs, 'lstat').rejects(new Error('bad karma')); + const isDir = await testwallet._isDirectory('adir'); + isDir.should.be.false; + }); + + }); + + describe('#getStateStore', () => { + it('should create a KV store', async () => { + // use Error as a class to be detected + sandbox.stub(FileSystemWallet, '_createFileKVS').resolves(new Error()); + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path'); + const store = await testwallet.getStateStore('test'); + sinon.assert.calledOnce(FileSystemWallet._createFileKVS); + sinon.assert.calledWith(FileSystemWallet._createFileKVS, '/partitioned/path'); + store.should.be.an.instanceof(Error); + }); + }); + + describe('#getCryptoSuite', () => { + it('should create a KV store', async () => { + sandbox.stub(Client, 'newCryptoKeyStore').returns({}); + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path2'); + const suite = await testwallet.getCryptoSuite('test'); + sinon.assert.calledOnce(Client.newCryptoKeyStore); + sinon.assert.calledWith(Client.newCryptoKeyStore, {path: '/partitioned/path2'}); + suite.should.be.an.instanceof(api.CryptoSuite); + }); + }); + + describe('#exists', () => { + it('should return true if directory and file of same name exists', async () => { + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path3/user1'); + sandbox.stub(fs, 'exists').withArgs('/partitioned/path3/user1/user1').resolves(true); + sinon.stub(testwallet, '_isDirectory').withArgs('user1').resolves(true); + + const exists = await testwallet.exists('user1'); + exists.should.equal(true); + sinon.assert.calledOnce(fs.exists); + sinon.assert.calledOnce(testwallet._isDirectory); + }); + + it('should return false if directory but file of same name does not exist', async () => { + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path3/user1'); + sandbox.stub(fs, 'exists').withArgs('/partitioned/path3/user1/user1').resolves(false); + sinon.stub(testwallet, '_isDirectory').withArgs('user1').resolves(true); + + const exists = await testwallet.exists('user1'); + exists.should.equal(false); + sinon.assert.calledOnce(fs.exists); + sinon.assert.calledOnce(testwallet._isDirectory); + }); + + it('should return false if directory does not exist', async () => { + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path3/user1'); + sinon.stub(testwallet, '_isDirectory').withArgs('user1').resolves(false); + sandbox.stub(fs, 'exists'); + + const exists = await testwallet.exists('user1'); + exists.should.equal(false); + sinon.assert.calledOnce(testwallet._isDirectory); + sinon.assert.notCalled(fs.exists); + }); + }); + + describe('#delete', () => { + const savedRimRaf = FileSystemWallet.__get__('rimraf'); + + afterEach(() => { + FileSystemWallet.__set__('rimraf', savedRimRaf); + }); + + it('should delete an identity from the wallet if it exists', async () => { + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path5/user1'); + sinon.stub(testwallet, 'exists').withArgs('user1').returns(true); + const rimrafStub = sinon.stub(); + FileSystemWallet.__set__('rimraf', rimrafStub.callsArg(1)); + const success = await testwallet.delete('user1'); + success.should.be.true; + sinon.assert.calledOnce(rimrafStub); + }); + + it('should return false if identity does not exist', async () => { + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path5/user1'); + sinon.stub(testwallet, 'exists').withArgs('user1').returns(false); + const rimrafStub = sinon.stub(); + FileSystemWallet.__set__('rimraf', rimrafStub.callsArg(1)); + const success = await testwallet.delete('user1'); + success.should.be.false; + sinon.assert.notCalled(rimrafStub); + }); + + + it('should throw an error if delete fails', () => { + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path6/user2'); + sinon.stub(testwallet, 'exists').withArgs('user2').returns(true); + + const rimrafStub = sinon.stub(); + FileSystemWallet.__set__('rimraf', rimrafStub.callsArgWith(1, new Error('Unable to delete'))); + testwallet.delete('user2').should.eventually.be.rejectedWith(/Unable to delete/); + }); + }); + + describe('#getAllLabels', () => { + // test readdir returns null, [], throws error + it('should list all identities in the wallet', async () => { + + // user1 and user3 are the only valid identities + sandbox.stub(fs, 'readdir').resolves(['user1', 'user2', 'user3', 'user4']); + const isDirStub = sinon.stub(testwallet, '_isDirectory'); + sinon.stub(testwallet, '_getPartitionedPath').returns('/partitioned/path7'); + const existsStub = sandbox.stub(fs, 'exists'); + isDirStub.withArgs('user1').resolves(true); + isDirStub.withArgs('user2').resolves(false); + isDirStub.returns(true); + + existsStub.withArgs('/partitioned/path7/user1').resolves(true); + existsStub.withArgs('/partitioned/path7/user3').resolves(true); + existsStub.withArgs('/partitioned/path7/user4').resolves(false); + + const labels = await testwallet.getAllLabels(); + labels.length.should.equal(2); + labels.includes('user1').should.equal(true); + labels.includes('user3').should.equal(true); + }); + + it('should handle no entries in the wallet - 1', async () => { + sandbox.stub(fs, 'readdir').resolves(null); + const labels = await testwallet.getAllLabels(); + labels.length.should.equal(0); + }); + + it('should handle no entries in the wallet - 2', async () => { + sandbox.stub(fs, 'readdir').resolves([]); + const labels = await testwallet.getAllLabels(); + labels.length.should.equal(0); + }); + + it('should handle no entries in the wallet - 3', async () => { + sandbox.stub(fs, 'readdir').rejects(new Error('no directory')); + const labels = await testwallet.getAllLabels(); + labels.length.should.equal(0); + }); + + }); + + describe('Functional tests', () => { + const tmpdir = Path.join(os.tmpdir(), 'unittest-network-test163'); + + after(async () => { + const rimRafPromise = new Promise((resolve) => { + rimraf(tmpdir, (err) => { + if (err) { + //eslint-disable-next-line no-console + console.log(`failed to delete ${tmpdir}, error was ${err}`); + resolve(); + } + resolve(); + }); + }); + await rimRafPromise; + }); + + it('should perform all the actions of a wallet correctly', async () => { + const cert = `-----BEGIN CERTIFICATE----- +MIICfzCCAiWgAwIBAgIUNAqZVk9s5/HR7k30feNp8DrYbK4wCgYIKoZIzj0EAwIw +cDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh +biBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xGTAXBgNVBAMT +EG9yZzEuZXhhbXBsZS5jb20wHhcNMTgwMjI2MjAwOTAwWhcNMTkwMjI2MjAxNDAw +WjBdMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExFDASBgNV +BAoTC0h5cGVybGVkZ2VyMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWlu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz05miTKv6Vz+qhc5362WIZ44fs/H +X5m9zDOifle5HIjt4Usj+TiUgT1hpbI8UI9pueWhbrZpZXlX6+mImi52HaOBrzCB +rDAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPnxMtT6jgYsMAgI38ponGs8sgbqMCsG +A1UdIwQkMCKAIKItrzVrKqtXkupT419m/M7x1/GqKzorktv7+WpEjqJqMCEGA1Ud +EQQaMBiCFnBlZXIwLm9yZzEuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDSAAwRQIh +AM1JowZMshCRs6dnOfRmUHV7399KnNvs5QoNw93cuQuAAiBtBEGh1Xt50tZjDcYN +j+yx4IraL4JvMrCHbR5/R+Xo1Q== +-----END CERTIFICATE-----`; + const key = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgbTXpl4NGXuPtSC/V +PTVNGVBgVv8pZ6kGktVcnQD0KiKhRANCAATPTmaJMq/pXP6qFznfrZYhnjh+z8df +mb3MM6J+V7kciO3hSyP5OJSBPWGlsjxQj2m55aFutmlleVfr6YiaLnYd +-----END PRIVATE KEY----- +`; + + const identityLabel = 'User1@org1.example.com'; + + const fileSystemWallet = new FileSystemWallet(tmpdir); + + // test list works + let list = await fileSystemWallet.list(); + list.length.should.equal(0); + + // test import and exists works + await fileSystemWallet.import(identityLabel, X509WalletMixin.createIdentity('Org1MSP', cert, key)); + let exists = await fileSystemWallet.exists(identityLabel); + exists = exists && await fs.exists(tmpdir); + exists = exists && await fs.exists(Path.join(tmpdir, identityLabel)); + exists.should.be.true; + + // test exports works + let exported = await fileSystemWallet.export(identityLabel); + exported.privateKey = exported.privateKey.replace(/(\r\n\t|\n|\r\t|\r)/gm, ''); + const orgKey = key.replace(/(\r\n\t|\n|\r\t|\r)/gm, ''); + + exported.should.deep.equal({ + certificate: cert, + privateKey: orgKey, + mspId: 'Org1MSP', + type: 'X509' + }); + exported = await fileSystemWallet.export('IdoNotExist'); + should.equal(null, exported); + + // test list works + list = await fileSystemWallet.list(); + list.length.should.equal(1); + list[0].label.should.equal(identityLabel); + + // test delete + let wasDeleted = await fileSystemWallet.delete(identityLabel); + exists = await fs.exists(Path.join(tmpdir, identityLabel)); + wasDeleted = wasDeleted && !exists; + wasDeleted.should.be.true; + wasDeleted = await fileSystemWallet.delete(identityLabel); + wasDeleted.should.be.false; + }); + }); +}); diff --git a/fabric-network/test/impl/wallet/inmemorywallet.js b/fabric-network/test/impl/wallet/inmemorywallet.js index 5a4bc436ed..25ee5e8767 100644 --- a/fabric-network/test/impl/wallet/inmemorywallet.js +++ b/fabric-network/test/impl/wallet/inmemorywallet.js @@ -172,9 +172,12 @@ mb3MM6J+V7kciO3hSyP5OJSBPWGlsjxQj2m55aFutmlleVfr6YiaLnYd it('should delete an identity from the wallet', async () => { let exists = await wallet.exists('user1'); exists.should.equal(true); - await wallet.delete('user1'); + let deleted = await wallet.delete('user1'); + deleted.should.be.true; exists = await wallet.exists('user1'); exists.should.equal(false); + deleted = await wallet.delete('user1'); + deleted.should.be.false; }); }); @@ -217,4 +220,4 @@ mb3MM6J+V7kciO3hSyP5OJSBPWGlsjxQj2m55aFutmlleVfr6YiaLnYd const value2 = await store.getValue('user3'); }); }); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 99eb221fa1..c564321c8c 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "nyc": "^11.8.0", "require-dir": "^1.0.0", "rewire": "^4.0.1", + "rimraf": "2.6.2", "sinon": "6.1.3", "tap-colorize": "^1.2.0", "tape": "^4.5.1", diff --git a/test/integration/network-e2e/invoke.js b/test/integration/network-e2e/invoke.js index 244326e106..c58fa41fa5 100644 --- a/test/integration/network-e2e/invoke.js +++ b/test/integration/network-e2e/invoke.js @@ -11,28 +11,31 @@ const tape = require('tape'); const _test = require('tape-promise').default; const test = _test(tape); -const {Network, InMemoryWallet, X509WalletMixin} = require('../../../fabric-network/index.js'); -const fs = require('fs'); +const {Network, InMemoryWallet, FileSystemWallet, X509WalletMixin} = require('../../../fabric-network/index.js'); +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); +const rimraf = require('rimraf'); const e2eUtils = require('../e2e/e2eUtils.js'); const testUtils = require('../../unit/util'); const channelName = testUtils.NETWORK_END2END.channel; const chaincodeId = testUtils.NETWORK_END2END.chaincodeId; -test('\n\n***** Network End-to-end flow: invoke transaction to move money *****\n\n', async (t) => { +test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet *****\n\n', async (t) => { try { const fixtures = process.cwd() + '/test/fixtures'; - const credPath = fixtures + '/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls'; - const cert = fs.readFileSync(credPath + '/cert.pem').toString(); - const key = fs.readFileSync(credPath + '/key.pem').toString(); + const credPath = fixtures + '/channel/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com'; + const cert = fs.readFileSync(credPath + '/signcerts/User1@org1.example.com-cert.pem').toString(); + const key = fs.readFileSync(credPath + '/keystore/e4af7f90fa89b3e63116da5d278855cfb11e048397261844db89244549918731_sk').toString(); const inMemoryWallet = new InMemoryWallet(); - await inMemoryWallet.import('admin', X509WalletMixin.createIdentity('Org1MSP', cert, key)); - const exists = await inMemoryWallet.exists('admin'); + await inMemoryWallet.import('User1@org1.example.com', X509WalletMixin.createIdentity('Org1MSP', cert, key)); + const exists = await inMemoryWallet.exists('User1@org1.example.com'); if(exists) { - t.pass('Successfully imported admin into wallet'); + t.pass('Successfully imported User1@org1.example.com into wallet'); } else { - t.fail('Failed to import admin into wallet'); + t.fail('Failed to import User1@org1.example.com into wallet'); } const network = new Network(); @@ -40,7 +43,7 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money *****\ const ccp = fs.readFileSync(fixtures + '/network.json'); await network.initialize(JSON.parse(ccp.toString()), { wallet: inMemoryWallet, - identity: 'admin' + identity: 'User1@org1.example.com' }); const tlsInfo = await e2eUtils.tlsEnroll('org1'); @@ -82,3 +85,82 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money *****\ t.end(); }); + +test('\n\n***** Network End-to-end flow: invoke transaction to move money using in file system wallet *****\n\n', async (t) => { + const tmpdir = path.join(os.tmpdir(), 'integration-network-test987'); + + try { + // define the identity to use + const fixtures = process.cwd() + '/test/fixtures'; + const credPath = fixtures + '/channel/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com'; + const cert = fs.readFileSync(credPath + '/signcerts/User1@org1.example.com-cert.pem').toString(); + const key = fs.readFileSync(credPath + '/keystore/e4af7f90fa89b3e63116da5d278855cfb11e048397261844db89244549918731_sk').toString(); + const identityLabel = 'User1@org1.example.com'; + + const fileSystemWallet = new FileSystemWallet(tmpdir); + + // prep wallet and test it at the same time + await fileSystemWallet.import(identityLabel, X509WalletMixin.createIdentity('Org1MSP', cert, key)); + const exists = await fileSystemWallet.exists(identityLabel); + t.ok(exists, 'Successfully imported User1@org1.example.com into wallet'); + + const network = new Network(); + + const ccp = fs.readFileSync(fixtures + '/network.json'); + await network.initialize(JSON.parse(ccp.toString()), { + wallet: fileSystemWallet, + identity: identityLabel + }); + + const tlsInfo = await e2eUtils.tlsEnroll('org1'); + network.getClient().setTlsClientCertAndKey(tlsInfo.certificate, tlsInfo.key); + + t.pass('Initialized the network'); + + const channel = await network.getChannel(channelName); + + t.pass('Initialized the channel, ' + channelName); + + const contract = await channel.getContract(chaincodeId); + + t.pass('Got the contract, about to submit "move" transaction'); + + let response = await contract.submitTransaction('move', 'a', 'b','100'); + + const expectedResult = 'move succeed'; + if(response.toString() === expectedResult){ + t.pass('Successfully invoked transaction chaincode on channel'); + } + else { + t.fail('Unexpected response from transaction chaincode: ' + response); + } + + try { + response = await contract.submitTransaction('throwError', 'a', 'b','100'); + t.fail('Transaction "throwError" should have thrown an error. Got response: ' + response.toString()); + } catch(expectedErr) { + if(expectedErr.message.includes('throwError: an error occurred')) { + t.pass('Successfully handled invocation errors'); + } else { + t.fail('Unexpected exception: ' + expectedErr.message); + } + } + } catch(err) { + t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); + } finally { + // delete the file system wallet. + const rimRafPromise = new Promise((resolve) => { + rimraf(tmpdir, (err) => { + if (err) { + //eslint-disable-next-line no-console + console.log(`failed to delete ${tmpdir}, error was ${err}`); + resolve(); + } + resolve(); + }); + }); + await rimRafPromise; + } + + t.end(); +});