Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/composer-client/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BusinessNetworkConnection extends EventEmitter {
+ Promise query(Object)
+ Promise ping()
+ Promise issueIdentity(string,object,boolean)
+ Promise bindIdentity(string)
+ Promise revokeIdentity(string)
}
class ParticipantRegistry extends Registry {
Expand Down
3 changes: 3 additions & 0 deletions packages/composer-client/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# Note that the latest public API is documented using JSDocs and is available in api.txt.
#

Version 0.9.2 {465d5a96640dce8240a392d058e06fe5} 2017-07-11
- Added bindIdentity

Version 0.9.0 {d6ebf2b722345ee0f7dac56f42730f12} 2017-06-26
- Added buildQuery and query
- Removed deprecated BusinesNetworkConnection.existsAssetRegistry method
Expand Down
111 changes: 95 additions & 16 deletions packages/composer-client/lib/businessnetworkconnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ class BusinessNetworkConnection extends EventEmitter {
})
.then((securityContext) => {
this.securityContext = securityContext;
return this.connection.ping(this.securityContext);
return this.ping();
})
.then(() => {
return Util.queryChainCode(this.securityContext, 'getBusinessNetwork', []);
Expand Down Expand Up @@ -525,16 +525,64 @@ class BusinessNetworkConnection extends EventEmitter {
* been tested. The promise will be rejected if the version is incompatible.
*/
ping() {
const method = 'ping';
LOG.entry(method);
return this.pingInner()
.catch((error) => {
if (error.message.match(/ACTIVATION_REQUIRED/)) {
LOG.debug(method, 'Activation required, activating ...');
return this.activate()
.then(() => {
return this.pingInner();
});
}
throw error;
})
.then((result) => {
LOG.exit(method, result);
return result;
});
}

/**
* 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.
* @private
* @return {Promise} A promise that will be fufilled when the connection has
* been tested. The promise will be rejected if the version is incompatible.
*/
pingInner() {
const method = 'pingInner';
LOG.entry(method);
Util.securityCheck(this.securityContext);
return this.connection.ping(this.securityContext);
return this.connection.ping(this.securityContext)
.then((result) => {
LOG.exit(method, result);
return result;
});
}

/**
* Issue an identity with the specified user ID and map it to the specified
* Activate the current identity on the currently connected business network.
* @private
* @return {Promise} A promise that will be fufilled when the connection has
* been tested. The promise will be rejected if the version is incompatible.
*/
activate() {
const method = 'activate';
LOG.entry(method);
return Util.invokeChainCode(this.securityContext, 'activateIdentity', [])
.then(() => {
LOG.exit(method);
});
}

/**
* Issue an identity with the specified name 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 {string} identityName The name for the new 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.
Expand All @@ -543,13 +591,13 @@ class BusinessNetworkConnection extends EventEmitter {
* the participant does not exist, or if the identity is already mapped to
* another participant.
*/
issueIdentity(participant, userID, options) {
issueIdentity(participant, identityName, options) {
const method = 'issueIdentity';
LOG.entry(method, participant, userID);
LOG.entry(method, participant, identityName);
if (!participant) {
throw new Error('participant not specified');
} else if (!userID) {
throw new Error('userID not specified');
} else if (!identityName) {
throw new Error('identityName not specified');
}
let participantFQI;
if (participant instanceof Resource) {
Expand All @@ -558,34 +606,65 @@ class BusinessNetworkConnection extends EventEmitter {
participantFQI = participant;
}
Util.securityCheck(this.securityContext);
return this.connection.createIdentity(this.securityContext, userID, options)
return this.connection.createIdentity(this.securityContext, identityName, options)
.then((identity) => {
return Util.invokeChainCode(this.securityContext, 'addParticipantIdentity', [participantFQI, userID])
return Util.invokeChainCode(this.securityContext, 'issueIdentity', [participantFQI, identityName])
.then(() => {
LOG.exit(method, identity);
return identity;
});
});
}

/**
* Bind an existing identity 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} certificate The certificate for the existing identity.
* @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.
*/
bindIdentity(participant, certificate) {
const method = 'bindIdentity';
LOG.entry(method, participant, certificate);
if (!participant) {
throw new Error('participant not specified');
} else if (!certificate) {
throw new Error('certificate not specified');
}
let participantFQI;
if (participant instanceof Resource) {
participantFQI = participant.getFullyQualifiedIdentifier();
} else {
participantFQI = participant;
}
Util.securityCheck(this.securityContext);
return Util.invokeChainCode(this.securityContext, 'bindIdentity', [participantFQI, certificate])
.then(() => {
LOG.exit(method);
});
}

/**
* Revoke the specified identity by removing any existing mapping to a participant.
* @param {string} identity The identity, for example the enrollment ID.
* @param {string} identityId 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) {
revokeIdentity(identityId) {
const method = 'revokeIdentity';
LOG.entry(method, identity);
if (!identity) {
throw new Error('identity not specified');
LOG.entry(method, identityId);
if (!identityId) {
throw new Error('identityId 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])
return Util.invokeChainCode(this.securityContext, 'revokeIdentity', [identityId])
.then(() => {
LOG.exit(method);
});
Expand Down
138 changes: 130 additions & 8 deletions packages/composer-client/test/businessnetworkconnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('BusinessNetworkConnection', () => {
sandbox = sinon.sandbox.create();
mockSecurityContext = sinon.createStubInstance(SecurityContext);
mockConnection = sinon.createStubInstance(Connection);
mockSecurityContext.getConnection.returns(mockConnection);
mockBusinessNetworkDefinition = sinon.createStubInstance(BusinessNetworkDefinition);
businessNetworkConnection = new BusinessNetworkConnection();
businessNetworkConnection.businessNetwork = mockBusinessNetworkDefinition;
Expand Down Expand Up @@ -858,6 +859,83 @@ describe('BusinessNetworkConnection', () => {
});
});

it('should throw any errors that do not match ACTIVATION_REQUIRED', () => {
mockConnection.ping.onFirstCall().rejects(new Error('something something ACTIVATION NOT REQUIRED'));
mockConnection.ping.onSecondCall().resolves(Buffer.from(JSON.stringify({
version: version
})));
mockConnection.invokeChainCode.withArgs(mockSecurityContext, 'activateIdentity', []).resolves();
businessNetworkConnection.connection = mockConnection;
return businessNetworkConnection.ping()
.should.be.rejectedWith(/ACTIVATION NOT REQUIRED/);
});

it('should activate the identity if the ping returns ACTIVATION_REQUIRED', () => {
mockConnection.ping.onFirstCall().rejects(new Error('something something ACTIVATION_REQUIRED'));
mockConnection.ping.onSecondCall().resolves(Buffer.from(JSON.stringify({
version: version
})));
mockConnection.invokeChainCode.withArgs(mockSecurityContext, 'activateIdentity', []).resolves();
businessNetworkConnection.connection = mockConnection;
return businessNetworkConnection.ping()
.then(() => {
sinon.assert.calledTwice(mockConnection.ping);
sinon.assert.calledOnce(mockConnection.invokeChainCode);
sinon.assert.calledWith(mockConnection.invokeChainCode, mockSecurityContext, 'activateIdentity', []);
});
});

});

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

it('should perform a security check', () => {
sandbox.stub(Util, 'securityCheck');
mockConnection.ping.resolves(Buffer.from(JSON.stringify({
version: version
})));
businessNetworkConnection.connection = mockConnection;
return businessNetworkConnection.pingInner()
.then(() => {
sinon.assert.calledOnce(Util.securityCheck);
});
});

it('should ping the connection', () => {
mockConnection.ping.resolves(Buffer.from(JSON.stringify({
version: version
})));
businessNetworkConnection.connection = mockConnection;
return businessNetworkConnection.pingInner()
.then(() => {
sinon.assert.calledOnce(mockConnection.ping);
});
});

});

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

it('should perform a security check', () => {
sandbox.stub(Util, 'securityCheck');
mockConnection.invokeChainCode.withArgs(mockSecurityContext, 'activateIdentity', []).resolves();
businessNetworkConnection.connection = mockConnection;
return businessNetworkConnection.activate()
.then(() => {
sinon.assert.calledOnce(Util.securityCheck);
});
});

it('should submit a request to the chaincode for activation', () => {
mockConnection.invokeChainCode.withArgs(mockSecurityContext, 'activateIdentity', []).resolves();
businessNetworkConnection.connection = mockConnection;
return businessNetworkConnection.activate()
.then(() => {
sinon.assert.calledOnce(mockConnection.invokeChainCode);
sinon.assert.calledWith(mockConnection.invokeChainCode, mockSecurityContext, 'activateIdentity', []);
});
});

});

describe('#issueIdentity', () => {
Expand All @@ -876,12 +954,12 @@ describe('BusinessNetworkConnection', () => {
}).should.throw(/participant not specified/);
});

it('should throw if userID not specified', () => {
it('should throw if identityName not specified', () => {
(() => {
let mockResource = sinon.createStubInstance(Resource);
mockResource.getFullyQualifiedIdentifier.returns('org.doge.Doge#DOGE_1');
businessNetworkConnection.issueIdentity(mockResource, null);
}).should.throw(/userID not specified/);
}).should.throw(/identityName not specified/);
});

it('should submit a request to the chaincode for a resource', () => {
Expand All @@ -893,7 +971,7 @@ describe('BusinessNetworkConnection', () => {
sinon.assert.calledOnce(mockConnection.createIdentity);
sinon.assert.calledWith(mockConnection.createIdentity, mockSecurityContext, 'dogeid1');
sinon.assert.calledOnce(Util.invokeChainCode);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'addParticipantIdentity', ['org.doge.Doge#DOGE_1', 'dogeid1']);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'issueIdentity', ['org.doge.Doge#DOGE_1', 'dogeid1']);
result.should.deep.equal({
userID: 'dogeid1',
userSecret: 'suchsecret'
Expand All @@ -908,7 +986,7 @@ describe('BusinessNetworkConnection', () => {
sinon.assert.calledOnce(mockConnection.createIdentity);
sinon.assert.calledWith(mockConnection.createIdentity, mockSecurityContext, 'dogeid1');
sinon.assert.calledOnce(Util.invokeChainCode);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'addParticipantIdentity', ['org.doge.Doge#DOGE_1', 'dogeid1']);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'issueIdentity', ['org.doge.Doge#DOGE_1', 'dogeid1']);
result.should.deep.equal({
userID: 'dogeid1',
userSecret: 'suchsecret'
Expand All @@ -923,7 +1001,7 @@ describe('BusinessNetworkConnection', () => {
sinon.assert.calledOnce(mockConnection.createIdentity);
sinon.assert.calledWith(mockConnection.createIdentity, mockSecurityContext, 'dogeid1', { issuer: true });
sinon.assert.calledOnce(Util.invokeChainCode);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'addParticipantIdentity', ['org.doge.Doge#DOGE_1', 'dogeid1']);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'issueIdentity', ['org.doge.Doge#DOGE_1', 'dogeid1']);
result.should.deep.equal({
userID: 'dogeid1',
userSecret: 'suchsecret'
Expand All @@ -933,22 +1011,66 @@ describe('BusinessNetworkConnection', () => {

});

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

const pem = '-----BEGIN CERTIFICATE-----\nMIIB8jCCAZmgAwIBAgIULKt4c4xcdMwGgjNef9IL92HQkyAwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTcwNzA4MTg1NzAwWhcNMTgwNzA4MTg1\nNzAwWjASMRAwDgYDVQQDEwdib29iaWVzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD\nQgAE5P4RNqfEy8pArDxAbVIjRxqkwlpHUY7ANR6X7a4uvVIzIPDx4p7lf37xuc+5\nI9VZCvcI1SA5nIRphet0yYSgZaNsMGowDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwEB\n/wQCMAAwHQYDVR0OBBYEFAmjJfUZvdB8pHvklsdd1HiVog+VMCsGA1UdIwQkMCKA\nIBmrZau7BIB9rRLkwKmqpmSecIaOOr0CF6Mi2J5H4aauMAoGCCqGSM49BAMCA0cA\nMEQCIGtqR9rUR2ESu2UfUpNUfEeeBsshMkMHmuP/r5uvo2fSAiBtFB9Aid/3nexB\nI5qkVbdRSRQpt7uxoKFDLV/LUDM9xw==\n-----END CERTIFICATE-----\n';

beforeEach(() => {
businessNetworkConnection.connection = mockConnection;
});

it('should throw if participant not specified', () => {
(() => {
businessNetworkConnection.bindIdentity(null, pem);
}).should.throw(/participant not specified/);
});

it('should throw if certificate not specified', () => {
(() => {
let mockResource = sinon.createStubInstance(Resource);
mockResource.getFullyQualifiedIdentifier.returns('org.doge.Doge#DOGE_1');
businessNetworkConnection.bindIdentity(mockResource, null);
}).should.throw(/certificate not specified/);
});

it('should submit a request to the chaincode for a resource', () => {
sandbox.stub(Util, 'invokeChainCode').resolves();
let mockResource = sinon.createStubInstance(Resource);
mockResource.getFullyQualifiedIdentifier.returns('org.doge.Doge#DOGE_1');
return businessNetworkConnection.bindIdentity(mockResource, pem)
.then(() => {
sinon.assert.calledOnce(Util.invokeChainCode);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'bindIdentity', ['org.doge.Doge#DOGE_1', pem]);
});
});

it('should submit a request to the chaincode for a fully qualified identifier', () => {
sandbox.stub(Util, 'invokeChainCode').resolves();
return businessNetworkConnection.bindIdentity('org.doge.Doge#DOGE_1', pem)
.then(() => {
sinon.assert.calledOnce(Util.invokeChainCode);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'bindIdentity', ['org.doge.Doge#DOGE_1', pem]);
});
});

});

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

it('should throw if identity not specified', () => {
it('should throw if identityId not specified', () => {
(() => {
let mockResource = sinon.createStubInstance(Resource);
mockResource.getFullyQualifiedIdentifier.returns('org.doge.Doge#DOGE_1');
businessNetworkConnection.revokeIdentity(null);
}).should.throw(/identity not specified/);
}).should.throw(/identityId not specified/);
});

it('should submit a request to the chaincode', () => {
sandbox.stub(Util, 'invokeChainCode').resolves();
return businessNetworkConnection.revokeIdentity('dogeid1')
.then(() => {
sinon.assert.calledOnce(Util.invokeChainCode);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'removeIdentity', ['dogeid1']);
sinon.assert.calledWith(Util.invokeChainCode, mockSecurityContext, 'revokeIdentity', ['dogeid1']);
});
});

Expand Down
Loading