/* * 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 AssetRegistry = require('./assetregistry'); const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition; const ConnectionProfileManager = require('composer-common').ConnectionProfileManager; const EventEmitter = require('events'); const fs = require('fs'); const FSConnectionProfileStore = require('composer-common').FSConnectionProfileStore; const Logger = require('composer-common').Logger; const ParticipantRegistry = require('./participantregistry'); const Resource = require('composer-common').Resource; const TransactionDeclaration = require('composer-common').TransactionDeclaration; const TransactionRegistry = require('./transactionregistry'); const Util = require('composer-common').Util; const uuid = require('uuid'); const LOG = Logger.getLog('BusinessNetworkConnection'); /** * Use this class to connect to and then interact with a deployed BusinessNetworkDefinition. * Use the AdminConnection class in the composer-admin module to deploy BusinessNetworksDefinitions. * @extends EventEmitter * @see See [EventEmitter]{@link module:composer-client.EventEmitter} * @class * @memberof module:composer-client */ class BusinessNetworkConnection extends EventEmitter { /** * Create an instance of the BusinessNetworkConnection class. * must be called to connect to a deployed BusinessNetworkDefinition. * @param {Object} [options] - an optional set of options to configure the instance. * @param {Object} [options.fs] - specify an fs implementation to use. * @param {boolean} [options.developmentMode] - specify whether or not the instance * is in development mode. Use only for testing purposes! */ constructor(options) { super(); options = options || {}; this.developmentMode = options.developmentMode || false; this.connection = null; this.connectionProfileStore = new FSConnectionProfileStore(options.fs || fs); this.connectionProfileManager = new ConnectionProfileManager(this.connectionProfileStore); this.connection = null; this.securityContext = null; this.businessNetwork = null; } /** * Returns the currently connected BusinessNetworkDefinition * @example * // Get the Business Network Definition * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.getBusinessNetwork(); * }) * .then(function(BusinessNetworkDefinition){ * // Retrieved Business Network Definition * }); * @returns {BusinessNetworkDefinition} the business network */ getBusinessNetwork() { return this.businessNetwork; } /** * Get a list of all existing asset registries. * @example * // Get all asset registries * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.getAllAssetRegistries(); * }) * .then(function(assetRegistries){ * // Retrieved Asset Registries * }); * @param {SecurityContext} securityContext - The user's security context * @return {Promise} - A promise that will be resolved with a list of existing * asset registries */ getAllAssetRegistries() { Util.securityCheck(this.securityContext); return AssetRegistry.getAllAssetRegistries(this.securityContext, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()); } /** * Get an existing asset registry. * @example * // Get a asset registry * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.getAssetRegistry('businessNetworkIdentifier.registryId'); * }) * .then(function(assetRegistry){ * // Retrieved Asset Registry * }); * @param {string} id - The unique identifier of the asset registry * @return {Promise} - A promise that will be resolved with the existing asset * registry, or rejected if the asset registry does not exist. */ getAssetRegistry(id) { Util.securityCheck(this.securityContext); return AssetRegistry.getAssetRegistry(this.securityContext, id, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()); } /** * Determine whether a asset registry exists. * @example * // Determine whether an asset registry exists * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.existsAssetRegistry('businessNetworkIdentifier.registryId'); * }) * .then(function(exists){ * // if (exists === true) { * // logic here... * //} * }); * @param {string} id - The unique identifier of the asset registry * @return {Promise} - A promise that will be resolved with a boolean indicating whether the asset * registry exists. */ existsAssetRegistry(id) { Util.securityCheck(this.securityContext); return AssetRegistry.existsAssetRegistry(this.securityContext, id, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()); } /** * Add a new asset registry. * @example * // Add a new asset registry * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.addAssetRegistry('registryId','registryName'); * }); * @param {string} id - The unique identifier of the asset registry * @param {string} name - The name of the asset registry * @return {Promise} - A promise that will be resolved with the new asset * registry after it has been added. */ addAssetRegistry(id, name) { Util.securityCheck(this.securityContext); return AssetRegistry.addAssetRegistry(this.securityContext, id, name, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()); } /** * Get a list of all existing participant registries. * @example * // Get all participant registries * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.getAllParticipantRegistries(); * }) * .then(function(participantRegistries){ * // Retrieved Participant Registries * }); * @param {SecurityContext} securityContext - The user's security context * @return {Promise} - A promise that will be resolved with a list of existing * participant registries */ getAllParticipantRegistries() { Util.securityCheck(this.securityContext); return ParticipantRegistry.getAllParticipantRegistries(this.securityContext, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()); } /** * Get an existing participant registry. * @example * // Get a participant registry * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.getParticipantRegistry('businessNetworkIdentifier.registryId'); * }) * .then(function(participantRegistry){ * // Retrieved Participant Registry * }); * @param {string} id - The unique identifier of the participant registry * @return {Promise} - A promise that will be resolved with the existing participant * registry, or rejected if the participant registry does not exist. */ getParticipantRegistry(id) { Util.securityCheck(this.securityContext); return ParticipantRegistry.getParticipantRegistry(this.securityContext, id, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()); } /** * Add a new participant registry. * @example * // Add a new participant registry * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.addParticipantRegistry('registryId','registryName'); * }); * @param {string} id - The unique identifier of the participant registry * @param {string} name - The name of the participant registry * @return {Promise} - A promise that will be resolved with the new participant * registry after it has been added. */ addParticipantRegistry(id, name) { Util.securityCheck(this.securityContext); return ParticipantRegistry.addParticipantRegistry(this.securityContext, id, name, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()); } /** * Get the transaction registry. * @example * // Get the transaction registry * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.getTransactionRegistry(); * }) * .then(function(transactionRegistry){ * // Retrieved Transaction Registry * }); * @return {Promise} - A promise that will be resolved to the {@link TransactionRegistry} */ getTransactionRegistry() { Util.securityCheck(this.securityContext); return TransactionRegistry .getAllTransactionRegistries(this.securityContext, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer()) .then((transactionRegistries) => { if (transactionRegistries.length >= 1) { return transactionRegistries[0]; } else { throw new Error('Failed to find the default transaction registry'); } }); } /** * Connects to a business network using a connection profile, and authenticates to the Hyperledger Fabric. * @example * // Connect and log in to HLF * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * // Connected * }); * @param {string} connectionProfile - The name of the connection profile * @param {string} businessNetwork - The identifier of the business network * @param {string} enrollmentID the enrollment ID of the user * @param {string} enrollmentSecret the enrollment secret of the user * @return {Promise} A promise to a BusinessNetworkDefinition that indicates the connection is complete */ connect(connectionProfile, businessNetwork, enrollmentID, enrollmentSecret) { return this.connectionProfileManager.connect(connectionProfile, businessNetwork) .then((connection) => { this.connection = connection; return connection.login(enrollmentID, enrollmentSecret); }) .then((securityContext) => { this.securityContext = securityContext; return this.connection.ping(this.securityContext); }) .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; return this.businessNetwork; }); } /** * Disconnects from the Hyperledger Fabric. * @example * // Disconnects from HLF * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.disconnect(); * }) * .then(function(){ * // Disconnected. * }); * @return {Promise} A promise that will be resolved when the connection is * terminated. */ disconnect() { if (!this.connection) { return Promise.resolve(); } return this.connection.disconnect() .then(() => { this.connection = null; this.securityContext = null; this.businessNetwork = null; }); } /** * Submit a transaction for processing by the currently connected business network. * @example * // Submits a transaction * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * var factory = businessNetworkDefinition.getBusinessNetwork().getFactory(); * var transaction = factory.newTransaction('network.transactions', 'TransactionType'); * return businessNetworkDefinition.submitTransaction(transaction); * }) * .then(function(){ * // Submitted a transaction. * }); * @param {Resource} transaction - The transaction to submit. Use {@link * Factory#newTransaction newTransaction} to create this object. * @return {Promise} A promise that will be fulfilled when the transaction has * been processed. */ submitTransaction(transaction) { const self = this; Util.securityCheck(this.securityContext); if (!transaction) { throw new Error('transaction not specified'); } let classDeclaration = transaction.getClassDeclaration(); if (!(classDeclaration instanceof TransactionDeclaration)) { throw new Error(classDeclaration.getFullyQualifiedName() + ' is not a transaction'); } let id = transaction.getIdentifier(); if (id === null || id === undefined) { id = uuid.v4(); transaction.setIdentifier(id); } let timestamp = transaction.timestamp; if (timestamp === null || timestamp === undefined) { timestamp = transaction.timestamp = new Date(); } let data = self.getBusinessNetwork().getSerializer().toJSON(transaction); return self.getTransactionRegistry(self.securityContext) .then((transactionRegistry) => { return Util.invokeChainCode(self.securityContext, 'submitTransaction', [transactionRegistry.id, JSON.stringify(data)]); }); } /** * Test the connection to the runtime and verify that the version of the * runtime is compatible with this level of the client node.js module. * @example * // Test the connection to the runtime * var businessNetwork = new BusinessNetworkConnection(); * return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d') * .then(function(businessNetworkDefinition){ * return businessNetworkDefinition.ping(); * }) * .then(function(){ * // Connection tested. * }); * @return {Promise} A promise that will be fufilled when the connection has * been tested. The promise will be rejected if the version is incompatible. */ ping() { Util.securityCheck(this.securityContext); return this.connection.ping(this.securityContext); } /** * Issue an identity with the specified user ID and map it to the specified * participant. * @param {Resource|string} participant The participant, or the fully qualified * identifier of the participant. The participant must already exist. * @param {string} userID The user ID for the identity. * @param {object} [options] Options for the new identity. * @param {boolean} [options.issuer] Whether or not the new identity should have * permissions to create additional new identities. False by default. * @return {Promise} A promise that will be fulfilled when the identity has * been added to the specified participant. The promise will be rejected if * the participant does not exist, or if the identity is already mapped to * another participant. */ issueIdentity(participant, userID, options) { const method = 'issueIdentity'; LOG.entry(method, participant, userID); if (!participant) { throw new Error('participant not specified'); } else if (!userID) { throw new Error('userID not specified'); } let participantFQI; if (participant instanceof Resource) { participantFQI = participant.getFullyQualifiedIdentifier(); } else { participantFQI = participant; } Util.securityCheck(this.securityContext); return this.connection.createIdentity(this.securityContext, userID, options) .then((identity) => { return Util.invokeChainCode(this.securityContext, 'addParticipantIdentity', [participantFQI, userID]) .then(() => { LOG.exit(method, identity); return identity; }); }); } /** * Revoke the specified identity by removing any existing mapping to a participant. * @param {string} identity The identity, for example the enrollment ID. * @return {Promise} A promise that will be fulfilled when the identity has * been removed from the specified participant. The promise will be rejected if * the participant does not exist, or if the identity is not mapped to the * participant. */ revokeIdentity(identity) { const method = 'revokeIdentity'; LOG.entry(method, identity); if (!identity) { throw new Error('identity not specified'); } Util.securityCheck(this.securityContext); // It is not currently possible to revoke the certificate, so we just call // the runtime to remove the mapping. return Util.invokeChainCode(this.securityContext, 'removeIdentity', [identity]) .then(() => { LOG.exit(method); }); } } module.exports = BusinessNetworkConnection;