Skip to content

Commit

Permalink
added in a connectWithCard API to support the clis (hyperledger-archi…
Browse files Browse the repository at this point in the history
…ves#2431)

* added in a connectWithCard API to support the clis

Signed-off-by: Matthew B White <whitemat@uk.ibm.com>

* review comments

Signed-off-by: Matthew B White <whitemat@uk.ibm.com>
  • Loading branch information
mbwhite authored Oct 20, 2017
1 parent accb86a commit 7a882b3
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 43 deletions.
3 changes: 2 additions & 1 deletion packages/composer-cli/lib/cmds/network/deployCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ module.exports.builder = {
enrollSecret: { alias: 's', required: false, describe: 'The enrollment secret of the user', type: 'string' },
networkAdmin: { alias: 'A', required: false, description: 'The identity name of the business network administrator', type: 'string' },
networkAdminCertificateFile: { alias: 'C', required: false, description: 'The certificate of the business network administrator', type: 'string' },
networkAdminEnrollSecret: { alias: 'S', required: false, description: 'Use enrollment secret for the business network administrator', type: 'boolean' }
networkAdminEnrollSecret: { alias: 'S', required: false, description: 'Use enrollment secret for the business network administrator', type: 'boolean' },
card: { alias: 'c', required: false, description: '', type:'string'}
};

module.exports.handler = (argv) => {
Expand Down
15 changes: 12 additions & 3 deletions packages/composer-cli/lib/cmds/network/lib/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class List {

/**
* Command process for network list command
* The --card option can be used instead of the combination of --enrollId --enrollSecret --connectionProfileName --businessNetworkName
* @param {string} argv argument list from composer command
* @return {Promise} promise when command complete
*/
Expand All @@ -42,11 +43,14 @@ class List {
let businessNetworkDefinition;
let listOutput;
let spinner;
let cardName = argv.card;

let usingCard = !(cardName===undefined);

return (() => {
spinner = ora('List business network '+businessNetworkName);
spinner = ora('List business network '+ (usingCard ? 'from card "'+ cardName +'"' : 'with name "'+businessNetworkName+'"' ));

if (!argv.enrollSecret) {
if (!argv.enrollSecret && !argv.card) {
return cmdUtil.prompt({
name: 'enrollmentSecret',
description: 'What is the enrollment secret of the user?',
Expand All @@ -66,7 +70,12 @@ class List {
enrollId = argv.enrollId;
enrollSecret = argv.enrollSecret;
businessNetworkConnection = cmdUtil.createBusinessNetworkConnection();
return businessNetworkConnection.connect(connectionProfileName, businessNetworkName, enrollId, enrollSecret);
// check to see if we are using a card, if so use card API
if (!usingCard){
return businessNetworkConnection.connect(connectionProfileName, businessNetworkName, enrollId, enrollSecret);
} else {
return businessNetworkConnection.connectWithCard(cardName);
}

})
.then ((result) => {
Expand Down
9 changes: 5 additions & 4 deletions packages/composer-cli/lib/cmds/network/listCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ const List = require ('./lib/list.js');
module.exports.command = 'list [options]';
module.exports.describe = 'List the contents of a business network';
module.exports.builder = {
businessNetworkName: {alias: 'n', required: true, describe: 'The business network name', type: 'string' },
connectionProfileName: {alias: 'p', required: true, describe: 'The connection profile name', type: 'string' },
businessNetworkName: {alias: 'n', required: false, describe: 'The business network name', type: 'string' },
connectionProfileName: {alias: 'p', required: false, describe: 'The connection profile name', type: 'string' },
registry: {alias: 'r', optional: true, describe: 'List specific registry', type: 'string' },
asset: {alias: 'a', optional: true, describe: 'List specific asset', type: 'string' },
enrollId: { alias: 'i', required: true, describe: 'The enrollment ID of the user', type: 'string' },
enrollSecret: { alias: 's', required: false, describe: 'The enrollment secret of the user', type: 'string' }
enrollId: { alias: 'i', required: false, describe: 'The enrollment ID of the user', type: 'string' },
enrollSecret: { alias: 's', required: false, describe: 'The enrollment secret of the user', type: 'string' },
card: {alias: 'c', required: false, describe: '', type: 'string'}
};

module.exports.handler = (argv) => {
Expand Down
109 changes: 84 additions & 25 deletions packages/composer-client/lib/businessnetworkconnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const TransactionRegistry = require('./transactionregistry');
const Util = require('composer-common').Util;
const uuid = require('uuid');
const Registry = require('./registry');

const FileSystemCardStore = require('composer-common').FileSystemCardStore;
const LOG = Logger.getLog('BusinessNetworkConnection');

/**
Expand Down Expand Up @@ -74,6 +74,9 @@ class BusinessNetworkConnection extends EventEmitter {
envConnectionProfileStore
);
}

this.cardStore = options.cardStore || new FileSystemCardStore();

this.connectionProfileStore = connectionProfileStore;
this.connectionProfileManager = new ConnectionProfileManager(this.connectionProfileStore);
this.connection = null;
Expand Down Expand Up @@ -412,37 +415,93 @@ class BusinessNetworkConnection extends EventEmitter {
connect(connectionProfile, businessNetwork, enrollmentID, enrollmentSecret, additionalConnectOptions) {
const method = 'connect';
LOG.entry(method, connectionProfile, businessNetwork, enrollmentID, enrollmentSecret, additionalConnectOptions);

return this.connectionProfileManager.connect(connectionProfile, businessNetwork, additionalConnectOptions)
.then((connection) => {
connection.on('events', (events) => {
events.forEach((event) => {
let serializedEvent = this.getBusinessNetwork().getSerializer().fromJSON(event);
this.emit('event', serializedEvent);
});
});
this.connection = connection;
return connection.login(enrollmentID, enrollmentSecret);
})
.then((securityContext) => {
this.securityContext = securityContext;
return this.ping();
})
.then(() => {
return Util.queryChainCode(this.securityContext, 'getBusinessNetwork', []);
})
.then((buffer) => {
let businessNetworkJSON = JSON.parse(buffer.toString());
let businessNetworkArchive = Buffer.from(businessNetworkJSON.data, 'base64');
return BusinessNetworkDefinition.fromArchive(businessNetworkArchive);
LOG.exit(method);
return this._connectionLogin(connection,enrollmentID, enrollmentSecret);
});

}

/**
* Connects to a business network using a business network card, and authenticates to the Hyperledger Fabric.
* @example
* // Connect and log in to HLF
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('cardName')
* .then(function(businessNetworkDefinition){
* // Connected
* });
* @private
* @param {String} cardName businessNetworkCard Name (must have been imported already)
* @param {Object} [additionalConnectOptions] Additional configuration options supplied
* at runtime that override options set in the connection profile.
* which will override those in the specified connection profile.
* @return {Promise} A promise to a BusinessNetworkDefinition that indicates the connection is complete
*/
connectWithCard(cardName,additionalConnectOptions){
const method = 'connectWithCard';
LOG.entry(method,cardName);

let card;

return this.cardStore.get(cardName)
.then((card_)=>{
card = card_;
return this.connectionProfileManager.connectWithData(card.getConnectionProfile(),card.getBusinessNetworkName(), additionalConnectOptions);
})
.then((businessNetwork) => {
this.businessNetwork = businessNetwork;
this.dynamicQueryFile = this.businessNetwork.getQueryManager().createQueryFile('$dynamic_queries.qry', '');
.then((connection) => {
LOG.exit(method);
return this.businessNetwork;
return this._connectionLogin(connection,card.getUserName(),card.getEnrollmentCredentials().secret);

});

}

/**
* Internal method to login and process the connection
* @private
* @param {Connection} connection connection just established
* @param {String} enrollId enrollment id
* @param {String} enrollmentSecret enrollment secret
* @return {Promise} resolved promise to a BusinessNetworkDefinition when complete
*
*/
_connectionLogin(connection,enrollId,enrollmentSecret){
const method = '_connectionLogin';
LOG.entry(method);

return Promise.resolve()
.then(() =>{
connection.on('events', (events) => {
events.forEach((event) => {
let serializedEvent = this.getBusinessNetwork().getSerializer().fromJSON(event);
this.emit('event', serializedEvent);
});
});
this.connection = connection;
return connection.login(enrollId,enrollmentSecret);
})
.then((securityContext) => {
this.securityContext = securityContext;
return this.ping();
})
.then(() => {
return Util.queryChainCode(this.securityContext, 'getBusinessNetwork', []);
})
.then((buffer) => {
let businessNetworkJSON = JSON.parse(buffer.toString());
let businessNetworkArchive = Buffer.from(businessNetworkJSON.data, 'base64');
return BusinessNetworkDefinition.fromArchive(businessNetworkArchive);
})
.then((businessNetwork) => {
this.businessNetwork = businessNetwork;
this.dynamicQueryFile = this.businessNetwork.getQueryManager().createQueryFile('$dynamic_queries.qry', '');
LOG.exit(method);
return this.businessNetwork;
});
}

/**
* Given a fully qualified name, works out and looks up the registry that this resource will be found in.
Expand Down
5 changes: 3 additions & 2 deletions packages/composer-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
"doc": "jsdoc --pedantic --recurse -c jsdoc.json",
"postdoc": "npm run lint",
"lint": "eslint .",
"test": "node ./scripts/api-changelog.js && npm run mocha",
"mocha": "nyc mocha --recursive -t 10000"
"test": "node ./scripts/api-changelog.js && npm run nyc",
"nyc": "nyc mocha --recursive -t 10000",
"mocha": " mocha --recursive -t 10000"
},
"repository": {
"type": "git",
Expand Down
39 changes: 39 additions & 0 deletions packages/composer-client/test/businessnetworkconnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
const AssetRegistry = require('../lib/assetregistry');
const BusinessNetworkConnection = require('..').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
const CardStore = require('composer-common').BusinessNetworkCardStore;
const ComboConnectionProfileStore = require('composer-common').ComboConnectionProfileStore;
const commonQuery = require('composer-common').Query;
const Connection = require('composer-common').Connection;
const ConnectionProfileStore = require('composer-common').ConnectionProfileStore;
const Factory = require('composer-common').Factory;
const FSConnectionProfileStore = require('composer-common').FSConnectionProfileStore;
const IdCard = require('composer-common').IdCard;
const IdentityRegistry = require('../lib/identityregistry');
const ModelManager = require('composer-common').ModelManager;
const ParticipantRegistry = require('../lib/participantregistry');
Expand Down Expand Up @@ -228,6 +230,43 @@ describe('BusinessNetworkConnection', () => {
});
});

describe('#connectWithCard',()=>{

it('Correct with with existing card name',()=>{
sandbox.stub(businessNetworkConnection.connectionProfileManager, 'connectWithData').resolves(mockConnection);
let mockCardStore = sinon.createStubInstance(CardStore);
let mockIdCard = sinon.createStubInstance(IdCard);
mockCardStore.get.resolves(mockIdCard);
mockIdCard.getEnrollmentCredentials.returns({secret:'password'});
mockIdCard.getUserName.returns('FredBloggs');
businessNetworkConnection.cardStore = mockCardStore;

mockConnection.login.resolves(mockSecurityContext);
mockConnection.ping.resolves();
const buffer = Buffer.from(JSON.stringify({
data: 'aGVsbG8='
}));
sandbox.stub(Util, 'queryChainCode').withArgs(mockSecurityContext, 'getBusinessNetwork', []).resolves(buffer);
sandbox.stub(BusinessNetworkDefinition, 'fromArchive').resolves(mockBusinessNetworkDefinition);
const cb = sinon.stub();
businessNetworkConnection.on('event', cb);
mockConnection.on.withArgs('events', sinon.match.func).yields([
{ $class: 'org.acme.sample.SampleEvent', eventId: 'event1' },
{ $class: 'org.acme.sample.SampleEvent', eventId: 'event2' }
]);

return businessNetworkConnection.connectWithCard('cardName')
.then((result)=>{
sinon.assert.calledOnce(mockCardStore.get);
sinon.assert.calledWith(mockCardStore.get,'cardName');
sinon.assert.calledWith(mockConnection.login,'FredBloggs','password');
});
});

});



describe('#disconnect', () => {

it('should do nothing if not connected', () => {
Expand Down
57 changes: 50 additions & 7 deletions packages/composer-common/lib/connectionprofilemanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,36 @@ class ConnectionProfileManager {
getConnectionManager(connectionProfile) {
const METHOD = 'getConnectionManager';
LOG.info(METHOD,'Looking up a connection manager for profile', connectionProfile);
let errorList = [];

return this.connectionProfileStore.load(connectionProfile)
.then((data) => {
LOG.debug(METHOD,data);
let connectionManager = connectionManagers[data.type];
return this.getConnectionManagerByType(data.type);
});
}

/**
* Retrieves the ConnectionManager for the given connection type.
*
* @param {string} connectionType The type of the connection type
* @return {Promise} A promise that is resolved with a {@link ConnectionManager}
* object once the connection is established, or rejected with a connection error.
*/
getConnectionManagerByType(connectionType) {
const METHOD = 'getConnectionManagerByType';
LOG.info(METHOD,'Looking up a connection manager for type', connectionType);
let errorList = [];

return Promise.resolve()
.then(() => {
let connectionManager = connectionManagers[connectionType];
if(!connectionManager) {
const mod = `composer-connector-${data.type}`;
const mod = `composer-connector-${connectionType}`;
LOG.debug(METHOD,'Looking for module',mod);
try {
// Check for the connection manager class registered using
// registerConnectionManager (used by the web connector).
let connectionManagerClass = connectionManagerClasses[data.type];
let connectionManagerClass = connectionManagerClasses[connectionType];
if (connectionManagerClass) {
connectionManager = new(connectionManagerClass)(this);
} else {
Expand Down Expand Up @@ -157,11 +174,11 @@ class ConnectionProfileManager {
return index === self.indexOf(element);
});

const newError = new Error(`Failed to load connector module "${mod}" for connection profile "${connectionProfile}". ${errorList.join('-')}`);
const newError = new Error(`Failed to load connector module "${mod}" for connection type "${connectionType}". ${errorList.join('-')}`);
LOG.error(METHOD, newError);
throw newError;
}
connectionManagers[data.type] = connectionManager;
connectionManagers[connectionType] = connectionManager;
}
return connectionManager;
});
Expand All @@ -177,7 +194,6 @@ class ConnectionProfileManager {
* at runtime that override options set in the connection profile.
* @return {Promise} A promise that is resolved with a {@link Connection}
* object once the connection is established, or rejected with a connection error.
* @abstract
*/
connect(connectionProfile, businessNetworkIdentifier, additionalConnectOptions) {
LOG.info('connect','Connecting using ' + connectionProfile, businessNetworkIdentifier);
Expand All @@ -196,6 +212,33 @@ class ConnectionProfileManager {
});
}


/**
* Connect with the actuall connection profile data, so the look up of the connection profile
* is by passed as this has come direct from the business network card.
*
* @param {Object} connectionProfileData object representing of the connection profile
* @param {String} businessNetworkIdentifier id of the business network
* @param {Object} [additionalConnectOptions] additional options
* @return {Promise} A promise that is resolved with a {@link Connection}
* object once the connection is established, or rejected with a connection error.
*/
connectWithData(connectionProfileData, businessNetworkIdentifier, additionalConnectOptions) {
let connectOptions = connectionProfileData;

return Promise.resolve().then( ()=>{
if (additionalConnectOptions) {
connectOptions = Object.assign(connectOptions, additionalConnectOptions);
}
return this.getConnectionManagerByType(connectOptions.type);
})
.then((connectionManager) => {
// todo - this connect is duplicating values
return connectionManager.connect(connectOptions.name, businessNetworkIdentifier, connectOptions);
});

}

/**
* Clear the static object containing all the connection managers
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/composer-common/lib/idcard.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class IdCard {
*/
getEnrollmentCredentials() {
const secret = this.metadata.enrollmentSecret;
return secret ? { secret: secret } : null;
return secret ? { secret : secret } : null;
}

/**
Expand Down
Loading

0 comments on commit 7a882b3

Please sign in to comment.